Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/libgit2.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore10
-rw-r--r--.mailmap21
-rw-r--r--.travis.yml67
-rw-r--r--AUTHORS75
-rw-r--r--CMakeLists.txt480
-rw-r--r--CONTRIBUTING.md104
-rw-r--r--CONVENTIONS107
-rw-r--r--CONVENTIONS.md234
-rw-r--r--COPYING566
-rw-r--r--Makefile.embed48
-rw-r--r--PROJECTS.md85
-rw-r--r--README.md202
-rw-r--r--cmake/Modules/AddCFlagIfSupported.cmake16
-rw-r--r--cmake/Modules/FindHTTP_Parser.cmake39
-rw-r--r--cmake/Modules/FindIconv.cmake43
-rw-r--r--cmake/Modules/FindLIBSSH2.cmake44
-rw-r--r--deps/http-parser/LICENSE-MIT23
-rw-r--r--deps/http-parser/http_parser.c2174
-rw-r--r--deps/http-parser/http_parser.h303
-rw-r--r--deps/regex/config.h7
-rw-r--r--deps/regex/regcomp.c3857
-rw-r--r--deps/regex/regex.c92
-rw-r--r--deps/regex/regex.h582
-rw-r--r--deps/regex/regex_internal.c1744
-rw-r--r--deps/regex/regex_internal.h819
-rw-r--r--deps/regex/regexec.c4369
-rw-r--r--docs/checkout-internals.md204
-rw-r--r--docs/diff-internals.md92
-rw-r--r--docs/error-handling.md270
-rw-r--r--docs/merge-df_conflicts.txt41
-rw-r--r--examples/.gitignore11
-rw-r--r--examples/CMakeLists.txt16
-rw-r--r--examples/COPYING121
-rw-r--r--examples/Makefile18
-rw-r--r--examples/README.md22
-rw-r--r--examples/add.c159
-rw-r--r--examples/blame.c210
-rw-r--r--examples/cat-file.c246
-rw-r--r--examples/common.c191
-rw-r--r--examples/common.h87
-rw-r--r--examples/diff.c337
-rw-r--r--examples/for-each-ref.c46
-rw-r--r--examples/general.c430
-rw-r--r--examples/init.c253
-rw-r--r--examples/log.c479
-rw-r--r--examples/network/.gitignore1
-rw-r--r--examples/network/Makefile22
-rw-r--r--examples/network/clone.c97
-rw-r--r--examples/network/common.c34
-rw-r--r--examples/network/common.h30
-rw-r--r--examples/network/fetch.c169
-rw-r--r--examples/network/git2.c66
-rw-r--r--examples/network/index-pack.c88
-rw-r--r--examples/network/ls-remote.c65
-rw-r--r--examples/rev-list.c121
-rw-r--r--examples/rev-parse.c115
-rw-r--r--examples/showindex.c105
-rw-r--r--examples/status.c523
-rw-r--r--examples/tag.c319
-rwxr-xr-xexamples/test/test-rev-list.sh95
-rw-r--r--git.git-authors20
-rw-r--r--include/git2.h94
-rw-r--r--include/git2/attr.h242
-rw-r--r--include/git2/blame.h213
-rw-r--r--include/git2/blob.h179
-rw-r--r--include/git2/branch.h268
-rw-r--r--include/git2/buffer.h112
-rw-r--r--include/git2/checkout.h327
-rw-r--r--include/git2/cherrypick.h87
-rw-r--r--include/git2/clone.h165
-rw-r--r--include/git2/commit.h289
-rw-r--r--include/git2/common.h223
-rw-r--r--include/git2/config.h577
-rw-r--r--include/git2/cred_helpers.h53
-rw-r--r--include/git2/diff.h1240
-rw-r--r--include/git2/errors.h243
-rw-r--r--include/git2/filter.h149
-rw-r--r--include/git2/graph.h55
-rw-r--r--include/git2/ignore.h78
-rw-r--r--include/git2/index.h683
-rw-r--r--include/git2/indexer.h65
-rw-r--r--include/git2/inttypes.h309
-rw-r--r--include/git2/merge.h536
-rw-r--r--include/git2/message.h44
-rw-r--r--include/git2/net.h49
-rw-r--r--include/git2/notes.h202
-rw-r--r--include/git2/object.h108
-rw-r--r--include/git2/odb.h328
-rw-r--r--include/git2/odb_backend.h190
-rw-r--r--include/git2/oid.h163
-rw-r--r--include/git2/pack.h215
-rw-r--r--include/git2/patch.h277
-rw-r--r--include/git2/pathspec.h263
-rw-r--r--include/git2/push.h181
-rw-r--r--include/git2/refdb.h68
-rw-r--r--include/git2/reflog.h127
-rw-r--r--include/git2/refs.h727
-rw-r--r--include/git2/refspec.h88
-rw-r--r--include/git2/remote.h624
-rw-r--r--include/git2/repository.h782
-rw-r--r--include/git2/reset.h92
-rw-r--r--include/git2/revert.h86
-rw-r--r--include/git2/revparse.h113
-rw-r--r--include/git2/revwalk.h189
-rw-r--r--include/git2/signature.h81
-rw-r--r--include/git2/stash.h117
-rw-r--r--include/git2/status.h378
-rw-r--r--include/git2/stdint.h247
-rw-r--r--include/git2/strarray.h60
-rw-r--r--include/git2/submodule.h559
-rw-r--r--include/git2/sys/commit.h80
-rw-r--r--include/git2/sys/config.h112
-rw-r--r--include/git2/sys/diff.h91
-rw-r--r--include/git2/sys/filter.h301
-rw-r--r--include/git2/sys/index.h177
-rw-r--r--include/git2/sys/mempack.h85
-rw-r--r--include/git2/sys/odb_backend.h108
-rw-r--r--include/git2/sys/refdb_backend.h204
-rw-r--r--include/git2/sys/reflog.h21
-rw-r--r--include/git2/sys/refs.h38
-rw-r--r--include/git2/sys/repository.h124
-rw-r--r--include/git2/tag.h227
-rw-r--r--include/git2/thread-utils.h78
-rw-r--r--include/git2/threads.h50
-rw-r--r--include/git2/trace.h67
-rw-r--r--include/git2/transport.h555
-rw-r--r--include/git2/tree.h330
-rw-r--r--include/git2/types.h258
-rw-r--r--include/git2/version.h17
-rw-r--r--include/git2/zlib.h58
-rw-r--r--libgit2.pc.in9
-rw-r--r--libgit2_clar.supp49
-rwxr-xr-xscript/cibuild.sh38
-rwxr-xr-xscript/coverity.sh57
-rwxr-xr-xscript/install-deps-linux.sh6
-rwxr-xr-xscript/install-deps-osx.sh5
-rw-r--r--src/array.h74
-rw-r--r--src/attr.c479
-rw-r--r--src/attr.h13
-rw-r--r--src/attr_file.c783
-rw-r--r--src/attr_file.h187
-rw-r--r--src/attrcache.c449
-rw-r--r--src/attrcache.h56
-rw-r--r--src/bitvec.h75
-rw-r--r--src/blame.c488
-rw-r--r--src/blame.h93
-rw-r--r--src/blame_git.c624
-rw-r--r--src/blame_git.h20
-rw-r--r--src/blob.c390
-rw-r--r--src/blob.h20
-rw-r--r--src/branch.c620
-rw-r--r--src/branch.h17
-rw-r--r--src/bswap.h12
-rw-r--r--src/buf_text.c302
-rw-r--r--src/buf_text.h122
-rw-r--r--src/buffer.c603
-rw-r--r--src/buffer.h191
-rw-r--r--src/cache.c321
-rw-r--r--src/cache.h63
-rw-r--r--src/cc-compat.h90
-rw-r--r--src/checkout.c2249
-rw-r--r--src/checkout.h24
-rw-r--r--src/cherrypick.c226
-rw-r--r--src/clone.c530
-rw-r--r--src/clone.h12
-rw-r--r--src/commit.c620
-rw-r--r--src/commit.h23
-rw-r--r--src/commit_list.c200
-rw-r--r--src/commit_list.h49
-rw-r--r--src/common.h154
-rw-r--r--src/config.c1319
-rw-r--r--src/config.h72
-rw-r--r--src/config_cache.c125
-rw-r--r--src/config_file.c1808
-rw-r--r--src/config_file.h61
-rw-r--r--src/crlf.c357
-rw-r--r--src/date.c904
-rw-r--r--src/delta-apply.c57
-rw-r--r--src/delta-apply.h25
-rw-r--r--src/delta.c424
-rw-r--r--src/delta.h114
-rw-r--r--src/diff.c1618
-rw-r--r--src/diff.h155
-rw-r--r--src/diff_driver.c503
-rw-r--r--src/diff_driver.h49
-rw-r--r--src/diff_file.c428
-rw-r--r--src/diff_file.h60
-rw-r--r--src/diff_patch.c936
-rw-r--r--src/diff_patch.h47
-rw-r--r--src/diff_print.c632
-rw-r--r--src/diff_stats.c336
-rw-r--r--src/diff_tform.c1126
-rw-r--r--src/diff_xdiff.c241
-rw-r--r--src/diff_xdiff.h28
-rw-r--r--src/dir.h41
-rw-r--r--src/errors.c236
-rw-r--r--src/fetch.c139
-rw-r--r--src/fetch.h25
-rw-r--r--src/fetchhead.c302
-rw-r--r--src/fetchhead.h34
-rw-r--r--src/filebuf.c390
-rw-r--r--src/filebuf.h45
-rw-r--r--src/fileops.c937
-rw-r--r--src/fileops.h277
-rw-r--r--src/filter.c693
-rw-r--r--src/filter.h35
-rw-r--r--src/fnmatch.c235
-rw-r--r--src/fnmatch.h (renamed from src/win32/fnmatch.h)23
-rw-r--r--src/global.c244
-rw-r--r--src/global.h28
-rw-r--r--src/graph.c192
-rw-r--r--src/hash.c99
-rw-r--r--src/hash.h31
-rw-r--r--src/hash/hash_generic.c (renamed from src/sha1.c)65
-rw-r--r--src/hash/hash_generic.h23
-rw-r--r--src/hash/hash_openssl.h44
-rw-r--r--src/hash/hash_win32.c276
-rw-r--r--src/hash/hash_win32.h140
-rw-r--r--src/hashsig.c347
-rw-r--r--src/hashsig.h72
-rw-r--r--src/hashtable.c261
-rw-r--r--src/hashtable.h74
-rw-r--r--src/ident.c125
-rw-r--r--src/ignore.c449
-rw-r--r--src/ignore.h63
-rw-r--r--src/index.c2501
-rw-r--r--src/index.h92
-rw-r--r--src/indexer.c1131
-rw-r--r--src/iterator.c1636
-rw-r--r--src/iterator.h283
-rw-r--r--src/khash.h610
-rw-r--r--src/map.h38
-rw-r--r--src/merge.c2863
-rw-r--r--src/merge.h156
-rw-r--r--src/merge_file.c279
-rw-r--r--src/merge_file.h14
-rw-r--r--src/message.c62
-rw-r--r--src/message.h15
-rw-r--r--src/mwindow.c245
-rw-r--r--src/mwindow.h35
-rw-r--r--src/netops.c737
-rw-r--r--src/netops.h112
-rw-r--r--src/notes.c649
-rw-r--r--src/notes.h29
-rw-r--r--src/object.c458
-rw-r--r--src/object.h31
-rw-r--r--src/object_api.c129
-rw-r--r--src/odb.c989
-rw-r--r--src/odb.h84
-rw-r--r--src/odb_loose.c733
-rw-r--r--src/odb_mempack.c182
-rw-r--r--src/odb_pack.c704
-rw-r--r--src/offmap.h65
-rw-r--r--src/oid.c267
-rw-r--r--src/oid.h36
-rw-r--r--src/oidmap.h35
-rw-r--r--src/pack-objects.c1425
-rw-r--r--src/pack-objects.h94
-rw-r--r--src/pack.c1140
-rw-r--r--src/pack.h121
-rw-r--r--src/path.c1159
-rw-r--r--src/path.h401
-rw-r--r--src/pathspec.c719
-rw-r--r--src/pathspec.h75
-rw-r--r--src/pkt.c211
-rw-r--r--src/pkt.h62
-rw-r--r--src/pool.c330
-rw-r--r--src/pool.h150
-rw-r--r--src/posix.c223
-rw-r--r--src/posix.h119
-rw-r--r--src/ppc/sha1.c72
-rw-r--r--src/ppc/sha1.h25
-rw-r--r--src/ppc/sha1ppc.S224
-rw-r--r--src/pqueue.c204
-rw-r--r--src/pqueue.h113
-rw-r--r--src/push.c724
-rw-r--r--src/push.h56
-rw-r--r--src/refdb.c244
-rw-r--r--src/refdb.h56
-rw-r--r--src/refdb_fs.c1856
-rw-r--r--src/refdb_fs.h15
-rw-r--r--src/reflog.c340
-rw-r--r--src/reflog.h18
-rw-r--r--src/refs.c2243
-rw-r--r--src/refs.h85
-rw-r--r--src/refspec.c329
-rw-r--r--src/refspec.h39
-rw-r--r--src/remote.c1984
-rw-r--r--src/remote.h33
-rw-r--r--src/repo_template.h58
-rw-r--r--src/repository.c2215
-rw-r--r--src/repository.h162
-rw-r--r--src/reset.c178
-rw-r--r--src/revert.c228
-rw-r--r--src/revparse.c916
-rw-r--r--src/revwalk.c667
-rw-r--r--src/revwalk.h49
-rw-r--r--src/settings.c140
-rw-r--r--src/sha1.h22
-rw-r--r--src/sha1_lookup.c167
-rw-r--r--src/sha1_lookup.h19
-rw-r--r--src/signature.c377
-rw-r--r--src/signature.h6
-rw-r--r--src/sortedcache.c378
-rw-r--r--src/sortedcache.h178
-rw-r--r--src/stash.c648
-rw-r--r--src/status.c709
-rw-r--r--src/status.h23
-rw-r--r--src/strmap.c32
-rw-r--r--src/strmap.h77
-rw-r--r--src/strnlen.h23
-rw-r--r--src/submodule.c1977
-rw-r--r--src/submodule.h170
-rw-r--r--src/sysdir.c252
-rw-r--r--src/sysdir.h101
-rw-r--r--src/tag.c465
-rw-r--r--src/tag.h10
-rw-r--r--src/thread-utils.c22
-rw-r--r--src/thread-utils.h255
-rw-r--r--src/trace.c38
-rw-r--r--src/trace.h54
-rw-r--r--src/transport.c227
-rw-r--r--src/transport.h77
-rw-r--r--src/transport_git.c327
-rw-r--r--src/transport_local.c222
-rw-r--r--src/transports/cred.c265
-rw-r--r--src/transports/cred_helpers.c49
-rw-r--r--src/transports/git.c345
-rw-r--r--src/transports/http.c909
-rw-r--r--src/transports/local.c673
-rw-r--r--src/transports/smart.c417
-rw-r--r--src/transports/smart.h187
-rw-r--r--src/transports/smart_pkt.c556
-rw-r--r--src/transports/smart_protocol.c1031
-rw-r--r--src/transports/ssh.c617
-rw-r--r--src/transports/winhttp.c1219
-rw-r--r--src/tree-cache.c182
-rw-r--r--src/tree-cache.h32
-rw-r--r--src/tree.c948
-rw-r--r--src/tree.h45
-rw-r--r--src/tsort.c103
-rw-r--r--src/unix/map.c46
-rw-r--r--src/unix/posix.h27
-rw-r--r--src/unix/realpath.c30
-rw-r--r--src/userdiff.h208
-rw-r--r--src/util.c425
-rw-r--r--src/util.h407
-rw-r--r--src/vector.c341
-rw-r--r--src/vector.h104
-rw-r--r--src/win32/dir.c131
-rw-r--r--src/win32/dir.h43
-rw-r--r--src/win32/error.c53
-rw-r--r--src/win32/error.h13
-rw-r--r--src/win32/findfile.c216
-rw-r--r--src/win32/findfile.h16
-rw-r--r--src/win32/fnmatch.c205
-rw-r--r--src/win32/git2.rc40
-rw-r--r--src/win32/map.c70
-rw-r--r--src/win32/mingw-compat.h11
-rw-r--r--src/win32/msvc-compat.h65
-rw-r--r--src/win32/posix.c220
-rw-r--r--src/win32/posix.h63
-rw-r--r--src/win32/posix_w32.c707
-rw-r--r--src/win32/precompiled.c1
-rw-r--r--src/win32/precompiled.h23
-rw-r--r--src/win32/pthread.c286
-rw-r--r--src/win32/pthread.h92
-rw-r--r--src/win32/reparse.h57
-rw-r--r--src/win32/utf-conv.c138
-rw-r--r--src/win32/utf-conv.h94
-rw-r--r--src/win32/version.h37
-rw-r--r--src/win32/w32_util.c139
-rw-r--r--src/win32/w32_util.h54
-rw-r--r--src/xdiff/xdiff.h135
-rw-r--r--src/xdiff/xdiffi.c572
-rw-r--r--src/xdiff/xdiffi.h63
-rw-r--r--src/xdiff/xemit.c253
-rw-r--r--src/xdiff/xemit.h36
-rw-r--r--src/xdiff/xhistogram.c371
-rw-r--r--src/xdiff/xinclude.h46
-rw-r--r--src/xdiff/xmacros.h54
-rw-r--r--src/xdiff/xmerge.c619
-rw-r--r--src/xdiff/xpatience.c358
-rw-r--r--src/xdiff/xprepare.c483
-rw-r--r--src/xdiff/xprepare.h34
-rw-r--r--src/xdiff/xtypes.h67
-rw-r--r--src/xdiff/xutils.c419
-rw-r--r--src/xdiff/xutils.h49
-rw-r--r--src/zstream.c156
-rw-r--r--src/zstream.h39
-rw-r--r--tests/.gitignore1
-rw-r--r--tests/NAMING52
-rw-r--r--tests/README.md22
-rw-r--r--tests/attr/attr_expect.h43
-rw-r--r--tests/attr/file.c224
-rw-r--r--tests/attr/flags.c108
-rw-r--r--tests/attr/ignore.c175
-rw-r--r--tests/attr/lookup.c262
-rw-r--r--tests/attr/repo.c335
-rw-r--r--tests/blame/blame_helpers.c67
-rw-r--r--tests/blame/blame_helpers.h16
-rw-r--r--tests/blame/buffer.c166
-rw-r--r--tests/blame/getters.c56
-rw-r--r--tests/blame/harder.c79
-rw-r--r--tests/blame/simple.c324
-rw-r--r--tests/buf/basic.c29
-rw-r--r--tests/buf/splice.c93
-rw-r--r--tests/checkout/binaryunicode.c58
-rw-r--r--tests/checkout/checkout_helpers.c130
-rw-r--r--tests/checkout/checkout_helpers.h29
-rw-r--r--tests/checkout/conflict.c1134
-rw-r--r--tests/checkout/crlf.c325
-rw-r--r--tests/checkout/head.c62
-rw-r--r--tests/checkout/index.c620
-rw-r--r--tests/checkout/tree.c950
-rw-r--r--tests/checkout/typechange.c240
-rw-r--r--tests/cherrypick/bare.c106
-rw-r--r--tests/cherrypick/workdir.c429
-rw-r--r--tests/clar.c568
-rw-r--r--tests/clar.h113
-rw-r--r--tests/clar/fixtures.h38
-rw-r--r--tests/clar/fs.h333
-rw-r--r--tests/clar/print.h66
-rw-r--r--tests/clar/sandbox.h134
-rw-r--r--tests/clar_libgit2.c533
-rw-r--r--tests/clar_libgit2.h144
-rw-r--r--tests/clone/empty.c85
-rw-r--r--tests/clone/local.c105
-rw-r--r--tests/clone/nonetwork.c318
-rw-r--r--tests/commit/commit.c83
-rw-r--r--tests/commit/parent.c60
-rw-r--r--tests/commit/parse.c413
-rw-r--r--tests/commit/signature.c81
-rw-r--r--tests/commit/write.c160
-rw-r--r--tests/config/add.c37
-rw-r--r--tests/config/backend.c24
-rw-r--r--tests/config/config_helpers.c65
-rw-r--r--tests/config/config_helpers.h13
-rw-r--r--tests/config/configlevel.c71
-rw-r--r--tests/config/global.c65
-rw-r--r--tests/config/include.c111
-rw-r--r--tests/config/multivar.c288
-rw-r--r--tests/config/new.c32
-rw-r--r--tests/config/read.c569
-rw-r--r--tests/config/refresh.c64
-rw-r--r--tests/config/rename.c85
-rw-r--r--tests/config/snapshot.c64
-rw-r--r--tests/config/stress.c92
-rw-r--r--tests/config/validkeyname.c48
-rw-r--r--tests/config/write.c333
-rw-r--r--tests/core/bitvec.c64
-rw-r--r--tests/core/buffer.c1122
-rw-r--r--tests/core/copy.c152
-rw-r--r--tests/core/dirent.c236
-rw-r--r--tests/core/env.c304
-rw-r--r--tests/core/errors.c111
-rw-r--r--tests/core/features.c31
-rw-r--r--tests/core/filebuf.c126
-rw-r--r--tests/core/hex.c22
-rw-r--r--tests/core/iconv.c78
-rw-r--r--tests/core/link.c602
-rw-r--r--tests/core/mkdir.c188
-rw-r--r--tests/core/oid.c70
-rw-r--r--tests/core/oidmap.c110
-rw-r--r--tests/core/opts.c19
-rw-r--r--tests/core/path.c631
-rw-r--r--tests/core/pool.c146
-rw-r--r--tests/core/posix.c99
-rw-r--r--tests/core/pqueue.c128
-rw-r--r--tests/core/rmdir.c98
-rw-r--r--tests/core/sortedcache.c363
-rw-r--r--tests/core/stat.c97
-rw-r--r--tests/core/string.c41
-rw-r--r--tests/core/strmap.c100
-rw-r--r--tests/core/strtol.c37
-rw-r--r--tests/core/vector.c276
-rw-r--r--tests/core/zstream.c143
-rw-r--r--tests/date/date.c15
-rw-r--r--tests/date/rfc2822.c40
-rw-r--r--tests/diff/binary.c263
-rw-r--r--tests/diff/blob.c1008
-rw-r--r--tests/diff/diff_helpers.c231
-rw-r--r--tests/diff/diff_helpers.h64
-rw-r--r--tests/diff/diffiter.c453
-rw-r--r--tests/diff/drivers.c252
-rw-r--r--tests/diff/format_email.c467
-rw-r--r--tests/diff/index.c165
-rw-r--r--tests/diff/iterator.c980
-rw-r--r--tests/diff/notify.c230
-rw-r--r--tests/diff/patch.c612
-rw-r--r--tests/diff/pathspec.c93
-rw-r--r--tests/diff/rename.c1588
-rw-r--r--tests/diff/stats.c289
-rw-r--r--tests/diff/submodules.c495
-rw-r--r--tests/diff/tree.c526
-rw-r--r--tests/diff/workdir.c1696
-rw-r--r--tests/fetchhead/fetchhead_data.h33
-rw-r--r--tests/fetchhead/nonetwork.c354
-rw-r--r--tests/filter/blob.c117
-rw-r--r--tests/filter/crlf.c239
-rw-r--r--tests/filter/crlf.h25
-rw-r--r--tests/filter/custom.c338
-rw-r--r--tests/filter/ident.c133
-rw-r--r--tests/generate.py244
-rw-r--r--tests/graph/descendant_of.c55
-rw-r--r--tests/index/addall.c356
-rw-r--r--tests/index/collision.c106
-rw-r--r--tests/index/conflicts.c287
-rw-r--r--tests/index/crlf.c154
-rw-r--r--tests/index/filemodes.c171
-rw-r--r--tests/index/inmemory.c22
-rw-r--r--tests/index/names.c148
-rw-r--r--tests/index/read_tree.c46
-rw-r--r--tests/index/rename.c50
-rw-r--r--tests/index/reuc.c372
-rw-r--r--tests/index/stage.c62
-rw-r--r--tests/index/tests.c565
-rw-r--r--tests/main.c25
-rw-r--r--tests/merge/files.c175
-rw-r--r--tests/merge/merge_helpers.c362
-rw-r--r--tests/merge/merge_helpers.h111
-rw-r--r--tests/merge/trees/automerge.c195
-rw-r--r--tests/merge/trees/commits.c131
-rw-r--r--tests/merge/trees/modeconflict.c59
-rw-r--r--tests/merge/trees/renames.c252
-rw-r--r--tests/merge/trees/treediff.c542
-rw-r--r--tests/merge/trees/trivial.c306
-rw-r--r--tests/merge/workdir/analysis.c140
-rw-r--r--tests/merge/workdir/dirty.c322
-rw-r--r--tests/merge/workdir/renames.c156
-rw-r--r--tests/merge/workdir/setup.c1048
-rw-r--r--tests/merge/workdir/simple.c627
-rw-r--r--tests/merge/workdir/submodules.c95
-rw-r--r--tests/merge/workdir/trivial.c262
-rw-r--r--tests/network/cred.c50
-rw-r--r--tests/network/fetchlocal.c114
-rw-r--r--tests/network/matchhost.c13
-rw-r--r--tests/network/refspecs.c87
-rw-r--r--tests/network/remote/createthenload.c37
-rw-r--r--tests/network/remote/defaultbranch.c50
-rw-r--r--tests/network/remote/delete.c61
-rw-r--r--tests/network/remote/isvalidname.c17
-rw-r--r--tests/network/remote/local.c358
-rw-r--r--tests/network/remote/remotes.c535
-rw-r--r--tests/network/remote/rename.c268
-rw-r--r--tests/network/urlparse.c193
-rw-r--r--tests/notes/notes.c387
-rw-r--r--tests/notes/notesref.c64
-rw-r--r--tests/object/blob/filter.c150
-rw-r--r--tests/object/blob/fromchunks.c156
-rw-r--r--tests/object/blob/write.c69
-rw-r--r--tests/object/cache.c287
-rw-r--r--tests/object/commit/commitstagedfile.c219
-rw-r--r--tests/object/lookup.c65
-rw-r--r--tests/object/lookupbypath.c83
-rw-r--r--tests/object/message.c199
-rw-r--r--tests/object/peel.c105
-rw-r--r--tests/object/raw/chars.c41
-rw-r--r--tests/object/raw/compare.c124
-rw-r--r--tests/object/raw/convert.c112
-rw-r--r--tests/object/raw/data.h (renamed from tests/t01-data.h)11
-rw-r--r--tests/object/raw/fromstr.c30
-rw-r--r--tests/object/raw/hash.c166
-rw-r--r--tests/object/raw/short.c137
-rw-r--r--tests/object/raw/size.c13
-rw-r--r--tests/object/raw/type2string.c54
-rw-r--r--tests/object/raw/write.c462
-rw-r--r--tests/object/shortid.c51
-rw-r--r--tests/object/tag/list.c115
-rw-r--r--tests/object/tag/peel.c61
-rw-r--r--tests/object/tag/read.c142
-rw-r--r--tests/object/tag/write.c260
-rw-r--r--tests/object/tree/attributes.c115
-rw-r--r--tests/object/tree/duplicateentries.c157
-rw-r--r--tests/object/tree/frompath.c68
-rw-r--r--tests/object/tree/read.c75
-rw-r--r--tests/object/tree/walk.c177
-rw-r--r--tests/object/tree/write.c398
-rw-r--r--tests/odb/alternates.c80
-rw-r--r--tests/odb/backend/nobackend.c46
-rw-r--r--tests/odb/backend/nonrefreshing.c274
-rw-r--r--tests/odb/foreach.c106
-rw-r--r--tests/odb/loose.c152
-rw-r--r--tests/odb/loose_data.h (renamed from tests/t02-data.h)13
-rw-r--r--tests/odb/mixed.c110
-rw-r--r--tests/odb/pack_data.h (renamed from tests/t02-oids.h)1
-rw-r--r--tests/odb/pack_data_one.h19
-rw-r--r--tests/odb/packed.c79
-rw-r--r--tests/odb/packed_one.c60
-rw-r--r--tests/odb/sorting.c69
-rw-r--r--tests/odb/streamwrite.c56
-rw-r--r--tests/online/clone.c319
-rw-r--r--tests/online/fetch.c204
-rw-r--r--tests/online/fetchhead.c91
-rw-r--r--tests/online/push.c868
-rw-r--r--tests/online/push_util.c132
-rw-r--r--tests/online/push_util.h71
-rw-r--r--tests/pack/indexer.c127
-rw-r--r--tests/pack/packbuilder.c225
-rw-r--r--tests/refs/branches/create.c198
-rw-r--r--tests/refs/branches/delete.c140
-rw-r--r--tests/refs/branches/ishead.c98
-rw-r--r--tests/refs/branches/iterator.c151
-rw-r--r--tests/refs/branches/lookup.c45
-rw-r--r--tests/refs/branches/move.c260
-rw-r--r--tests/refs/branches/name.c45
-rw-r--r--tests/refs/branches/remote.c73
-rw-r--r--tests/refs/branches/upstream.c143
-rw-r--r--tests/refs/branches/upstreamname.c36
-rw-r--r--tests/refs/crashes.c20
-rw-r--r--tests/refs/create.c168
-rw-r--r--tests/refs/createwithlog.c51
-rw-r--r--tests/refs/delete.c107
-rw-r--r--tests/refs/foreachglob.c95
-rw-r--r--tests/refs/isvalidname.c31
-rw-r--r--tests/refs/iterator.c221
-rw-r--r--tests/refs/list.c57
-rw-r--r--tests/refs/listall.c47
-rw-r--r--tests/refs/lookup.c60
-rw-r--r--tests/refs/normalize.c403
-rw-r--r--tests/refs/overwrite.c136
-rw-r--r--tests/refs/pack.c105
-rw-r--r--tests/refs/peel.c119
-rw-r--r--tests/refs/races.c152
-rw-r--r--tests/refs/read.c299
-rw-r--r--tests/refs/ref_helpers.c25
-rw-r--r--tests/refs/ref_helpers.h1
-rw-r--r--tests/refs/reflog/drop.c115
-rw-r--r--tests/refs/reflog/reflog.c336
-rw-r--r--tests/refs/rename.c390
-rw-r--r--tests/refs/revparse.c814
-rw-r--r--tests/refs/settargetwithlog.c55
-rw-r--r--tests/refs/setter.c99
-rw-r--r--tests/refs/shorthand.c27
-rw-r--r--tests/refs/unicode.c56
-rw-r--r--tests/refs/update.c26
-rw-r--r--tests/repo/config.c211
-rw-r--r--tests/repo/discover.c143
-rw-r--r--tests/repo/getters.c40
-rw-r--r--tests/repo/hashfile.c85
-rw-r--r--tests/repo/head.c469
-rw-r--r--tests/repo/headtree.c53
-rw-r--r--tests/repo/init.c606
-rw-r--r--tests/repo/iterator.c962
-rw-r--r--tests/repo/message.c39
-rw-r--r--tests/repo/open.c379
-rw-r--r--tests/repo/pathspec.c385
-rw-r--r--tests/repo/repo_helpers.c22
-rw-r--r--tests/repo/repo_helpers.h6
-rw-r--r--tests/repo/setters.c107
-rw-r--r--tests/repo/shallow.c39
-rw-r--r--tests/repo/state.c114
-rw-r--r--tests/reset/default.c212
-rw-r--r--tests/reset/hard.c232
-rw-r--r--tests/reset/mixed.c83
-rw-r--r--tests/reset/reset_helpers.c20
-rw-r--r--tests/reset/reset_helpers.h7
-rw-r--r--tests/reset/soft.c182
-rw-r--r--tests/resources/.gitattributes1
-rw-r--r--tests/resources/attr/.gitted/HEAD1
-rw-r--r--tests/resources/attr/.gitted/config6
-rw-r--r--tests/resources/attr/.gitted/description1
-rw-r--r--tests/resources/attr/.gitted/indexbin0 -> 1856 bytes
-rw-r--r--tests/resources/attr/.gitted/info/attributes4
-rw-r--r--tests/resources/attr/.gitted/info/exclude6
-rw-r--r--tests/resources/attr/.gitted/logs/HEAD9
-rw-r--r--tests/resources/attr/.gitted/logs/refs/heads/master9
-rw-r--r--tests/resources/attr/.gitted/objects/10/8bb4e7fd7b16490dc33ff7d972151e73d7166ebin0 -> 130 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/16/983da6643656bb44c43965ecb6855c6d574512bin0 -> 446 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/21/7878ab49e1314388ea2e32dc6fdb58a1b969e04
-rw-r--r--tests/resources/attr/.gitted/objects/24/fa9a9fc4e202313e24b648087495441dab432bbin0 -> 180 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/29/29de282ce999e95183aedac6451d3384559c4bbin0 -> 58 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/2b/40c5aca159b04ea8d20ffe36cdf8b09369b14a1
-rw-r--r--tests/resources/attr/.gitted/objects/2c/66e14f77196ea763fb1e41612c1aa2bc2d8ed2bin0 -> 316 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/2d/e7dfe3588f3c7e9ad59e7d50ba90e3329df9d9bin0 -> 124 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/37/0fe9ec224ce33e71f9e5ec2bd1142ce9937a6abin0 -> 177 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/3a/6df026462ebafe455af9867d27eda20a9e0974bin0 -> 84 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/3b/74db7ab381105dc0d28f8295a77f6a82989292bin0 -> 276 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/3e/42ffc54a663f9401cc25843d6c0e71a33e4249bin0 -> 596 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/45/141a79a77842c59a63229403220a4e4be74e3dbin0 -> 36 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/45/5a314fa848d52ae1f11d254da4f60858fc97f4bin0 -> 446 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057bin0 -> 18 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/4d/713dc48e6b1bd75b0d61ad078ba9ca3a56745d2
-rw-r--r--tests/resources/attr/.gitted/objects/4e/49ba8c5b6c32ff28cd9dcb60be34df50fcc485bin0 -> 81 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/55/6f8c827b8e4a02ad5cab77dca2bcb3e226b0b3bin0 -> 24 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/58/19a185d77b03325aaf87cafc771db36f6ddca7bin0 -> 19 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/60/5812ab7fe421fdd325a935d35cb06a9234a7d72
-rw-r--r--tests/resources/attr/.gitted/objects/6b/ab5c79cd5140d0f800917f550eb2a3dc32b0da3
-rw-r--r--tests/resources/attr/.gitted/objects/6d/968d62c89c7d9ea23a4c9a7b665d017c3d8ffdbin0 -> 422 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/71/7fc31f6b84f9d6fc3a4edbca259d7fc92beee2bin0 -> 422 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/8d/0b9df9bd30be7910ddda60548d485bc302b9111
-rw-r--r--tests/resources/attr/.gitted/objects/93/61f40bb97239cf55811892e14de2e344168ba1bin0 -> 45 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/94/da4faa0a6bfb8ee6ccf7153801a69202b31857bin0 -> 124 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/96/089fd31ce1d3ee2afb0ba09ba063066932f027bin0 -> 422 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/99/eae476896f4907224978b88e5ecaa6c5bb67a9bin0 -> 95 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/9e/5bdc47d6a80f2be0ea3049ad74231b94609242bin0 -> 20 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/9f/b40b6675dde60b5697afceae91b66d908c02d9bin0 -> 151 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/a0/f7217ae99f5ac3e88534f5cea267febc5fa85b1
-rw-r--r--tests/resources/attr/.gitted/objects/a5/6bbcecaeac760cc26239384d2d4c614e7e4320bin0 -> 351 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/a5/d76cad53f66f1312bd995909a5bab3c08207704
-rw-r--r--tests/resources/attr/.gitted/objects/a9/7cc019851d401a4f1d091cb91a15890a0dd1ba2
-rw-r--r--tests/resources/attr/.gitted/objects/b4/35cd5689a0fb54afbeda4ac20368aa480e8f04bin0 -> 40 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/c0/091889c0c77142b87a1fa5123a6398a61d33e7bin0 -> 290 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/c4/85abe35abd4aa6fd83b076a78bbea9e2e7e06cbin0 -> 129 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/c7/aadd770d5907a8475c29e9ee21a27b88bf675dbin0 -> 60 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/c9/6bbb2c2557a8325ae1559e3ba79cdcecb230762
-rw-r--r--tests/resources/attr/.gitted/objects/ce/39a97a7fb1fa90bcf5e711249c1e507476ae0ebin0 -> 446 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/d5/7da33c16b14326ecb05d19bbea908f5e4c47d9bin0 -> 379 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/d8/00886d9c86731ae5c4a62b0b77c437015e00d2bin0 -> 18 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/dc/cada462d3df8ac6de596fb8c896aba9344f941bin0 -> 35 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/de/863bff4976c9ed7e17a4da0fd524908dc84049bin0 -> 4115 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/e5/63cf4758f0d646f1b14b76016aa17fa9e549a4bin0 -> 39 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/ec/b97df2a174987475ac816e3847fc8e9f6c596bbin0 -> 171 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/ed/f3dcee4003d71f139777898882ccd097e34c53bin0 -> 6289 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/f2/c6d717cf4a5a3e6b02684155ab07b766982165bin0 -> 44 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/f5/b0af1fb4f5c0cd7aad880711d368a07333c3072
-rw-r--r--tests/resources/attr/.gitted/objects/fb/5067b1aef3ac1ada4b379dbcb7d17255df7d78bin0 -> 28 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/fe/773770c5a6cc7185580c9204b1ff18a33ff3fc1
-rw-r--r--tests/resources/attr/.gitted/objects/ff/69f8639ce2e6010b3f33a74160aad98b48da2bbin0 -> 18 bytes
-rw-r--r--tests/resources/attr/.gitted/refs/heads/master1
-rw-r--r--tests/resources/attr/attr01
-rw-r--r--tests/resources/attr/attr129
-rw-r--r--tests/resources/attr/attr221
-rw-r--r--tests/resources/attr/attr34
-rw-r--r--tests/resources/attr/binfile1
-rw-r--r--tests/resources/attr/dir/file0
-rw-r--r--tests/resources/attr/file1
-rw-r--r--tests/resources/attr/gitattributes29
-rw-r--r--tests/resources/attr/gitignore2
-rw-r--r--tests/resources/attr/ign1
-rw-r--r--tests/resources/attr/macro_bad1
-rw-r--r--tests/resources/attr/macro_test1
-rw-r--r--tests/resources/attr/root_test11
-rw-r--r--tests/resources/attr/root_test26
-rw-r--r--tests/resources/attr/root_test319
-rw-r--r--tests/resources/attr/root_test4.txt14
-rw-r--r--tests/resources/attr/sub/.gitattributes7
-rw-r--r--tests/resources/attr/sub/abc37
-rw-r--r--tests/resources/attr/sub/dir/file0
-rw-r--r--tests/resources/attr/sub/file1
-rw-r--r--tests/resources/attr/sub/ign/file1
-rw-r--r--tests/resources/attr/sub/ign/sub/file1
-rw-r--r--tests/resources/attr/sub/sub/.gitattributes3
-rw-r--r--tests/resources/attr/sub/sub/dir0
-rw-r--r--tests/resources/attr/sub/sub/file1
-rw-r--r--tests/resources/attr/sub/sub/subsub.txt1
-rw-r--r--tests/resources/attr/sub/subdir_test12
-rw-r--r--tests/resources/attr/sub/subdir_test2.txt1
-rw-r--r--tests/resources/attr_index/.gitted/HEAD1
-rw-r--r--tests/resources/attr_index/.gitted/config6
-rw-r--r--tests/resources/attr_index/.gitted/description1
-rw-r--r--tests/resources/attr_index/.gitted/indexbin0 -> 520 bytes
-rw-r--r--tests/resources/attr_index/.gitted/info/exclude6
-rw-r--r--tests/resources/attr_index/.gitted/info/refs1
-rw-r--r--tests/resources/attr_index/.gitted/logs/HEAD4
-rw-r--r--tests/resources/attr_index/.gitted/logs/refs/heads/master4
-rw-r--r--tests/resources/attr_index/.gitted/objects/38/12cfef36615db1788d4e63f90028007e17a3483
-rw-r--r--tests/resources/attr_index/.gitted/objects/59/d942b8be2784bc96db9b22202c10815c9a077b1
-rw-r--r--tests/resources/attr_index/.gitted/objects/cd/f17ea3fe625ef812f4dce7f423f4f299287505bin0 -> 61 bytes
-rw-r--r--tests/resources/attr_index/.gitted/objects/f7/2502ddd01412bb20796ff812af56fd53b82b52bin0 -> 149 bytes
-rw-r--r--tests/resources/attr_index/.gitted/objects/info/packs2
-rw-r--r--tests/resources/attr_index/.gitted/objects/pack/pack-4e6438607204ce78827e3885594b2c0bb4f13895.idxbin0 -> 1492 bytes
-rw-r--r--tests/resources/attr_index/.gitted/objects/pack/pack-4e6438607204ce78827e3885594b2c0bb4f13895.packbin0 -> 1106 bytes
-rw-r--r--tests/resources/attr_index/.gitted/packed-refs2
-rw-r--r--tests/resources/attr_index/.gitted/refs/heads/master1
-rw-r--r--tests/resources/attr_index/README.md1
-rw-r--r--tests/resources/attr_index/README.txt1
-rw-r--r--tests/resources/attr_index/gitattributes4
-rw-r--r--tests/resources/attr_index/sub/sub/.gitattributes3
-rw-r--r--tests/resources/attr_index/sub/sub/README.md1
-rw-r--r--tests/resources/attr_index/sub/sub/README.txt1
-rw-r--r--tests/resources/bad.indexbin0 -> 412 bytes
-rw-r--r--tests/resources/bad_tag.git/HEAD1
-rw-r--r--tests/resources/bad_tag.git/config5
-rw-r--r--tests/resources/bad_tag.git/objects/pack/pack-7a28f4e000a17f49a41d7a79fc2f762a8a7d9164.idxbin0 -> 1268 bytes
-rw-r--r--tests/resources/bad_tag.git/objects/pack/pack-7a28f4e000a17f49a41d7a79fc2f762a8a7d9164.packbin0 -> 596 bytes
-rw-r--r--tests/resources/bad_tag.git/packed-refs5
-rw-r--r--tests/resources/bad_tag.git/refs/dummy-marker.txt0
-rw-r--r--tests/resources/binaryunicode/.gitted/HEAD1
-rw-r--r--tests/resources/binaryunicode/.gitted/config6
-rw-r--r--tests/resources/binaryunicode/.gitted/description1
-rw-r--r--tests/resources/binaryunicode/.gitted/indexbin0 -> 104 bytes
-rw-r--r--tests/resources/binaryunicode/.gitted/info/exclude6
-rw-r--r--tests/resources/binaryunicode/.gitted/info/refs3
-rw-r--r--tests/resources/binaryunicode/.gitted/objects/info/packs2
-rw-r--r--tests/resources/binaryunicode/.gitted/objects/pack/pack-c5bfca875b4995d7aba6e5abf36241f3c397327d.idxbin0 -> 1380 bytes
-rw-r--r--tests/resources/binaryunicode/.gitted/objects/pack/pack-c5bfca875b4995d7aba6e5abf36241f3c397327d.packbin0 -> 20879 bytes
-rw-r--r--tests/resources/binaryunicode/.gitted/refs/heads/branch11
-rw-r--r--tests/resources/binaryunicode/.gitted/refs/heads/branch21
-rw-r--r--tests/resources/binaryunicode/.gitted/refs/heads/master1
-rw-r--r--tests/resources/binaryunicode/file.txt1
-rw-r--r--tests/resources/blametest.git/HEAD1
-rw-r--r--tests/resources/blametest.git/config5
-rw-r--r--tests/resources/blametest.git/description1
-rw-r--r--tests/resources/blametest.git/objects/0c/bab4d45fd61e55a1c9697f9f9cb07a12e15448bin0 -> 46 bytes
-rw-r--r--tests/resources/blametest.git/objects/1a/ac69ae5d96461afc4d81d0066cb12f5b05a35bbin0 -> 28 bytes
-rw-r--r--tests/resources/blametest.git/objects/1b/5f0775af166331c854bd8d1bca3450eaf2532abin0 -> 35 bytes
-rw-r--r--tests/resources/blametest.git/objects/48/2f2c370e35c2c314fc1f96db2beb33f955a26abin0 -> 35 bytes
-rw-r--r--tests/resources/blametest.git/objects/63/d671eb32d250e4a83766ebbc60e818c1e1e93a3
-rw-r--r--tests/resources/blametest.git/objects/63/eb57322e363e18d460da5ea8284f3cd2340b36bin0 -> 76 bytes
-rw-r--r--tests/resources/blametest.git/objects/8b/137891791fe96927ad78e64b0aad7bded08bdcbin0 -> 16 bytes
-rw-r--r--tests/resources/blametest.git/objects/96/679d59cf9f74d69b3c920f258559b5e8c9a18abin0 -> 47 bytes
-rw-r--r--tests/resources/blametest.git/objects/98/89d6e5557761aa8e3607e80c874a6dc51ada7cbin0 -> 43 bytes
-rw-r--r--tests/resources/blametest.git/objects/aa/06ecca6c4ad6432ab9313e556ca92ba4bcf9e91
-rw-r--r--tests/resources/blametest.git/objects/b1/76dfc3a4dc8734e4c579f77236a9c8d0a965d2bin0 -> 76 bytes
-rw-r--r--tests/resources/blametest.git/objects/b9/0bb887b7c03750ae6b352ffe76ab9d2e86ee7dbin0 -> 56 bytes
-rw-r--r--tests/resources/blametest.git/objects/b9/9f7ac0b88909253d829554c14af488c3b0f3a52
-rw-r--r--tests/resources/blametest.git/objects/bc/7c5ac2bafe828a68e9d1d460343718d6fbe1363
-rw-r--r--tests/resources/blametest.git/objects/cf/e0e1e1e3ba18f149fd47f5e1aef6016b2260c3bin0 -> 76 bytes
-rw-r--r--tests/resources/blametest.git/objects/d0/67729932057cdb7527a833d6799c4ddc5206401
-rw-r--r--tests/resources/blametest.git/objects/da/237394e6132d20d30f175b9b73c8638fddddda4
-rw-r--r--tests/resources/blametest.git/objects/e5/b41c1ea533f87388ab69b13baf0b5a562d6243bin0 -> 76 bytes
-rw-r--r--tests/resources/blametest.git/objects/ef/32df4d259143933715c74951f932d9892364d1bin0 -> 42 bytes
-rw-r--r--tests/resources/blametest.git/refs/heads/master1
-rw-r--r--tests/resources/cherrypick/.gitted/HEAD1
-rw-r--r--tests/resources/cherrypick/.gitted/config7
-rw-r--r--tests/resources/cherrypick/.gitted/indexbin0 -> 248 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/info/exclude6
-rw-r--r--tests/resources/cherrypick/.gitted/objects/01/a2b453c2647c71ccfefc285f2266d1f00b8253bin0 -> 30 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/02/67838e09bbc5969bba035be2d27c8a6de694d8bin0 -> 38 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/06/3fc9f01e6e9ec2a8d8f749885e931875e50d37bin0 -> 141 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/08/9ac03f76058b5ba0b44bb268f317f9242481e93
-rw-r--r--tests/resources/cherrypick/.gitted/objects/0d/447a6c2528b06616cde3b209a4b4ea3dcb8d65bin0 -> 107 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/11/24c2c1ae07b26fded662d6c3f3631d9dc16f88bin0 -> 31 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/12/905f4ea5b76f9d3fdcfe73e462201c06ae632abin0 -> 108 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/19/c5c7207054604b69c84d08a7571ef9672bb5c2bin0 -> 28 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/1c/2116845780455ecf916538c1cc27c4222452afbin0 -> 116 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/1c/c85eb4ff0a8438fde1b14274c6f87f891b36a0bin0 -> 117 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/1e/1cb7391d25dcd8daba88f1f627f3045982286cbin0 -> 32 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/20/fc1a4c9d994021f43d33ab75e4252e27ca661dbin0 -> 126 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/28/d9eb4208074ad1cc84e71ccc908b34573f05d2bin0 -> 28 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/2a/26c7e88b285613b302ba76712bc998863f3cbc1
-rw-r--r--tests/resources/cherrypick/.gitted/objects/2a/c3b376093de405b0a951bff578655b1c2b7fa11
-rw-r--r--tests/resources/cherrypick/.gitted/objects/2c/acbcaabf785f1ac231e8519849d4ad38692f2cbin0 -> 26 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/35/cb210149022c7379b0a67b0dec13cc628ff87dbin0 -> 137 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/38/c05a857e831a7e759d83778bfc85d003e21c45bin0 -> 27 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/3f/9eed8946df9e2c737d3b8dc0b8e78959aacd925
-rw-r--r--tests/resources/cherrypick/.gitted/objects/40/9a1bec58bf35348e8b62b72bb9c1f45cf5a587bin0 -> 33 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/44/cd2ed2052c9c68f9a439d208e9614dc2a55c701
-rw-r--r--tests/resources/cherrypick/.gitted/objects/48/7434cace79238a7091e2220611d4f20a765690bin0 -> 33 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/49/20ad2f17162dcc8823ad491444dcb87f5899c9bin0 -> 36 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904bin0 -> 15 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/4c/532774cc1fea37f6efc2256763a64d38c8cddebin0 -> 26 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/51/145af30d411a50195b66517d825e69bf57ed22bin0 -> 107 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/54/61de53ffadbf15be4dd6345997c156895732094
-rw-r--r--tests/resources/cherrypick/.gitted/objects/54/784f10955e92ab27e4fa832e40cb2baf1edbdcbin0 -> 74 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/56/3f6473a3858f99b80e5f93c660512ed38e1e6fbin0 -> 31 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/58/a957ef0061c1a8ef995c855dfab4f5da8d6617bin0 -> 32 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/5d/c7e1f440ce74d5503a0dfbc6c30e091475f774bin0 -> 31 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/5e/2206cda1c56430ad107a6866a829c159e0b9ea1
-rw-r--r--tests/resources/cherrypick/.gitted/objects/5f/77a2a13935ac62a629553f8944ad57b1ed8b4abin0 -> 106 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/63/c0d92b95253c4a40d3883f423a54be47d2c4c8bin0 -> 30 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/6c/e83eb5f0fd34a10c3d25c6b36d2ed7ec0d6ce7bin0 -> 108 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/6d/1c2afe5eeb9e497528e2780ac468a5465cbc961
-rw-r--r--tests/resources/cherrypick/.gitted/objects/74/f06b5bfec6d33d7264f73606b57a7c0b963819bin0 -> 141 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/82/8b08c52d2cba30952e0e008f60b25b5ba0d41abin0 -> 107 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/85/36dd6f0ec3ddecb9f9b6c8c64c6d322cd01211bin0 -> 36 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/85/a4a1d791973644f24c72f5e89420d3064cc452bin0 -> 27 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/8b/5c30499a71001189b647f4d5b57fa8f04897cebin0 -> 107 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/96/4ea3da044d9083181a88ba6701de9e35778bf4bin0 -> 181 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/9c/c39fca3765a2facbe31157f7d60c2602193f36bin0 -> 107 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/9c/cb9bf50c011fd58dcbaa65df917bf79539717fbin0 -> 30 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/a1/0b59f4280491afe6e430c30654a7acc67d4a33bin0 -> 30 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/a2/1b4bfe7a04ab18024fb57f4ae9a52a1acef394bin0 -> 173 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/a4/3a050c588d4e92f11a6b139680923e9728477d1
-rw-r--r--tests/resources/cherrypick/.gitted/objects/a5/8ca3fee5eb68b11adc2703e5843f968c9dad1ebin0 -> 28 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/a6/61b5dec1004e2c62654ded3762370c27cf266bbin0 -> 27 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/a6/9ef8fcbb9a2c509a7dbf4f23d257eb551d56101
-rw-r--r--tests/resources/cherrypick/.gitted/objects/a8/3c6f70297b805dedc549e6583582966f6ebcabbin0 -> 138 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/a9/020cd240774e4d672732bcb82d516d9685da76bin0 -> 26 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/ab/4115f808bc585b60f822da7020af86d20f62c8bin0 -> 213 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/ab/e4603bc7cd5b8167a267e0e2418fd2348f8cff4
-rw-r--r--tests/resources/cherrypick/.gitted/objects/b8/26e9b36e22e949ec885e7a1f3db496bbab6cd0bin0 -> 108 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/ba/fbf6912c09505ac60575cd43d3f2aba3bd84d8bin0 -> 175 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/bb/14296ffa9dfbf935ec9ce2f9ed7808d952226bbin0 -> 38 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/bc/4dd0744364d1db380a9811bd264c101065231ebin0 -> 55 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/bd/65d4083845ed5ed4e1fe5feb85ac395d0760c82
-rw-r--r--tests/resources/cherrypick/.gitted/objects/bd/6ffc8c6c41f0f85ff9e3d61c9479516bac0024bin0 -> 31 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/bd/a51965cb36c0c5731c8cb50b80a36cac81018ebin0 -> 107 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/ce/d8fb81b6ec534d5deaf2a48b4b96c7997125071
-rw-r--r--tests/resources/cherrypick/.gitted/objects/cf/c4f0999a8367568e049af4f72e452d40828a15bin0 -> 180 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/d0/f21e17beb5b9d953b1d8349049818a4f2edd1e1
-rw-r--r--tests/resources/cherrypick/.gitted/objects/d3/d77487660ee3c0194ee01dc5eaf478782b1c7e1
-rw-r--r--tests/resources/cherrypick/.gitted/objects/e2/33b9ed408a95e9d4b65fec7fc34943a556deb2bin0 -> 31 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/e5/183bfd18e3a0a691fadde2f0d5610b73282d31bin0 -> 33 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/e6/ae8889c40c77d7be02758235b5b3f7a4f2a129bin0 -> 107 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/e7/811a2bc55635f182750f0420da5ad232c1af91bin0 -> 107 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/e9/b63f3655b2ad80c0ff587389b5a9589a3a71102
-rw-r--r--tests/resources/cherrypick/.gitted/objects/eb/da71fe44dcb60c53b8fbd53208a1204d32e959bin0 -> 36 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/f0/5ed049854c1596a7cc0e957fab34961077f3aebin0 -> 36 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/f0/a4e1c66bb548cd2b22eebefda703872e969775bin0 -> 191 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/f2/ec8c8cf1a9fb7aa047a25a4308bfe860237ad4bin0 -> 32 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/f5/684c96bf40c709877b56404cd8a5dd2d2a7978bin0 -> 106 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/objects/f9/0f9dcbdac2cce5cc166346160e19cb693ef4e8bin0 -> 31 bytes
-rw-r--r--tests/resources/cherrypick/.gitted/refs/heads/automerge-branch1
-rw-r--r--tests/resources/cherrypick/.gitted/refs/heads/master1
-rw-r--r--tests/resources/cherrypick/.gitted/refs/heads/merge-branch1
-rw-r--r--tests/resources/cherrypick/.gitted/refs/heads/merge-conflicts1
-rw-r--r--tests/resources/cherrypick/.gitted/refs/heads/merge-mainline1
-rw-r--r--tests/resources/cherrypick/.gitted/refs/heads/orphan1
-rw-r--r--tests/resources/cherrypick/.gitted/refs/heads/renames1
-rw-r--r--tests/resources/cherrypick/file1.txt15
-rw-r--r--tests/resources/cherrypick/file2.txt15
-rw-r--r--tests/resources/cherrypick/file3.txt15
-rw-r--r--tests/resources/config/config-include2
-rw-r--r--tests/resources/config/config-included2
-rw-r--r--tests/resources/config/config115
-rw-r--r--tests/resources/config/config127
-rw-r--r--tests/resources/config/config132
-rw-r--r--tests/resources/config/config144
-rw-r--r--tests/resources/config/config153
-rw-r--r--tests/resources/config/config163
-rw-r--r--tests/resources/config/config173
-rw-r--r--tests/resources/config/config185
-rw-r--r--tests/resources/config/config195
-rw-r--r--tests/resources/config/config2011
-rw-r--r--tests/resources/config/config42
-rw-r--r--tests/resources/config/config96
-rw-r--r--tests/resources/crlf/.gitted/HEAD1
-rw-r--r--tests/resources/crlf/.gitted/config0
-rw-r--r--tests/resources/crlf/.gitted/objects/04/de00b358f13389948756732158eaaaefa1448cbin0 -> 28 bytes
-rw-r--r--tests/resources/crlf/.gitted/objects/0a/a76e474d259bd7c13eb726a1396c381db55c88bin0 -> 27 bytes
-rw-r--r--tests/resources/crlf/.gitted/objects/0d/06894e14df22e066763ae906e0ed3eb79c205fbin0 -> 134 bytes
-rw-r--r--tests/resources/crlf/.gitted/objects/0f/f5a53f19bfd2b5eea1ba550295c47515678987bin0 -> 29 bytes
-rw-r--r--tests/resources/crlf/.gitted/objects/12/faf3c1ea55f572473cec9052fca468c3584ccb1
-rw-r--r--tests/resources/crlf/.gitted/objects/2c/9a868cfdf8e270d0ec68164433376c68fb1789bin0 -> 169 bytes
-rw-r--r--tests/resources/crlf/.gitted/objects/38/1cfe630df902bc29271a202d3277981180e4a6bin0 -> 25 bytes
-rw-r--r--tests/resources/crlf/.gitted/objects/79/9770d1cff46753a57db7a066159b5610da6e3abin0 -> 20 bytes
-rw-r--r--tests/resources/crlf/.gitted/objects/7c/ce67e58173e2b01f7db124ceaabe3183d19c49bin0 -> 24 bytes
-rw-r--r--tests/resources/crlf/.gitted/objects/a9/a2e8913c1dbe2812fac5e6b4e0a4bd5d0d59661
-rw-r--r--tests/resources/crlf/.gitted/objects/ba/aa042ab2976f8264e467988e6112ee518ec62ebin0 -> 159 bytes
-rw-r--r--tests/resources/crlf/.gitted/objects/dc/88e3b917de821e25962bea7ec1f55c4ce2112cbin0 -> 32 bytes
-rw-r--r--tests/resources/crlf/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391bin0 -> 15 bytes
-rw-r--r--tests/resources/crlf/.gitted/objects/ea/030d3c6cec212069eca698cabaa5b4550f1511bin0 -> 32 bytes
-rw-r--r--tests/resources/crlf/.gitted/objects/ef/0dcd356d77221e9c27f4f3928ad28e80b87cebbin0 -> 168 bytes
-rw-r--r--tests/resources/crlf/.gitted/objects/fe/085d9ace90cc675b87df15e1aeed0c3a31407fbin0 -> 139 bytes
-rw-r--r--tests/resources/crlf/.gitted/refs/heads/master1
-rw-r--r--tests/resources/crlf/.gitted/refs/heads/utf81
-rw-r--r--tests/resources/deprecated-mode.git/HEAD1
-rw-r--r--tests/resources/deprecated-mode.git/config6
-rw-r--r--tests/resources/deprecated-mode.git/description1
-rw-r--r--tests/resources/deprecated-mode.git/indexbin0 -> 112 bytes
-rw-r--r--tests/resources/deprecated-mode.git/info/exclude2
-rw-r--r--tests/resources/deprecated-mode.git/objects/06/262edc257418e9987caf999f9a7a3e1547adffbin0 -> 124 bytes
-rw-r--r--tests/resources/deprecated-mode.git/objects/08/10fb7818088ff5ac41ee49199b51473b1bd6c7bin0 -> 350 bytes
-rw-r--r--tests/resources/deprecated-mode.git/objects/1b/05fdaa881ee45b48cbaa5e9b037d667a47745ebin0 -> 57 bytes
-rw-r--r--tests/resources/deprecated-mode.git/objects/3d/0970ec547fc41ef8a5882dde99c6adce65b021bin0 -> 29 bytes
-rw-r--r--tests/resources/deprecated-mode.git/refs/heads/master1
-rw-r--r--tests/resources/diff/.gitted/HEAD1
-rw-r--r--tests/resources/diff/.gitted/config6
-rw-r--r--tests/resources/diff/.gitted/description1
-rw-r--r--tests/resources/diff/.gitted/indexbin0 -> 225 bytes
-rw-r--r--tests/resources/diff/.gitted/info/exclude6
-rw-r--r--tests/resources/diff/.gitted/logs/HEAD2
-rw-r--r--tests/resources/diff/.gitted/logs/refs/heads/master2
-rw-r--r--tests/resources/diff/.gitted/objects/29/ab7053bb4dde0298e03e2c179e890b7dd465a7bin0 -> 730 bytes
-rw-r--r--tests/resources/diff/.gitted/objects/3e/5bcbad2a68e5bc60a53b8388eea53a1a7ab847bin0 -> 1108 bytes
-rw-r--r--tests/resources/diff/.gitted/objects/54/6c735f16a3b44d9784075c2c0dab2ac9bf1989bin0 -> 1110 bytes
-rw-r--r--tests/resources/diff/.gitted/objects/7a/9e0b02e63179929fed24f0a3e0f19168114d10bin0 -> 160 bytes
-rw-r--r--tests/resources/diff/.gitted/objects/7b/808f723a8ca90df319682c221187235af76693bin0 -> 922 bytes
-rw-r--r--tests/resources/diff/.gitted/objects/88/789109439c1e1c3cd45224001edee5304ed53c1
-rw-r--r--tests/resources/diff/.gitted/objects/cb/8294e696339863df760b2ff5d1e275bee72455bin0 -> 86 bytes
-rw-r--r--tests/resources/diff/.gitted/objects/d7/0d245ed97ed2aa596dd1af6536e4bfdb047b691
-rw-r--r--tests/resources/diff/.gitted/refs/heads/master1
-rw-r--r--tests/resources/diff/another.txt38
-rw-r--r--tests/resources/diff/readme.txt36
-rw-r--r--tests/resources/diff_format_email/.gitted/HEAD1
-rw-r--r--tests/resources/diff_format_email/.gitted/config7
-rw-r--r--tests/resources/diff_format_email/.gitted/indexbin0 -> 256 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/info/exclude6
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/0a/37045ca6d8503e9bcf06a12abbbc8e92664ccebin0 -> 29 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/10/808fe9c9be5a190c0ba68d1a002233fb363508bin0 -> 176 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/13/ecf3d572dbc5e5b32c8ba067d1d1e0939572e8bin0 -> 34 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/17/cfad36e93db7706b16bef5ef842ba1e5ca06abbin0 -> 155 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/1a/9932083f96b0db42552103d40076f62fa8235ebin0 -> 54 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/1a/e3be57f869687d983066a0f5d2aaea1b82ddc5bin0 -> 162 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/1b/525b0a6c5218b069b601ce91fce8eaf0a54e20bin0 -> 31 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/1e/82c3b234e37da82e5b23e0e2a70bca68ee12c6bin0 -> 28 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/1e/875da9b1e67f853b2eec3e202c21c867097234bin0 -> 121 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/20/609dbbc32bbfc827528eec3fcea2d024e6dd8abin0 -> 121 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/23/f92946d3f38bd090f700d3e8e7b728ffc58264bin0 -> 155 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/24/97c5249408494e66e25070a8c74e49eaeeb6c3bin0 -> 162 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/24/9a4263be23b4d1c02484cb840b6eca4c6cf74dbin0 -> 171 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/25/2a3e19fd2c6fb7b20c111142c5bd5fb9ea6b8ebin0 -> 121 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/27/93544db9060bab4f9169e5b89c82f9fa7c7fa6bin0 -> 120 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/29/1f1ff3cbb9a6f153678d9657679e3d4bf257dfbin0 -> 29 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/2f/f7b811eee62a73959350b1f7349f6f4d0c882dbin0 -> 54 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/39/91dce9e71a0641ca49a6a4eea6c9e7ff402ed4bin0 -> 166 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/45/eef2a9317e179984649de247269e38cd5d99cf2
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/4a/076277b884c519a932be67e346db2ac80a98fabin0 -> 40 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/4c/3bd7182ad66ea7aa20ba47ae82812b710d169cbin0 -> 179 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/4c/a10087e696d2ba78d07b146a118e9a7096ed4fbin0 -> 173 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/4d/de2b17d1c982cd988f21d24350a214401e4a1ebin0 -> 121 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/4f/31e0248ac800a1edc78b74f74e86f5eba90e87bin0 -> 54 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/50/17c9456d013b2c7712d29aab73b681c880f509bin0 -> 54 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/50/438cfa585c1d15cf3650ed1bf641da937cc261bin0 -> 123 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/52/c3cd1ff6234b95fecbaf9ef13624da17697b8dbin0 -> 41 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/55/0d730ba1b8c4937ea170b37c7ba91d792c0aaabin0 -> 123 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/66/81f1844dc677e5ff07ffd993461f5c441e6af5bin0 -> 35 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/69/ddefb5c245e2f9ee62bd4cabd8ebe60a01e448bin0 -> 54 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/6b/6c2067c6d968f9bddb9b900ee1ab7e5b0674302
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/6b/ef49b206b29d9c46456e075722cd1a48b41e4cbin0 -> 121 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/6c/15659c036377aebf3b4569959ca1f5bedb551fbin0 -> 167 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/6e/05acc5a5dab507d91a0a0cc0fb05a3dd98892d2
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/74/6d514eae0c330261d37940cab33aa97fefbd931
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/74/a4d5394ebcfa7e9f445680897dfbc96586bc86bin0 -> 38 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/77/d0a3ed37236a7941d564f08d68d3b36462d2312
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/7a/de76dd34bba4733cf9878079f9fd4a456a91893
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/7a/ff11da95ca2be0bfb74b06e7cc1c480559dbe7bin0 -> 26 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/7f/854619451620f7fbcec7ea171675e615ce92b6bin0 -> 179 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/87/3806f6f27e631eb0b23e4b56bea2bfac14a373bin0 -> 181 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/89/47a46e2097638ca6040ad4877246f4186ec3bd2
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/89/7d3af16ca9e420cd071b1c4541bd2b91d04c8c1
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/8d/7523f6fcb2404257889abe0d96f093d9f524f91
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/8d/fa038554d5b682a51bda8ee3038cee6c63be76bin0 -> 120 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/92/64b96c6d104d0e07ae33d3007b6a48246c6f92bin0 -> 181 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/94/350226b3aa14efac831c803a51f7a09f3fc31abin0 -> 24 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/94/75e21dcbc515af8f641576400e4b450e5f4c03bin0 -> 62 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/94/aaae8954e8bb613de636071da663a621695911bin0 -> 29 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/9a/2d780ac2ea0aeabdb9d2a876e6bbfff17b2c44bin0 -> 28 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/9a/c0329b8b7a4046210d8b8b02ac02055667de63bin0 -> 29 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/9a/c35ff15cd8864aeafd889e4826a3150f0b06c4bin0 -> 20 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/9b/997daca2a0beb5cc44b32c64f100a9a26d4d4bbin0 -> 22 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/a3/ac918e3a6604294b239cb956363e83d71abb3b1
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/a5/ac978d4f2a1784f847f41223a34c3e78934238bin0 -> 54 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/a7/29eab45c84563135e8631d4010230bc0479f1fbin0 -> 40 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/a9/7157a0d0571698728b6f2f7675b456c98c5961bin0 -> 62 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/af/8f41d0cb7a3079a8f8e231ea2ab8b97837ce13bin0 -> 50 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/b0/5cecf1949d192b6df852b3f71853ef820ee235bin0 -> 37 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/b4/f457c219dbb3517be908d4e70f0ada2fd8b8f9bin0 -> 54 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/bd/474b2519cc15eab801ff851cc7d50f0dee49a1bin0 -> 18 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/bd/f7ba6bc5c4e57ca6595928dcbe6753c8a663ffbin0 -> 35 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/cb/a89408dc016f4caddb6dc886fcb58f587a78df3
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/cd/471f0d8770371e1bc78bcbb38db4c7e4106bd2bin0 -> 180 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/cd/ed722d05305c6b181f188c118d2d9810f39bb8bin0 -> 163 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/ce/2792fcae8d704a56901754a0583a7418a21d8abin0 -> 121 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/d1/4aa252e52a709d03a3d3d0d965e177eb0a674e1
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/d7/bb447df12c6a8aba8727005482fb211f11297abin0 -> 156 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/db/e8727e4806ae88ccc3f0755cae8f8cb7efa2ccbin0 -> 175 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/e1/2af77c510e8ce4c261a3758736109c2c2dd1f0bin0 -> 51 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391bin0 -> 15 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/e9/091231467304a5ef112de02361d795ef051ee1bin0 -> 24 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/ee/251372f131d82e575f16fe51c778406d88f8c22
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/f3/d35bd592fefd8280fc0c302fa9f27dbdd721a31
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/f4/07be01334e07bfb8f57cd2078f0ee3eb61e0851
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/f9/e215d309644e24fa50d6bd6e6eedba166e56bc2
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/fc/a0c10eb9f1af6494a448d5733d283f5232a514bin0 -> 176 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/objects/ff/8d35b41494f7f0dc92f95d67f54fff274d3fcbbin0 -> 29 bytes
-rw-r--r--tests/resources/diff_format_email/.gitted/refs/heads/binary1
-rw-r--r--tests/resources/diff_format_email/.gitted/refs/heads/master1
-rw-r--r--tests/resources/diff_format_email/.gitted/refs/heads/multihunk1
-rw-r--r--tests/resources/diff_format_email/.gitted/refs/heads/rename1
-rwxr-xr-xtests/resources/diff_format_email/file1.txt.renamed17
-rw-r--r--tests/resources/diff_format_email/file2.txt5
-rw-r--r--tests/resources/diff_format_email/file3.txt5
-rw-r--r--tests/resources/duplicate.git/COMMIT_EDITMSG1
-rw-r--r--tests/resources/duplicate.git/HEAD1
-rw-r--r--tests/resources/duplicate.git/config5
-rw-r--r--tests/resources/duplicate.git/description1
-rw-r--r--tests/resources/duplicate.git/indexbin0 -> 104 bytes
-rw-r--r--tests/resources/duplicate.git/info/exclude6
-rw-r--r--tests/resources/duplicate.git/info/refs1
-rw-r--r--tests/resources/duplicate.git/logs/HEAD1
-rw-r--r--tests/resources/duplicate.git/logs/refs/heads/master1
-rw-r--r--tests/resources/duplicate.git/objects/03/8d718da6a1ebbc6a7780a96ed75a70cc2ad6e2bin0 -> 23 bytes
-rw-r--r--tests/resources/duplicate.git/objects/0d/deadede9e6d6ccddce0ee1e5749eed0485e5eabin0 -> 22 bytes
-rw-r--r--tests/resources/duplicate.git/objects/ce/013625030ba8dba906f756967f9e9ca394464abin0 -> 21 bytes
-rw-r--r--tests/resources/duplicate.git/objects/info/packs3
-rw-r--r--tests/resources/duplicate.git/objects/pack/pack-29a4896f0a0b9c9947b0927c57a5c03dcae052e3.idxbin0 -> 1184 bytes
-rw-r--r--tests/resources/duplicate.git/objects/pack/pack-29a4896f0a0b9c9947b0927c57a5c03dcae052e3.packbin0 -> 249 bytes
-rw-r--r--tests/resources/duplicate.git/objects/pack/pack-b18eeacbd65cbd30a365d7564b45a468e8bd43d6.idxbin0 -> 1268 bytes
-rw-r--r--tests/resources/duplicate.git/objects/pack/pack-b18eeacbd65cbd30a365d7564b45a468e8bd43d6.packbin0 -> 369 bytes
-rw-r--r--tests/resources/duplicate.git/objects/pack/pack-e87994ad581c9af946de0eb890175c08cd005f38.idxbin0 -> 1156 bytes
-rw-r--r--tests/resources/duplicate.git/objects/pack/pack-e87994ad581c9af946de0eb890175c08cd005f38.packbin0 -> 213 bytes
-rw-r--r--tests/resources/duplicate.git/objects/pack/pack-f4ef1aa326265de7d05018ee51acc0a8717fe1ea.idxbin0 -> 1100 bytes
-rw-r--r--tests/resources/duplicate.git/objects/pack/pack-f4ef1aa326265de7d05018ee51acc0a8717fe1ea.packbin0 -> 47 bytes
-rw-r--r--tests/resources/duplicate.git/packed-refs2
-rw-r--r--tests/resources/duplicate.git/refs/heads/dummy-marker.txt1
-rw-r--r--tests/resources/filemodes/.gitted/HEAD1
-rw-r--r--tests/resources/filemodes/.gitted/config6
-rw-r--r--tests/resources/filemodes/.gitted/description1
-rw-r--r--tests/resources/filemodes/.gitted/indexbin0 -> 528 bytes
-rw-r--r--tests/resources/filemodes/.gitted/info/exclude6
-rw-r--r--tests/resources/filemodes/.gitted/logs/HEAD1
-rw-r--r--tests/resources/filemodes/.gitted/logs/refs/heads/master1
-rw-r--r--tests/resources/filemodes/.gitted/objects/99/62c8453ba6f0cf8dac7c5dcc2fa2897fa9964abin0 -> 139 bytes
-rw-r--r--tests/resources/filemodes/.gitted/objects/a5/c5dd0fc6c313159a69b1d19d7f61a9f978e8f1bin0 -> 21 bytes
-rw-r--r--tests/resources/filemodes/.gitted/objects/e7/48d196331bcb20267eaaee4ff3326cb73b8182bin0 -> 99 bytes
-rw-r--r--tests/resources/filemodes/.gitted/refs/heads/master1
-rw-r--r--tests/resources/filemodes/exec_off1
-rwxr-xr-xtests/resources/filemodes/exec_off2on_staged1
-rwxr-xr-xtests/resources/filemodes/exec_off2on_workdir1
-rw-r--r--tests/resources/filemodes/exec_off_untracked1
-rwxr-xr-xtests/resources/filemodes/exec_on1
-rw-r--r--tests/resources/filemodes/exec_on2off_staged1
-rw-r--r--tests/resources/filemodes/exec_on2off_workdir1
-rwxr-xr-xtests/resources/filemodes/exec_on_untracked1
-rw-r--r--tests/resources/icase/.gitted/HEAD1
-rw-r--r--tests/resources/icase/.gitted/config7
-rw-r--r--tests/resources/icase/.gitted/description1
-rw-r--r--tests/resources/icase/.gitted/indexbin0 -> 1392 bytes
-rw-r--r--tests/resources/icase/.gitted/info/exclude6
-rw-r--r--tests/resources/icase/.gitted/logs/HEAD1
-rw-r--r--tests/resources/icase/.gitted/logs/refs/heads/master1
-rw-r--r--tests/resources/icase/.gitted/objects/3e/257c57f136a1cb8f2b8e9a2e5bc8ec0258bdcebin0 -> 114 bytes
-rw-r--r--tests/resources/icase/.gitted/objects/4d/d6027d083575c7431396dc2a3174afeb393c93bin0 -> 61 bytes
-rw-r--r--tests/resources/icase/.gitted/objects/62/e0af52c199ec731fe4ad230041cd3286192d49bin0 -> 19 bytes
-rw-r--r--tests/resources/icase/.gitted/objects/76/d6e1d231b1085fcce151427e9899335de74be63
-rw-r--r--tests/resources/icase/.gitted/objects/d4/4e18fb93b7107b5cd1b95d601591d77869a1b6bin0 -> 21 bytes
-rw-r--r--tests/resources/icase/.gitted/refs/heads/master1
-rw-r--r--tests/resources/icase/B1
-rw-r--r--tests/resources/icase/D1
-rw-r--r--tests/resources/icase/F1
-rw-r--r--tests/resources/icase/H1
-rw-r--r--tests/resources/icase/J1
-rw-r--r--tests/resources/icase/L/11
-rw-r--r--tests/resources/icase/L/B1
-rw-r--r--tests/resources/icase/L/D1
-rw-r--r--tests/resources/icase/L/a1
-rw-r--r--tests/resources/icase/L/c1
-rw-r--r--tests/resources/icase/a1
-rw-r--r--tests/resources/icase/c1
-rw-r--r--tests/resources/icase/e1
-rw-r--r--tests/resources/icase/g1
-rw-r--r--tests/resources/icase/i1
-rw-r--r--tests/resources/icase/k/11
-rw-r--r--tests/resources/icase/k/B1
-rw-r--r--tests/resources/icase/k/D1
-rw-r--r--tests/resources/icase/k/a1
-rw-r--r--tests/resources/icase/k/c1
-rw-r--r--tests/resources/issue_1397/.gitted/HEAD1
-rw-r--r--tests/resources/issue_1397/.gitted/config6
-rw-r--r--tests/resources/issue_1397/.gitted/indexbin0 -> 233 bytes
-rw-r--r--tests/resources/issue_1397/.gitted/objects/7f/483a738f867e5b21c8f377d70311f011eb48b53
-rw-r--r--tests/resources/issue_1397/.gitted/objects/83/12e0889a9cbab77c732b6bc39b51a683e3a318bin0 -> 48 bytes
-rw-r--r--tests/resources/issue_1397/.gitted/objects/8a/7ef047fc933edb62e84e7977b0612ec3f6f283bin0 -> 141 bytes
-rw-r--r--tests/resources/issue_1397/.gitted/objects/8e/8f80088a9274fd23584992f587083ca1bcbbacbin0 -> 63 bytes
-rw-r--r--tests/resources/issue_1397/.gitted/objects/f2/c62dea0372a0578e053697d5c1ba1ac05e774abin0 -> 94 bytes
-rw-r--r--tests/resources/issue_1397/.gitted/objects/ff/3578d64d199d5b48d92bbb569e0a273e411741bin0 -> 73 bytes
-rw-r--r--tests/resources/issue_1397/.gitted/refs/heads/master1
-rw-r--r--tests/resources/issue_1397/crlf_file.txt3
-rw-r--r--tests/resources/issue_1397/some_other_crlf_file.txt3
-rw-r--r--tests/resources/issue_592/.gitted/COMMIT_EDITMSG1
-rw-r--r--tests/resources/issue_592/.gitted/HEAD1
-rw-r--r--tests/resources/issue_592/.gitted/config8
-rw-r--r--tests/resources/issue_592/.gitted/indexbin0 -> 392 bytes
-rw-r--r--tests/resources/issue_592/.gitted/info/exclude6
-rw-r--r--tests/resources/issue_592/.gitted/logs/HEAD2
-rw-r--r--tests/resources/issue_592/.gitted/logs/refs/heads/master2
-rw-r--r--tests/resources/issue_592/.gitted/objects/06/07ee9d4ccce8e4c4fa13c2c7d727e7faba4e0ebin0 -> 87 bytes
-rw-r--r--tests/resources/issue_592/.gitted/objects/49/363a72a90d9424240258cd3759f23788ecf1d8bin0 -> 55 bytes
-rw-r--r--tests/resources/issue_592/.gitted/objects/4d/383e87f0371ba8fa353f3912db6862b2625e852
-rw-r--r--tests/resources/issue_592/.gitted/objects/71/44be264b61825fbff68046fe999bdfe96a1792bin0 -> 50 bytes
-rw-r--r--tests/resources/issue_592/.gitted/objects/be/de83ee10b5b3f00239660b00acec2d55fd0b84bin0 -> 107 bytes
-rw-r--r--tests/resources/issue_592/.gitted/objects/e3/8fcc7a6060f5eb5b876e836b52ae4769363f21bin0 -> 137 bytes
-rw-r--r--tests/resources/issue_592/.gitted/objects/f1/adef63cb08891a0942b76fc4b9c50c6c494bc7bin0 -> 29 bytes
-rw-r--r--tests/resources/issue_592/.gitted/refs/heads/master1
-rw-r--r--tests/resources/issue_592/a.txt1
-rw-r--r--tests/resources/issue_592/c/a.txt1
-rw-r--r--tests/resources/issue_592/l.txt1
-rw-r--r--tests/resources/issue_592/t/a.txt1
-rw-r--r--tests/resources/issue_592/t/b.txt1
-rw-r--r--tests/resources/issue_592b/.gitted/HEAD1
-rw-r--r--tests/resources/issue_592b/.gitted/config6
-rw-r--r--tests/resources/issue_592b/.gitted/description1
-rw-r--r--tests/resources/issue_592b/.gitted/indexbin0 -> 376 bytes
-rw-r--r--tests/resources/issue_592b/.gitted/info/exclude6
-rw-r--r--tests/resources/issue_592b/.gitted/logs/HEAD1
-rw-r--r--tests/resources/issue_592b/.gitted/logs/refs/heads/master1
-rw-r--r--tests/resources/issue_592b/.gitted/objects/3f/bf1852f72fd268e36457b13a18cdd9a4c9ea352
-rw-r--r--tests/resources/issue_592b/.gitted/objects/6f/a891d3e578c83e1c03bdb9e0fdd8e6e934157fbin0 -> 28 bytes
-rw-r--r--tests/resources/issue_592b/.gitted/objects/80/07d41d5794e6ce4d4d2c97e370d5a9aa6d5213bin0 -> 24 bytes
-rw-r--r--tests/resources/issue_592b/.gitted/objects/a6/5fb6583a7c425284142f285bc359a2d6565513bin0 -> 93 bytes
-rw-r--r--tests/resources/issue_592b/.gitted/objects/ae/be7a55922c7097ef91ca3a7bc327a901d87c2cbin0 -> 122 bytes
-rw-r--r--tests/resources/issue_592b/.gitted/objects/b3/44b055867fcdc1f01eaa75056a43e868eb4fbcbin0 -> 36 bytes
-rw-r--r--tests/resources/issue_592b/.gitted/objects/f7/d75fbfad8b1d2e307ced287ea78aad403cdce3bin0 -> 57 bytes
-rw-r--r--tests/resources/issue_592b/.gitted/refs/heads/master1
-rw-r--r--tests/resources/issue_592b/gitignore1
-rw-r--r--tests/resources/issue_592b/ignored/contained/ignored3.txt1
-rw-r--r--tests/resources/issue_592b/ignored/contained/tracked3.txt1
-rw-r--r--tests/resources/issue_592b/ignored/ignored2.txt1
-rw-r--r--tests/resources/issue_592b/ignored/tracked2.txt1
-rw-r--r--tests/resources/issue_592b/ignored1.txt1
-rw-r--r--tests/resources/issue_592b/tracked1.txt1
-rw-r--r--tests/resources/merge-resolve/.gitted/COMMIT_EDITMSG1
-rw-r--r--tests/resources/merge-resolve/.gitted/HEAD1
-rw-r--r--tests/resources/merge-resolve/.gitted/ORIG_HEAD1
-rw-r--r--tests/resources/merge-resolve/.gitted/config8
-rw-r--r--tests/resources/merge-resolve/.gitted/description1
-rw-r--r--tests/resources/merge-resolve/.gitted/indexbin0 -> 624 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/HEAD236
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/branch2
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/df_ancestor5
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/df_side114
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/df_side29
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/ff_branch5
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/master5
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/octo12
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/octo22
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/octo32
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/octo42
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/octo52
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/octo63
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/renames12
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/renames23
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-103
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-10-branch2
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-113
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-11-branch2
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-133
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-13-branch2
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-143
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-14-branch2
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-2alt2
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-2alt-branch2
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-3alt3
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-3alt-branch1
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-42
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-4-branch2
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-12
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-1-branch2
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-23
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-2-branch2
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-63
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-6-branch2
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-73
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-7-branch5
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-83
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-8-branch2
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-93
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-9-branch2
-rw-r--r--tests/resources/merge-resolve/.gitted/logs/refs/heads/unrelated1
-rw-r--r--tests/resources/merge-resolve/.gitted/modules/submodule/HEAD1
-rw-r--r--tests/resources/merge-resolve/.gitted/modules/submodule/ORIG_HEAD1
-rw-r--r--tests/resources/merge-resolve/.gitted/modules/submodule/config15
-rw-r--r--tests/resources/merge-resolve/.gitted/modules/submodule/indexbin0 -> 153 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/modules/submodule/info/exclude6
-rw-r--r--tests/resources/merge-resolve/.gitted/modules/submodule/objects/18/fae1354bba0a5f1e6a531f9988369142c24a9ebin0 -> 54 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/modules/submodule/objects/29/7aa6cd028b3336c7802c7a6f49143da4e1602dbin0 -> 161 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/modules/submodule/objects/38/6c80dc813b89d719797668f40c1be0a6efa996bin0 -> 32 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/modules/submodule/objects/ab/435a147bae6d5906ecfd0916a570c4ab3eeea8bin0 -> 64 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/modules/submodule/objects/ad/16e0a7684ea95bf892980a2ee412293ae979ccbin0 -> 64 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/modules/submodule/objects/ae/39c77c70cb6bad18bb471912460c4e1ba0f5862
-rw-r--r--tests/resources/merge-resolve/.gitted/modules/submodule/objects/c2/0765f6e24e8bbb63a648d0d11d84da63170190bin0 -> 52 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/modules/submodule/objects/d3/d806a4bef96889117fd7ebac0e3cb5ec1529323
-rw-r--r--tests/resources/merge-resolve/.gitted/modules/submodule/objects/f1/065ff5593604072837fecaad3e2e268cb0147bbin0 -> 64 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/modules/submodule/packed-refs3
-rw-r--r--tests/resources/merge-resolve/.gitted/modules/submodule/refs/heads/master1
-rw-r--r--tests/resources/merge-resolve/.gitted/modules/submodule/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/00/5b6fcc8fec71d2550bef8462d169b3c26aa14bbin0 -> 168 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/00/9b9cab6fdac02915a88ecd078b7a792ed802d8bin0 -> 164 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/00/c7d33f1ffa79d19c2272b370fcaeaadba49c08bin0 -> 147 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/01/f149e1b8f84bd8896aaff6d6b22af88459ded0bin0 -> 166 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/02/04a84f822acbf6386b36d33f1f6bc68bbbf858bin0 -> 168 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/02/251f990ca8e92e7ae61d3426163fa821c64001bin0 -> 264 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/03/21415405cb906c46869919af56d51dbbe5e85cbin0 -> 271 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/03/2ebc5ab85d9553bb187d3cd40875ff23a63ed0bin0 -> 29 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/03/b87706555accbf874ccd410dbda01e8e70a67fbin0 -> 353 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/03/dad1005e5d06d418f50b12e0bcd48ff2306a03bin0 -> 264 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/05/1ffd7901a442faf56b226161649074f15c7c471
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/05/8541fc37114bfc1dddf6bd6bffc7fae5c2e6febin0 -> 63 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/05/f3c1a2a56ca95c3d2ef28dc9ddf32b5cd6c91cbin0 -> 170 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/07/a759da919f737221791d542f176ab49c88837fbin0 -> 165 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/07/c514b04698e068892b31c8d352b85813b99c6ebin0 -> 32 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/09/055301463b7f2f8ee5d368f8ed5c0a40ad8515bin0 -> 41 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/09/17bb159596aea4d295f4857da77e8f96b3c7dcbin0 -> 36 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/09/2ce8682d7f3a2a3a769a6daca58950168ba5c4bin0 -> 163 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/09/3bebf072dd4bbba88833667d6ffe454df199e1bin0 -> 266 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/09/768bed22680cdb0859683fa9677ccc8d5a25c1bin0 -> 275 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/0a/75d9aac1dc84fb5aa51f7325c0ab53242ddef7bin0 -> 275 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/0c/fd6c54ef6532d862408f562309dc9c74a401e8bin0 -> 28 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/0d/52e3a556e189ba0948ae56780918011c1b167dbin0 -> 235 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/0d/872f8e871a30208305978ecbf9e66d864f1638bin0 -> 89 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/0e/c5f433959cd46177f745903353efb5be08d151bin0 -> 165 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/0f/3fc5dddc8964b9ac1040d0e957f9eb02d9efb3bin0 -> 47 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/11/aeee27ac45a8402c2fd5b875d66dd844e5df00bin0 -> 51 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/11/deab00b2d3a6f5a3073988ac050c2d7b6655e2bin0 -> 34 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/11/f4f3c08b737f5fd896cbefa1425ee63b21b2fa1
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/13/d1be4ea52a6ced1d7a1d832f0ee3c399348e5ebin0 -> 168 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/14/39088f509b79b1535b64193137d3ce4b240734bin0 -> 58 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/15/8dc7bedb202f5b26502bf3574faa7f4238d56c2
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/16/f825815cfd20a07a75c71554e82d8eede0b0611
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/17/8940b450f238a56c0d75b7955cb57b38191982bin0 -> 65 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/18/3310e30fb1499af8c619108ffea4d300b5e778bin0 -> 170 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/18/cb316b1cefa0f8a6946f0e201a8e1a6f845ab9bin0 -> 68 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/19/b7ac485269b672a101060894de3ba9c2a24dd1bin0 -> 53 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/1a/010b1c0f081b2e8901d55307a15c29ff30af0ebin0 -> 19 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/1c/51d885170f57a0c4e8c69ff6363d91a5b51f85bin0 -> 30 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/1c/ff9ec6a47a537380dedfdd17c9e76d74259a2bbin0 -> 33 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/1e/4ff029aee68d0d69ef9eb6efa6cbf1ec732f99bin0 -> 29 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/1f/81433e3161efbf250576c58fede7f6b836f3d3bin0 -> 262 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/20/91d94c8bd3eb0835dc5220de5e8bb310fa1513bin0 -> 271 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/21/671e290278286fb2ce4c63d01699b67adce331bin0 -> 79 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/22/7792b52aaa0b238bea00ec7e509b02623f168cbin0 -> 102 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/23/3c0919c998ed110a4b6ff36f353aec8b713487bin0 -> 43 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/23/92a2dacc9efb562b8635d6579fb458751c7c5bbin0 -> 142 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/23/ed141a6ae1e798b2f721afedbe947c119111babin0 -> 30 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/24/1a1005cd9b980732741b74385b891142bcba28bin0 -> 67 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/24/2591eb280ee9eeb2ce63524b9a8b9bc4cb515dbin0 -> 30 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/24/90b9f1a079420870027deefb49f51d6656cf74bin0 -> 268 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/25/9d08ca43af9200e9ea9a098e44a5a350ebd9b3bin0 -> 381 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/25/c40b7660c08c8fb581f770312f41b9b03119d1bin0 -> 31 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/26/153a3ff3649b6c2bb652d3f06878c6e0a172f9bin0 -> 48 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/27/133da702ba3c60af2a01e96c2555ff4045d692bin0 -> 32 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/27/4bbe983022fb4c02f8a2bf2ebe8da4fe130054bin0 -> 24 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/2b/0de5dc27505dcdd83a75c8bf1fcd9462cd7addbin0 -> 147 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/2b/5f1f181ee3b58ea751f5dd5d8f9b445520a136bin0 -> 53 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/2b/d0a343aeef7a2cf0d158478966a6e587ff3863bin0 -> 56 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/2b/fdd7e1b6c6ae993f23dfe8e84a8e06a772fa2abin0 -> 231 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/2d/a538570bc1e5b2c3e855bf702f35248ad0735f2
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/2f/2e37b7ebbae467978610896ca3aafcdad2ee67bin0 -> 52 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/2f/4024ce528d36d8670c289cce5a7963e625bb0cbin0 -> 179 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/2f/56120107d680129a5d9791b521cb1e73a2ed313
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/2f/598248eeccfc27e5ca44d9d96383f6dfea7b161
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/31/68dca1a561889b045a6441909f4c56145e666d2
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/31/d5472536041a83d986829240bbbdc897c6f8a6bin0 -> 41 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/32/21dd512b7e2dc4b5bd03046df6c81b2ab2070bbin0 -> 47 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/33/46d64325b39e5323733492cd55f808994a2475bin0 -> 33 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/33/d500f588fbbe65901d82b4e6b008e549064be02
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/34/8dcd41e2b467991578e92bedd16971b877ef1ebin0 -> 51 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/34/bfafff88eaf118402b44e6f3e2dbbf1a582b051
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/35/0c6eb3010efc403a6bed682332635314e9ed58bin0 -> 92 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/35/411bfb77cd2cc431f3a03a2b4976ed94b5d241bin0 -> 31 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/35/4704d3613ad4228e4786fc76656b11e98236c4bin0 -> 41 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/35/632e43612c06a3ea924bfbacd48333da874c291
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/35/75826c96a975031d2c14368529cc5c4353a8fdbin0 -> 163 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/36/219b49367146cb2e6a1555b5a9ebd4d0328495bin0 -> 68 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/36/4bbe4ce80c7bd31e6307dce77d46e3e1759fb3bin0 -> 35 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/37/48859b001c6e627e712a07951aee40afd19b41bin0 -> 41 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/38/5c8a0f26ddf79e9041e15e17dc352ed2c4cced2
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/3b/47b031b3e55ae11e14a05260b1c3ffd6838d55bin0 -> 161 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/3b/bf0bf59b20df5d5fc58b9fc1dc07be637c301fbin0 -> 269 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/3e/f4d30382ca33fdeba9fda895a99e0891ba37aabin0 -> 36 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/3e/f9bfe82f9635518ae89152322f3b46fd4ba25bbin0 -> 172 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/40/2784a46a4a3982294231594cbeb431f506d22cbin0 -> 83 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/41/2b32fb66137366147f1801ecc962452757d48a2
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/42/18670ab81cc219a9f94befb5c5dad90ec52648bin0 -> 47 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/43/aafd43bea779ec74317dc361f45ae3f532a505bin0 -> 37 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/43/c338656342227a3a3cd3aa85cbf784061f5425bin0 -> 266 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/45/299c1ca5e07bba1fd90843056fb559f96b1f5abin0 -> 58 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/46/6daf8552b891e5c22bc58c9d7fc1a2eb8f0289bin0 -> 382 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/47/6dbb3e207313d1d8aaa120c6ad204bf1295e53bin0 -> 522 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/47/8172cb2f5ff9b514bc9d04d3bd5ef5840cb3b2bin0 -> 165 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/49/130a28ef567af9a6a6104c38773fedfa5f9742bin0 -> 37 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/49/9df817155e4bdd3c6ee192a72c52f481818230bin0 -> 35 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/49/fd9edac79d15c8fbfca2d481cbb900beba22a63
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/4a/9550ebcc97ce22b22f45af7b829bb030d003f5bin0 -> 53 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/4b/253da36a0ae8bfce63aeabd8c5b584299255942
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/4b/48deed3a433909bfd6b6ab3d4b91348b6af464bin0 -> 24 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904bin0 -> 15 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/4c/9fac0707f8d4195037ae5a681aa48626491541bin0 -> 167 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/4c/a408a8c88655f7586a1b580be6fad138121e98bin0 -> 159 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/4e/0d9401aee78eb345a8685a859d37c8c3c0bbedbin0 -> 262 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/4e/886e602529caa9ab11d71f86634bd1b6e0de10bin0 -> 56 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/4e/b04c9e79e88f6640d01ff5b25ca2a60764f216bin0 -> 34 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/4f/e93c0ec83eb6305cbace3dace88ecee1b63cb6bin0 -> 161 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/50/12fd565b1393bdfda1805d4ec38ce6619e1fd1bin0 -> 29 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/50/4f75ac95a71ef98051817618576a68505b92f9bin0 -> 93 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/50/84fc2a88b6bdba8db93bd3953a8f4fdb470238bin0 -> 53 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/50/ce7d7d01217679e26c55939eef119e0c93e272bin0 -> 159 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/51/95a1b480f66691b667f10a9e41e70115a78351bin0 -> 170 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/52/d8bc572af2b6d4ee0d5e62ed5d1fbad92210a93
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/53/825f41ac8d640612f9423a2f03a69f3d96809abin0 -> 164 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/54/269b3f6ec3d7d4ede24dd350dd5d605495c3ae2
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/54/59c89aa0026d543ce8343bd89871bce543f9c23
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/54/7607c690372fe81fab8e3bb44c530e129118fdbin0 -> 58 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/55/b4e4687e7a0d9ca367016ed930f385d4022e6f1
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/56/6ab53c220a2eafc1212af1a024513230280ab93
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/56/a638b76b75e068590ac999c2f8621e7f3e264c1
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/57/079a46233ae2b6df62e9ade71c4948512abefbbin0 -> 168 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/58/43febcb23480df0b5edb22a21c59c772bb8e29bin0 -> 71 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/58/87a5e516c53bd58efb0f02ec6aa031b6fe9ad7bin0 -> 47 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/58/e853f66699fd02629fd50bde08082bc005933abin0 -> 160 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/59/6803b523203a4851c824c07366906f8353f4adbin0 -> 163 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/5c/2411f8075f48a6b2fdb85ebc0d371747c4df15bin0 -> 37 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/5c/341ead2ba6f2af98ce5ec3fe84f6b6d2899c0dbin0 -> 37 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/5c/3b68a71fc4fa5d362fd3875e53137c6a5ab7a5bin0 -> 40 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/5d/c1018e90b19654bee986b7a0c268804d39659dbin0 -> 168 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/5d/dd0fe66f990dc0e5cf9fec6d9b465240e9537fbin0 -> 43 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/5e/b7bb6a146eb3c7fd3990b240a2308eceb1cf8dbin0 -> 268 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/5f/bfbdc04b4eca46f54f4853a3c5a1dce28f5165bin0 -> 283 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/60/61fe116ecba0800c26113ea1a7dfac2e16eeafbin0 -> 87 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/60/91fc2c036a382a69489e3f518ee5aae9a4e567bin0 -> 258 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/61/340eeed7340fa6a8792def9a5938bb5d4434bbbin0 -> 92 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/61/78885b38fe96e825ac0f492c0a941f288b37f6bin0 -> 289 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/62/12c31dab5e482247d7977e4f0dd3601decf13bbin0 -> 45 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/62/269111c3b02a9355badcb9da8678b1bf41787bbin0 -> 269 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/62/33c6a0670228627f93c01cef32485a30403670bin0 -> 44 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/62/c4f6533c9a3894191fdcb96a3be935ade63f1abin0 -> 53 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/63/247125386de9ec90a27ad36169307bf8a11a381
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/67/110d77886b2af6309b9212961e72b8583e5fa91
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/67/18a45909532d1fcf5600d0877f7fe7e78f0b861
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/68/c6c84b091926c7d90aa6a79b2bc3bb6adccd8ebin0 -> 55 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/69/f570c57b24ea7c086e94c5e574964798321435bin0 -> 266 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/6a/e1a3967031a42cf955d9d5c2395211ac82f6cfbin0 -> 272 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/6b/7e37be8ce0b897093f2878a9dcd8f396beda2cbin0 -> 53 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/6c/06dcd163587c2cc18be44857e0b71116382aebbin0 -> 30 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/6e/3b9eb35214d4e31ed5789afc7d520ac798ce55bin0 -> 51 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/6f/32739c3724d1d5f855299309f388606f407468bin0 -> 630 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/6f/a33014764bf1120a454eb8437ae098238e409bbin0 -> 168 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/6f/be9fb85c86d7d1435f728da418bdff52c640a9bin0 -> 83 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/71/17467b18605a660ebe5586df69e2311ed5609fbin0 -> 265 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/71/2ebba6669ea847d9829e4f1059d6c830c8b531bin0 -> 152 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/71/add2d7b93d55bf3600f8a1582beceebbd050c8bin0 -> 264 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/72/cdb057b340205164478565e91eb71647e66891bin0 -> 65 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/72/ea499e108df5ff0a4a913e7655bbeeb1fb69f2bin0 -> 26 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/74/df13f0793afdaa972150bba976f7de8284914ebin0 -> 26 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/75/a811bf6bc57694adb3fe604786f3a4efd1cd1b2
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/76/63fce0130db092936b137cabd693ec234eb060bin0 -> 49 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/76/ab0e2868197ec158ddd6c78d8a0d2fd73d38f9bin0 -> 37 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/7a/a3edf2bcfee22398e6b55295aa56366b7aaf76bin0 -> 271 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/7a/f14d9c679baaef35555095f4f5d33e9a569ab9bin0 -> 149 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/7c/04ca611203ed320c5f495b9813054dd23be3be2
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/7c/2c5228c9e90170d4a35e6558e47163daf092e5bin0 -> 172 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/7c/b63eed597130ba4abb87b3e544b850219055203
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/7e/2d058d5fedf8329db44db4fac610d6b1a89159bin0 -> 165 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/7f/7a2da58126226986d71c6ddfab4afba693280dbin0 -> 199 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/80/a8fbb3abb1ba423d554e9630b8fc2e5698f86bbin0 -> 168 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/81/1c70fcb6d5bbd022d04cc31836d30b436f9551bin0 -> 169 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/81/87117062b750eed4f93fd7e899f17b52ce554dbin0 -> 170 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/83/07d93a155903a5c49576583f0ce1f6ff897c0ebin0 -> 30 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/83/6b8b82b26cab22eaaed8820877c76d6c8bca19bin0 -> 30 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/83/824a8c6658768e2013905219cc8c64cc3d9a2ebin0 -> 382 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/84/9619b03ae540acee4d1edec96b86993da6b4973
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/84/de84f8f3a6d63e636ee9ad81f4b80512fa9bbebin0 -> 41 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/86/088dae8bade454995b21a1c88107b0e1accdabbin0 -> 47 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/87/b4926260d77a3b851e71ecce06839bd650b231bin0 -> 43 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/88/e185910a15cd13bdf44854ad037f4842b03b29bin0 -> 177 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/8a/ad9d0ea334951da47b621a475b39cc6ed759bfbin0 -> 51 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/8a/ae714f7d939309d7f132b30646d96743134a9f1
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/8b/095d8fd01594f4d14454d073e3ac57b9ce485fbin0 -> 201 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/8b/5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a1
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/8b/7cd60d49ce3a1a770ece43b7d29b5cf462a33abin0 -> 82 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/8b/fb012a6d809e499bd8d3e194a3929bc8995b93bin0 -> 34 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/8c/749d9968d4b10dcfb06c9f97d0e5d92d3370712
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/8f/4433f8593ddd65b7dd43dd4564d841f4d9c8aabin0 -> 164 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/90/a336c7dacbe295159413559b0043b8bdc60d57bin0 -> 271 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/91/2b2d7819cf9c1029e414883857ed61d597a1a5bin0 -> 295 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/91/8bb3e09090a9995d48af9a2a6296d7e6088d1cbin0 -> 38 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/91/f44111cb1cb1358ac6944ad356ca1738813ea1bin0 -> 149 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/92/7d4943cdbdc9a667db8e62cfd0a41870235c51bin0 -> 535 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/93/77fccdb210540b8c0520cc6e80eb632c20bd25bin0 -> 53 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/94/4f5dd1a867cab4c2bbcb896493435cae1dcc1a2
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/94/8ba6e701c1edab0c2d394fb7c5538334129793bin0 -> 71 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/95/646149ab6b6ba6edc83cff678582538b457b2b3
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/95/9de65e568274120fdf9e3af9f77b1550122149bin0 -> 40 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/96/8ca794a4597f7f6abbb2b8d940b4078a0f3fd4bin0 -> 53 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/96/bca8d4f05cc4c5e33e4389f80a1309e86fe054bin0 -> 149 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/97/7c696519c5a3004c5f1d15d60c89dbeb8f235fbin0 -> 160 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/98/ba4205fcf31f5dd93c916d35fe3f3b3d0e67141
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/98/d52d07c0b0bbf2b46548f6aa521295c2cb55db3
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/99/b4f7e4f24470fa06b980bc21f1095c2a9425c0bin0 -> 164 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/9a/301fbe6fada7dcb74fcd7c20269b5c743459a7bin0 -> 163 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/9a/f731fa116d1eb9a6c0109562472cfee6f5a979bin0 -> 48 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/9c/0b6c34ef379a42d858f03fef38630f476b9102bin0 -> 38 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/9e/7f4359c469f309b6057febf4c6e80742cbed5bbin0 -> 539 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/9e/fe7723802d4305142eee177e018fee1572c4f4bin0 -> 36 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/9f/74397a3397b3585faf09e9926b110d7f654254bin0 -> 621 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/a0/31a28ae70e33a641ce4b8a8f6317f1ab79dee4bin0 -> 37 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/a3/9a620dae5bc8b4e771cd4d251b7d080401a21ebin0 -> 29 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/a3/fabece9eb8748da810e1e08266fef9b7136ad4bin0 -> 164 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/a4/1b1bb6d0be3c22fb654234c33b428e15c8cc27bin0 -> 92 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/a4/3150a738849c59376cf30bb2a68348a83c8f48bin0 -> 162 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/a5/563304ddf6caba25cb50323a2ea6f7dbfcadcabin0 -> 48 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/a7/08b253bd507417ec42d1467a7fd2d7519c4956bin0 -> 40 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/a7/65fb87eb2f7a1920b73b2d5a057f8f8476a42bbin0 -> 170 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/a7/7a56a49f8f3ae242e02717f18ebbc60c5cc543bin0 -> 65 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/a7/dbfcbfc1a60709cb80b5ca24539008456531d01
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/a8/02e06f1782a9645b9851bc7202cee74a8a4972bin0 -> 172 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/a8/87dd39ad3edd610fc9083dcb61e40ab50673d11
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/a9/0bc3fb6f15181972a2959a921429efbd81a4732
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ab/40af3cb8a3ed2e2843e96d9aa7871336b94573bin0 -> 161 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ab/6c44a2e84492ad4b41bb6bac87353e9d02ac8bbin0 -> 33 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ab/929391ac42572f92110f3deeb4f0844a951e22bin0 -> 40 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ac/4045f965119e6998f4340ed0f411decfb3ec05bin0 -> 29 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ad/01aebfdf2ac13145efafe3f9fcf798882f1730bin0 -> 158 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ad/26b598134264fd284292cb233fc0b2f25851dabin0 -> 43 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ad/a14492498136771f69dd451866cabcb0e9ef9abin0 -> 39 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ad/a55a45d14527dc3dfc714ea1c65d2e1e6fbe871
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/b2/d399ae15224e1d58066e3c8df70ce37de7a6562
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/b4/2712cfe99a1a500b2a51fe984e0b8a7702ba115
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/b6/9fe837e4cecfd4c9a40cdca7c138468687df072
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/b6/f610aef53bd343e6c96227de874c66f00ee8e8bin0 -> 162 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/b7/a2576f9fc20024ac9ef17cb134acbd1ac73127bin0 -> 320 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/b8/a3a806d3950e8c0a03a34f234a92eff0e2c68dbin0 -> 286 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ba/cac9b3493509aa15e1730e1545fc0919d1dae0bin0 -> 29 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/bc/744705e1d8a019993cf88f62bc4020f1b809192
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/bc/95c75d59386147d1e79a87c33068d8dbfd71f2bin0 -> 348 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/bd/593285fc7fe4ca18ccdbabf027f5d689101452bin0 -> 159 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/bd/867fbae2faa80b920b002b80b1c91bcade7784bin0 -> 48 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/bd/9cb4cd0a770cb9adcb5fce212142ef40ea1c35bin0 -> 51 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/be/f6e37b3ee632ba74159168836f382fed21d77d2
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/c0/6a9be584ac49aa02c5551312d9e2982c91df10bin0 -> 348 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/c1/b17981db0840109a820dae8674ee29684134ffbin0 -> 348 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/c1/b6a51bbb87c2f82b161412c3d20b59fc69b090bin0 -> 47 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/c3/5dee9bcc0e989f3b0c40f68372a9a51b6c4e6abin0 -> 162 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/c3/d02eeef75183df7584d8d13ac03053910c1301bin0 -> 67 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/c4/efe31e9decccc8b2b4d3df9aac2cdfe2995618bin0 -> 538 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/c5/0d0f1cb60b8b0fe1615ad20ace557e9d68d7bd1
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/c5/bbe550b9f09444bdddd3ecf3d97c0b42aa786cbin0 -> 269 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/c6/07fc30883e335def28cd686b51f6cfa02b06ec2
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/c6/92ecf62007c0ac9fb26e2aa884de2933de15edbin0 -> 40 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/c8/f06f2e3bb2964174677e91f0abead0e43c9e5dbin0 -> 45 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/c9/174cef549ec94ecbc43ef03cdc775b4950becb2
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/c9/4b27e41064c521120627e07e2035cca1d24ffabin0 -> 162 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ca/b2cf23998b40f1af2d9d9a756dc9e285a8df4bbin0 -> 40 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ca/ff6b7d44973f53e3e0cf31d0d695188b19aec6bin0 -> 54 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/cb/491780d82e46dc88a065b965ab307a038f2bc2bin0 -> 163 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/cb/6693a788715b82440a54e0eacd19ba9f6ec559bin0 -> 41 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/cc/338e4710c9b257106b8d16d82f86458d5beaf12
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/cc/3e3009134cb88014129fc8858d1101359e5e2f2
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ce/8860d49e3bea6fd745874a01b7c3e46da8cbc3bin0 -> 48 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ce/e656c392ad0557b3aae0fb411475c206e2926fbin0 -> 32 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/cf/8c5cc8a85a1ff5a4ba51e0bc7cf5665669924dbin0 -> 29 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/d0/7ec190c306ec690bac349e87d01c4358e49bb22
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/d0/d4594e16f2e19107e3fa7ea63e7aaaff305ffbbin0 -> 51 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/d2/f8637f2eab2507a1e13cbc9df4729ec386627ebin0 -> 268 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/d3/3cedf513c059e0515653fa2c2e386631387a05bin0 -> 46 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/d3/719a5ae8e4d92276b5313ce976f6ee5af2b4362
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/d3/7aa3bbfe1c0c49b909781251b956dbabe85f96bin0 -> 80 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/d3/7ad72a2052685fc6201c2af90103ad42d2079bbin0 -> 233 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/d4/207f77243500bec335ab477f9227fcdb1e271a2
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/d4/27e0b2e138501a3d15cc376077a3631e15bd46bin0 -> 38 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/d5/093787ef302b941b6aab081b99fb4880038bd8bin0 -> 30 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/d5/a61b0b4992a4f0caa887fa08b52431e727bb6fbin0 -> 81 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/d5/b6fc965c926a1bfc9ee456042b94088b5c5d21bin0 -> 319 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/d5/ec1152fe25e9fec00189eb00b3db71db24c218bin0 -> 24 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/d6/42b9770c66bba94a08df09b5efb095001f76d7bin0 -> 539 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/d6/462fa3f5292857db599c54aea2bf91616230c5bin0 -> 48 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/d6/cf6c7741b3316826af1314042550c97ded1d502
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/d7/308cc367b2cc23f710834ec1fd8ffbacf1b4601
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/d8/74671ef5b20184836cb983bb273e5280384d0bbin0 -> 162 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/d8/dec75ff2f8b41d1c5bfef0cd57b7300c834f66bin0 -> 164 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/d8/fa77b6833082c1ea36b7828a582d4c438824501
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/d9/63979c237d08b6ba39062ee7bf64c7d34a27f8bin0 -> 48 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/da/178208145ef585a1bd5ca5f4c9785d738df2cfbin0 -> 41 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/db/6261a7c65c7fd678520c9bb6f2c47582ab9ed5bin0 -> 624 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/dd/2ae5ab264e5592aa754235d5ad5eac8f0ecdfdbin0 -> 149 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/dd/9a570c3400e6e07bc4d7651d6e20b08926b3d9bin0 -> 36 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/de/872ee3618b894992e9d1e18ba2ebe256a112f91
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/df/e3f22baa1f6fce5447901c3086bae368de6bddbin0 -> 40 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/e0/67f9361140f19391472df8a82d6610813c73b7bin0 -> 53 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/e1/129b3cfb5898e0fbd606e0cb80b2755e50d161bin0 -> 92 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/e1/7ace1492648c9dc5701bad5c47af9d1b60c4e9bin0 -> 264 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/e2/c6abbd55fed5ac71a5f2751e29b4a34726a5951
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/e3/1e7ad3ed298f24e383c4950f4671993ec078e4bin0 -> 210 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/e3/76fbdd06ebf021c92724da9f26f44212734e3e3
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/e4/9f917b448d1340b31d76e54ba388268fd4c922bin0 -> 36 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/e4/f618a2c3ed0669308735727df5ebf2447f022f2
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/e5/060729746ca9888239cba08fdcf4bee907b406bin0 -> 24 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/e6/5a9bb2af9f4c2d1c375dd0f8f8a46cf9c68812bin0 -> 160 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/e8/107f24196736b870a318a0e28f048e29f6feff3
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/e9/2cdb7017dc6c5aed25cb4202c5b0104b872246bin0 -> 48 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/e9/ad6ec3e38364a3d07feda7c4197d4d845c53b5bin0 -> 36 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/e9/f48beccc62d535739bfbdebe0a55ed716d8366bin0 -> 382 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/eb/c09d0137cfb0c26697aed0109fb943ad906f3fbin0 -> 166 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ec/67e5a86adff465359f1c8f995e12dbdfa08d8abin0 -> 166 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ed/9523e62e453e50dd9be1606af19399b96e397abin0 -> 87 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ee/1d6f164893c1866a323f072eeed36b855656bebin0 -> 291 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ee/3fa1b8c00aff7fe02065fdb50864bb0d932ccfbin0 -> 64 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ee/a9286df54245fea72c5b557291470eb825f38fbin0 -> 235 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ef/58fdd8086c243bdc81f99e379acacfd21d32d62
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ef/c499524cf105d5264ac7fc54e07e95764e8075bin0 -> 32 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ef/c9121fdedaf08ba180b53ebfbcf71bd488ed09bin0 -> 160 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/f0/053b8060bb3f0be5cbcc3147a07ece26bf097ebin0 -> 163 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/f0/ce2b8e4986084d9b308fb72709e414c23eb5e6bin0 -> 125 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/f2/0c9063fa0bda9a397c96947a7b687305c49753bin0 -> 29 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/f2/9e7fb590551095230c6149cbe72f2e9104a796bin0 -> 41 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/f2/e1550a0c9e53d5811175864a29536642ae3821bin0 -> 73 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/f3/293571dcd708b6a3faf03818cd2844d000e1981
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/f3/f1164b68b57b1995b658a828320e6df3081faebin0 -> 310 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/f4/15caf3fcad16304cb424b67f0ee6b12dc03aaebin0 -> 320 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/f4/8097eb340dc5a7cae55aabcf1faf4548aa821fbin0 -> 165 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/f5/504f36e6f4eb797a56fc5bac6c6c7f32969bf2bin0 -> 42 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/f5/b50c85a87cac64d7eb3254cdd1aec9564c0293bin0 -> 35 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/f5/f9dd5886a6ee20272be0aafc790cba43b31931bin0 -> 244 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/f6/65b45cde9b568009c6e6b7b568e89cfe717df8bin0 -> 132 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/f6/be049e284c0f9dcbbc745543885be3502ea521bin0 -> 265 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/f7/c332bd4d4d4b777366cae4d24d1687477576bfbin0 -> 156 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/f8/958bdf4d365a84a9a178b1f5f35ff1dacbd8842
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/fa/c03f2c5139618d87d53614c153823bf1f31396bin0 -> 76 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/fa/da9356aa3f74622327a3038ae9c6f92e1c5c1dbin0 -> 168 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/fb/738a106cfd097a4acb96ce132ecb1ad6c46b03bin0 -> 264 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/fc/4c636d6515e9e261f9260dbcf3cc6eca97ea08bin0 -> 29 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/fc/7d7b805f7a9428574f4f802b2e34cd20ab9d99bin0 -> 575 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/fc/90237dc4891fa6c69827fc465632225e391618bin0 -> 163 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/fd/57d2d6770fad8e9959124793a17f441b571e66bin0 -> 279 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/fd/89f8cffb663ac89095a0f9764902e93ceaca6a2
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/fe/5407fc50a53aecb41d1a6e9ea7b612e581af87bin0 -> 48 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ff/49d07869831ad761bbdaea026086f8789bcb00bin0 -> 24 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/objects/ff/b312248d607284c290023f9502eea010d34efdbin0 -> 68 bytes
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/branch1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/df_ancestor1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/df_side11
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/df_side21
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/ff_branch1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/master1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/octo11
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/octo21
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/octo31
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/octo41
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/octo51
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/octo61
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/previous1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/rename_conflict_ancestor1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/rename_conflict_ours1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/rename_conflict_theirs1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/renames11
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/renames21
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/submodules1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/submodules-branch1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/submodules-branch21
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-101
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-10-branch1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-111
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-11-branch1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-131
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-13-branch1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-141
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-14-branch1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-2alt1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-2alt-branch1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-3alt1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-3alt-branch1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-41
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-4-branch1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-11
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-1-branch1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-21
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-2-branch1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-61
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-6-branch1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-71
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-7-branch1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-81
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-8-branch1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-91
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/trivial-9-branch1
-rw-r--r--tests/resources/merge-resolve/.gitted/refs/heads/unrelated1
-rw-r--r--tests/resources/merge-resolve/added-in-master.txt1
-rw-r--r--tests/resources/merge-resolve/automergeable.txt9
-rw-r--r--tests/resources/merge-resolve/changed-in-branch.txt1
-rw-r--r--tests/resources/merge-resolve/changed-in-master.txt1
-rw-r--r--tests/resources/merge-resolve/conflicting.txt1
-rw-r--r--tests/resources/merge-resolve/removed-in-branch.txt1
-rw-r--r--tests/resources/merge-resolve/unchanged.txt1
-rw-r--r--tests/resources/mergedrepo/.gitted/COMMIT_EDITMSG1
-rw-r--r--tests/resources/mergedrepo/.gitted/HEAD1
-rw-r--r--tests/resources/mergedrepo/.gitted/MERGE_HEAD1
-rw-r--r--tests/resources/mergedrepo/.gitted/MERGE_MODE0
-rw-r--r--tests/resources/mergedrepo/.gitted/MERGE_MSG5
-rw-r--r--tests/resources/mergedrepo/.gitted/ORIG_HEAD1
-rw-r--r--tests/resources/mergedrepo/.gitted/config6
-rw-r--r--tests/resources/mergedrepo/.gitted/description1
-rw-r--r--tests/resources/mergedrepo/.gitted/indexbin0 -> 842 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/info/exclude6
-rw-r--r--tests/resources/mergedrepo/.gitted/logs/HEAD5
-rw-r--r--tests/resources/mergedrepo/.gitted/logs/refs/heads/branch2
-rw-r--r--tests/resources/mergedrepo/.gitted/logs/refs/heads/master2
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/03/db1d37504ca0c4f7c26d7776b0e28bdea08712bin0 -> 141 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/17/0efc1023e0ed2390150bb4469c8456b63e8f91bin0 -> 141 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/1f/85ca51b8e0aac893a621b61a9c2661d6aa6d81bin0 -> 34 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/22/0bd62631c8cf7a83ef39c6b94595f00517211ebin0 -> 42 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/32/d55d59265db86dd690f0a7fc563db43e2bc6a6bin0 -> 159 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/38/e2d82b9065a237904af4b780b4d68da6950534bin0 -> 74 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/3a/34580a35add43a4cf361e8e9a30060a905c8762
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/44/58b8bc9e72b6c8755ae456f60e9844d0538d8cbin0 -> 39 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/47/8871385b9cd03908c5383acfd568bef023c6b3bin0 -> 36 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/51/6bd85f78061e09ccc714561d7b504672cb52dabin0 -> 36 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/53/c1d95a01f4514b162066fc98564500c96c46adbin0 -> 45 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/6a/ea5f295304c36144ad6e9247a291b7f8112399bin0 -> 49 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/70/68e30a7f0090ae32db35dfa1e4189d8780fcb8bin0 -> 85 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/75/938de1e367098b3e9a7b1ec3c4ac4548afffe4bin0 -> 41 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/7b/26923aaf452b1977eb08617c59475fb3f74b71bin0 -> 41 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/84/af62840be1b1c47b778a8a249f3ff45155038cbin0 -> 40 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/88/71f7a2ee3addfc4ba39fbd0783c8e738d04cdabin0 -> 66 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/88/7b153b165d32409c70163e0f734c090f12f673bin0 -> 38 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/8a/ad34cc83733590e74b93d0f7cf00375e2a735abin0 -> 78 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/8b/3f43d2402825c200f835ca1762413e386fd0b2bin0 -> 57 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/8b/72416545c7e761b64cecad4f1686eae4078aa8bin0 -> 38 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/8f/3c06cff9a83757cec40c80bc9bf31a2582bde9bin0 -> 39 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/8f/fcc405925511824a2240a6d3686aa7f8c7ac50bin0 -> 140 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/9a/05ccb4e0f948de03128e095f39dae6976751c51
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/9d/81f82fccc7dcd7de7a1ffead1815294c2e092cbin0 -> 36 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/b7/cedb8ad4cbb22b6363f9578cbd749797f7ef0dbin0 -> 66 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/d0/1885ea594926eae9ba5b54ad76692af5969f51bin0 -> 55 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/e2/809157a7766f272e4cfe26e61ef2678a5357ff3
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/e6/2cac5c88b9928f2695b934c70efa4285324478bin0 -> 87 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/objects/f7/2784290c151092abf04ce6b875068547f70406bin0 -> 141 bytes
-rw-r--r--tests/resources/mergedrepo/.gitted/refs/heads/branch1
-rw-r--r--tests/resources/mergedrepo/.gitted/refs/heads/master1
-rw-r--r--tests/resources/mergedrepo/conflicts-one.txt5
-rw-r--r--tests/resources/mergedrepo/conflicts-two.txt5
-rw-r--r--tests/resources/mergedrepo/one.txt10
-rw-r--r--tests/resources/mergedrepo/two.txt12
-rw-r--r--tests/resources/partial-testrepo/.gitted/HEAD1
-rw-r--r--tests/resources/partial-testrepo/.gitted/config7
-rw-r--r--tests/resources/partial-testrepo/.gitted/indexbin0 -> 328 bytes
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08bin0 -> 19 bytes
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0ebin0 -> 163 bytes
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925bin0 -> 147 bytes
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7bin0 -> 51 bytes
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccdbin0 -> 119 bytes
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057bin0 -> 18 bytes
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd20452
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63bin0 -> 50 bytes
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf36442
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dccbin0 -> 50 bytes
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735bin0 -> 19 bytes
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60abin0 -> 119 bytes
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344dbin0 -> 82 bytes
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479bin0 -> 126 bytes
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a3
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f2
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bdbin0 -> 28 bytes
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6bin0 -> 26 bytes
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd3
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9bin0 -> 162 bytes
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83bin0 -> 147 bytes
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1bin0 -> 82 bytes
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92bin0 -> 24 bytes
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765bin0 -> 82 bytes
-rw-r--r--tests/resources/partial-testrepo/.gitted/objects/pack/.gitkeep0
-rw-r--r--tests/resources/partial-testrepo/.gitted/refs/heads/dir1
-rw-r--r--tests/resources/peeled.git/HEAD1
-rw-r--r--tests/resources/peeled.git/config8
-rw-r--r--tests/resources/peeled.git/objects/info/packs2
-rw-r--r--tests/resources/peeled.git/objects/pack/pack-e84773eaf3fce1774755580e3dbb8d9f3a1adc45.idxbin0 -> 1156 bytes
-rw-r--r--tests/resources/peeled.git/objects/pack/pack-e84773eaf3fce1774755580e3dbb8d9f3a1adc45.packbin0 -> 274 bytes
-rw-r--r--tests/resources/peeled.git/packed-refs6
-rw-r--r--tests/resources/peeled.git/refs/heads/master1
-rw-r--r--tests/resources/push.sh55
-rw-r--r--tests/resources/push_src/.gitted/COMMIT_EDITMSG1
-rw-r--r--tests/resources/push_src/.gitted/HEAD1
-rw-r--r--tests/resources/push_src/.gitted/ORIG_HEAD1
-rw-r--r--tests/resources/push_src/.gitted/config10
-rw-r--r--tests/resources/push_src/.gitted/description1
-rw-r--r--tests/resources/push_src/.gitted/indexbin0 -> 470 bytes
-rw-r--r--tests/resources/push_src/.gitted/info/exclude6
-rw-r--r--tests/resources/push_src/.gitted/logs/HEAD10
-rw-r--r--tests/resources/push_src/.gitted/logs/refs/heads/b11
-rw-r--r--tests/resources/push_src/.gitted/logs/refs/heads/b21
-rw-r--r--tests/resources/push_src/.gitted/logs/refs/heads/b32
-rw-r--r--tests/resources/push_src/.gitted/logs/refs/heads/b42
-rw-r--r--tests/resources/push_src/.gitted/logs/refs/heads/b52
-rw-r--r--tests/resources/push_src/.gitted/logs/refs/heads/master3
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/HEAD1
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/config15
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/description1
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/indexbin0 -> 256 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/info/exclude6
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/logs/HEAD1
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/logs/refs/heads/master1
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/logs/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/08/b041783f40edfe12bb406c9c9a8a040177c125bin0 -> 54 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08bin0 -> 19 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/18/1037049a54a1eb5fab404658a3a250b44335d7bin0 -> 51 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/18/10dff58d8a660512d4832e740f692884338ccdbin0 -> 119 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fdbin0 -> 122 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/1b/8cbad43e867676df601306689fe7c3def5e689bin0 -> 51 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9bbin0 -> 21 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10bin0 -> 168 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/27/0b8ea76056d5cad83af921837702d3e3c2924dbin0 -> 21 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7bin0 -> 44 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54bin0 -> 50 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022ccbin0 -> 23 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057bin0 -> 18 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd20452
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/4b/22b35d44b5a4f589edf3dc89196399771796eabin0 -> 44 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91bin0 -> 152 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/5b/5b025afb0b4c913b4c338a42934a3863bf36442
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/75/057dd4114e74cca1d750d0aee1647c903cb60abin0 -> 119 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af1
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/7b/4384978d2493e851f9cca7858815fac9b10980bin0 -> 145 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/81/4889a078c031f61ed08ab5fa863aea9314344dbin0 -> 82 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/84/96071c1b46c854b31185ea97743be6a8774479bin0 -> 126 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe2
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe181621
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4bin0 -> 50 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d22
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a3
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f2
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe45477503
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bdbin0 -> 28 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6bin0 -> 26 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/ae/90f12eea699729ed24555e40b9fd669da12a12bin0 -> 148 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d12
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593bin0 -> 80 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f6443
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd3
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/d0/7b0f9a8c89f1d9e74dc4fce6421dec5ef8a659bin0 -> 149 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487fbin0 -> 21 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/d7/1aab4f9b04b45ce09bcaa636a9be6231474759bin0 -> 79 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391bin0 -> 15 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0bin0 -> 21 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3bin0 -> 103 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1bin0 -> 82 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/fa/49b077972391ad58037050f2a75f74e3671e92bin0 -> 24 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/fd/093bff70906175335656e6ce6ae05783708765bin0 -> 82 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/fd/4959ce7510db09d4d8217fa2d1780413e05a09bin0 -> 152 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idxbin0 -> 46656 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.packbin0 -> 386089 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idxbin0 -> 1240 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.packbin0 -> 491 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idxbin0 -> 1240 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.packbin0 -> 498 bytes
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/packed-refs24
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/refs/heads/master1
-rw-r--r--tests/resources/push_src/.gitted/modules/submodule/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/push_src/.gitted/objects/08/585692ce06452da6f82ae66b90d98b55536fca1
-rw-r--r--tests/resources/push_src/.gitted/objects/27/b7ce66243eb1403862d05f958c002312df173d4
-rw-r--r--tests/resources/push_src/.gitted/objects/28/905c54ea45a4bed8d7b90f51bd8bd81eec8840bin0 -> 109 bytes
-rw-r--r--tests/resources/push_src/.gitted/objects/36/6226fb970ac0caa9d3f55967ab01334a548f60bin0 -> 20 bytes
-rw-r--r--tests/resources/push_src/.gitted/objects/36/f79b2846017d3761e0a02d0bccd573e0f90c572
-rw-r--r--tests/resources/push_src/.gitted/objects/5c/0bb3d1b9449d1cc69d7519fd05166f01840915bin0 -> 128 bytes
-rw-r--r--tests/resources/push_src/.gitted/objects/61/780798228d17af2d34fce4cfbdf35556832472bin0 -> 17 bytes
-rw-r--r--tests/resources/push_src/.gitted/objects/64/fd55f9b6390202db5e5666fd1fb339089fba4dbin0 -> 176 bytes
-rw-r--r--tests/resources/push_src/.gitted/objects/78/981922613b2afb6025042ff6bd878ac1994e85bin0 -> 17 bytes
-rw-r--r--tests/resources/push_src/.gitted/objects/80/5c54522e614f29f70d2413a0470247d8b424acbin0 -> 131 bytes
-rw-r--r--tests/resources/push_src/.gitted/objects/95/1bbbb90e2259a4c8950db78946784fb53fcbce2
-rw-r--r--tests/resources/push_src/.gitted/objects/a7/8705c3b2725f931d3ee05348d83cc26700f247bin0 -> 166 bytes
-rw-r--r--tests/resources/push_src/.gitted/objects/b4/83ae7ba66decee9aee971f501221dea84b14983
-rw-r--r--tests/resources/push_src/.gitted/objects/b4/e1f2b375a64c1ccd40c5ff6aa8bc96839ba4fdbin0 -> 148 bytes
-rw-r--r--tests/resources/push_src/.gitted/objects/c1/0409136a7a75e025fa502a1b2fd7b62b77d279bin0 -> 22 bytes
-rw-r--r--tests/resources/push_src/.gitted/objects/cd/881f90f2933db2e4cc26b8c71fe6037ac7fe4cbin0 -> 80 bytes
-rw-r--r--tests/resources/push_src/.gitted/objects/d9/b63a88223d8367516f50bd131a5f7349b7f3e42
-rw-r--r--tests/resources/push_src/.gitted/objects/dc/ab83249f6f9d1ed735d651352a80519339b591bin0 -> 80 bytes
-rw-r--r--tests/resources/push_src/.gitted/objects/ee/a4f2705eeec2db3813f2430829afce99cd00b5bin0 -> 141 bytes
-rw-r--r--tests/resources/push_src/.gitted/objects/f7/8a3106c85fb549c65198b2a2086276c6174928bin0 -> 65 bytes
-rw-r--r--tests/resources/push_src/.gitted/objects/f8/f7aefc2900a3d737cea9eee45729fd55761e1abin0 -> 50 bytes
-rw-r--r--tests/resources/push_src/.gitted/objects/fa/38b91f199934685819bea316186d8b008c52a22
-rw-r--r--tests/resources/push_src/.gitted/objects/ff/83aa4c5e5d28e3bcba2f5c6e2adc61286a4e5e4
-rw-r--r--tests/resources/push_src/.gitted/objects/ff/fe95c7fd0a37fa2ed702f8f93b56b2196b3925bin0 -> 109 bytes
-rw-r--r--tests/resources/push_src/.gitted/objects/pack/dummy0
-rw-r--r--tests/resources/push_src/.gitted/refs/heads/b11
-rw-r--r--tests/resources/push_src/.gitted/refs/heads/b21
-rw-r--r--tests/resources/push_src/.gitted/refs/heads/b31
-rw-r--r--tests/resources/push_src/.gitted/refs/heads/b41
-rw-r--r--tests/resources/push_src/.gitted/refs/heads/b51
-rw-r--r--tests/resources/push_src/.gitted/refs/heads/b61
-rw-r--r--tests/resources/push_src/.gitted/refs/tags/tag-blob1
-rw-r--r--tests/resources/push_src/.gitted/refs/tags/tag-commit1
-rw-r--r--tests/resources/push_src/.gitted/refs/tags/tag-commit-two1
-rw-r--r--tests/resources/push_src/.gitted/refs/tags/tag-lightweight1
-rw-r--r--tests/resources/push_src/.gitted/refs/tags/tag-tag1
-rw-r--r--tests/resources/push_src/.gitted/refs/tags/tag-tree1
-rw-r--r--tests/resources/push_src/a.txt2
-rw-r--r--tests/resources/push_src/fold/b.txt1
-rw-r--r--tests/resources/push_src/foldb.txt1
-rw-r--r--tests/resources/push_src/gitmodules3
-rw-r--r--tests/resources/push_src/submodule/.gitted1
-rw-r--r--tests/resources/push_src/submodule/README1
-rw-r--r--tests/resources/push_src/submodule/branch_file.txt2
-rw-r--r--tests/resources/push_src/submodule/new.txt1
-rw-r--r--tests/resources/renames/.gitted/HEAD1
-rw-r--r--tests/resources/renames/.gitted/config7
-rw-r--r--tests/resources/renames/.gitted/description1
-rw-r--r--tests/resources/renames/.gitted/indexbin0 -> 352 bytes
-rw-r--r--tests/resources/renames/.gitted/info/exclude6
-rw-r--r--tests/resources/renames/.gitted/logs/HEAD4
-rw-r--r--tests/resources/renames/.gitted/logs/refs/heads/master4
-rw-r--r--tests/resources/renames/.gitted/objects/03/da7ad872536bd448da8d88eb7165338bf923a7bin0 -> 90 bytes
-rw-r--r--tests/resources/renames/.gitted/objects/17/58bdd7c16a72ff7c17d8de0c957ced3ccad6455
-rw-r--r--tests/resources/renames/.gitted/objects/19/dd32dfb1520a64e5bbaae8dce6ef423dfa2f131
-rw-r--r--tests/resources/renames/.gitted/objects/1c/068dee5790ef1580cfc4cd670915b48d790084bin0 -> 176 bytes
-rw-r--r--tests/resources/renames/.gitted/objects/2b/c7f351d20b53f1c72c16c4b036e491c478c49abin0 -> 173 bytes
-rw-r--r--tests/resources/renames/.gitted/objects/31/e47d8c1fa36d7f8d537b96158e3f024de0a9f2bin0 -> 131 bytes
-rw-r--r--tests/resources/renames/.gitted/objects/35/92953ff3ea5e8ba700c429f3aefe33c8806754bin0 -> 80 bytes
-rw-r--r--tests/resources/renames/.gitted/objects/36/020db6cdacaa93497f31edcd8f242ff9bc366dbin0 -> 431 bytes
-rw-r--r--tests/resources/renames/.gitted/objects/3c/04741dd4b96c4ae4b00ec0f6e10c816a30aad2bin0 -> 159 bytes
-rw-r--r--tests/resources/renames/.gitted/objects/42/10ffd5c390b21dd5483375e75288dea9ede512bin0 -> 1145 bytes
-rw-r--r--tests/resources/renames/.gitted/objects/44/4a76ed3e45b183753f49376af30da8c3fe276abin0 -> 135 bytes
-rw-r--r--tests/resources/renames/.gitted/objects/47/184c1e7eb22abcbed2bf4ee87d4e38096f7951bin0 -> 229 bytes
-rw-r--r--tests/resources/renames/.gitted/objects/4e/4cae3e7dd56ed74bff39526d0469e554432953bin0 -> 452 bytes
-rw-r--r--tests/resources/renames/.gitted/objects/50/e90273af7d826ff0a95865bcd3ba8412c447d93
-rw-r--r--tests/resources/renames/.gitted/objects/5e/26abc56a5a84d89790f45416648899cbe13109bin0 -> 163 bytes
-rw-r--r--tests/resources/renames/.gitted/objects/61/8c6f2f8740bd6049b2fb9eb93fc15726462745bin0 -> 106 bytes
-rw-r--r--tests/resources/renames/.gitted/objects/66/311f5cfbe7836c27510a3ba2f43e282e2c8bbabin0 -> 1155 bytes
-rw-r--r--tests/resources/renames/.gitted/objects/93/f538c45a57a87eb4c1e86f91c6ee41d66c7ba7bin0 -> 229 bytes
-rw-r--r--tests/resources/renames/.gitted/objects/9a/69d960ae94b060f56c2a8702545e2bb1abb935bin0 -> 464 bytes
-rw-r--r--tests/resources/renames/.gitted/objects/ad/0a8e55a104ac54a8a29ed4b84b49e76837a113bin0 -> 415 bytes
-rw-r--r--tests/resources/renames/.gitted/objects/b9/25b224cc91f897001a9993fbce169fdaa8858fbin0 -> 76 bytes
-rw-r--r--tests/resources/renames/.gitted/objects/d7/9b202de198fa61b02424b9e25e840dc75e1323bin0 -> 421 bytes
-rw-r--r--tests/resources/renames/.gitted/objects/ea/c43f5195a2cee53b7458d8dad16aedde10711bbin0 -> 118 bytes
-rw-r--r--tests/resources/renames/.gitted/objects/ea/f4a3e3bfe68585e90cada20736ace491cd100b5
-rw-r--r--tests/resources/renames/.gitted/objects/f9/0d4fc20ecddf21eebe6a37e9225d244339d2b5bin0 -> 441 bytes
-rw-r--r--tests/resources/renames/.gitted/refs/heads/master1
-rw-r--r--tests/resources/renames/.gitted/refs/heads/renames_similar1
-rw-r--r--tests/resources/renames/.gitted/refs/heads/renames_similar_two1
-rw-r--r--tests/resources/renames/ikeepsix.txt27
-rw-r--r--tests/resources/renames/sixserving.txt25
-rw-r--r--tests/resources/renames/songof7cities.txt49
-rw-r--r--tests/resources/renames/untimely.txt24
-rw-r--r--tests/resources/revert/.gitted/HEAD1
-rw-r--r--tests/resources/revert/.gitted/config8
-rw-r--r--tests/resources/revert/.gitted/indexbin0 -> 464 bytes
-rw-r--r--tests/resources/revert/.gitted/info/exclude6
-rw-r--r--tests/resources/revert/.gitted/objects/0a/a8c7e40d342fff78d60b29a4ba8e993ed79c512
-rw-r--r--tests/resources/revert/.gitted/objects/0a/b09ea6d4c3634bdf6c221626d8b6f7dd890767bin0 -> 28 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/0a/d19525be6d8cae5e5deb2770fc244b65255057bin0 -> 133 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/0c/db66192ee192f70f891f05a47636057420e871bin0 -> 35 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/0f/5bfcf58c558d865da6be0281d7795993646ceebin0 -> 45 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/13/a6fdfd10bd74b1f258fb58801215985dd2e797bin0 -> 121 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/13/ee9cd5d8e1023c218e0e1ea684ec0c582b5050bin0 -> 161 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/15/6ef9bcb968dccec8472a0f2eff49f1a713bc6bbin0 -> 133 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/18/1aab27ddb37b40d9a284fb4733497006d57091bin0 -> 133 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/1b/c915c5cb7185a9438de28a7b1a7dfe8c01ee7fbin0 -> 1169 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/1f/a4e069a641f10f5fb7588138b2d147fcd22c36bin0 -> 133 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/1f/f0c423042b46cb1d617b81efb715defbe8054dbin0 -> 751 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/21/a96a98ed84d45866e1de6e266fd3a61a4ae9dcbin0 -> 19 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/29/6a6d3be1dff05c5d1f631d2459389fa7b619ebbin0 -> 40 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/2d/440f2b3147d3dc7ad1085813478d6d869d5a4d2
-rw-r--r--tests/resources/revert/.gitted/objects/33/c6fd981c49a2abf2971482089350bfc5cda8eabin0 -> 47 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/39/467716290f6df775a91cdb9a4eb39295018145bin0 -> 162 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/39/9fb3aba3d9d13f7d40a9254ce4402067ef31492
-rw-r--r--tests/resources/revert/.gitted/objects/3a/3ef367eaf3fe79effbfb0a56b269c04c2b59febin0 -> 33 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/46/ff0854663aeb2182b9838c8da68e33ac23bc1ebin0 -> 20 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/4b/8fcff56437e60f58e9a6bc630dd242ebf6ea2cbin0 -> 43 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/52/c95c4264245469a0617e289a7d737f156826b42
-rw-r--r--tests/resources/revert/.gitted/objects/55/568c8de5322ff9a95d72747a239cdb64a199651
-rw-r--r--tests/resources/revert/.gitted/objects/55/acf326a69f0aab7a974ec53ffa55a50bcac14ebin0 -> 30 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/5a/cdc74af27172ec491d213ee36cea7eb9ef25793
-rw-r--r--tests/resources/revert/.gitted/objects/5c/f1d643f100d8112834e540264546ba2c159976bin0 -> 86 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/6b/ccd0dc58cea5ccff86014f3d64b31bd8c02a37bin0 -> 171 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/71/eb9c2b53dbbf3c45fb28b27c850db4b7fb8011bin0 -> 148 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/72/333f47d4e83616630ff3b0ffe4c0faebcc3c45bin0 -> 172 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/73/ec36fa120f8066963a0bc9105bb273dbd903d7bin0 -> 31 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/74/7726e021bc5f44b86de60e3032fd6f9f1b8383bin0 -> 28 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/77/31926a337c4eaba1e2187d90ebfa0a93659382bin0 -> 37 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/83/f65df4606c4f8dbf8da43de25de1b7e4c03238bin0 -> 113 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/87/59ad453cf01cf7daf14e2a668f8218f9a678ebbin0 -> 122 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/8f/d40e13fff575b63e86af87175e70fa7fb92f80bin0 -> 80 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/97/f3574e92f1730d365fb9e00c10e3c507c1cfe9bin0 -> 115 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/9a/95fd974e03c5b93828ceedd28755965b5d5c60bin0 -> 122 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/a6/9f74efcb51634b88e04ea81273158a85257f41bin0 -> 146 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/a8/c86221b400b836010567cc3593db6e96c1a83abin0 -> 24 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/ac/c4d33902092efeb3b714aa0b1007c329e2f2e62
-rw-r--r--tests/resources/revert/.gitted/objects/b6/9d88e177455579896e2be495046e2a51456a9a1
-rw-r--r--tests/resources/revert/.gitted/objects/b7/a55408832174c54708906a372a9be2ffe3649bbin0 -> 133 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/be/ead165e017269e8dc0dd6f01195726a2e1e01bbin0 -> 133 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/ca/f99de3a49827117bb66721010eac461b06a80cbin0 -> 33 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/ce/f56612d71a6af8d8015691e4865f7fece905b5bin0 -> 174 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/d1/d403d22cbe24592d725f442835cf46fe60c8acbin0 -> 164 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/dd/9a159c89509e73fd37d6af99619994cf7dfc06bin0 -> 133 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/de/03538407ed18914ff05657eeff70425c0f304d2
-rw-r--r--tests/resources/revert/.gitted/objects/e3/4ef1afe54eb526fd92eec66084125f340f1d65bin0 -> 150 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/ea/392a157085bc32daccd59aa1998fe2f5fb9fc0bin0 -> 134 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/eb/b03002cee5d66c7732dd06241119fe72ab96a52
-rw-r--r--tests/resources/revert/.gitted/objects/ee/c6adcb2f3ceca0cadeccfe01b19382252ece9bbin0 -> 66 bytes
-rw-r--r--tests/resources/revert/.gitted/objects/f4/e107c230d08a60fb419d19869f1f282b272d9cbin0 -> 30 bytes
-rw-r--r--tests/resources/revert/.gitted/refs/heads/master1
-rw-r--r--tests/resources/revert/.gitted/refs/heads/merges1
-rw-r--r--tests/resources/revert/.gitted/refs/heads/merges-branch1
-rw-r--r--tests/resources/revert/.gitted/refs/heads/reverted-branch1
-rw-r--r--tests/resources/revert/.gitted/refs/heads/two1
-rw-r--r--tests/resources/revert/file1.txt14
-rw-r--r--tests/resources/revert/file2.txt16
-rw-r--r--tests/resources/revert/file3.txt16
-rw-r--r--tests/resources/revert/file6.txt14
-rw-r--r--tests/resources/shallow.git/HEAD1
-rw-r--r--tests/resources/shallow.git/config8
-rw-r--r--tests/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.idxbin0 -> 1324 bytes
-rw-r--r--tests/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.packbin0 -> 791 bytes
-rw-r--r--tests/resources/shallow.git/packed-refs2
-rw-r--r--tests/resources/shallow.git/refs/.gitkeep0
-rw-r--r--tests/resources/shallow.git/shallow1
-rw-r--r--tests/resources/short_tag.git/HEAD1
-rw-r--r--tests/resources/short_tag.git/config5
-rw-r--r--tests/resources/short_tag.git/indexbin0 -> 104 bytes
-rw-r--r--tests/resources/short_tag.git/objects/4a/5ed60bafcf4638b7c8356bd4ce1916bfede93cbin0 -> 169 bytes
-rw-r--r--tests/resources/short_tag.git/objects/4d/5fcadc293a348e88f777dc0920f11e7d71441cbin0 -> 48 bytes
-rw-r--r--tests/resources/short_tag.git/objects/5d/a7760512a953e3c7c4e47e4392c7a4338fb7291
-rw-r--r--tests/resources/short_tag.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391bin0 -> 15 bytes
-rw-r--r--tests/resources/short_tag.git/packed-refs1
-rw-r--r--tests/resources/short_tag.git/refs/heads/master1
-rw-r--r--tests/resources/status/.gitted/COMMIT_EDITMSG2
-rw-r--r--tests/resources/status/.gitted/ORIG_HEAD2
-rw-r--r--tests/resources/status/.gitted/indexbin1080 -> 1160 bytes
-rw-r--r--tests/resources/status/.gitted/info/exclude2
-rw-r--r--tests/resources/status/.gitted/logs/HEAD1
-rw-r--r--tests/resources/status/.gitted/logs/refs/heads/master1
-rw-r--r--tests/resources/status/.gitted/objects/26/a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f2
-rw-r--r--tests/resources/status/.gitted/objects/37/fcb02ccc1a85d1941e7f106d52dc3702dcf0d0bin0 -> 331 bytes
-rw-r--r--tests/resources/status/.gitted/objects/e8/ee89e15bbe9b20137715232387b3de5b28972ebin0 -> 38 bytes
-rw-r--r--tests/resources/status/.gitted/refs/heads/master2
-rw-r--r--tests/resources/status/ignored_file1
-rw-r--r--tests/resources/status/subdir.txt2
-rw-r--r--tests/resources/status/è¿™1
-rw-r--r--tests/resources/submod2/.gitted/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/config20
-rw-r--r--tests/resources/submod2/.gitted/description1
-rw-r--r--tests/resources/submod2/.gitted/indexbin0 -> 944 bytes
-rw-r--r--tests/resources/submod2/.gitted/info/exclude6
-rw-r--r--tests/resources/submod2/.gitted/logs/HEAD4
-rw-r--r--tests/resources/submod2/.gitted/logs/refs/heads/master4
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/config13
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/description1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/indexbin0 -> 192 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/info/exclude6
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/refs/heads/master1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1bin0 -> 55 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484bin0 -> 53 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5bin0 -> 93 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/41/bd4bc3df978de695f67ace64c560913da11653bin0 -> 163 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/48/0095882d281ed676fe5b863569520e54a7d5c0bin0 -> 163 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/5e/4963595a9774b90524d35a807169049de8ccadbin0 -> 167 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/6b/31c659545507c381e9cd34ec508f16c04e149e2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/73/ba924a80437097795ae839e66e187c55d3babfbin0 -> 93 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/78/9efbdadaa4a582778d4584385495559ea0994b2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6ebin0 -> 81 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5bin0 -> 93 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/packed-refs2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/refs/heads/master1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/config13
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/description1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/indexbin0 -> 192 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/info/exclude6
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/logs/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/logs/refs/heads/master1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/logs/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1bin0 -> 55 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484bin0 -> 53 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5bin0 -> 93 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/objects/41/bd4bc3df978de695f67ace64c560913da11653bin0 -> 163 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0bin0 -> 163 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/objects/5e/4963595a9774b90524d35a807169049de8ccadbin0 -> 167 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/objects/73/ba924a80437097795ae839e66e187c55d3babfbin0 -> 93 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6ebin0 -> 81 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5bin0 -> 93 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/packed-refs2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/refs/heads/master1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_file/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/COMMIT_EDITMSG1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/config13
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/description1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/indexbin0 -> 192 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/info/exclude6
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/logs/HEAD2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/logs/refs/heads/master2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/logs/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1bin0 -> 55 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484bin0 -> 53 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5bin0 -> 93 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/objects/3d/9386c507f6b093471a3e324085657a3c2b42473
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/objects/41/bd4bc3df978de695f67ace64c560913da11653bin0 -> 163 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/objects/48/0095882d281ed676fe5b863569520e54a7d5c0bin0 -> 163 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/objects/5e/4963595a9774b90524d35a807169049de8ccadbin0 -> 167 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/objects/6b/31c659545507c381e9cd34ec508f16c04e149e2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/objects/73/ba924a80437097795ae839e66e187c55d3babfbin0 -> 93 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/objects/77/fb0ed3e58568d6ad362c78de08ab8649d76e29bin0 -> 93 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/objects/78/9efbdadaa4a582778d4584385495559ea0994b2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6ebin0 -> 81 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/objects/8e/b1e637ed9fc8e5454fa20d38f809091f9395f42
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5bin0 -> 93 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/packed-refs2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/refs/heads/master1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_head/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/config13
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/description1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/indexbin0 -> 192 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/info/exclude6
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/logs/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/logs/refs/heads/master1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/logs/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1bin0 -> 55 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484bin0 -> 53 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5bin0 -> 93 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/objects/41/bd4bc3df978de695f67ace64c560913da11653bin0 -> 163 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/objects/48/0095882d281ed676fe5b863569520e54a7d5c0bin0 -> 163 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/objects/5e/4963595a9774b90524d35a807169049de8ccadbin0 -> 167 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/objects/6b/31c659545507c381e9cd34ec508f16c04e149e2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/objects/73/ba924a80437097795ae839e66e187c55d3babfbin0 -> 93 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/objects/78/9efbdadaa4a582778d4584385495559ea0994b2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6ebin0 -> 81 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/objects/a0/2d31770687965547ab7a04cee199b29ee458d6bin0 -> 134 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5bin0 -> 93 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/packed-refs2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/refs/heads/master1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_index/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/config13
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/description1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/indexbin0 -> 192 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/info/exclude6
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/refs/heads/master1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1bin0 -> 55 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484bin0 -> 53 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5bin0 -> 93 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/41/bd4bc3df978de695f67ace64c560913da11653bin0 -> 163 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0bin0 -> 163 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/5e/4963595a9774b90524d35a807169049de8ccadbin0 -> 167 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/73/ba924a80437097795ae839e66e187c55d3babfbin0 -> 93 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6ebin0 -> 81 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5bin0 -> 93 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/packed-refs2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/refs/heads/master1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_missing_commits/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_missing_commits/config13
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_missing_commits/description1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_missing_commits/indexbin0 -> 192 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_missing_commits/info/exclude6
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_missing_commits/logs/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_missing_commits/logs/refs/heads/master1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_missing_commits/logs/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1bin0 -> 55 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484bin0 -> 53 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5bin0 -> 93 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/41/bd4bc3df978de695f67ace64c560913da11653bin0 -> 163 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/5e/4963595a9774b90524d35a807169049de8ccadbin0 -> 167 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/6b/31c659545507c381e9cd34ec508f16c04e149e2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6ebin0 -> 81 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5bin0 -> 93 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_missing_commits/packed-refs2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_missing_commits/refs/heads/master1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_missing_commits/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/config13
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/description1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/indexbin0 -> 192 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/info/exclude6
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/logs/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/logs/refs/heads/master1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/logs/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1bin0 -> 55 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484bin0 -> 53 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5bin0 -> 93 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/objects/41/bd4bc3df978de695f67ace64c560913da11653bin0 -> 163 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/objects/48/0095882d281ed676fe5b863569520e54a7d5c0bin0 -> 163 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/objects/5e/4963595a9774b90524d35a807169049de8ccadbin0 -> 167 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/objects/6b/31c659545507c381e9cd34ec508f16c04e149e2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/objects/73/ba924a80437097795ae839e66e187c55d3babfbin0 -> 93 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/objects/78/9efbdadaa4a582778d4584385495559ea0994b2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6ebin0 -> 81 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5bin0 -> 93 bytes
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/packed-refs2
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/refs/heads/master1
-rw-r--r--tests/resources/submod2/.gitted/modules/sm_unchanged/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/submod2/.gitted/objects/09/460e5b6cbcb05a3e404593c32a3aa7221eca0ebin0 -> 197 bytes
-rw-r--r--tests/resources/submod2/.gitted/objects/14/fe9ccf104058df25e0a08361c4494e167ef2431
-rw-r--r--tests/resources/submod2/.gitted/objects/22/ce3e0311dda73a5992d54a4a595518d3876ea74
-rw-r--r--tests/resources/submod2/.gitted/objects/25/5546424b0efb847b1bfc91dbf7348b277f8970bin0 -> 157 bytes
-rw-r--r--tests/resources/submod2/.gitted/objects/2a/30f1e6f94b20917005a21273f65b406d0f8badbin0 -> 144 bytes
-rw-r--r--tests/resources/submod2/.gitted/objects/42/cfb95cd01bf9225b659b5ee3edcc78e8eeb478bin0 -> 40 bytes
-rw-r--r--tests/resources/submod2/.gitted/objects/57/958699c2dc394f81cfc76950e9c3ac3025c398bin0 -> 136 bytes
-rw-r--r--tests/resources/submod2/.gitted/objects/59/01da4f1c67756eeadc5121d206bec2431f253b2
-rw-r--r--tests/resources/submod2/.gitted/objects/60/7d96653d4d0a4f733107f7890c2e67b55b620dbin0 -> 53 bytes
-rw-r--r--tests/resources/submod2/.gitted/objects/74/84482eb8db738cafa696993664607500a3f2b9bin0 -> 173 bytes
-rw-r--r--tests/resources/submod2/.gitted/objects/7b/a4c5c3561daa5ab1a86215cfb0587e96d404d6bin0 -> 48 bytes
-rw-r--r--tests/resources/submod2/.gitted/objects/87/3585b94bdeabccea991ea5e3ec1a277895b698bin0 -> 137 bytes
-rw-r--r--tests/resources/submod2/.gitted/objects/97/4cf7c73de336b0c4e019f918f3cee367d72e842
-rw-r--r--tests/resources/submod2/.gitted/objects/9d/bc299bc013ea253583b40bf327b5a6e4037b89bin0 -> 80 bytes
-rw-r--r--tests/resources/submod2/.gitted/objects/a9/104bf89e911387244ef499413960ba472066d9bin0 -> 165 bytes
-rw-r--r--tests/resources/submod2/.gitted/objects/b6/14088620bbdc1d29549d223ceba0f4419fd4cbbin0 -> 110 bytes
-rw-r--r--tests/resources/submod2/.gitted/objects/d4/07f19e50c1da1ff584beafe0d6dac7237c5d06bin0 -> 55 bytes
-rw-r--r--tests/resources/submod2/.gitted/objects/d9/3e95571d92cceb5de28c205f1d5f3cc8b88bc82
-rw-r--r--tests/resources/submod2/.gitted/objects/e3/b83bf274ee065eee48734cf8c6dfaf5e81471cbin0 -> 246 bytes
-rw-r--r--tests/resources/submod2/.gitted/objects/f5/4414c25e6d24fe39f5c3f128d7c8a17bc238332
-rw-r--r--tests/resources/submod2/.gitted/objects/f9/90a25a74d1a8281ce2ab018ea8df66795cd60b1
-rw-r--r--tests/resources/submod2/.gitted/refs/heads/master1
-rw-r--r--tests/resources/submod2/README.txt3
-rw-r--r--tests/resources/submod2/gitmodules24
-rw-r--r--tests/resources/submod2/just_a_dir/contents1
-rw-r--r--tests/resources/submod2/just_a_file1
-rw-r--r--tests/resources/submod2/not-submodule/.gitted/HEAD1
-rw-r--r--tests/resources/submod2/not-submodule/.gitted/config6
-rw-r--r--tests/resources/submod2/not-submodule/.gitted/description1
-rw-r--r--tests/resources/submod2/not-submodule/.gitted/indexbin0 -> 112 bytes
-rw-r--r--tests/resources/submod2/not-submodule/.gitted/info/exclude6
-rw-r--r--tests/resources/submod2/not-submodule/.gitted/logs/HEAD1
-rw-r--r--tests/resources/submod2/not-submodule/.gitted/logs/refs/heads/master1
-rw-r--r--tests/resources/submod2/not-submodule/.gitted/objects/68/e92c611b80ee1ed8f38314ff9577f0d15b2444bin0 -> 132 bytes
-rw-r--r--tests/resources/submod2/not-submodule/.gitted/objects/71/ff9927d7c8a5639e062c38a7d35c433c424627bin0 -> 52 bytes
-rw-r--r--tests/resources/submod2/not-submodule/.gitted/objects/f0/1d56b18efd353ef2bb93a4585d590a0847195ebin0 -> 55 bytes
-rw-r--r--tests/resources/submod2/not-submodule/.gitted/refs/heads/master1
-rw-r--r--tests/resources/submod2/not-submodule/README.txt1
-rw-r--r--tests/resources/submod2/not/.gitted/notempty1
-rw-r--r--tests/resources/submod2/not/README.txt1
-rw-r--r--tests/resources/submod2/sm_added_and_uncommited/.gitted1
-rw-r--r--tests/resources/submod2/sm_added_and_uncommited/README.txt3
-rw-r--r--tests/resources/submod2/sm_added_and_uncommited/file_to_modify3
-rw-r--r--tests/resources/submod2/sm_changed_file/.gitted1
-rw-r--r--tests/resources/submod2/sm_changed_file/README.txt3
-rw-r--r--tests/resources/submod2/sm_changed_file/file_to_modify4
-rw-r--r--tests/resources/submod2/sm_changed_head/.gitted1
-rw-r--r--tests/resources/submod2/sm_changed_head/README.txt3
-rw-r--r--tests/resources/submod2/sm_changed_head/file_to_modify4
-rw-r--r--tests/resources/submod2/sm_changed_index/.gitted1
-rw-r--r--tests/resources/submod2/sm_changed_index/README.txt3
-rw-r--r--tests/resources/submod2/sm_changed_index/file_to_modify4
-rw-r--r--tests/resources/submod2/sm_changed_untracked_file/.gitted1
-rw-r--r--tests/resources/submod2/sm_changed_untracked_file/README.txt3
-rw-r--r--tests/resources/submod2/sm_changed_untracked_file/file_to_modify3
-rw-r--r--tests/resources/submod2/sm_changed_untracked_file/i_am_untracked1
-rw-r--r--tests/resources/submod2/sm_missing_commits/.gitted1
-rw-r--r--tests/resources/submod2/sm_missing_commits/README.txt3
-rw-r--r--tests/resources/submod2/sm_missing_commits/file_to_modify3
-rw-r--r--tests/resources/submod2/sm_unchanged/.gitted1
-rw-r--r--tests/resources/submod2/sm_unchanged/README.txt3
-rw-r--r--tests/resources/submod2/sm_unchanged/file_to_modify3
-rw-r--r--tests/resources/submod2_target/.gitted/HEAD1
-rw-r--r--tests/resources/submod2_target/.gitted/config6
-rw-r--r--tests/resources/submod2_target/.gitted/description1
-rw-r--r--tests/resources/submod2_target/.gitted/indexbin0 -> 192 bytes
-rw-r--r--tests/resources/submod2_target/.gitted/info/exclude6
-rw-r--r--tests/resources/submod2_target/.gitted/logs/HEAD4
-rw-r--r--tests/resources/submod2_target/.gitted/logs/refs/heads/master4
-rw-r--r--tests/resources/submod2_target/.gitted/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1bin0 -> 55 bytes
-rw-r--r--tests/resources/submod2_target/.gitted/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484bin0 -> 53 bytes
-rw-r--r--tests/resources/submod2_target/.gitted/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5bin0 -> 93 bytes
-rw-r--r--tests/resources/submod2_target/.gitted/objects/41/bd4bc3df978de695f67ace64c560913da11653bin0 -> 163 bytes
-rw-r--r--tests/resources/submod2_target/.gitted/objects/48/0095882d281ed676fe5b863569520e54a7d5c0bin0 -> 163 bytes
-rw-r--r--tests/resources/submod2_target/.gitted/objects/5e/4963595a9774b90524d35a807169049de8ccadbin0 -> 167 bytes
-rw-r--r--tests/resources/submod2_target/.gitted/objects/6b/31c659545507c381e9cd34ec508f16c04e149e2
-rw-r--r--tests/resources/submod2_target/.gitted/objects/73/ba924a80437097795ae839e66e187c55d3babfbin0 -> 93 bytes
-rw-r--r--tests/resources/submod2_target/.gitted/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a2
-rw-r--r--tests/resources/submod2_target/.gitted/objects/78/9efbdadaa4a582778d4584385495559ea0994b2
-rw-r--r--tests/resources/submod2_target/.gitted/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6ebin0 -> 81 bytes
-rw-r--r--tests/resources/submod2_target/.gitted/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5bin0 -> 93 bytes
-rw-r--r--tests/resources/submod2_target/.gitted/refs/heads/master1
-rw-r--r--tests/resources/submod2_target/README.txt3
-rw-r--r--tests/resources/submod2_target/file_to_modify3
-rw-r--r--tests/resources/submodules/.gitted/HEAD1
-rw-r--r--tests/resources/submodules/.gitted/config6
-rw-r--r--tests/resources/submodules/.gitted/description1
-rw-r--r--tests/resources/submodules/.gitted/indexbin0 -> 408 bytes
-rw-r--r--tests/resources/submodules/.gitted/info/exclude8
-rw-r--r--tests/resources/submodules/.gitted/info/refs1
-rw-r--r--tests/resources/submodules/.gitted/logs/HEAD2
-rw-r--r--tests/resources/submodules/.gitted/logs/refs/heads/master2
-rw-r--r--tests/resources/submodules/.gitted/objects/26/a3b32a9b7d97486c5557f5902e8ac94638145e2
-rw-r--r--tests/resources/submodules/.gitted/objects/78/308c9251cf4eee8b25a76c7d2790c73d797357bin0 -> 97 bytes
-rw-r--r--tests/resources/submodules/.gitted/objects/97/896810b3210244a62a82458b8e0819ecfc68503
-rw-r--r--tests/resources/submodules/.gitted/objects/b6/0fd986699ba4e9e68bea07cf8e793f323ef888bin0 -> 138 bytes
-rw-r--r--tests/resources/submodules/.gitted/objects/d5/f7fc3f74f7dec08280f370a975b112e8f60818bin0 -> 21 bytes
-rw-r--r--tests/resources/submodules/.gitted/objects/e3/50052cc767cd1fcb37e84e9a89e701925be4aebin0 -> 120 bytes
-rw-r--r--tests/resources/submodules/.gitted/objects/info/packs2
-rw-r--r--tests/resources/submodules/.gitted/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.idxbin0 -> 1156 bytes
-rw-r--r--tests/resources/submodules/.gitted/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.packbin0 -> 228 bytes
-rw-r--r--tests/resources/submodules/.gitted/packed-refs2
-rw-r--r--tests/resources/submodules/.gitted/refs/heads/master1
-rw-r--r--tests/resources/submodules/added1
-rw-r--r--tests/resources/submodules/gitmodules6
-rw-r--r--tests/resources/submodules/ignored1
-rw-r--r--tests/resources/submodules/modified2
-rw-r--r--tests/resources/submodules/testrepo/.gitted/HEAD1
-rw-r--r--tests/resources/submodules/testrepo/.gitted/config12
-rw-r--r--tests/resources/submodules/testrepo/.gitted/description1
-rw-r--r--tests/resources/submodules/testrepo/.gitted/indexbin0 -> 256 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/info/exclude6
-rw-r--r--tests/resources/submodules/testrepo/.gitted/logs/HEAD1
-rw-r--r--tests/resources/submodules/testrepo/.gitted/logs/refs/heads/master1
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08bin0 -> 19 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7bin0 -> 51 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccdbin0 -> 119 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9bbin0 -> 21 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/27/0b8ea76056d5cad83af921837702d3e3c2924dbin0 -> 21 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54bin0 -> 50 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022ccbin0 -> 23 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057bin0 -> 18 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd20452
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf36442
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60abin0 -> 119 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af1
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/7b/4384978d2493e851f9cca7858815fac9b10980bin0 -> 145 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344dbin0 -> 82 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479bin0 -> 126 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe181621
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4bin0 -> 50 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a3
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f2
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe45477503
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bdbin0 -> 28 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6bin0 -> 26 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/ae/90f12eea699729ed24555e40b9fd669da12a12bin0 -> 148 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d12
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593bin0 -> 80 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f6443
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd3
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487fbin0 -> 21 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391bin0 -> 15 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0bin0 -> 21 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3bin0 -> 103 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1bin0 -> 82 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92bin0 -> 24 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765bin0 -> 82 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idxbin0 -> 46656 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.packbin0 -> 386089 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idxbin0 -> 1240 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.packbin0 -> 491 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idxbin0 -> 1240 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.packbin0 -> 498 bytes
-rw-r--r--tests/resources/submodules/testrepo/.gitted/packed-refs12
-rw-r--r--tests/resources/submodules/testrepo/.gitted/refs/heads/master1
-rw-r--r--tests/resources/submodules/testrepo/.gitted/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/submodules/testrepo/README1
-rw-r--r--tests/resources/submodules/testrepo/branch_file.txt2
-rw-r--r--tests/resources/submodules/testrepo/new.txt1
-rw-r--r--tests/resources/submodules/unmodified1
-rw-r--r--tests/resources/submodules/untracked1
-rw-r--r--tests/resources/template/branches/.gitignore2
-rw-r--r--tests/resources/template/description1
l---------tests/resources/template/hooks/link.sample1
-rwxr-xr-xtests/resources/template/hooks/update.sample9
-rw-r--r--tests/resources/template/info/exclude6
-rw-r--r--tests/resources/testrepo.git/FETCH_HEAD2
-rw-r--r--tests/resources/testrepo.git/HEAD_TRACKER (renamed from tests/resources/testrepo.git/head-tracker)0
-rw-r--r--tests/resources/testrepo.git/config32
-rw-r--r--tests/resources/testrepo.git/logs/HEAD7
-rw-r--r--tests/resources/testrepo.git/logs/refs/heads/br22
-rw-r--r--tests/resources/testrepo.git/logs/refs/heads/master2
-rw-r--r--tests/resources/testrepo.git/logs/refs/heads/not-good1
-rw-r--r--tests/resources/testrepo.git/logs/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/testrepo.git/logs/refs/remotes/test/master2
-rw-r--r--tests/resources/testrepo.git/objects/08/b041783f40edfe12bb406c9c9a8a040177c125bin0 -> 54 bytes
-rw-r--r--tests/resources/testrepo.git/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fdbin0 -> 122 bytes
-rw-r--r--tests/resources/testrepo.git/objects/1b/8cbad43e867676df601306689fe7c3def5e689bin0 -> 51 bytes
-rw-r--r--tests/resources/testrepo.git/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9bbin0 -> 21 bytes
-rw-r--r--tests/resources/testrepo.git/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10bin0 -> 168 bytes
-rw-r--r--tests/resources/testrepo.git/objects/27/0b8ea76056d5cad83af921837702d3e3c2924dbin0 -> 21 bytes
-rw-r--r--tests/resources/testrepo.git/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7bin0 -> 44 bytes
-rw-r--r--tests/resources/testrepo.git/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54bin0 -> 50 bytes
-rw-r--r--tests/resources/testrepo.git/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022ccbin0 -> 23 bytes
-rw-r--r--tests/resources/testrepo.git/objects/4a/23e2e65ad4e31c4c9db7dc746650bfad082679bin0 -> 83 bytes
-rw-r--r--tests/resources/testrepo.git/objects/4b/22b35d44b5a4f589edf3dc89196399771796eabin0 -> 44 bytes
-rw-r--r--tests/resources/testrepo.git/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91bin0 -> 152 bytes
-rw-r--r--tests/resources/testrepo.git/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af1
-rw-r--r--tests/resources/testrepo.git/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe2
-rw-r--r--tests/resources/testrepo.git/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe181621
-rw-r--r--tests/resources/testrepo.git/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4bin0 -> 50 bytes
-rw-r--r--tests/resources/testrepo.git/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d22
-rw-r--r--tests/resources/testrepo.git/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe45477503
-rw-r--r--tests/resources/testrepo.git/objects/ae/90f12eea699729ed24555e40b9fd669da12a12bin0 -> 148 bytes
-rw-r--r--tests/resources/testrepo.git/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593bin0 -> 80 bytes
-rw-r--r--tests/resources/testrepo.git/objects/d0/7b0f9a8c89f1d9e74dc4fce6421dec5ef8a659bin0 -> 149 bytes
-rw-r--r--tests/resources/testrepo.git/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487fbin0 -> 21 bytes
-rw-r--r--tests/resources/testrepo.git/objects/d7/1aab4f9b04b45ce09bcaa636a9be6231474759bin0 -> 79 bytes
-rw-r--r--tests/resources/testrepo.git/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0bin0 -> 21 bytes
-rw-r--r--tests/resources/testrepo.git/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3bin0 -> 103 bytes
-rw-r--r--tests/resources/testrepo.git/objects/fd/4959ce7510db09d4d8217fa2d1780413e05a09bin0 -> 152 bytes
-rw-r--r--tests/resources/testrepo.git/refs/heads/cannot-fetch1
-rw-r--r--tests/resources/testrepo.git/refs/heads/chomped1
-rw-r--r--tests/resources/testrepo.git/refs/heads/haacked1
-rw-r--r--tests/resources/testrepo.git/refs/heads/master2
-rw-r--r--tests/resources/testrepo.git/refs/heads/not-good1
-rw-r--r--tests/resources/testrepo.git/refs/heads/subtrees1
-rw-r--r--tests/resources/testrepo.git/refs/heads/track-local1
-rw-r--r--tests/resources/testrepo.git/refs/heads/trailing1
-rw-r--r--tests/resources/testrepo.git/refs/notes/fanout1
-rw-r--r--tests/resources/testrepo.git/refs/remotes/test/master1
-rw-r--r--tests/resources/testrepo.git/refs/tags/annotated_tag_to_blob1
-rw-r--r--tests/resources/testrepo.git/refs/tags/hard_tag1
-rw-r--r--tests/resources/testrepo.git/refs/tags/taggerless1
-rw-r--r--tests/resources/testrepo.git/refs/tags/wrapped_tag1
-rw-r--r--tests/resources/testrepo/.gitted/HEAD1
-rw-r--r--tests/resources/testrepo/.gitted/HEAD_TRACKER1
-rw-r--r--tests/resources/testrepo/.gitted/config8
-rw-r--r--tests/resources/testrepo/.gitted/indexbin0 -> 10041 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/09/9fabac3a9ea935598528c27f866e34089c2eff1
-rw-r--r--tests/resources/testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08bin0 -> 19 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0ebin0 -> 163 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925bin0 -> 147 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7bin0 -> 51 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccdbin0 -> 119 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/1d/d0968be3ff95fcaecb6fa4245662db9fdc4568bin0 -> 73 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9bbin0 -> 21 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/27/0b8ea76056d5cad83af921837702d3e3c2924dbin0 -> 21 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54bin0 -> 50 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022ccbin0 -> 23 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057bin0 -> 18 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/45/dd856fdd4d89b884c340ba0e047752d9b085d6bin0 -> 156 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd20452
-rw-r--r--tests/resources/testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63bin0 -> 50 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf36442
-rw-r--r--tests/resources/testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dccbin0 -> 50 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735bin0 -> 19 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/6b/377958d8c6a4906e8573b53672a1a23a4e8ce6bin0 -> 167 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/6b/9b767af9992b4abad5e24ffb1ba2d688ca602ebin0 -> 41 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60abin0 -> 119 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af1
-rw-r--r--tests/resources/testrepo/.gitted/objects/7b/2417a23b63e1fdde88c80e14b33247c6e5785abin0 -> 187 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/7b/4384978d2493e851f9cca7858815fac9b10980bin0 -> 145 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344dbin0 -> 82 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479bin0 -> 126 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/87/380ae84009e9c503506c2f6143a4fc6c60bf80bin0 -> 161 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe181621
-rw-r--r--tests/resources/testrepo/.gitted/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4bin0 -> 50 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a3
-rw-r--r--tests/resources/testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f2
-rw-r--r--tests/resources/testrepo/.gitted/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe45477503
-rw-r--r--tests/resources/testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bdbin0 -> 28 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6bin0 -> 26 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/ae/90f12eea699729ed24555e40b9fd669da12a12bin0 -> 148 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/af/e4393b2b2a965f06acf2ca9658eaa01e0cd6b6bin0 -> 171 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d12
-rw-r--r--tests/resources/testrepo/.gitted/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593bin0 -> 80 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f6443
-rw-r--r--tests/resources/testrepo/.gitted/objects/c0/528fd6cc988c0a40ce0be11bc192fc8dc5346ebin0 -> 22 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd3
-rw-r--r--tests/resources/testrepo/.gitted/objects/ce/054d4c5e3c83522aed8bc061987b46b7ede3bebin0 -> 194 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9bin0 -> 162 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83bin0 -> 147 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487fbin0 -> 21 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391bin0 -> 15 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0bin0 -> 21 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3bin0 -> 103 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1bin0 -> 82 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92bin0 -> 24 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765bin0 -> 82 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idxbin0 -> 46656 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.packbin0 -> 386089 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idxbin0 -> 1240 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.packbin0 -> 491 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idxbin0 -> 1240 bytes
-rw-r--r--tests/resources/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.packbin0 -> 498 bytes
-rw-r--r--tests/resources/testrepo/.gitted/packed-refs4
-rw-r--r--tests/resources/testrepo/.gitted/refs/heads/br21
-rw-r--r--tests/resources/testrepo/.gitted/refs/heads/dir1
-rw-r--r--tests/resources/testrepo/.gitted/refs/heads/long-file-name1
-rw-r--r--tests/resources/testrepo/.gitted/refs/heads/master1
-rw-r--r--tests/resources/testrepo/.gitted/refs/heads/packed-test1
-rw-r--r--tests/resources/testrepo/.gitted/refs/heads/subtrees1
-rw-r--r--tests/resources/testrepo/.gitted/refs/heads/test1
-rw-r--r--tests/resources/testrepo/.gitted/refs/tags/e90810b1
-rw-r--r--tests/resources/testrepo/.gitted/refs/tags/foo/bar1
-rw-r--r--tests/resources/testrepo/.gitted/refs/tags/foo/foo/bar1
-rw-r--r--tests/resources/testrepo/.gitted/refs/tags/point_to_blob1
-rw-r--r--tests/resources/testrepo/.gitted/refs/tags/test1
-rw-r--r--tests/resources/testrepo2/.gitted/HEAD1
-rw-r--r--tests/resources/testrepo2/.gitted/config14
-rw-r--r--tests/resources/testrepo2/.gitted/description1
-rw-r--r--tests/resources/testrepo2/.gitted/indexbin0 -> 512 bytes
-rw-r--r--tests/resources/testrepo2/.gitted/info/exclude6
-rw-r--r--tests/resources/testrepo2/.gitted/logs/HEAD1
-rw-r--r--tests/resources/testrepo2/.gitted/logs/refs/heads/master1
-rw-r--r--tests/resources/testrepo2/.gitted/logs/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/testrepo2/.gitted/objects/0c/37a5391bbff43c37f0d0371823a5509eed5b1dbin0 -> 134 bytes
-rw-r--r--tests/resources/testrepo2/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08bin0 -> 19 bytes
-rw-r--r--tests/resources/testrepo2/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7bin0 -> 51 bytes
-rw-r--r--tests/resources/testrepo2/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccdbin0 -> 119 bytes
-rw-r--r--tests/resources/testrepo2/.gitted/objects/2d/2eff63372b08adf0a9eb84109ccf7d19e2f3a2bin0 -> 125 bytes
-rw-r--r--tests/resources/testrepo2/.gitted/objects/36/060c58702ed4c2a40832c51758d5344201d89a2
-rw-r--r--tests/resources/testrepo2/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057bin0 -> 18 bytes
-rw-r--r--tests/resources/testrepo2/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd20452
-rw-r--r--tests/resources/testrepo2/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf36442
-rw-r--r--tests/resources/testrepo2/.gitted/objects/61/9f9935957e010c419cb9d15621916ddfcc0b96bin0 -> 116 bytes
-rw-r--r--tests/resources/testrepo2/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60abin0 -> 119 bytes
-rw-r--r--tests/resources/testrepo2/.gitted/objects/7f/043268ea43ce18e3540acaabf9e090c91965b0bin0 -> 55 bytes
-rw-r--r--tests/resources/testrepo2/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344dbin0 -> 82 bytes
-rw-r--r--tests/resources/testrepo2/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479bin0 -> 126 bytes
-rw-r--r--tests/resources/testrepo2/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a3
-rw-r--r--tests/resources/testrepo2/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f2
-rw-r--r--tests/resources/testrepo2/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bdbin0 -> 28 bytes
-rw-r--r--tests/resources/testrepo2/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6bin0 -> 26 bytes
-rw-r--r--tests/resources/testrepo2/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f6443
-rw-r--r--tests/resources/testrepo2/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd3
-rw-r--r--tests/resources/testrepo2/.gitted/objects/c4/dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90bbin0 -> 116 bytes
-rw-r--r--tests/resources/testrepo2/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391bin0 -> 15 bytes
-rw-r--r--tests/resources/testrepo2/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1bin0 -> 82 bytes
-rw-r--r--tests/resources/testrepo2/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92bin0 -> 24 bytes
-rw-r--r--tests/resources/testrepo2/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765bin0 -> 82 bytes
-rw-r--r--tests/resources/testrepo2/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idxbin0 -> 1240 bytes
-rw-r--r--tests/resources/testrepo2/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.packbin0 -> 491 bytes
-rw-r--r--tests/resources/testrepo2/.gitted/packed-refs6
-rw-r--r--tests/resources/testrepo2/.gitted/refs/heads/master1
-rw-r--r--tests/resources/testrepo2/.gitted/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/testrepo2/README1
-rw-r--r--tests/resources/testrepo2/new.txt1
-rw-r--r--tests/resources/testrepo2/subdir/README1
-rw-r--r--tests/resources/testrepo2/subdir/new.txt1
-rw-r--r--tests/resources/testrepo2/subdir/subdir2/README1
-rw-r--r--tests/resources/testrepo2/subdir/subdir2/new.txt1
-rw-r--r--tests/resources/twowaymerge.git/HEAD1
-rw-r--r--tests/resources/twowaymerge.git/config5
-rw-r--r--tests/resources/twowaymerge.git/description1
-rw-r--r--tests/resources/twowaymerge.git/info/exclude6
-rw-r--r--tests/resources/twowaymerge.git/objects/0c/8a3f1f3d5f421cf83048c7c73ee3b55a5e0f29bin0 -> 157 bytes
-rw-r--r--tests/resources/twowaymerge.git/objects/10/2dce8e3081f398e4bdd9fd894dc85ac3ca6a67bin0 -> 54 bytes
-rw-r--r--tests/resources/twowaymerge.git/objects/17/7d8634a28e26ec7819284752757ebe01a479d5bin0 -> 80 bytes
-rw-r--r--tests/resources/twowaymerge.git/objects/1c/30b88f5f3ee66d78df6520a7de9e89b890818b3
-rw-r--r--tests/resources/twowaymerge.git/objects/1f/4c0311a24b63f6fc209a59a1e404942d4a50062
-rw-r--r--tests/resources/twowaymerge.git/objects/22/24e191514cb4bd8c566d80dac22dfcb1e9bb833
-rw-r--r--tests/resources/twowaymerge.git/objects/29/6e56023cdc034d2735fee8c0d85a659d1b07f4bin0 -> 51 bytes
-rw-r--r--tests/resources/twowaymerge.git/objects/31/51880ae2b363f1c262cf98b750c1f169a0d432bin0 -> 68 bytes
-rw-r--r--tests/resources/twowaymerge.git/objects/3b/287f8730c81d0b763c2d294618a5e32b67b4f8bin0 -> 54 bytes
-rw-r--r--tests/resources/twowaymerge.git/objects/42/b7311aa626e712891940c1ec5d5cba201946a43
-rw-r--r--tests/resources/twowaymerge.git/objects/49/6d6428b9cf92981dc9495211e6e1120fb6f2babin0 -> 46 bytes
-rw-r--r--tests/resources/twowaymerge.git/objects/59/b0cf7d74659e1cdb13305319d6d4ce2733c118bin0 -> 65 bytes
-rw-r--r--tests/resources/twowaymerge.git/objects/6a/b5d28acbf3c3bdff276f7ccfdf29c1520e542f1
-rw-r--r--tests/resources/twowaymerge.git/objects/6c/fca542b55b8b37017e6125a4b8f59a6eae6f11bin0 -> 68 bytes
-rw-r--r--tests/resources/twowaymerge.git/objects/76/5b32c65d38f04c4f287abda055818ec0f26912bin0 -> 54 bytes
-rw-r--r--tests/resources/twowaymerge.git/objects/7b/8c336c45fc6895c1c60827260fe5d798e5d2473
-rw-r--r--tests/resources/twowaymerge.git/objects/82/bf9a1a10a4b25c1f14c9607b60970705e925451
-rw-r--r--tests/resources/twowaymerge.git/objects/8b/82fb1794cb1c8c7f172ec730a4c2db0ae3e6503
-rw-r--r--tests/resources/twowaymerge.git/objects/9a/40a2f11c191f180c47e54b11567cb3c1e89b30bin0 -> 62 bytes
-rw-r--r--tests/resources/twowaymerge.git/objects/9b/219343610c88a1187c996d0dc58330b55cee282
-rw-r--r--tests/resources/twowaymerge.git/objects/9f/e06a50f4d1634d6c6879854d01d80857388706bin0 -> 65 bytes
-rw-r--r--tests/resources/twowaymerge.git/objects/a4/1a49f8f5cd9b6cb14a076bf8394881ed0b4d193
-rw-r--r--tests/resources/twowaymerge.git/objects/a9/53a018c5b10b20c86e69fef55ebc8ad4c5a4171
-rw-r--r--tests/resources/twowaymerge.git/objects/a9/cce3cd1b3efbda5b1f4a6dcc3f1570b2d3d74c1
-rw-r--r--tests/resources/twowaymerge.git/objects/bd/1732c43c68d712ad09e1d872b9be6d4b9efdc4bin0 -> 158 bytes
-rw-r--r--tests/resources/twowaymerge.git/objects/c3/7a783c20d92ac92362a78a32860f7eebf938efbin0 -> 158 bytes
-rw-r--r--tests/resources/twowaymerge.git/objects/cb/dd40facab1682754eb67f7a43f29e672903cf6bin0 -> 51 bytes
-rw-r--r--tests/resources/twowaymerge.git/objects/cd/f97fd3bb48eb3827638bb33d208f5fd32d0aa6bin0 -> 158 bytes
-rw-r--r--tests/resources/twowaymerge.git/objects/d6/f10d549cb335b9e6d38afc1f0088be69b50494bin0 -> 62 bytes
-rw-r--r--tests/resources/twowaymerge.git/objects/d9/acdc7ae7632adfeec67fa73c1e343cf4d1f47e1
-rw-r--r--tests/resources/twowaymerge.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391bin0 -> 15 bytes
-rw-r--r--tests/resources/twowaymerge.git/objects/ef/0488f0b722f0be8bcb90a7730ac7efafd1d6941
-rw-r--r--tests/resources/twowaymerge.git/objects/fc/f7e3f51c11d199ab7a78403ee4f9ccd028da25bin0 -> 62 bytes
-rw-r--r--tests/resources/twowaymerge.git/refs/heads/first-branch1
-rw-r--r--tests/resources/twowaymerge.git/refs/heads/master1
-rw-r--r--tests/resources/twowaymerge.git/refs/heads/second-branch1
-rw-r--r--tests/resources/typechanges/.gitted/HEAD1
-rw-r--r--tests/resources/typechanges/.gitted/config12
-rw-r--r--tests/resources/typechanges/.gitted/description1
-rw-r--r--tests/resources/typechanges/.gitted/indexbin0 -> 184 bytes
-rw-r--r--tests/resources/typechanges/.gitted/info/exclude6
-rw-r--r--tests/resources/typechanges/.gitted/modules/b/HEAD1
-rw-r--r--tests/resources/typechanges/.gitted/modules/b/config13
-rw-r--r--tests/resources/typechanges/.gitted/modules/b/description1
-rw-r--r--tests/resources/typechanges/.gitted/modules/b/indexbin0 -> 192 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/b/info/exclude6
-rw-r--r--tests/resources/typechanges/.gitted/modules/b/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1bin0 -> 55 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/b/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484bin0 -> 53 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/b/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5bin0 -> 93 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/b/objects/41/bd4bc3df978de695f67ace64c560913da11653bin0 -> 163 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/b/objects/48/0095882d281ed676fe5b863569520e54a7d5c0bin0 -> 163 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/b/objects/5e/4963595a9774b90524d35a807169049de8ccadbin0 -> 167 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/b/objects/6b/31c659545507c381e9cd34ec508f16c04e149e2
-rw-r--r--tests/resources/typechanges/.gitted/modules/b/objects/73/ba924a80437097795ae839e66e187c55d3babfbin0 -> 93 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/b/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a2
-rw-r--r--tests/resources/typechanges/.gitted/modules/b/objects/78/9efbdadaa4a582778d4584385495559ea0994b2
-rw-r--r--tests/resources/typechanges/.gitted/modules/b/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6ebin0 -> 81 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/b/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5bin0 -> 93 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/b/packed-refs2
-rw-r--r--tests/resources/typechanges/.gitted/modules/b/refs/heads/master1
-rw-r--r--tests/resources/typechanges/.gitted/modules/b/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/typechanges/.gitted/modules/d/HEAD1
-rw-r--r--tests/resources/typechanges/.gitted/modules/d/config13
-rw-r--r--tests/resources/typechanges/.gitted/modules/d/description1
-rw-r--r--tests/resources/typechanges/.gitted/modules/d/indexbin0 -> 192 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/d/info/exclude6
-rw-r--r--tests/resources/typechanges/.gitted/modules/d/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1bin0 -> 55 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/d/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484bin0 -> 53 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/d/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5bin0 -> 93 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/d/objects/41/bd4bc3df978de695f67ace64c560913da11653bin0 -> 163 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/d/objects/48/0095882d281ed676fe5b863569520e54a7d5c0bin0 -> 163 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/d/objects/5e/4963595a9774b90524d35a807169049de8ccadbin0 -> 167 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/d/objects/6b/31c659545507c381e9cd34ec508f16c04e149e2
-rw-r--r--tests/resources/typechanges/.gitted/modules/d/objects/73/ba924a80437097795ae839e66e187c55d3babfbin0 -> 93 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/d/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a2
-rw-r--r--tests/resources/typechanges/.gitted/modules/d/objects/78/9efbdadaa4a582778d4584385495559ea0994b2
-rw-r--r--tests/resources/typechanges/.gitted/modules/d/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6ebin0 -> 81 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/d/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5bin0 -> 93 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/d/packed-refs2
-rw-r--r--tests/resources/typechanges/.gitted/modules/d/refs/heads/master1
-rw-r--r--tests/resources/typechanges/.gitted/modules/d/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/typechanges/.gitted/modules/e/HEAD1
-rw-r--r--tests/resources/typechanges/.gitted/modules/e/config13
-rw-r--r--tests/resources/typechanges/.gitted/modules/e/description1
-rw-r--r--tests/resources/typechanges/.gitted/modules/e/indexbin0 -> 192 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/e/info/exclude6
-rw-r--r--tests/resources/typechanges/.gitted/modules/e/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1bin0 -> 55 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/e/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484bin0 -> 53 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/e/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5bin0 -> 93 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/e/objects/41/bd4bc3df978de695f67ace64c560913da11653bin0 -> 163 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/e/objects/48/0095882d281ed676fe5b863569520e54a7d5c0bin0 -> 163 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/e/objects/5e/4963595a9774b90524d35a807169049de8ccadbin0 -> 167 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/e/objects/6b/31c659545507c381e9cd34ec508f16c04e149e2
-rw-r--r--tests/resources/typechanges/.gitted/modules/e/objects/73/ba924a80437097795ae839e66e187c55d3babfbin0 -> 93 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/e/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a2
-rw-r--r--tests/resources/typechanges/.gitted/modules/e/objects/78/9efbdadaa4a582778d4584385495559ea0994b2
-rw-r--r--tests/resources/typechanges/.gitted/modules/e/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6ebin0 -> 81 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/e/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5bin0 -> 93 bytes
-rw-r--r--tests/resources/typechanges/.gitted/modules/e/packed-refs2
-rw-r--r--tests/resources/typechanges/.gitted/modules/e/refs/heads/master1
-rw-r--r--tests/resources/typechanges/.gitted/modules/e/refs/remotes/origin/HEAD1
-rw-r--r--tests/resources/typechanges/.gitted/objects/0d/78578795b7ca49fd8df6c4b6d27c5c02d991d8bin0 -> 76 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/0e/7ed140b514b8cae23254cb8656fe1674403affbin0 -> 162 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/0f/f461da9689266f482d8f6654a4400b4e33c586bin0 -> 486 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/18/aa7e45bbe4c3cc24a0b079696c59d36675af97bin0 -> 89 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/1b/63caae4a5ca96f78e8dfefc376c6a39a142475bin0 -> 161 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/1e/abe82aa3b2365a394f6108f24435df6e193d02bin0 -> 549 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/42/061c01a1c70097d1e4579f29a5adf40abdec95bin0 -> 24 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/46/2838cee476a87e7cff32196b66fa18ed756592bin0 -> 76 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/63/499e4ea8e096b831515ceb1d5a7593e4d87ae5bin0 -> 18 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/68/1af94e10eaf262f3ab7cb9b8fd5f4158ba4d3ebin0 -> 24 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/6a/9008602b811e69a9b7a2d83496f39a794fdeebbin0 -> 602 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/6e/ae26c90e8ccc4d16208972119c40635489c6f0bin0 -> 160 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/6f/39eabbb8a7541515e0d35971078bccb502e7e0bin0 -> 66 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/71/54d3083461536dfc71ad5542f3e65e723a06c4bin0 -> 657 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/75/56c1d893a4c0ca85ac8ac51de47ff399758729bin0 -> 226 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/76/fef844064c26d5e06c2508240dae661e7231b2bin0 -> 66 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/79/b9f23e85f55ea36a472a902e875bc1121a94cb2
-rw-r--r--tests/resources/typechanges/.gitted/objects/85/28da0ea65eacf1f74f9ed6696adbac547963adbin0 -> 451 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/8b/3726b365824ad5a07c537247f4bc73ed7d37eabin0 -> 76 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/93/3e28c1c8a68838a763d250bdf0b2c6068289c3bin0 -> 226 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/96/2710fe5b4e453e9e827945b3487c525968ec4abin0 -> 76 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/96/6cf1b3598e195b31b2cde3784f9a19f0728a6fbin0 -> 226 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/99/e8bab9ece009f0fba7eb41f850f4c12bedb9b7bin0 -> 701 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/9b/19edf33a03a0c59cdfc113bfa5c06179bf9b1a5
-rw-r--r--tests/resources/typechanges/.gitted/objects/9b/db75b73836a99e3dbeea640a81de81031fdc29bin0 -> 162 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/9d/0235c7a7edc0889a18f97a42ee6db9fe688447bin0 -> 160 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/9e/ffc457877f109b2a4319e14bee613a15f2a00dbin0 -> 226 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/a0/a9bad6f6f40325198f938a0e3ae981622d7707bin0 -> 54 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/b1/977dc4e573b812d4619754c98138c56999dc0dbin0 -> 518 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/d7/5992dd02391e128dac332dcc78d649dd9ab095bin0 -> 577 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/da/e2709d638df52212b1f43ff61797ebfedfcc7cbin0 -> 78 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/e1/152adcb9adf37ec551ada9ba377ab53aec3badbin0 -> 19 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/e4/ed436a9eb0f198cda722886a5f8d6d6c836b7bbin0 -> 225 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391bin0 -> 15 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/f2/0b79342712e0b2315647cd8227a573fd3bc46ebin0 -> 66 bytes
-rw-r--r--tests/resources/typechanges/.gitted/objects/fd/e0147e3b59f381635a3b016e3fe6dacb70779dbin0 -> 53 bytes
-rw-r--r--tests/resources/typechanges/.gitted/refs/heads/master1
-rw-r--r--tests/resources/typechanges/README.md43
-rw-r--r--tests/resources/typechanges/gitmodules0
-rw-r--r--tests/resources/unsymlinked.git/HEAD1
-rw-r--r--tests/resources/unsymlinked.git/config6
-rw-r--r--tests/resources/unsymlinked.git/description1
-rw-r--r--tests/resources/unsymlinked.git/info/exclude2
-rw-r--r--tests/resources/unsymlinked.git/objects/08/8b64704e0d6b8bd061dea879418cb5442a3fbfbin0 -> 49 bytes
-rw-r--r--tests/resources/unsymlinked.git/objects/13/a5e939bca25940c069fd2169d993dba328e30bbin0 -> 44 bytes
-rw-r--r--tests/resources/unsymlinked.git/objects/19/bf568e59e3a0b363cafb4106226e62d4a4c41cbin0 -> 29 bytes
-rw-r--r--tests/resources/unsymlinked.git/objects/58/1fadd35b4cf320d102a152f918729011604773bin0 -> 47 bytes
-rw-r--r--tests/resources/unsymlinked.git/objects/5c/87b6791e8b13da658a14d1ef7e09b5dc3bac8cbin0 -> 78 bytes
-rw-r--r--tests/resources/unsymlinked.git/objects/6f/e5f5398af85fb3de8a6aba0339b6d3bfa26a27bin0 -> 49 bytes
-rw-r--r--tests/resources/unsymlinked.git/objects/7f/ccd75616ec188b8f1b23d67506a334cc34a49dbin0 -> 132 bytes
-rw-r--r--tests/resources/unsymlinked.git/objects/80/6999882bf91d24241e4077906b9017605eb1f3bin0 -> 170 bytes
-rw-r--r--tests/resources/unsymlinked.git/objects/83/7d176303c5005505ec1e4a30231c40930c0230bin0 -> 44 bytes
-rw-r--r--tests/resources/unsymlinked.git/objects/a8/595ccca04f40818ae0155c8f9c77a230e597b62
-rw-r--r--tests/resources/unsymlinked.git/objects/cf/8f1cf5cce859c438d6cc067284cb5e161206e7bin0 -> 49 bytes
-rw-r--r--tests/resources/unsymlinked.git/objects/d5/278d05c8607ec420bfee4cf219fbc0eeebfd6abin0 -> 49 bytes
-rw-r--r--tests/resources/unsymlinked.git/objects/f4/e16fb76536591a41454194058d048d8e4dd2e9bin0 -> 44 bytes
-rw-r--r--tests/resources/unsymlinked.git/objects/f9/e65619d93fdf2673882e0a261c5e93b1a84006bin0 -> 32 bytes
-rw-r--r--tests/resources/unsymlinked.git/refs/heads/exe-file1
-rw-r--r--tests/resources/unsymlinked.git/refs/heads/master1
-rw-r--r--tests/resources/unsymlinked.git/refs/heads/reg-file1
-rw-r--r--tests/resources/userdiff/.gitted/HEAD1
-rw-r--r--tests/resources/userdiff/.gitted/config7
-rw-r--r--tests/resources/userdiff/.gitted/description1
-rw-r--r--tests/resources/userdiff/.gitted/indexbin0 -> 1336 bytes
-rw-r--r--tests/resources/userdiff/.gitted/info/refs1
-rw-r--r--tests/resources/userdiff/.gitted/objects/info/packs2
-rw-r--r--tests/resources/userdiff/.gitted/objects/pack/pack-1652578900ac63564f2a24b9714529821276ceb9.idxbin0 -> 2500 bytes
-rw-r--r--tests/resources/userdiff/.gitted/objects/pack/pack-1652578900ac63564f2a24b9714529821276ceb9.packbin0 -> 7102 bytes
-rw-r--r--tests/resources/userdiff/.gitted/packed-refs2
-rw-r--r--tests/resources/userdiff/.gitted/refs/dummy-marker.txt0
-rw-r--r--tests/resources/userdiff/after/file.html41
-rw-r--r--tests/resources/userdiff/after/file.javascript108
-rw-r--r--tests/resources/userdiff/after/file.php50
-rw-r--r--tests/resources/userdiff/before/file.html41
-rw-r--r--tests/resources/userdiff/before/file.javascript109
-rw-r--r--tests/resources/userdiff/before/file.php49
-rw-r--r--tests/resources/userdiff/expected/driver/diff.html26
-rw-r--r--tests/resources/userdiff/expected/driver/diff.javascript40
-rw-r--r--tests/resources/userdiff/expected/driver/diff.php26
-rw-r--r--tests/resources/userdiff/expected/nodriver/diff.html26
-rw-r--r--tests/resources/userdiff/expected/nodriver/diff.javascript40
-rw-r--r--tests/resources/userdiff/expected/nodriver/diff.php26
-rw-r--r--tests/resources/userdiff/files/file.html41
-rw-r--r--tests/resources/userdiff/files/file.javascript108
-rw-r--r--tests/resources/userdiff/files/file.php50
-rw-r--r--tests/revert/bare.c107
-rw-r--r--tests/revert/workdir.c577
-rw-r--r--tests/revwalk/basic.c422
-rw-r--r--tests/revwalk/hidecb.c207
-rw-r--r--tests/revwalk/mergebase.c455
-rw-r--r--tests/revwalk/signatureparsing.c47
-rw-r--r--tests/revwalk/simplify.c55
-rw-r--r--tests/stash/drop.c174
-rw-r--r--tests/stash/foreach.c124
-rw-r--r--tests/stash/save.c390
-rw-r--r--tests/stash/stash_helpers.c52
-rw-r--r--tests/stash/stash_helpers.h8
-rw-r--r--tests/stash/submodules.c83
-rw-r--r--tests/status/ignore.c790
-rw-r--r--tests/status/renames.c711
-rw-r--r--tests/status/single.c45
-rw-r--r--tests/status/status_data.h326
-rw-r--r--tests/status/status_helpers.c94
-rw-r--r--tests/status/status_helpers.h49
-rw-r--r--tests/status/submodules.c478
-rw-r--r--tests/status/worktree.c937
-rw-r--r--tests/status/worktree_init.c341
-rw-r--r--tests/stress/diff.c146
-rw-r--r--tests/structinit/structinit.c127
-rw-r--r--tests/submodule/add.c120
-rw-r--r--tests/submodule/lookup.c271
-rw-r--r--tests/submodule/modify.c245
-rw-r--r--tests/submodule/nosubs.c176
-rw-r--r--tests/submodule/status.c360
-rw-r--r--tests/submodule/submodule_helpers.c178
-rw-r--r--tests/submodule/submodule_helpers.h21
-rw-r--r--tests/t00-core.c584
-rw-r--r--tests/t01-rawobj.c638
-rw-r--r--tests/t02-objread.c269
-rw-r--r--tests/t03-data.h344
-rw-r--r--tests/t03-objwrite.c255
-rw-r--r--tests/t04-commit.c775
-rw-r--r--tests/t05-revwalk.c140
-rw-r--r--tests/t06-index.c219
-rw-r--r--tests/t07-hashtable.c192
-rw-r--r--tests/t08-tag.c316
-rw-r--r--tests/t09-tree.c218
-rw-r--r--tests/t10-refs.c1146
-rw-r--r--tests/t12-repo.c463
-rw-r--r--tests/t13-threads.c41
-rw-r--r--tests/t15-config.c325
-rw-r--r--tests/t16-remotes.c106
-rw-r--r--tests/t17-bufs.c61
-rw-r--r--tests/t18-status.c184
-rw-r--r--tests/test_helpers.c263
-rw-r--r--tests/test_helpers.h77
-rwxr-xr-xtests/test_lib.c198
-rwxr-xr-xtests/test_lib.h53
-rw-r--r--tests/test_main.c91
-rw-r--r--tests/tests.supp6
-rw-r--r--tests/threads/basic.c36
-rw-r--r--tests/threads/diff.c182
-rw-r--r--tests/threads/iterator.c49
-rw-r--r--tests/threads/refdb.c221
-rw-r--r--tests/threads/thread_helpers.c44
-rw-r--r--tests/threads/thread_helpers.h8
-rw-r--r--tests/trace/trace.c87
-rw-r--r--tests/valgrind-supp-mac.txt176
2745 files changed, 174816 insertions, 20500 deletions
diff --git a/.gitignore b/.gitignore
index 254e63db7..d4e0454fa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
+/tests/clar.suite
+/tests/clar.suite.rule
+/tests/.clarcache
/apidocs
/trash-*.exe
/libgit2.pc
@@ -11,6 +14,7 @@
.lock-wafbuild
.waf*
build/
+build-amiga/
tests/tmp/
msvc/Debug/
msvc/Release/
@@ -19,6 +23,10 @@ msvc/Release/
*.vc*proj*
*.sdf
*.opensdf
-CMake*
+*.aps
*.cmake
+!cmake/Modules/*.cmake
.DS_Store
+*~
+tags
+mkmf.log
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 000000000..c656f64c7
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,21 @@
+Vicent Martí <vicent@github.com> Vicent Marti <tanoku@gmail.com>
+Vicent Martí <vicent@github.com> Vicent Martí <tanoku@gmail.com>
+Michael Schubert <schu@schu.io> schu <schu-github@schulog.org>
+Ben Straub <bs@github.com> Ben Straub <ben@straubnet.net>
+Ben Straub <bs@github.com> Ben Straub <bstraub@github.com>
+Carlos Martín Nieto <cmn@dwim.me> <carlos@cmartin.tk>
+Carlos Martín Nieto <cmn@dwim.me> <cmn@elego.de>
+nulltoken <emeric.fermas@gmail.com> <emeric.fermas@gmail.com>
+Scott J. Goldman <scottjg@github.com> <scottjgo@gmail.com>
+Martin Woodward <martin.woodward@microsoft.com> <martinwo@microsoft.com>
+Peter Drahoš <drahosp@gmail.com> <drahosp@gmail.com>
+Adam Roben <adam@github.com> <adam@roben.org>
+Adam Roben <adam@github.com> <adam@github.com>
+Xavier L. <xavier.l@afrosoft.tk> <xavier.l@afrosoft.ca>
+Xavier L. <xavier.l@afrosoft.tk> <xavier.l@afrosoft.tk>
+Sascha Cunz <sascha@babbelbox.org> <Sascha@BabbelBox.org>
+Authmillenon <authmillenon@googlemail.com> <martin@ucsmail.de>
+Authmillenon <authmillenon@googlemail.com> <authmillenon@googlemail.com>
+Edward Thomson <ethomson@microsoft.com> <ethomson@edwardthomson.com>
+J. David Ibáñez <jdavid.ibp@gmail.com> <jdavid@itaapy.com>
+Russell Belfer <rb@github.com> <arrbee@arrbee.com>
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 000000000..bab02bb44
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,67 @@
+# Travis-CI Build for libgit2
+# see travis-ci.org for details
+
+language: c
+
+os:
+ - linux
+ - osx
+
+compiler:
+ - gcc
+ - clang
+
+# Settings to try
+env:
+ global:
+ - secure: "YnhS+8n6B+uoyaYfaJ3Lei7cSJqHDPiKJCKFIF2c87YDfmCvAJke8QtE7IzjYDs7UFkTCM4ox+ph2bERUrxZbSCyEkHdjIZpKuMJfYWja/jgMqTMxdyOH9y8JLFbZsSXDIXDwqBlC6vVyl1fP90M35wuWcNTs6tctfVWVofEFbs="
+ matrix:
+ - OPTIONS="-DTHREADSAFE=ON -DCMAKE_BUILD_TYPE=Release"
+ - OPTIONS="-DBUILD_CLAR=ON -DBUILD_EXAMPLES=ON"
+
+matrix:
+ fast_finish: true
+ exclude:
+ - os: osx
+ compiler: gcc
+ include:
+ - compiler: i586-mingw32msvc-gcc
+ env: OPTIONS="-DBUILD_CLAR=OFF -DWIN32=ON -DMINGW=ON -DUSE_SSH=OFF"
+ os: linux
+ - compiler: gcc
+ env: COVERITY=1
+ os: linux
+ allow_failures:
+ - env: COVERITY=1
+
+install:
+ - ./script/install-deps-${TRAVIS_OS_NAME}.sh
+
+# Run the Build script and tests
+script:
+ - script/cibuild.sh
+
+# Run Tests
+after_success:
+ - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq install valgrind; fi
+ - if [ "$TRAVIS_OS_NAME" = "linux" ]; then valgrind --leak-check=full --show-reachable=yes --suppressions=./libgit2_clar.supp _build/libgit2_clar -ionline; fi
+
+# Only watch the development branch
+branches:
+ only:
+ - development
+
+# Notify development list when needed
+notifications:
+ irc:
+ channels:
+ - irc.freenode.net#libgit2
+ on_success: change
+ on_failure: always
+ use_notice: true
+ skip_join: true
+ campfire:
+ on_success: always
+ on_failure: always
+ rooms:
+ - secure: "sH0dpPWMirbEe7AvLddZ2yOp8rzHalGmv0bYL/LIhVw3JDI589HCYckeLMSB\n3e/FeXw4bn0EqXWEXijVa4ijbilVY6d8oprdqMdWHEodng4KvY5vID3iZSGT\nxylhahO1XHmRynKQLOAvxlc93IlpVW38vQfby8giIY1nkpspb2w="
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 000000000..2eaec0ff6
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,75 @@
+The following people contribute or have contributed
+to the libgit2 project (sorted alphabetically):
+
+Alex Budovski
+Alexei Sholik
+Andreas Ericsson
+Anton "antong" Gyllenberg
+Ankur Sethi
+Arthur Schreiber
+Ben Noordhuis
+Ben Straub
+Benjamin C Meyer
+Brian Downing
+Brian Lopez
+Carlos Martín Nieto
+Colin Timmermans
+Daniel Huckstep
+Dave Borowitz
+David Boyce
+David Glesser
+Dmitry Kakurin
+Dmitry Kovega
+Emeric Fermas
+Emmanuel Rodriguez
+Florian Forster
+Holger Weiss
+Ingmar Vanhassel
+J. David Ibáñez
+Jacques Germishuys
+Jakob Pfender
+Jason Penny
+Jason R. McNeil
+Jerome Lambourg
+Johan 't Hart
+John Wiegley
+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
+Przemyslaw Pawelczyk
+Ramsay Jones
+Robert G. Jakabosky
+Romain Geissler
+Romain Muller
+Russell Belfer
+Sakari Jokinen
+Samuel Charles "Sam" Day
+Sarath Lakshman
+Sascha Cunz
+Sascha Peilicke
+Scott Chacon
+Sebastian Schuberth
+Sergey Nikishin
+Shawn O. Pearce
+Shuhei Tanuma
+Steve Frécinaux
+Sven Strickroth
+Tim Branyen
+Tim Clem
+Tim Harder
+Torsten Bögershausen
+Trent Mick
+Vicent Marti
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0e516cacb..6f1a97edb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -14,65 +14,342 @@
PROJECT(libgit2 C)
CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
-FILE(STRINGS "include/git2.h" GIT2_HEADER REGEX "^#define LIBGIT2_VERSION \"[^\"]*\"$")
+# Add find modules to the path
+SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/")
+
+INCLUDE(CheckLibraryExists)
+INCLUDE(AddCFlagIfSupported)
+
+# Build options
+#
+OPTION( SONAME "Set the (SO)VERSION of the target" ON )
+OPTION( BUILD_SHARED_LIBS "Build Shared Library (OFF for Static)" ON )
+OPTION( THREADSAFE "Build libgit2 as threadsafe" OFF )
+OPTION( BUILD_CLAR "Build Tests using the Clar suite" ON )
+OPTION( BUILD_EXAMPLES "Build library usage example apps" OFF )
+OPTION( TAGS "Generate tags" OFF )
+OPTION( PROFILE "Generate profiling information" OFF )
+OPTION( ENABLE_TRACE "Enables tracing support" OFF )
+OPTION( LIBGIT2_FILENAME "Name of the produced binary" OFF )
+
+OPTION( ANDROID "Build for android NDK" OFF )
+
+OPTION( USE_ICONV "Link with and use iconv library" OFF )
+OPTION( USE_SSH "Link with libssh to enable SSH support" ON )
+OPTION( VALGRIND "Configure build for valgrind" OFF )
+
+IF(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+ SET( USE_ICONV ON )
+ENDIF()
+
+IF(MSVC)
+ # This option is only available when building with MSVC. By default, libgit2
+ # is build using the cdecl calling convention, which is useful if you're
+ # writing C. However, the CLR and Win32 API both expect stdcall.
+ #
+ # If you are writing a CLR program and want to link to libgit2, you'll want
+ # to turn this on by invoking CMake with the "-DSTDCALL=ON" argument.
+ OPTION( STDCALL "Build libgit2 with the __stdcall convention" OFF )
+
+ # This option must match the settings used in your program, in particular if you
+ # are linking statically
+ OPTION( STATIC_CRT "Link the static CRT libraries" ON )
+
+ # By default, libgit2 is built with WinHTTP. To use the built-in
+ # HTTP transport, invoke CMake with the "-DWINHTTP=OFF" argument.
+ OPTION( WINHTTP "Use Win32 WinHTTP routines" ON )
+
+ ADD_DEFINITIONS(-D_SCL_SECURE_NO_WARNINGS)
+ ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE)
+ ADD_DEFINITIONS(-D_CRT_NONSTDC_NO_DEPRECATE)
+ENDIF()
+
+# This variable will contain the libraries we need to put into
+# libgit2.pc's Requires.private. That is, what we're linking to or
+# what someone who's statically linking us needs to link to.
+SET(LIBGIT2_PC_REQUIRES "")
+# This will be set later if we use the system's http-parser library or
+# use iconv (OSX) and will be written to the Libs.private field in the
+# pc file.
+SET(LIBGIT2_PC_LIBS "")
+
+# Installation paths
+#
+SET(BIN_INSTALL_DIR bin CACHE PATH "Where to install binaries to.")
+SET(LIB_INSTALL_DIR lib CACHE PATH "Where to install libraries to.")
+SET(INCLUDE_INSTALL_DIR include CACHE PATH "Where to install headers to.")
+
+FUNCTION(TARGET_OS_LIBRARIES target)
+ IF(WIN32)
+ TARGET_LINK_LIBRARIES(${target} ws2_32)
+ ELSEIF(CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)")
+ TARGET_LINK_LIBRARIES(${target} socket nsl)
+ SET(LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS} -lsocket -lnsl" PARENT_SCOPE)
+ ENDIF()
+ CHECK_LIBRARY_EXISTS(rt clock_gettime "time.h" NEED_LIBRT)
+ IF(NEED_LIBRT)
+ TARGET_LINK_LIBRARIES(${target} rt)
+ SET(LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS} -lrt" PARENT_SCOPE)
+ ENDIF()
+
+ IF(THREADSAFE)
+ TARGET_LINK_LIBRARIES(${target} ${CMAKE_THREAD_LIBS_INIT})
+ ENDIF()
+ENDFUNCTION()
+
+# For the MSVC IDE, this function splits up the source files like windows
+# explorer does. This is esp. useful with the libgit2_clar project, were
+# usually 2 or more files share the same name. Sadly, this file grouping
+# is a per-directory option in cmake and not per-target, resulting in
+# empty virtual folders "tests" for the git2.dll
+FUNCTION(MSVC_SPLIT_SOURCES target)
+ IF(MSVC_IDE)
+ GET_TARGET_PROPERTY(sources ${target} SOURCES)
+ FOREACH(source ${sources})
+ IF(source MATCHES ".*/")
+ STRING(REPLACE ${CMAKE_CURRENT_SOURCE_DIR}/ "" rel ${source})
+ IF(rel)
+ STRING(REGEX REPLACE "/([^/]*)$" "" rel ${rel})
+ IF(rel)
+ STRING(REPLACE "/" "\\\\" rel ${rel})
+ SOURCE_GROUP(${rel} FILES ${source})
+ ENDIF()
+ ENDIF()
+ ENDIF()
+ ENDFOREACH()
+ ENDIF()
+ENDFUNCTION()
+
+FILE(STRINGS "include/git2/version.h" GIT2_HEADER REGEX "^#define LIBGIT2_VERSION \"[^\"]*\"$")
STRING(REGEX REPLACE "^.*LIBGIT2_VERSION \"([0-9]+).*$" "\\1" LIBGIT2_VERSION_MAJOR "${GIT2_HEADER}")
STRING(REGEX REPLACE "^.*LIBGIT2_VERSION \"[0-9]+\\.([0-9]+).*$" "\\1" LIBGIT2_VERSION_MINOR "${GIT2_HEADER}")
STRING(REGEX REPLACE "^.*LIBGIT2_VERSION \"[0-9]+\\.[0-9]+\\.([0-9]+).*$" "\\1" LIBGIT2_VERSION_REV "${GIT2_HEADER}")
SET(LIBGIT2_VERSION_STRING "${LIBGIT2_VERSION_MAJOR}.${LIBGIT2_VERSION_MINOR}.${LIBGIT2_VERSION_REV}")
+FILE(STRINGS "include/git2/version.h" GIT2_HEADER_SOVERSION REGEX "^#define LIBGIT2_SOVERSION [0-9]+$")
+STRING(REGEX REPLACE "^.*LIBGIT2_SOVERSION ([0-9]+)$" "\\1" LIBGIT2_SOVERSION "${GIT2_HEADER_SOVERSION}")
+
# Find required dependencies
INCLUDE_DIRECTORIES(src include)
-IF (NOT WIN32)
- FIND_PACKAGE(ZLIB)
+
+IF (WIN32 AND WINHTTP AND NOT MINGW)
+ ADD_DEFINITIONS(-DGIT_WINHTTP)
+ INCLUDE_DIRECTORIES(deps/http-parser)
+ FILE(GLOB SRC_HTTP deps/http-parser/*.c deps/http-parser/*.h)
+ELSE ()
+ IF (NOT AMIGA)
+ FIND_PACKAGE(OpenSSL)
+ ENDIF ()
+
+ FIND_PACKAGE(HTTP_Parser)
+ IF (HTTP_PARSER_FOUND AND HTTP_PARSER_VERSION_MAJOR EQUAL 2)
+ INCLUDE_DIRECTORIES(${HTTP_PARSER_INCLUDE_DIRS})
+ LINK_LIBRARIES(${HTTP_PARSER_LIBRARIES})
+ SET(LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS} -lhttp_parser")
+ ELSE()
+ MESSAGE(STATUS "http-parser was not found or is too old; using bundled 3rd-party sources.")
+ INCLUDE_DIRECTORIES(deps/http-parser)
+ FILE(GLOB SRC_HTTP deps/http-parser/*.c deps/http-parser/*.h)
+ ENDIF()
+ENDIF()
+
+# Specify sha1 implementation
+IF (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)
+ IF (CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
+ SET(LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS} -lssl")
+ ELSE()
+ SET(LIBGIT2_PC_REQUIRES "${LIBGIT2_PC_REQUIRES} openssl")
+ ENDIF ()
+ELSE()
+ FILE(GLOB SRC_SHA1 src/hash/hash_generic.c)
+ENDIF()
+
+# Enable tracing
+IF (ENABLE_TRACE STREQUAL "ON")
+ ADD_DEFINITIONS(-DGIT_TRACE)
ENDIF()
+# Include POSIX regex when it is required
+IF(WIN32 OR AMIGA OR ANDROID OR CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)")
+ INCLUDE_DIRECTORIES(deps/regex)
+ SET(SRC_REGEX deps/regex/regex.c)
+ENDIF()
+
+# Optional external dependency: zlib
+FIND_PACKAGE(ZLIB)
IF (ZLIB_FOUND)
INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIRS})
LINK_LIBRARIES(${ZLIB_LIBRARIES})
-ELSE (ZLIB_FOUND)
+ IF(APPLE OR CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
+ SET(LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS} -lz")
+ ELSE()
+ SET(LIBGIT2_PC_REQUIRES "${LIBGIT2_PC_REQUIRES} zlib")
+ ENDIF()
+ELSE()
+ MESSAGE(STATUS "zlib was not found; using bundled 3rd-party sources." )
INCLUDE_DIRECTORIES(deps/zlib)
ADD_DEFINITIONS(-DNO_VIZ -DSTDC -DNO_GZIP)
- FILE(GLOB SRC_ZLIB deps/zlib/*.c)
+ FILE(GLOB SRC_ZLIB deps/zlib/*.c deps/zlib/*.h)
ENDIF()
-# Installation paths
-SET(INSTALL_BIN bin CACHE PATH "Where to install binaries to.")
-SET(INSTALL_LIB lib CACHE PATH "Where to install libraries to.")
-SET(INSTALL_INC include CACHE PATH "Where to install headers to.")
+# Optional external dependency: libssh2
+IF (USE_SSH)
+ FIND_PACKAGE(LIBSSH2)
+ENDIF()
+IF (LIBSSH2_FOUND)
+ ADD_DEFINITIONS(-DGIT_SSH)
+ INCLUDE_DIRECTORIES(${LIBSSH2_INCLUDE_DIR})
+ SET(LIBGIT2_PC_REQUIRES "${LIBGIT2_PC_REQUIRES} libssh2")
+ SET(SSH_LIBRARIES ${LIBSSH2_LIBRARIES})
+ENDIF()
-# Build options
-OPTION (BUILD_SHARED_LIBS "Build Shared Library (OFF for Static)" ON)
-OPTION (BUILD_TESTS "Build Tests" ON)
-OPTION (THREADSAFE "Build libgit2 as threadsafe" OFF)
-OPTION (STDCALL "Buildl libgit2 with the __stdcall convention (Windows)" ON)
+# Optional external dependency: iconv
+IF (USE_ICONV)
+ FIND_PACKAGE(Iconv)
+ENDIF()
+IF (ICONV_FOUND)
+ ADD_DEFINITIONS(-DGIT_USE_ICONV)
+ INCLUDE_DIRECTORIES(${ICONV_INCLUDE_DIR})
+ SET(LIBGIT2_PC_LIBS "${LIBGIT2_PC_LIBS} ${ICONV_LIBRARIES}")
+ENDIF()
# Platform specific compilation flags
IF (MSVC)
- SET(CMAKE_C_FLAGS "/W4 /WX /nologo /Zi")
+
+ STRING(REPLACE "/Zm1000" " " CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
+
+ # /GF - String pooling
+ # /MP - Parallel build
+ SET(CMAKE_C_FLAGS "/GF /MP /nologo ${CMAKE_C_FLAGS}")
+
IF (STDCALL)
- SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Gz")
+ # /Gz - stdcall calling convention
+ SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Gz")
ENDIF ()
- # TODO: bring back /RTC1 /RTCc
- SET(CMAKE_C_FLAGS_DEBUG "/Od /DEBUG /MTd")
- SET(CMAKE_C_FLAGS_RELEASE "/MT /O2")
+
+ IF (STATIC_CRT)
+ SET(CRT_FLAG_DEBUG "/MTd")
+ SET(CRT_FLAG_RELEASE "/MT")
+ ELSE()
+ SET(CRT_FLAG_DEBUG "/MDd")
+ SET(CRT_FLAG_RELEASE "/MD")
+ ENDIF()
+
+ # /Zi - Create debugging information
+ # /Od - Disable optimization
+ # /D_DEBUG - #define _DEBUG
+ # /MTd - Statically link the multithreaded debug version of the CRT
+ # /MDd - Dynamically link the multithreaded debug version of the CRT
+ # /RTC1 - Run time checks
+ SET(CMAKE_C_FLAGS_DEBUG "/Zi /Od /D_DEBUG /RTC1 ${CRT_FLAG_DEBUG}")
+
+ # /DNDEBUG - Disables asserts
+ # /MT - Statically link the multithreaded release version of the CRT
+ # /MD - Dynamically link the multithreaded release version of the CRT
+ # /O2 - Optimize for speed
+ # /Oy - Enable frame pointer omission (FPO) (otherwise CMake will automatically turn it off)
+ # /GL - Link time code generation (whole program optimization)
+ # /Gy - Function-level linking
+ SET(CMAKE_C_FLAGS_RELEASE "/DNDEBUG /O2 /Oy /GL /Gy ${CRT_FLAG_RELEASE}")
+
+ # /Oy- - Disable frame pointer omission (FPO)
+ SET(CMAKE_C_FLAGS_RELWITHDEBINFO "/DNDEBUG /Zi /O2 /Oy- /GL /Gy ${CRT_FLAG_RELEASE}")
+
+ # /O1 - Optimize for size
+ SET(CMAKE_C_FLAGS_MINSIZEREL "/DNDEBUG /O1 /Oy /GL /Gy ${CRT_FLAG_RELEASE}")
+
+ # /DYNAMICBASE - Address space load randomization (ASLR)
+ # /NXCOMPAT - Data execution prevention (DEP)
+ # /LARGEADDRESSAWARE - >2GB user address space on x86
+ # /VERSION - Embed version information in PE header
+ SET(CMAKE_EXE_LINKER_FLAGS "/DYNAMICBASE /NXCOMPAT /LARGEADDRESSAWARE /VERSION:${LIBGIT2_VERSION_MAJOR}.${LIBGIT2_VERSION_MINOR}")
+
+ # /DEBUG - Create a PDB
+ # /LTCG - Link time code generation (whole program optimization)
+ # /OPT:REF /OPT:ICF - Fold out duplicate code at link step
+ # /INCREMENTAL:NO - Required to use /LTCG
+ # /DEBUGTYPE:cv,fixup - Additional data embedded in the PDB (requires /INCREMENTAL:NO, so not on for Debug)
+ SET(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG")
+ SET(CMAKE_EXE_LINKER_FLAGS_RELEASE "/RELEASE /LTCG /OPT:REF /OPT:ICF /INCREMENTAL:NO")
+ SET(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "/DEBUG /RELEASE /LTCG /OPT:REF /OPT:ICF /INCREMENTAL:NO /DEBUGTYPE:cv,fixup")
+ SET(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "/RELEASE /LTCG /OPT:REF /OPT:ICF /INCREMENTAL:NO")
+
+ # Same linker settings for DLL as EXE
+ SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}")
+ SET(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG}")
+ SET(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
+ SET(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO}")
+ SET(CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL "${CMAKE_EXE_LINKER_FLAGS_MINSIZEREL}")
+
+ SET(WIN_RC "src/win32/git2.rc")
+
+ # Precompiled headers
+
ELSE ()
- SET(CMAKE_C_FLAGS "-Wall -Wextra")
- IF (NOT MINGW) # MinGW always does PIC and complains if we tell it to
+ SET(CMAKE_C_FLAGS "-D_GNU_SOURCE -Wall -Wextra ${CMAKE_C_FLAGS}")
+
+ IF (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)")
+ SET(CMAKE_C_FLAGS "-std=c99 -D_POSIX_C_SOURCE=200112L -D__EXTENSIONS__ -D_POSIX_PTHREAD_SEMANTICS ${CMAKE_C_FLAGS}")
+ ENDIF()
+
+ IF (WIN32 AND NOT CYGWIN)
+ SET(CMAKE_C_FLAGS_DEBUG "-D_DEBUG")
+ ENDIF ()
+
+ 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}")
+ # MinGW >= 3.14 uses the C99-style stdio functions
+ # automatically, but forks like mingw-w64 still want
+ # us to define this in order to use them
+ ADD_DEFINITIONS(-D__USE_MINGW_ANSI_STDIO=1)
+
+ ELSEIF (BUILD_SHARED_LIBS)
+ ADD_C_FLAG_IF_SUPPORTED(-fvisibility=hidden)
+
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC")
ENDIF ()
- SET(CMAKE_C_FLAGS_DEBUG "-g -O0")
- SET(CMAKE_C_FLAGS_RELEASE "-O2")
+
+ ADD_C_FLAG_IF_SUPPORTED(-Wno-missing-field-initializers)
+ ADD_C_FLAG_IF_SUPPORTED(-Wstrict-aliasing=2)
+ ADD_C_FLAG_IF_SUPPORTED(-Wstrict-prototypes)
+ ADD_C_FLAG_IF_SUPPORTED(-Wdeclaration-after-statement)
+ ADD_C_FLAG_IF_SUPPORTED(-Wno-unused-const-variable)
+ ADD_C_FLAG_IF_SUPPORTED(-Wno-unused-function)
+
+ IF (APPLE) # Apple deprecated OpenSSL
+ ADD_C_FLAG_IF_SUPPORTED(-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)
+ INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR})
+ SET(SSL_LIBRARIES ${OPENSSL_LIBRARIES})
+ENDIF()
IF (THREADSAFE)
IF (NOT WIN32)
- find_package(Threads REQUIRED)
+ FIND_PACKAGE(Threads REQUIRED)
ENDIF()
ADD_DEFINITIONS(-DGIT_THREADS)
@@ -81,56 +358,133 @@ ENDIF()
ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64)
# Collect sourcefiles
-FILE(GLOB SRC_H include/git2/*.h)
+FILE(GLOB SRC_H include/git2.h include/git2/*.h include/git2/sys/*.h)
# On Windows use specific platform sources
IF (WIN32 AND NOT CYGWIN)
- ADD_DEFINITIONS(-DWIN32 -D_DEBUG -D_LIB)
- FILE(GLOB SRC src/*.c src/win32/*.c)
+ ADD_DEFINITIONS(-DWIN32 -D_WIN32_WINNT=0x0501)
+ FILE(GLOB SRC_OS src/win32/*.c src/win32/*.h)
+ELSEIF (AMIGA)
+ ADD_DEFINITIONS(-DNO_ADDRINFO -DNO_READDIR_R -DNO_MMAP)
ELSE()
- FILE(GLOB SRC src/*.c src/unix/*.c)
-ENDIF ()
+ IF (VALGRIND)
+ ADD_DEFINITIONS(-DNO_MMAP)
+ ENDIF()
+ FILE(GLOB SRC_OS src/unix/*.c src/unix/*.h)
+ENDIF()
+FILE(GLOB SRC_GIT2 src/*.c src/*.h src/transports/*.c src/transports/*.h src/xdiff/*.c src/xdiff/*.h)
+
+# Determine architecture of the machine
+IF (CMAKE_SIZEOF_VOID_P EQUAL 8)
+ ADD_DEFINITIONS(-DGIT_ARCH_64)
+ELSEIF (CMAKE_SIZEOF_VOID_P EQUAL 4)
+ ADD_DEFINITIONS(-DGIT_ARCH_32)
+ELSE()
+ MESSAGE(FATAL_ERROR "Unsupported architecture")
+ENDIF()
# Compile and link libgit2
-ADD_LIBRARY(git2 ${SRC} ${SRC_ZLIB})
+ADD_LIBRARY(git2 ${SRC_H} ${SRC_GIT2} ${SRC_OS} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SHA1} ${WIN_RC})
+TARGET_LINK_LIBRARIES(git2 ${SSL_LIBRARIES})
+TARGET_LINK_LIBRARIES(git2 ${SSH_LIBRARIES})
+TARGET_LINK_LIBRARIES(git2 ${ICONV_LIBRARIES})
+TARGET_OS_LIBRARIES(git2)
-IF (WIN32)
- TARGET_LINK_LIBRARIES(git2 ws2_32)
-ELSEIF (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)")
- TARGET_LINK_LIBRARIES(git2 socket nsl)
-ENDIF ()
+# Workaround for Cmake bug #0011240 (see http://public.kitware.com/Bug/view.php?id=11240)
+# Win64+MSVC+static libs = linker error
+IF(MSVC AND GIT_ARCH_64 AND NOT BUILD_SHARED_LIBS)
+ SET_TARGET_PROPERTIES(git2 PROPERTIES STATIC_LIBRARY_FLAGS "/MACHINE:x64")
+ENDIF()
-TARGET_LINK_LIBRARIES(git2 ${CMAKE_THREAD_LIBS_INIT})
-SET_TARGET_PROPERTIES(git2 PROPERTIES VERSION ${LIBGIT2_VERSION_STRING})
-SET_TARGET_PROPERTIES(git2 PROPERTIES SOVERSION ${LIBGIT2_VERSION_MAJOR})
-CONFIGURE_FILE(libgit2.pc.in libgit2.pc)
+MSVC_SPLIT_SOURCES(git2)
+
+IF (SONAME)
+ SET_TARGET_PROPERTIES(git2 PROPERTIES VERSION ${LIBGIT2_VERSION_STRING})
+ SET_TARGET_PROPERTIES(git2 PROPERTIES SOVERSION ${LIBGIT2_SOVERSION})
+ IF (LIBGIT2_FILENAME)
+ ADD_DEFINITIONS(-DLIBGIT2_FILENAME=\"${LIBGIT2_FILENAME}\")
+ SET_TARGET_PROPERTIES(git2 PROPERTIES OUTPUT_NAME ${LIBGIT2_FILENAME})
+ ENDIF()
+ENDIF()
+CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/libgit2.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libgit2.pc @ONLY)
+
+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")
+ENDIF ()
# Install
INSTALL(TARGETS git2
- RUNTIME DESTINATION ${INSTALL_BIN}
- LIBRARY DESTINATION ${INSTALL_LIB}
- ARCHIVE DESTINATION ${INSTALL_LIB}
+ RUNTIME DESTINATION ${BIN_INSTALL_DIR}
+ LIBRARY DESTINATION ${LIB_INSTALL_DIR}
+ ARCHIVE DESTINATION ${LIB_INSTALL_DIR}
)
-INSTALL(FILES libgit2.pc DESTINATION ${INSTALL_LIB}/pkgconfig )
-INSTALL(DIRECTORY include/git2 DESTINATION ${INSTALL_INC} )
-INSTALL(FILES include/git2.h DESTINATION ${INSTALL_INC} )
+INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/libgit2.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig )
+INSTALL(DIRECTORY include/git2 DESTINATION ${INCLUDE_INSTALL_DIR} )
+INSTALL(FILES include/git2.h DESTINATION ${INCLUDE_INSTALL_DIR} )
# Tests
-IF (BUILD_TESTS)
- SET(TEST_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/tests/resources" CACHE PATH "Path to test resources.")
- ADD_DEFINITIONS(-DTEST_RESOURCES=\"${TEST_RESOURCES}\")
-
- INCLUDE_DIRECTORIES(tests)
- FILE(GLOB SRC_TEST tests/t??-*.c)
-
- ADD_EXECUTABLE(libgit2_test tests/test_main.c tests/test_lib.c tests/test_helpers.c ${SRC} ${SRC_TEST} ${SRC_ZLIB})
- TARGET_LINK_LIBRARIES(libgit2_test ${CMAKE_THREAD_LIBS_INIT})
- IF (WIN32)
- TARGET_LINK_LIBRARIES(libgit2_test ws2_32)
- ELSEIF (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)")
- TARGET_LINK_LIBRARIES(libgit2_test socket nsl)
+IF (BUILD_CLAR)
+ FIND_PACKAGE(PythonInterp REQUIRED)
+
+ SET(CLAR_FIXTURES "${CMAKE_CURRENT_SOURCE_DIR}/tests/resources/")
+ SET(CLAR_PATH "${CMAKE_CURRENT_SOURCE_DIR}/tests")
+ SET(CLAR_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/tests/resources" CACHE PATH "Path to test resources.")
+ ADD_DEFINITIONS(-DCLAR_FIXTURE_PATH=\"${CLAR_FIXTURES}\")
+ ADD_DEFINITIONS(-DCLAR_RESOURCES=\"${TEST_RESOURCES}\")
+
+ INCLUDE_DIRECTORIES(${CLAR_PATH})
+ FILE(GLOB_RECURSE SRC_TEST ${CLAR_PATH}/*/*.c ${CLAR_PATH}/*/*.h)
+ SET(SRC_CLAR "${CLAR_PATH}/main.c" "${CLAR_PATH}/clar_libgit2.c" "${CLAR_PATH}/clar.c")
+
+ ADD_CUSTOM_COMMAND(
+ OUTPUT ${CLAR_PATH}/clar.suite
+ COMMAND ${PYTHON_EXECUTABLE} generate.py -f -xonline -xstress .
+ DEPENDS ${SRC_TEST}
+ WORKING_DIRECTORY ${CLAR_PATH}
+ )
+
+ SET_SOURCE_FILES_PROPERTIES(
+ ${CLAR_PATH}/clar.c
+ PROPERTIES OBJECT_DEPENDS ${CLAR_PATH}/clar.suite)
+
+ ADD_EXECUTABLE(libgit2_clar ${SRC_H} ${SRC_GIT2} ${SRC_OS} ${SRC_CLAR} ${SRC_TEST} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SHA1})
+
+ TARGET_LINK_LIBRARIES(libgit2_clar ${SSL_LIBRARIES})
+ TARGET_LINK_LIBRARIES(libgit2_clar ${SSH_LIBRARIES})
+ TARGET_LINK_LIBRARIES(libgit2_clar ${ICONV_LIBRARIES})
+ TARGET_OS_LIBRARIES(libgit2_clar)
+ MSVC_SPLIT_SOURCES(libgit2_clar)
+
+ IF (MSVC_IDE)
+ # Precompiled headers
+ SET_TARGET_PROPERTIES(libgit2_clar PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h")
ENDIF ()
ENABLE_TESTING()
- ADD_TEST(libgit2_test libgit2_test)
+ ADD_TEST(libgit2_clar libgit2_clar -ionline)
+ENDIF ()
+
+IF (TAGS)
+ FIND_PROGRAM(CTAGS ctags)
+ IF (NOT CTAGS)
+ MESSAGE(FATAL_ERROR "Could not find ctags command")
+ ENDIF ()
+
+ FILE(GLOB_RECURSE SRC_ALL *.[ch])
+
+ ADD_CUSTOM_COMMAND(
+ OUTPUT tags
+ COMMAND ${CTAGS} -a ${SRC_ALL}
+ DEPENDS ${SRC_ALL}
+ )
+ ADD_CUSTOM_TARGET(
+ do_tags ALL
+ DEPENDS tags
+ )
+ENDIF ()
+
+IF (BUILD_EXAMPLES)
+ ADD_SUBDIRECTORY(examples)
ENDIF ()
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..4efe28ed3
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,104 @@
+# Welcome to libgit2!
+
+We're making it easy to do interesting things with git, and we'd love to have
+your help.
+
+## Licensing
+
+By contributing to libgit2, you agree to release your contribution under
+the terms of the license. Except for the `examples` directory, all code
+is released under the [GPL v2 with linking exception](COPYING).
+
+The `examples` code is governed by the
+[CC0 Public Domain Dedication](examples/COPYING), so that you may copy
+from them into your own application.
+
+## Discussion & Chat
+
+We hang out in the #libgit2 channel on irc.freenode.net.
+
+Also, feel free to open an
+[Issue](https://github.com/libgit2/libgit2/issues/new) to start a discussion
+about any concerns you have. We like to use Issues for that so there is an
+easily accessible permanent record of the conversation.
+
+## Reporting Bugs
+
+First, know which version of libgit2 your problem is in and include it in
+your bug report. This can either be a tag (e.g.
+[v0.17.0](https://github.com/libgit2/libgit2/tree/v0.17.0) ) or a commit
+SHA (e.g.
+[01be7863](https://github.com/libgit2/libgit2/commit/01be786319238fd6507a08316d1c265c1a89407f)
+). Using [`git describe`](http://git-scm.com/docs/git-describe) is a great
+way to tell us what version you're working with.
+
+If you're not running against the latest `development` branch version,
+please compile and test against that to avoid re-reporting an issue that's
+already been fixed.
+
+It's *incredibly* helpful to be able to reproduce the problem. Please
+include a list of steps, a bit of code, and/or a zipped repository (if
+possible). Note that some of the libgit2 developers are employees of
+GitHub, so if your repository is private, find us on IRC and we'll figure
+out a way to help you.
+
+## Pull Requests
+
+Our work flow is a typical GitHub flow, where contributors fork the
+[libgit2 repository](https://github.com/libgit2/libgit2), make their changes
+on branch, and submit a
+[Pull Request](https://help.github.com/articles/using-pull-requests)
+(a.k.a. "PR").
+
+Life will be a lot easier for you (and us) if you follow this pattern
+(i.e. fork, named branch, submit PR). If you use your fork's `development`
+branch, things can get messy.
+
+Please include a nice description of your changes with your PR; if we have
+to read the whole diff to figure out why you're contributing in the first
+place, you're less likely to get feedback and have your change merged in.
+
+If you are working on a particular area then feel free to submit a PR that
+highlights your work in progress (and flag in the PR title that it's not
+ready to merge). This will help in getting visibility for your fix, allow
+others to comment early on the changes and also let others know that you
+are currently working on something.
+
+## Porting Code From Other Open-Source Projects
+
+`libgit2` is licensed under the terms of the GPL v2 with a linking
+exception. Any code brought in must be compatible with those terms.
+
+The most common case is porting code from core Git. Git is a pure GPL
+project, which means that in order to port code to this project, we need the
+explicit permission of the author. Check the
+[`git.git-authors`](https://github.com/libgit2/libgit2/blob/development/git.git-authors)
+file for authors who have already consented.
+
+Other licenses have other requirements; check the license of the library
+you're porting code *from* to see what you need to do. As a general rule,
+MIT and BSD (3-clause) licenses are typically no problem. Apache 2.0
+license typically doesn't work due to GPL incompatibility.
+
+If you are pulling in code from core Git, another project or code you've
+pulled from a forum / Stack Overflow then please flag this in your PR and
+also make sure you've given proper credit to the original author in the
+code snippet.
+
+## Style Guide
+
+The public API of `libgit2` is [ANSI C](http://en.wikipedia.org/wiki/ANSI_C)
+(a.k.a. C89) compatible. Internally, `libgit2` is written using a portable
+subset of C99 - in order to compile with GCC, Clang, MSVC, etc., we keep
+local variable declarations at the tops of blocks only and avoid `//` style
+comments. Additionally, `libgit2` follows some extra conventions for
+function and type naming, code formatting, and testing.
+
+We like to keep the source code consistent and easy to read. Maintaining
+this takes some discipline, but it's been more than worth it. Take a look
+at the
+[conventions file](https://github.com/libgit2/libgit2/blob/development/CONVENTIONS.md).
+
+## Starter Projects
+
+See our [projects list](https://github.com/libgit2/libgit2/blob/development/PROJECTS.md).
diff --git a/CONVENTIONS b/CONVENTIONS
deleted file mode 100644
index 575cdc563..000000000
--- a/CONVENTIONS
+++ /dev/null
@@ -1,107 +0,0 @@
-libgit2 conventions
-===================
-
-Namespace Prefixes
-------------------
-
-All types and functions start with 'git_'.
-
-All #define macros start with 'GIT_'.
-
-
-Type Definitions
-----------------
-
-Most types should be opaque, e.g.:
-
-----
- typedef struct git_odb git_odb;
-----
-
-with allocation functions returning an "instance" created within
-the library, and not within the application. This allows the type
-to grow (or shrink) in size without rebuilding client code.
-
-
-Public Exported Function Definitions
-------------------------------------
-
-All exported functions must be declared as:
-
-----
- GIT_EXTERN(result_type) git_modulename_functionname(arg_list);
-----
-
-
-Semi-Private Exported Functions
--------------------------------
-
-Functions whose modulename is followed by two underscores,
-for example 'git_odb__read_packed', are semi-private functions.
-They are primarily intended for use within the library itself,
-and may disappear or change their signature in a future release.
-
-
-Calling Conventions
--------------------
-
-Functions should prefer to return a 'int' to indicate success or
-failure and supply any output through the first argument (or first
-few arguments if multiple outputs are supplied).
-
-int status codes are 0 for GIT_SUCCESS and < 0 for an error.
-This permits common POSIX result testing:
-
-----
- if (git_odb_open(&odb, path))
- abort("odb open failed");
-----
-
-Functions returning a pointer may return NULL instead of an int
-if there is only one type of failure (ENOMEM).
-
-Functions returning a pointer may also return NULL if the common
-case needed by the application is strictly success/failure and a
-(possibly slower) function exists that the caller can use to get
-more detailed information. Parsing common data structures from
-on-disk formats is a good example of this pattern; in general a
-"corrupt" entity can be treated as though it does not exist but
-a more sophisticated "fsck" support function can report how the
-entity is malformed.
-
-
-Documentation Fomatting
------------------------
-
-All comments should conform to Doxygen "javadoc" style conventions
-for formatting the public API documentation.
-
-
-Public Header Format
---------------------
-
-All public headers defining types, functions or macros must use
-the following form, where ${filename} is the name of the file,
-after replacing non-identifier characters with '_'.
-
-----
- #ifndef INCLUDE_git_${filename}_h__
- #define INCLUDE_git_${filename}_h__
-
- #include "git/common.h"
-
- /**
- * @file git/${filename}.h
- * @brief Git some description
- * @defgroup git_${filename} some description routines
- * @ingroup Git
- * @{
- */
- GIT_BEGIN_DECL
-
- ... definitions ...
-
- /** @} */
- GIT_END_DECL
- #endif
-----
diff --git a/CONVENTIONS.md b/CONVENTIONS.md
new file mode 100644
index 000000000..5b8238a78
--- /dev/null
+++ b/CONVENTIONS.md
@@ -0,0 +1,234 @@
+# Libgit2 Conventions
+
+We like to keep the source consistent and readable. Herein are some
+guidelines that should help with that.
+
+## Compatibility
+
+`libgit2` runs on many different platforms with many different compilers.
+
+The public API of `libgit2` is [ANSI C](http://en.wikipedia.org/wiki/ANSI_C)
+(a.k.a. C89) compatible.
+
+Internally, `libgit2` is written using a portable subset of C99 - in order
+to maximize compatibility (e.g. with MSVC) we avoid certain C99
+extensions. Specifically, we keep local variable declarations at the tops
+of blocks only and we avoid `//` style comments.
+
+Also, to the greatest extent possible, we try to avoid lots of `#ifdef`s
+inside the core code base. This is somewhat unavoidable, but since it can
+really hamper maintainability, we keep it to a minimum.
+
+## Match Surrounding Code
+
+If there is one rule to take away from this document, it is *new code should
+match the surrounding code in a way that makes it impossible to distinguish
+the new from the old.* Consistency is more important to us than anyone's
+personal opinion about where braces should be placed or spaces vs. tabs.
+
+If a section of code is being completely rewritten, it is okay to bring it
+in line with the standards that are laid out here, but we will not accept
+submissions that contain a large number of changes that are merely
+reformatting.
+
+## Naming Things
+
+All external types and functions start with `git_` and all `#define` macros
+start with `GIT_`. The `libgit2` API is mostly broken into related
+functional modules each with a corresponding header. All functions in a
+module should be named like `git_modulename_functioname()`
+(e.g. `git_repository_open()`).
+
+Functions with a single output parameter should name that parameter `out`.
+Multiple outputs should be named `foo_out`, `bar_out`, etc.
+
+Parameters of type `git_oid` should be named `id`, or `foo_id`. Calls that
+return an OID should be named `git_foo_id`.
+
+Where a callback function is used, the function should also include a
+user-supplied extra input that is a `void *` named "payload" that will be
+passed through to the callback at each invocation.
+
+## Typedefs
+
+Wherever possible, use `typedef`. In some cases, if a structure is just a
+collection of function pointers, the pointer types don't need to be
+separately typedef'd, but loose function pointer types should be.
+
+## Exports
+
+All exported functions must be declared as:
+
+```c
+GIT_EXTERN(result_type) git_modulename_functionname(arg_list);
+```
+
+## Internals
+
+Functions whose *modulename* is followed by two underscores,
+for example `git_odb__read_packed`, are semi-private functions.
+They are primarily intended for use within the library itself,
+and may disappear or change their signature in a future release.
+
+## Parameters
+
+Out parameters come first.
+
+Whenever possible, pass argument pointers as `const`. Some structures (such
+as `git_repository` and `git_index`) have mutable internal structure that
+prevents this.
+
+Callbacks should always take a `void *` payload as their last parameter.
+Callback pointers are grouped with their payloads, and typically come last
+when passed as arguments:
+
+```c
+int git_foo(git_repository *repo, git_foo_cb callback, void *payload);
+```
+
+## Memory Ownership
+
+Some APIs allocate memory which the caller is responsible for freeing; others
+return a pointer into a buffer that's owned by some other object. Make this
+explicit in the documentation.
+
+## Return codes
+
+Most public APIs should return an `int` error code. As is typical with most
+C library functions, a zero value indicates success and a negative value
+indicates failure.
+
+Some bindings will transform these returned error codes into exception
+types, so returning a semantically appropriate error code is important.
+Check
+[`include/git2/errors.h`](https://github.com/libgit2/libgit2/blob/development/include/git2/errors.h)
+for the return codes already defined.
+
+In your implementation, use `giterr_set()` to provide extended error
+information to callers.
+
+If a `libgit2` function internally invokes another function that reports an
+error, but the error is not propagated up, use `giterr_clear()` to prevent
+callers from getting the wrong error message later on.
+
+
+## Structs
+
+Most public types should be opaque, e.g.:
+
+```C
+typedef struct git_odb git_odb;
+```
+
+...with allocation functions returning an "instance" created within
+the library, and not within the application. This allows the type
+to grow (or shrink) in size without rebuilding client code.
+
+To preserve ABI compatibility, include an `int version` field in all opaque
+structures, and initialize to the latest version in the construction call.
+Increment the "latest" version whenever the structure changes, and try to only
+append to the end of the structure.
+
+## Option Structures
+
+If a function's parameter count is too high, it may be desirable to package
+up the options in a structure. Make them transparent, include a version
+field, and provide an initializer constant or constructor. Using these
+structures should be this easy:
+
+```C
+git_foo_options opts = GIT_FOO_OPTIONS_INIT;
+opts.baz = BAZ_OPTION_ONE;
+git_foo(&opts);
+```
+
+## Enumerations
+
+Typedef all enumerated types. If each option stands alone, use the enum
+type for passing them as parameters; if they are flags to be OR'ed together,
+pass them as `unsigned int` or `uint32_t` or some appropriate type.
+
+## Code Layout
+
+Try to keep lines less than 80 characters long. This is a loose
+requirement, but going significantly over 80 columns is not nice.
+
+Use common sense to wrap most code lines; public function declarations
+can use a couple of different styles:
+
+```c
+/** All on one line is okay if it fits */
+GIT_EXTERN(int) git_foo_simple(git_oid *id);
+
+/** Otherwise one argument per line is a good next step */
+GIT_EXTERN(int) git_foo_id(
+ git_oid **out,
+ int a,
+ int b);
+```
+
+Indent with tabs; set your editor's tab width to 4 for best effect.
+
+Avoid trailing whitespace and only commit Unix-style newlines (i.e. no CRLF
+in the repository - just set `core.autocrlf` to true if you are writing code
+on a Windows machine).
+
+## Documentation
+
+All comments should conform to Doxygen "javadoc" style conventions for
+formatting the public API documentation. Try to document every parameter,
+and keep the comments up to date if you change the parameter list.
+
+## Public Header Template
+
+Use this template when creating a new public header.
+
+```C
+#ifndef INCLUDE_git_${filename}_h__
+#define INCLUDE_git_${filename}_h__
+
+#include "git/common.h"
+
+/**
+ * @file git/${filename}.h
+ * @brief Git some description
+ * @defgroup git_${filename} some description routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/* ... definitions ... */
+
+/** @} */
+GIT_END_DECL
+#endif
+```
+
+## Inlined functions
+
+All inlined functions must be declared as:
+
+```C
+GIT_INLINE(result_type) git_modulename_functionname(arg_list);
+```
+
+`GIT_INLINE` (or `inline`) should not be used in public headers in order
+to preserve ANSI C compatibility.
+
+## Tests
+
+`libgit2` uses the [clar](https://github.com/vmg/clar) testing framework.
+
+All PRs should have corresponding tests.
+
+* If the PR fixes an existing issue, the test should fail prior to applying
+ the PR and succeed after applying it.
+* If the PR is for new functionality, then the tests should exercise that
+ new functionality to a certain extent. We don't require 100% coverage
+ right now (although we are getting stricter over time).
+
+When adding new tests, we prefer if you attempt to reuse existing test data
+(in `tests-clar/resources/`) if possible. If you are going to add new test
+repositories, please try to strip them of unnecessary files (e.g. sample
+hooks, etc).
diff --git a/COPYING b/COPYING
index 75bc6a1fe..181737284 100644
--- a/COPYING
+++ b/COPYING
@@ -1,11 +1,17 @@
+ libgit2 is Copyright (C) the libgit2 contributors,
+ unless otherwise stated. See the AUTHORS file for details.
Note that the only valid version of the GPL as far as this project
is concerned is _this_ particular version of the license (ie v2, not
v2.2 or v3.x or whatever), unless explicitly otherwise stated.
+----------------------------------------------------------------------
+
+ LINKING EXCEPTION
+
In addition to the permissions in the GNU General Public License,
the authors give you unlimited permission to link the compiled
- version of this file into combinations with other programs,
+ version of this library into combinations with other programs,
and to distribute those combinations without any restriction
coming from the use of this file. (The General Public License
restrictions do apply in other respects; for example, they cover
@@ -354,3 +360,561 @@ proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.
+
+----------------------------------------------------------------------
+
+The bundled ZLib code is licensed under the ZLib license:
+
+Copyright (C) 1995-2010 Jean-loup Gailly and Mark Adler
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+ Jean-loup Gailly Mark Adler
+ jloup@gzip.org madler@alumni.caltech.edu
+
+----------------------------------------------------------------------
+
+The Clar framework is licensed under the MIT license:
+
+Copyright (C) 2011 by Vicent Marti
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+----------------------------------------------------------------------
+
+The regex library (deps/regex/) is licensed under the GNU LGPL
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+----------------------------------------------------------------------
diff --git a/Makefile.embed b/Makefile.embed
index fec090fa7..eb8a78ebf 100644
--- a/Makefile.embed
+++ b/Makefile.embed
@@ -1,15 +1,49 @@
+PLATFORM=$(shell uname -s)
+
+ifneq (,$(CROSS_COMPILE))
+ PREFIX=$(CROSS_COMPILE)-
+else
+ PREFIX=
+endif
+
+MINGW=0
+ifneq (,$(findstring MINGW32,$(PLATFORM)))
+ MINGW=1
+endif
+ifneq (,$(findstring mingw,$(CROSS_COMPILE)))
+ MINGW=1
+endif
+
rm=rm -f
-CC=cc
-AR=ar cq
-RANLIB=ranlib
+AR=$(PREFIX)ar cq
+RANLIB=$(PREFIX)ranlib
+
LIBNAME=libgit2.a
-INCLUDES= -I. -Isrc -Iinclude -Ideps/zlib
+ifeq ($(MINGW),1)
+ CC=gcc
+else
+ CC=cc
+endif
+
+CC:=$(PREFIX)$(CC)
+
+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 -Wno-missing-field-initializers -O2 $(EXTRA_CFLAGS)
+
+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
-DEFINES= $(INCLUDES) -DNO_VIZ -DSTDC -DNO_GZIP -D_FILE_OFFSET_BITS=64
-CFLAGS= -g $(DEFINES) -Wall -Wextra -fPIC -O2
+ifeq ($(MINGW),1)
+ SRCS += $(wildcard src/win32/*.c) $(wildcard src/compat/*.c) deps/regex/regex.c
+ INCLUDES += -Ideps/regex
+ DEFINES += -DWIN32 -D_WIN32_WINNT=0x0501 -D__USE_MINGW_ANSI_STDIO=1
+else
+ SRCS += $(wildcard src/unix/*.c)
+ CFLAGS += -fPIC
+endif
-SRCS = $(wildcard src/*.c) $(wildcard src/unix/*.c) $(wildcard deps/zlib/*.c)
OBJS = $(patsubst %.c,%.o,$(SRCS))
%.c.o:
diff --git a/PROJECTS.md b/PROJECTS.md
new file mode 100644
index 000000000..d17214471
--- /dev/null
+++ b/PROJECTS.md
@@ -0,0 +1,85 @@
+Projects For LibGit2
+====================
+
+So, you want to start helping out with `libgit2`? That's fantastic! We
+welcome contributions and we promise we'll try to be nice.
+
+This is a list of libgit2 related projects that new contributors can take
+on. It includes a number of good starter projects and well as some larger
+ideas that no one is actively working on.
+
+## Before You Start
+
+Please start by reading the README.md, CONTRIBUTING.md, and CONVENTIONS.md
+files before diving into one of these projects. Those will explain our
+work flow and coding conventions to help ensure that your work will be
+easily integrated into libgit2.
+
+Next, work through the build instructions and make sure you can clone the
+repository, compile it, and run the tests successfully. That will make
+sure that your development environment is set up correctly and you are
+ready to start on libgit2 development.
+
+## Starter Projects
+
+These are good small projects to get started with libgit2.
+
+* Look at the `examples/` programs, find an existing one that mirrors a
+ core Git command and add a missing command-line option. There are many
+ gaps right now and this helps demonstrate how to use the library. Here
+ are some specific ideas:
+ * Fix the `examples/diff.c` implementation of the `-B`
+ (a.k.a. `--break-rewrites`) command line option to actually look for
+ the optional `[<n>][/<m>]` configuration values. There is an
+ existing comment that reads `/* TODO: parse thresholds */`. The
+ trick to this one will be doing it in a manner that is clean and
+ simple, but still handles the various cases correctly (e.g. `-B/70%`
+ is apparently a legal setting).
+ * Implement the `--log-size` option for `examples/log.c`. I think all
+ the data is available, you would just need to add the code into the
+ `print_commit()` routine (along with a way of passing the option
+ into that function).
+ * As an extension to the matching idea for `examples/log.c`, add the
+ `-i` option to use `strcasestr()` for matches.
+ * For `examples/log.c`, implement the `--first-parent` option now that
+ libgit2 supports it in the revwalk API.
+* Pick a Git command that is not already emulated in `examples/` and write
+ a new example that mirrors the behavior. Examples don't have to be
+ perfect emulations, but should demonstrate how to use the libgit2 APIs
+ to get results that are similar to Git commands. This lets you (and us)
+ easily exercise a particular facet of the API and measure compatability
+ and feature parity with core git.
+* Submit a PR to clarify documentation! While we do try to document all of
+ the APIs, your fresh eyes on the documentation will find areas that are
+ confusing much more easily.
+* Add support for the symref protocol extension, so we don't guess
+ what the remote's default branch is
+ [#2006](https://github.com/libgit2/libgit2/issues/2006)
+
+If none of these appeal to you, take a look at our issues list to see if
+there are any unresolved issues you'd like to jump in on.
+
+## Larger Projects
+
+These are ideas for larger projects mostly taken from our backlog of
+[Issues](https://github.com/libgit2/libgit2/issues). Please don't dive
+into one of these as a first project for libgit2 - we'd rather get to
+know you first by successfully shipping your work on one of the smaller
+projects above.
+
+* Port part of the Git test suite to run against the command line emulation
+ in examples/
+* Fix symlink support for files in the .git directory (i.e. don't overwrite
+ the symlinks when writing the file contents back out)
+* Implement a 'git describe' like API
+* Add hooks API to enumerate and manage hooks (not run them at this point)
+* Isolate logic of ignore evaluation into a standalone API
+* Upgrade internal libxdiff code to latest from core Git
+* Add a hashtable lookup for files in the index instead of binary search
+ every time
+* Make the index write the cache out to disk (with tests to gain
+ confidence that the caching invalidation works correctly)
+* Have the tree builder use a hash table when building instead of a
+ list.
+* Move the tagopt mechanism to the newer git 1.9 interpretation of
+ --tags [#2120](https://github.com/libgit2/libgit2/issues/2120)
diff --git a/README.md b/README.md
index b5c76a6be..b60e8a234 100644
--- a/README.md
+++ b/README.md
@@ -1,26 +1,41 @@
libgit2 - the Git linkable library
-======================
+==================================
+
+[![Build Status](https://secure.travis-ci.org/libgit2/libgit2.png?branch=development)](http://travis-ci.org/libgit2/libgit2)
+[![Coverity Scan Build Status](https://scan.coverity.com/projects/639/badge.svg)](https://scan.coverity.com/projects/639)
-libgit2 is a portable, pure C implementation of the Git core methods provided as a
-re-entrant linkable library with a solid API, allowing you to write native
-speed custom Git applications in any language with bindings.
+`libgit2` is a portable, pure C implementation of the Git core methods
+provided as a re-entrant linkable library with a solid API, allowing you to
+write native speed custom Git applications in any language with bindings.
-libgit2 is licensed under a **very permissive license** (GPLv2 with a special Linking Exception).
-This basically means that you can link it (unmodified) with any kind of software without having to
-release its source code.
+`libgit2` is licensed under a **very permissive license** (GPLv2 with a special
+Linking Exception). This basically means that you can link it (unmodified)
+with any kind of software without having to release its source code.
+Additionally, the example code has been released to the public domain (see the
+[separate license](examples/COPYING) for more information).
-* Mailing list: <libgit2@librelist.org>
-* Website: <http://libgit2.github.com>
+* Website: [libgit2.github.com](http://libgit2.github.com)
+* StackOverflow Tag: [libgit2](http://stackoverflow.com/questions/tagged/libgit2)
+* Issues: [GitHub Issues](https://github.com/libgit2/libgit2/issues) (Right here!)
* API documentation: <http://libgit2.github.com/libgit2>
-* Usage guide: <http://libgit2.github.com/api.html>
+* IRC: [#libgit2](irc://irc.freenode.net/libgit2) on irc.freenode.net.
+* Mailing list: The libgit2 mailing list was
+ traditionally hosted in Librelist but has been deprecated. We encourage you to
+ [use StackOverflow](http://stackoverflow.com/questions/tagged/libgit2) instead for any questions regarding
+ the library, or [open an issue](https://github.com/libgit2/libgit2/issues)
+ on GitHub for bug reports. The mailing list archives are still available at
+ <http://librelist.com/browser/libgit2/>.
+
What It Can Do
-==================================
+==============
-libgit2 is already very usable.
+`libgit2` is already very usable and is being used in production for many
+applications including the GitHub.com site, in Plastic SCM and also powering
+Microsoft's Visual Studio tools for Git. The library provides:
* SHA conversions, formatting and shortening
-* abstracked ODB backend system
+* abstracted ODB backend system
* commit, tag, tree and blob parsing, editing, and write-back
* tree traversal
* revision walking
@@ -32,15 +47,26 @@ libgit2 is already very usable.
* descriptive and detailed error messages
* ...and more (over 175 different API calls)
+Optional dependencies
+=====================
+
+While the library provides git functionality without the need for
+dependencies, it can make use of a few libraries to add to it:
+
+- pthreads (non-Windows) to enable threadsafe access as well as multi-threaded pack generation
+- OpenSSL (non-Windows) to talk over HTTPS and provide the SHA-1 functions
+- LibSSH2 to enable the ssh transport
+- iconv (OSX) to handle the HFS+ path encoding peculiarities
+
Building libgit2 - Using CMake
==============================
-libgit2 builds cleanly on most platforms without any external dependencies.
-Under Unix-like systems, like Linux, * BSD and Mac OS X, libgit2 expects `pthreads` to be available;
+`libgit2` builds cleanly on most platforms without any external dependencies.
+Under Unix-like systems, like Linux, \*BSD and Mac OS X, libgit2 expects `pthreads` to be available;
they should be installed by default on all systems. Under Windows, libgit2 uses the native Windows API
for threading.
-The libgit2 library is built using CMake 2.6+ (<http://www.cmake.org>) on all platforms.
+The `libgit2` library is built using [CMake](<http://www.cmake.org>) (version 2.6 or newer) on all platforms.
On most systems you can build the library using the following commands
@@ -59,55 +85,133 @@ For more advanced use or questions about CMake please read <http://www.cmake.org
The following CMake variables are declared:
-- `INSTALL_BIN`: Where to install binaries to.
-- `INSTALL_LIB`: Where to install libraries to.
-- `INSTALL_INC`: Where to install headers to.
+- `BIN_INSTALL_DIR`: Where to install binaries to.
+- `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_TESTS`: Build the libgit2 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.
+
+Android
+-------
+
+Extract toolchain from NDK using, `make-standalone-toolchain.sh` script.
+Optionally, crosscompile and install OpenSSL inside of it. Then create CMake
+toolchain file that configures paths to your crosscompiler (substitute `{PATH}`
+with full path to the toolchain):
+
+ SET(CMAKE_SYSTEM_NAME Linux)
+ SET(CMAKE_SYSTEM_VERSION Android)
+
+ SET(CMAKE_C_COMPILER {PATH}/bin/arm-linux-androideabi-gcc)
+ SET(CMAKE_CXX_COMPILER {PATH}/bin/arm-linux-androideabi-g++)
+ SET(CMAKE_FIND_ROOT_PATH {PATH}/sysroot/)
+
+ SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+ SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+ SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+
+Add `-DCMAKE_TOOLCHAIN_FILE={pathToToolchainFile} -DANDROID=1` to cmake command
+when configuring.
Language Bindings
==================================
Here are the bindings to libgit2 that are currently available:
-* Rugged (Ruby bindings) <https://github.com/libgit2/rugged>
-* objective-git (Objective-C bindings) <https://github.com/libgit2/objective-git>
-* pygit2 (Python bindings) <https://github.com/libgit2/pygit2>
-* libgit2sharp (.NET bindings) <https://github.com/libgit2/libgit2sharp>
-* php-git (PHP bindings) <https://github.com/libgit2/php-git>
-* luagit2 (Lua bindings) <https://github.com/libgit2/luagit2>
-* GitForDelphi (Delphi bindings) <https://github.com/libgit2/GitForDelphi>
-* node-gitteh (Node.js bindings) <https://github.com/libgit2/node-gitteh>
-* nodegit (Node.js bindings) <https://github.com/tbranyen/nodegit>
-* go-git (Go bindings) <https://github.com/str1ngs/go-git>
-* libqgit2 (C++ QT bindings) <https://projects.kde.org/projects/playground/libs/libqgit2/>
-* libgit2-ocaml (ocaml bindings) <https://github.com/burdges/libgit2-ocaml>
-* Geef (Erlang bindings) <https://github.com/schacon/geef>
-* libgit2net (.NET bindings, low level) <https://github.com/txdv/libgit2net>
-* parrot-libgit2 (Parrot Virtual Machine bindings) <https://github.com/letolabs/parrot-libgit2>
-* hgit2 (Haskell bindings) <https://github.com/norm2782/hgit2>
+* C++
+ * libqgit2, Qt bindings <https://projects.kde.org/projects/playground/libs/libqgit2/>
+* Chicken Scheme
+ * chicken-git <https://wiki.call-cc.org/egg/git>
+* D
+ * dlibgit <https://github.com/AndrejMitrovic/dlibgit>
+* Delphi
+ * GitForDelphi <https://github.com/libgit2/GitForDelphi>
+* Erlang
+ * Geef <https://github.com/carlosmn/geef>
+* Go
+ * git2go <https://github.com/libgit2/git2go>
+* GObject
+ * libgit2-glib <https://live.gnome.org/Libgit2-glib>
+* Haskell
+ * hgit2 <https://github.com/fpco/gitlib>
+* Java
+ * Jagged <https://github.com/ethomson/jagged>
+* Lua
+ * luagit2 <https://github.com/libgit2/luagit2>
+* .NET
+ * libgit2sharp <https://github.com/libgit2/libgit2sharp>
+* Node.js
+ * node-gitteh <https://github.com/libgit2/node-gitteh>
+ * nodegit <https://github.com/tbranyen/nodegit>
+* Objective-C
+ * objective-git <https://github.com/libgit2/objective-git>
+* OCaml
+ * libgit2-ocaml <https://github.com/burdges/libgit2-ocaml>
+* Parrot Virtual Machine
+ * parrot-libgit2 <https://github.com/letolabs/parrot-libgit2>
+* Perl
+ * Git-Raw <https://github.com/ghedo/p5-Git-Raw>
+* PHP
+ * php-git <https://github.com/libgit2/php-git>
+* PowerShell
+ * GitPowerShell <https://github.com/ethomson/gitpowershell>
+* Python
+ * pygit2 <https://github.com/libgit2/pygit2>
+* R
+ * git2r <https://github.com/ropensci/git2r>
+* Ruby
+ * Rugged <https://github.com/libgit2/rugged>
+* Vala
+ * libgit2.vapi <https://github.com/apmasell/vapis/blob/master/libgit2.vapi>
If you start another language binding to libgit2, please let us know so
we can add it to the list.
-How Can I Contribute
+How Can I Contribute?
==================================
-Fork libgit2/libgit2 on GitHub, add your improvement, push it to a branch
-in your fork named for the topic, send a pull request.
-
-You can also file bugs or feature requests under the libgit2 project on
-GitHub, or join us on the mailing list by sending an email to:
-
-libgit2@librelist.com
-
+Check the [contribution guidelines](CONTRIBUTING.md) to understand our
+workflow, the libgit2 [coding conventions](CONVENTIONS.md), and out list of
+[good starting projects](PROJECTS.md).
License
==================================
-libgit2 is under GPL2 **with linking exemption**. This means you
-can link to the library with any program, commercial, open source or
-other. However, you cannot modify libgit2 and distribute it without
+
+`libgit2` is under GPL2 **with linking exemption**. This means you can link to
+and use the library from any program, proprietary or open source; paid or
+gratis. However, you cannot modify libgit2 and distribute it without
supplying the source.
-See the COPYING file for the full license text.
+See the [COPYING file](COPYING) for the full license text.
diff --git a/cmake/Modules/AddCFlagIfSupported.cmake b/cmake/Modules/AddCFlagIfSupported.cmake
new file mode 100644
index 000000000..67fc89510
--- /dev/null
+++ b/cmake/Modules/AddCFlagIfSupported.cmake
@@ -0,0 +1,16 @@
+# - Append compiler flag to CMAKE_C_FLAGS if compiler supports it
+# ADD_C_FLAG_IF_SUPPORTED(<flag>)
+# <flag> - the compiler flag to test
+# This internally calls the CHECK_C_COMPILER_FLAG macro.
+
+INCLUDE(CheckCCompilerFlag)
+
+MACRO(ADD_C_FLAG_IF_SUPPORTED _FLAG)
+ STRING(TOUPPER ${_FLAG} UPCASE)
+ STRING(REGEX REPLACE "^-" "" UPCASE_PRETTY ${UPCASE})
+ CHECK_C_COMPILER_FLAG(${_FLAG} IS_${UPCASE_PRETTY}_SUPPORTED)
+
+ IF(IS_${UPCASE_PRETTY}_SUPPORTED)
+ SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${_FLAG}")
+ ENDIF()
+ENDMACRO()
diff --git a/cmake/Modules/FindHTTP_Parser.cmake b/cmake/Modules/FindHTTP_Parser.cmake
new file mode 100644
index 000000000..d92bf75cc
--- /dev/null
+++ b/cmake/Modules/FindHTTP_Parser.cmake
@@ -0,0 +1,39 @@
+# - Try to find http-parser
+#
+# Defines the following variables:
+#
+# HTTP_PARSER_FOUND - system has http-parser
+# HTTP_PARSER_INCLUDE_DIR - the http-parser include directory
+# HTTP_PARSER_LIBRARIES - Link these to use http-parser
+# HTTP_PARSER_VERSION_MAJOR - major version
+# HTTP_PARSER_VERSION_MINOR - minor version
+# HTTP_PARSER_VERSION_STRING - the version of http-parser found
+
+# Find the header and library
+FIND_PATH(HTTP_PARSER_INCLUDE_DIR NAMES http_parser.h)
+FIND_LIBRARY(HTTP_PARSER_LIBRARY NAMES http_parser libhttp_parser)
+
+# Found the header, read version
+if (HTTP_PARSER_INCLUDE_DIR AND EXISTS "${HTTP_PARSER_INCLUDE_DIR}/http_parser.h")
+ FILE(READ "${HTTP_PARSER_INCLUDE_DIR}/http_parser.h" HTTP_PARSER_H)
+ IF (HTTP_PARSER_H)
+ STRING(REGEX REPLACE ".*#define[\t ]+HTTP_PARSER_VERSION_MAJOR[\t ]+([0-9]+).*" "\\1" HTTP_PARSER_VERSION_MAJOR "${HTTP_PARSER_H}")
+ STRING(REGEX REPLACE ".*#define[\t ]+HTTP_PARSER_VERSION_MINOR[\t ]+([0-9]+).*" "\\1" HTTP_PARSER_VERSION_MINOR "${HTTP_PARSER_H}")
+ SET(HTTP_PARSER_VERSION_STRING "${HTTP_PARSER_VERSION_MAJOR}.${HTTP_PARSER_VERSION_MINOR}")
+ ENDIF()
+ UNSET(HTTP_PARSER_H)
+ENDIF()
+
+# Handle the QUIETLY and REQUIRED arguments and set HTTP_PARSER_FOUND
+# to TRUE if all listed variables are TRUE
+INCLUDE(FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(HTTP_Parser REQUIRED_VARS HTTP_PARSER_INCLUDE_DIR HTTP_PARSER_LIBRARY)
+
+# Hide advanced variables
+MARK_AS_ADVANCED(HTTP_PARSER_INCLUDE_DIR HTTP_PARSER_LIBRARY)
+
+# Set standard variables
+IF (HTTP_PARSER_FOUND)
+ SET(HTTP_PARSER_LIBRARIES ${HTTP_PARSER_LIBRARY})
+ set(HTTP_PARSER_INCLUDE_DIRS ${HTTP_PARSER_INCLUDE_DIR})
+ENDIF()
diff --git a/cmake/Modules/FindIconv.cmake b/cmake/Modules/FindIconv.cmake
new file mode 100644
index 000000000..c5a419011
--- /dev/null
+++ b/cmake/Modules/FindIconv.cmake
@@ -0,0 +1,43 @@
+# - Try to find Iconv
+# Once done this will define
+#
+# ICONV_FOUND - system has Iconv
+# ICONV_INCLUDE_DIR - the Iconv include directory
+# ICONV_LIBRARIES - Link these to use Iconv
+#
+
+IF(ICONV_INCLUDE_DIR AND ICONV_LIBRARIES)
+ # Already in cache, be silent
+ SET(ICONV_FIND_QUIETLY TRUE)
+ENDIF()
+
+FIND_PATH(ICONV_INCLUDE_DIR iconv.h PATHS /opt/local/include NO_DEFAULT_PATH)
+FIND_PATH(ICONV_INCLUDE_DIR iconv.h)
+
+FIND_LIBRARY(iconv_lib NAMES iconv libiconv libiconv-2 c NO_DEFAULT_PATH PATHS /opt/local/lib)
+FIND_LIBRARY(iconv_lib NAMES iconv libiconv libiconv-2 c)
+
+IF(ICONV_INCLUDE_DIR AND iconv_lib)
+ SET(ICONV_FOUND TRUE)
+ENDIF()
+
+IF(ICONV_FOUND)
+ # split iconv into -L and -l linker options, so we can set them for pkg-config
+ GET_FILENAME_COMPONENT(iconv_path ${iconv_lib} PATH)
+ GET_FILENAME_COMPONENT(iconv_name ${iconv_lib} NAME_WE)
+ STRING(REGEX REPLACE "^lib" "" iconv_name ${iconv_name})
+ SET(ICONV_LIBRARIES "-L${iconv_path} -l${iconv_name}")
+
+ IF(NOT ICONV_FIND_QUIETLY)
+ MESSAGE(STATUS "Found Iconv: ${ICONV_LIBRARIES}")
+ ENDIF(NOT ICONV_FIND_QUIETLY)
+ELSE()
+ IF(Iconv_FIND_REQUIRED)
+ MESSAGE(FATAL_ERROR "Could not find Iconv")
+ ENDIF(Iconv_FIND_REQUIRED)
+ENDIF()
+
+MARK_AS_ADVANCED(
+ ICONV_INCLUDE_DIR
+ ICONV_LIBRARIES
+)
diff --git a/cmake/Modules/FindLIBSSH2.cmake b/cmake/Modules/FindLIBSSH2.cmake
new file mode 100644
index 000000000..6347d60ea
--- /dev/null
+++ b/cmake/Modules/FindLIBSSH2.cmake
@@ -0,0 +1,44 @@
+if (LIBSSH2_LIBRARIES AND LIBSSH2_INCLUDE_DIRS)
+ set(LIBSSH2_FOUND TRUE)
+else (LIBSSH2_LIBRARIES AND LIBSSH2_INCLUDE_DIRS)
+ find_path(LIBSSH2_INCLUDE_DIR
+ NAMES
+ libssh2.h
+ PATHS
+ /usr/include
+ /usr/local/include
+ /opt/local/include
+ /sw/include
+ ${CMAKE_INCLUDE_PATH}
+ ${CMAKE_INSTALL_PREFIX}/include
+ )
+
+ find_library(LIBSSH2_LIBRARY
+ NAMES
+ ssh2
+ libssh2
+ PATHS
+ /usr/lib
+ /usr/local/lib
+ /opt/local/lib
+ /sw/lib
+ ${CMAKE_LIBRARY_PATH}
+ ${CMAKE_INSTALL_PREFIX}/lib
+ )
+
+ if (LIBSSH2_INCLUDE_DIR AND LIBSSH2_LIBRARY)
+ set(LIBSSH2_FOUND TRUE)
+ endif (LIBSSH2_INCLUDE_DIR AND LIBSSH2_LIBRARY)
+
+ if (LIBSSH2_FOUND)
+ set(LIBSSH2_INCLUDE_DIRS
+ ${LIBSSH2_INCLUDE_DIR}
+ )
+
+ set(LIBSSH2_LIBRARIES
+ ${LIBSSH2_LIBRARIES}
+ ${LIBSSH2_LIBRARY}
+ )
+ endif (LIBSSH2_FOUND)
+endif (LIBSSH2_LIBRARIES AND LIBSSH2_INCLUDE_DIRS)
+
diff --git a/deps/http-parser/LICENSE-MIT b/deps/http-parser/LICENSE-MIT
new file mode 100644
index 000000000..58010b388
--- /dev/null
+++ b/deps/http-parser/LICENSE-MIT
@@ -0,0 +1,23 @@
+http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright
+Igor Sysoev.
+
+Additional changes are licensed under the same terms as NGINX and
+copyright Joyent, Inc. and other Node contributors. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
diff --git a/deps/http-parser/http_parser.c b/deps/http-parser/http_parser.c
new file mode 100644
index 000000000..203530254
--- /dev/null
+++ b/deps/http-parser/http_parser.c
@@ -0,0 +1,2174 @@
+/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev
+ *
+ * Additional changes are licensed under the same terms as NGINX and
+ * copyright Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "http_parser.h"
+#include <assert.h>
+#include <stddef.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#ifndef ULLONG_MAX
+# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */
+#endif
+
+#ifndef MIN
+# define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+#ifndef ARRAY_SIZE
+# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#endif
+
+#ifndef BIT_AT
+# define BIT_AT(a, i) \
+ (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \
+ (1 << ((unsigned int) (i) & 7))))
+#endif
+
+#ifndef ELEM_AT
+# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v))
+#endif
+
+#define SET_ERRNO(e) \
+do { \
+ parser->http_errno = (e); \
+} while(0)
+
+
+/* Run the notify callback FOR, returning ER if it fails */
+#define CALLBACK_NOTIFY_(FOR, ER) \
+do { \
+ assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \
+ \
+ if (settings->on_##FOR) { \
+ if (0 != settings->on_##FOR(parser)) { \
+ SET_ERRNO(HPE_CB_##FOR); \
+ } \
+ \
+ /* We either errored above or got paused; get out */ \
+ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \
+ return (ER); \
+ } \
+ } \
+} while (0)
+
+/* Run the notify callback FOR and consume the current byte */
+#define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1)
+
+/* Run the notify callback FOR and don't consume the current byte */
+#define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data)
+
+/* Run data callback FOR with LEN bytes, returning ER if it fails */
+#define CALLBACK_DATA_(FOR, LEN, ER) \
+do { \
+ assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \
+ \
+ if (FOR##_mark) { \
+ if (settings->on_##FOR) { \
+ if (0 != settings->on_##FOR(parser, FOR##_mark, (LEN))) { \
+ SET_ERRNO(HPE_CB_##FOR); \
+ } \
+ \
+ /* We either errored above or got paused; get out */ \
+ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \
+ return (ER); \
+ } \
+ } \
+ FOR##_mark = NULL; \
+ } \
+} while (0)
+
+/* Run the data callback FOR and consume the current byte */
+#define CALLBACK_DATA(FOR) \
+ CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1)
+
+/* Run the data callback FOR and don't consume the current byte */
+#define CALLBACK_DATA_NOADVANCE(FOR) \
+ CALLBACK_DATA_(FOR, p - FOR##_mark, p - data)
+
+/* Set the mark FOR; non-destructive if mark is already set */
+#define MARK(FOR) \
+do { \
+ if (!FOR##_mark) { \
+ FOR##_mark = p; \
+ } \
+} while (0)
+
+
+#define PROXY_CONNECTION "proxy-connection"
+#define CONNECTION "connection"
+#define CONTENT_LENGTH "content-length"
+#define TRANSFER_ENCODING "transfer-encoding"
+#define UPGRADE "upgrade"
+#define CHUNKED "chunked"
+#define KEEP_ALIVE "keep-alive"
+#define CLOSE "close"
+
+
+static const char *method_strings[] =
+ {
+#define XX(num, name, string) #string,
+ HTTP_METHOD_MAP(XX)
+#undef XX
+ };
+
+
+/* Tokens as defined by rfc 2616. Also lowercases them.
+ * token = 1*<any CHAR except CTLs or separators>
+ * separators = "(" | ")" | "<" | ">" | "@"
+ * | "," | ";" | ":" | "\" | <">
+ * | "/" | "[" | "]" | "?" | "="
+ * | "{" | "}" | SP | HT
+ */
+static const char tokens[256] = {
+/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
+ 0, '!', 0, '#', '$', '%', '&', '\'',
+/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
+ 0, 0, '*', '+', 0, '-', '.', 0,
+/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
+ '0', '1', '2', '3', '4', '5', '6', '7',
+/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
+ '8', '9', 0, 0, 0, 0, 0, 0,
+/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
+ 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
+ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
+/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
+ 'x', 'y', 'z', 0, 0, 0, '^', '_',
+/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
+ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
+ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
+/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
+ 'x', 'y', 'z', 0, '|', 0, '~', 0 };
+
+
+static const int8_t unhex[256] =
+ {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1
+ ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+ };
+
+
+#if HTTP_PARSER_STRICT
+# define T(v) 0
+#else
+# define T(v) v
+#endif
+
+
+static const uint8_t normal_url_char[32] = {
+/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
+ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
+/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
+ 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0,
+/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
+ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
+/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
+ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
+/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
+ 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128,
+/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0,
+/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, };
+
+#undef T
+
+enum state
+ { s_dead = 1 /* important that this is > 0 */
+
+ , s_start_req_or_res
+ , s_res_or_resp_H
+ , s_start_res
+ , s_res_H
+ , s_res_HT
+ , s_res_HTT
+ , s_res_HTTP
+ , s_res_first_http_major
+ , s_res_http_major
+ , s_res_first_http_minor
+ , s_res_http_minor
+ , s_res_first_status_code
+ , s_res_status_code
+ , s_res_status
+ , s_res_line_almost_done
+
+ , s_start_req
+
+ , s_req_method
+ , s_req_spaces_before_url
+ , s_req_schema
+ , s_req_schema_slash
+ , s_req_schema_slash_slash
+ , s_req_server_start
+ , s_req_server
+ , s_req_server_with_at
+ , s_req_path
+ , s_req_query_string_start
+ , s_req_query_string
+ , s_req_fragment_start
+ , s_req_fragment
+ , s_req_http_start
+ , s_req_http_H
+ , s_req_http_HT
+ , s_req_http_HTT
+ , s_req_http_HTTP
+ , s_req_first_http_major
+ , s_req_http_major
+ , s_req_first_http_minor
+ , s_req_http_minor
+ , s_req_line_almost_done
+
+ , s_header_field_start
+ , s_header_field
+ , s_header_value_start
+ , s_header_value
+ , s_header_value_lws
+
+ , s_header_almost_done
+
+ , s_chunk_size_start
+ , s_chunk_size
+ , s_chunk_parameters
+ , s_chunk_size_almost_done
+
+ , s_headers_almost_done
+ , s_headers_done
+
+ /* Important: 's_headers_done' must be the last 'header' state. All
+ * states beyond this must be 'body' states. It is used for overflow
+ * checking. See the PARSING_HEADER() macro.
+ */
+
+ , s_chunk_data
+ , s_chunk_data_almost_done
+ , s_chunk_data_done
+
+ , s_body_identity
+ , s_body_identity_eof
+
+ , s_message_done
+ };
+
+
+#define PARSING_HEADER(state) (state <= s_headers_done)
+
+
+enum header_states
+ { h_general = 0
+ , h_C
+ , h_CO
+ , h_CON
+
+ , h_matching_connection
+ , h_matching_proxy_connection
+ , h_matching_content_length
+ , h_matching_transfer_encoding
+ , h_matching_upgrade
+
+ , h_connection
+ , h_content_length
+ , h_transfer_encoding
+ , h_upgrade
+
+ , h_matching_transfer_encoding_chunked
+ , h_matching_connection_keep_alive
+ , h_matching_connection_close
+
+ , h_transfer_encoding_chunked
+ , h_connection_keep_alive
+ , h_connection_close
+ };
+
+enum http_host_state
+ {
+ s_http_host_dead = 1
+ , s_http_userinfo_start
+ , s_http_userinfo
+ , s_http_host_start
+ , s_http_host_v6_start
+ , s_http_host
+ , s_http_host_v6
+ , s_http_host_v6_end
+ , s_http_host_port_start
+ , s_http_host_port
+};
+
+/* Macros for character classes; depends on strict-mode */
+#define CR '\r'
+#define LF '\n'
+#define LOWER(c) (unsigned char)(c | 0x20)
+#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z')
+#define IS_NUM(c) ((c) >= '0' && (c) <= '9')
+#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c))
+#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f'))
+#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \
+ (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \
+ (c) == ')')
+#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \
+ (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \
+ (c) == '$' || (c) == ',')
+
+#if HTTP_PARSER_STRICT
+#define TOKEN(c) (tokens[(unsigned char)c])
+#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c))
+#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-')
+#else
+#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c])
+#define IS_URL_CHAR(c) \
+ (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80))
+#define IS_HOST_CHAR(c) \
+ (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_')
+#endif
+
+
+#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res)
+
+
+#if HTTP_PARSER_STRICT
+# define STRICT_CHECK(cond) \
+do { \
+ if (cond) { \
+ SET_ERRNO(HPE_STRICT); \
+ goto error; \
+ } \
+} while (0)
+# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead)
+#else
+# define STRICT_CHECK(cond)
+# define NEW_MESSAGE() start_state
+#endif
+
+
+/* Map errno values to strings for human-readable output */
+#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s },
+static struct {
+ const char *name;
+ const char *description;
+} http_strerror_tab[] = {
+ HTTP_ERRNO_MAP(HTTP_STRERROR_GEN)
+};
+#undef HTTP_STRERROR_GEN
+
+int http_message_needs_eof(const http_parser *parser);
+
+/* Our URL parser.
+ *
+ * This is designed to be shared by http_parser_execute() for URL validation,
+ * hence it has a state transition + byte-for-byte interface. In addition, it
+ * is meant to be embedded in http_parser_parse_url(), which does the dirty
+ * work of turning state transitions URL components for its API.
+ *
+ * This function should only be invoked with non-space characters. It is
+ * assumed that the caller cares about (and can detect) the transition between
+ * URL and non-URL states by looking for these.
+ */
+static enum state
+parse_url_char(enum state s, const char ch)
+{
+ if (ch == ' ' || ch == '\r' || ch == '\n') {
+ return s_dead;
+ }
+
+#if HTTP_PARSER_STRICT
+ if (ch == '\t' || ch == '\f') {
+ return s_dead;
+ }
+#endif
+
+ switch (s) {
+ case s_req_spaces_before_url:
+ /* Proxied requests are followed by scheme of an absolute URI (alpha).
+ * All methods except CONNECT are followed by '/' or '*'.
+ */
+
+ if (ch == '/' || ch == '*') {
+ return s_req_path;
+ }
+
+ if (IS_ALPHA(ch)) {
+ return s_req_schema;
+ }
+
+ break;
+
+ case s_req_schema:
+ if (IS_ALPHA(ch)) {
+ return s;
+ }
+
+ if (ch == ':') {
+ return s_req_schema_slash;
+ }
+
+ break;
+
+ case s_req_schema_slash:
+ if (ch == '/') {
+ return s_req_schema_slash_slash;
+ }
+
+ break;
+
+ case s_req_schema_slash_slash:
+ if (ch == '/') {
+ return s_req_server_start;
+ }
+
+ break;
+
+ case s_req_server_with_at:
+ if (ch == '@') {
+ return s_dead;
+ }
+
+ /* FALLTHROUGH */
+ case s_req_server_start:
+ case s_req_server:
+ if (ch == '/') {
+ return s_req_path;
+ }
+
+ if (ch == '?') {
+ return s_req_query_string_start;
+ }
+
+ if (ch == '@') {
+ return s_req_server_with_at;
+ }
+
+ if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') {
+ return s_req_server;
+ }
+
+ break;
+
+ case s_req_path:
+ if (IS_URL_CHAR(ch)) {
+ return s;
+ }
+
+ switch (ch) {
+ case '?':
+ return s_req_query_string_start;
+
+ case '#':
+ return s_req_fragment_start;
+ }
+
+ break;
+
+ case s_req_query_string_start:
+ case s_req_query_string:
+ if (IS_URL_CHAR(ch)) {
+ return s_req_query_string;
+ }
+
+ switch (ch) {
+ case '?':
+ /* allow extra '?' in query string */
+ return s_req_query_string;
+
+ case '#':
+ return s_req_fragment_start;
+ }
+
+ break;
+
+ case s_req_fragment_start:
+ if (IS_URL_CHAR(ch)) {
+ return s_req_fragment;
+ }
+
+ switch (ch) {
+ case '?':
+ return s_req_fragment;
+
+ case '#':
+ return s;
+ }
+
+ break;
+
+ case s_req_fragment:
+ if (IS_URL_CHAR(ch)) {
+ return s;
+ }
+
+ switch (ch) {
+ case '?':
+ case '#':
+ return s;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ /* We should never fall out of the switch above unless there's an error */
+ return s_dead;
+}
+
+size_t http_parser_execute (http_parser *parser,
+ const http_parser_settings *settings,
+ const char *data,
+ size_t len)
+{
+ char c, ch;
+ int8_t unhex_val;
+ const char *p = data;
+ const char *header_field_mark = 0;
+ const char *header_value_mark = 0;
+ const char *url_mark = 0;
+ const char *body_mark = 0;
+
+ /* We're in an error state. Don't bother doing anything. */
+ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
+ return 0;
+ }
+
+ if (len == 0) {
+ switch (parser->state) {
+ case s_body_identity_eof:
+ /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if
+ * we got paused.
+ */
+ CALLBACK_NOTIFY_NOADVANCE(message_complete);
+ return 0;
+
+ case s_dead:
+ case s_start_req_or_res:
+ case s_start_res:
+ case s_start_req:
+ return 0;
+
+ default:
+ SET_ERRNO(HPE_INVALID_EOF_STATE);
+ return 1;
+ }
+ }
+
+
+ if (parser->state == s_header_field)
+ header_field_mark = data;
+ if (parser->state == s_header_value)
+ header_value_mark = data;
+ switch (parser->state) {
+ case s_req_path:
+ case s_req_schema:
+ case s_req_schema_slash:
+ case s_req_schema_slash_slash:
+ case s_req_server_start:
+ case s_req_server:
+ case s_req_server_with_at:
+ case s_req_query_string_start:
+ case s_req_query_string:
+ case s_req_fragment_start:
+ case s_req_fragment:
+ url_mark = data;
+ break;
+ }
+
+ for (p=data; p != data + len; p++) {
+ ch = *p;
+
+ if (PARSING_HEADER(parser->state)) {
+ ++parser->nread;
+ /* Buffer overflow attack */
+ if (parser->nread > HTTP_MAX_HEADER_SIZE) {
+ SET_ERRNO(HPE_HEADER_OVERFLOW);
+ goto error;
+ }
+ }
+
+ reexecute_byte:
+ switch (parser->state) {
+
+ case s_dead:
+ /* this state is used after a 'Connection: close' message
+ * the parser will error out if it reads another message
+ */
+ if (ch == CR || ch == LF)
+ break;
+
+ SET_ERRNO(HPE_CLOSED_CONNECTION);
+ goto error;
+
+ case s_start_req_or_res:
+ {
+ if (ch == CR || ch == LF)
+ break;
+ parser->flags = 0;
+ parser->content_length = ULLONG_MAX;
+
+ if (ch == 'H') {
+ parser->state = s_res_or_resp_H;
+
+ CALLBACK_NOTIFY(message_begin);
+ } else {
+ parser->type = HTTP_REQUEST;
+ parser->state = s_start_req;
+ goto reexecute_byte;
+ }
+
+ break;
+ }
+
+ case s_res_or_resp_H:
+ if (ch == 'T') {
+ parser->type = HTTP_RESPONSE;
+ parser->state = s_res_HT;
+ } else {
+ if (ch != 'E') {
+ SET_ERRNO(HPE_INVALID_CONSTANT);
+ goto error;
+ }
+
+ parser->type = HTTP_REQUEST;
+ parser->method = HTTP_HEAD;
+ parser->index = 2;
+ parser->state = s_req_method;
+ }
+ break;
+
+ case s_start_res:
+ {
+ parser->flags = 0;
+ parser->content_length = ULLONG_MAX;
+
+ switch (ch) {
+ case 'H':
+ parser->state = s_res_H;
+ break;
+
+ case CR:
+ case LF:
+ break;
+
+ default:
+ SET_ERRNO(HPE_INVALID_CONSTANT);
+ goto error;
+ }
+
+ CALLBACK_NOTIFY(message_begin);
+ break;
+ }
+
+ case s_res_H:
+ STRICT_CHECK(ch != 'T');
+ parser->state = s_res_HT;
+ break;
+
+ case s_res_HT:
+ STRICT_CHECK(ch != 'T');
+ parser->state = s_res_HTT;
+ break;
+
+ case s_res_HTT:
+ STRICT_CHECK(ch != 'P');
+ parser->state = s_res_HTTP;
+ break;
+
+ case s_res_HTTP:
+ STRICT_CHECK(ch != '/');
+ parser->state = s_res_first_http_major;
+ break;
+
+ case s_res_first_http_major:
+ if (ch < '0' || ch > '9') {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->http_major = ch - '0';
+ parser->state = s_res_http_major;
+ break;
+
+ /* major HTTP version or dot */
+ case s_res_http_major:
+ {
+ if (ch == '.') {
+ parser->state = s_res_first_http_minor;
+ break;
+ }
+
+ if (!IS_NUM(ch)) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->http_major *= 10;
+ parser->http_major += ch - '0';
+
+ if (parser->http_major > 999) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ break;
+ }
+
+ /* first digit of minor HTTP version */
+ case s_res_first_http_minor:
+ if (!IS_NUM(ch)) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->http_minor = ch - '0';
+ parser->state = s_res_http_minor;
+ break;
+
+ /* minor HTTP version or end of request line */
+ case s_res_http_minor:
+ {
+ if (ch == ' ') {
+ parser->state = s_res_first_status_code;
+ break;
+ }
+
+ if (!IS_NUM(ch)) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->http_minor *= 10;
+ parser->http_minor += ch - '0';
+
+ if (parser->http_minor > 999) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ break;
+ }
+
+ case s_res_first_status_code:
+ {
+ if (!IS_NUM(ch)) {
+ if (ch == ' ') {
+ break;
+ }
+
+ SET_ERRNO(HPE_INVALID_STATUS);
+ goto error;
+ }
+ parser->status_code = ch - '0';
+ parser->state = s_res_status_code;
+ break;
+ }
+
+ case s_res_status_code:
+ {
+ if (!IS_NUM(ch)) {
+ switch (ch) {
+ case ' ':
+ parser->state = s_res_status;
+ break;
+ case CR:
+ parser->state = s_res_line_almost_done;
+ break;
+ case LF:
+ parser->state = s_header_field_start;
+ break;
+ default:
+ SET_ERRNO(HPE_INVALID_STATUS);
+ goto error;
+ }
+ break;
+ }
+
+ parser->status_code *= 10;
+ parser->status_code += ch - '0';
+
+ if (parser->status_code > 999) {
+ SET_ERRNO(HPE_INVALID_STATUS);
+ goto error;
+ }
+
+ break;
+ }
+
+ case s_res_status:
+ /* the human readable status. e.g. "NOT FOUND"
+ * we are not humans so just ignore this */
+ if (ch == CR) {
+ parser->state = s_res_line_almost_done;
+ break;
+ }
+
+ if (ch == LF) {
+ parser->state = s_header_field_start;
+ break;
+ }
+ break;
+
+ case s_res_line_almost_done:
+ STRICT_CHECK(ch != LF);
+ parser->state = s_header_field_start;
+ break;
+
+ case s_start_req:
+ {
+ if (ch == CR || ch == LF)
+ break;
+ parser->flags = 0;
+ parser->content_length = ULLONG_MAX;
+
+ if (!IS_ALPHA(ch)) {
+ SET_ERRNO(HPE_INVALID_METHOD);
+ goto error;
+ }
+
+ parser->method = (enum http_method) 0;
+ parser->index = 1;
+ switch (ch) {
+ case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break;
+ case 'D': parser->method = HTTP_DELETE; break;
+ case 'G': parser->method = HTTP_GET; break;
+ case 'H': parser->method = HTTP_HEAD; break;
+ case 'L': parser->method = HTTP_LOCK; break;
+ case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH */ break;
+ case 'N': parser->method = HTTP_NOTIFY; break;
+ case 'O': parser->method = HTTP_OPTIONS; break;
+ case 'P': parser->method = HTTP_POST;
+ /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */
+ break;
+ case 'R': parser->method = HTTP_REPORT; break;
+ case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break;
+ case 'T': parser->method = HTTP_TRACE; break;
+ case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break;
+ default:
+ SET_ERRNO(HPE_INVALID_METHOD);
+ goto error;
+ }
+ parser->state = s_req_method;
+
+ CALLBACK_NOTIFY(message_begin);
+
+ break;
+ }
+
+ case s_req_method:
+ {
+ const char *matcher;
+ if (ch == '\0') {
+ SET_ERRNO(HPE_INVALID_METHOD);
+ goto error;
+ }
+
+ matcher = method_strings[parser->method];
+ if (ch == ' ' && matcher[parser->index] == '\0') {
+ parser->state = s_req_spaces_before_url;
+ } else if (ch == matcher[parser->index]) {
+ ; /* nada */
+ } else if (parser->method == HTTP_CONNECT) {
+ if (parser->index == 1 && ch == 'H') {
+ parser->method = HTTP_CHECKOUT;
+ } else if (parser->index == 2 && ch == 'P') {
+ parser->method = HTTP_COPY;
+ } else {
+ goto error;
+ }
+ } else if (parser->method == HTTP_MKCOL) {
+ if (parser->index == 1 && ch == 'O') {
+ parser->method = HTTP_MOVE;
+ } else if (parser->index == 1 && ch == 'E') {
+ parser->method = HTTP_MERGE;
+ } else if (parser->index == 1 && ch == '-') {
+ parser->method = HTTP_MSEARCH;
+ } else if (parser->index == 2 && ch == 'A') {
+ parser->method = HTTP_MKACTIVITY;
+ } else {
+ goto error;
+ }
+ } else if (parser->method == HTTP_SUBSCRIBE) {
+ if (parser->index == 1 && ch == 'E') {
+ parser->method = HTTP_SEARCH;
+ } else {
+ goto error;
+ }
+ } else if (parser->index == 1 && parser->method == HTTP_POST) {
+ if (ch == 'R') {
+ parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */
+ } else if (ch == 'U') {
+ parser->method = HTTP_PUT; /* or HTTP_PURGE */
+ } else if (ch == 'A') {
+ parser->method = HTTP_PATCH;
+ } else {
+ goto error;
+ }
+ } else if (parser->index == 2) {
+ if (parser->method == HTTP_PUT) {
+ if (ch == 'R') parser->method = HTTP_PURGE;
+ } else if (parser->method == HTTP_UNLOCK) {
+ if (ch == 'S') parser->method = HTTP_UNSUBSCRIBE;
+ }
+ } else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') {
+ parser->method = HTTP_PROPPATCH;
+ } else {
+ SET_ERRNO(HPE_INVALID_METHOD);
+ goto error;
+ }
+
+ ++parser->index;
+ break;
+ }
+
+ case s_req_spaces_before_url:
+ {
+ if (ch == ' ') break;
+
+ MARK(url);
+ if (parser->method == HTTP_CONNECT) {
+ parser->state = s_req_server_start;
+ }
+
+ parser->state = parse_url_char((enum state)parser->state, ch);
+ if (parser->state == s_dead) {
+ SET_ERRNO(HPE_INVALID_URL);
+ goto error;
+ }
+
+ break;
+ }
+
+ case s_req_schema:
+ case s_req_schema_slash:
+ case s_req_schema_slash_slash:
+ case s_req_server_start:
+ {
+ switch (ch) {
+ /* No whitespace allowed here */
+ case ' ':
+ case CR:
+ case LF:
+ SET_ERRNO(HPE_INVALID_URL);
+ goto error;
+ default:
+ parser->state = parse_url_char((enum state)parser->state, ch);
+ if (parser->state == s_dead) {
+ SET_ERRNO(HPE_INVALID_URL);
+ goto error;
+ }
+ }
+
+ break;
+ }
+
+ case s_req_server:
+ case s_req_server_with_at:
+ case s_req_path:
+ case s_req_query_string_start:
+ case s_req_query_string:
+ case s_req_fragment_start:
+ case s_req_fragment:
+ {
+ switch (ch) {
+ case ' ':
+ parser->state = s_req_http_start;
+ CALLBACK_DATA(url);
+ break;
+ case CR:
+ case LF:
+ parser->http_major = 0;
+ parser->http_minor = 9;
+ parser->state = (ch == CR) ?
+ s_req_line_almost_done :
+ s_header_field_start;
+ CALLBACK_DATA(url);
+ break;
+ default:
+ parser->state = parse_url_char((enum state)parser->state, ch);
+ if (parser->state == s_dead) {
+ SET_ERRNO(HPE_INVALID_URL);
+ goto error;
+ }
+ }
+ break;
+ }
+
+ case s_req_http_start:
+ switch (ch) {
+ case 'H':
+ parser->state = s_req_http_H;
+ break;
+ case ' ':
+ break;
+ default:
+ SET_ERRNO(HPE_INVALID_CONSTANT);
+ goto error;
+ }
+ break;
+
+ case s_req_http_H:
+ STRICT_CHECK(ch != 'T');
+ parser->state = s_req_http_HT;
+ break;
+
+ case s_req_http_HT:
+ STRICT_CHECK(ch != 'T');
+ parser->state = s_req_http_HTT;
+ break;
+
+ case s_req_http_HTT:
+ STRICT_CHECK(ch != 'P');
+ parser->state = s_req_http_HTTP;
+ break;
+
+ case s_req_http_HTTP:
+ STRICT_CHECK(ch != '/');
+ parser->state = s_req_first_http_major;
+ break;
+
+ /* first digit of major HTTP version */
+ case s_req_first_http_major:
+ if (ch < '1' || ch > '9') {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->http_major = ch - '0';
+ parser->state = s_req_http_major;
+ break;
+
+ /* major HTTP version or dot */
+ case s_req_http_major:
+ {
+ if (ch == '.') {
+ parser->state = s_req_first_http_minor;
+ break;
+ }
+
+ if (!IS_NUM(ch)) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->http_major *= 10;
+ parser->http_major += ch - '0';
+
+ if (parser->http_major > 999) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ break;
+ }
+
+ /* first digit of minor HTTP version */
+ case s_req_first_http_minor:
+ if (!IS_NUM(ch)) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->http_minor = ch - '0';
+ parser->state = s_req_http_minor;
+ break;
+
+ /* minor HTTP version or end of request line */
+ case s_req_http_minor:
+ {
+ if (ch == CR) {
+ parser->state = s_req_line_almost_done;
+ break;
+ }
+
+ if (ch == LF) {
+ parser->state = s_header_field_start;
+ break;
+ }
+
+ /* XXX allow spaces after digit? */
+
+ if (!IS_NUM(ch)) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ parser->http_minor *= 10;
+ parser->http_minor += ch - '0';
+
+ if (parser->http_minor > 999) {
+ SET_ERRNO(HPE_INVALID_VERSION);
+ goto error;
+ }
+
+ break;
+ }
+
+ /* end of request line */
+ case s_req_line_almost_done:
+ {
+ if (ch != LF) {
+ SET_ERRNO(HPE_LF_EXPECTED);
+ goto error;
+ }
+
+ parser->state = s_header_field_start;
+ break;
+ }
+
+ case s_header_field_start:
+ {
+ if (ch == CR) {
+ parser->state = s_headers_almost_done;
+ break;
+ }
+
+ if (ch == LF) {
+ /* they might be just sending \n instead of \r\n so this would be
+ * the second \n to denote the end of headers*/
+ parser->state = s_headers_almost_done;
+ goto reexecute_byte;
+ }
+
+ c = TOKEN(ch);
+
+ if (!c) {
+ SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
+ goto error;
+ }
+
+ MARK(header_field);
+
+ parser->index = 0;
+ parser->state = s_header_field;
+
+ switch (c) {
+ case 'c':
+ parser->header_state = h_C;
+ break;
+
+ case 'p':
+ parser->header_state = h_matching_proxy_connection;
+ break;
+
+ case 't':
+ parser->header_state = h_matching_transfer_encoding;
+ break;
+
+ case 'u':
+ parser->header_state = h_matching_upgrade;
+ break;
+
+ default:
+ parser->header_state = h_general;
+ break;
+ }
+ break;
+ }
+
+ case s_header_field:
+ {
+ c = TOKEN(ch);
+
+ if (c) {
+ switch (parser->header_state) {
+ case h_general:
+ break;
+
+ case h_C:
+ parser->index++;
+ parser->header_state = (c == 'o' ? h_CO : h_general);
+ break;
+
+ case h_CO:
+ parser->index++;
+ parser->header_state = (c == 'n' ? h_CON : h_general);
+ break;
+
+ case h_CON:
+ parser->index++;
+ switch (c) {
+ case 'n':
+ parser->header_state = h_matching_connection;
+ break;
+ case 't':
+ parser->header_state = h_matching_content_length;
+ break;
+ default:
+ parser->header_state = h_general;
+ break;
+ }
+ break;
+
+ /* connection */
+
+ case h_matching_connection:
+ parser->index++;
+ if (parser->index > sizeof(CONNECTION)-1
+ || c != CONNECTION[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(CONNECTION)-2) {
+ parser->header_state = h_connection;
+ }
+ break;
+
+ /* proxy-connection */
+
+ case h_matching_proxy_connection:
+ parser->index++;
+ if (parser->index > sizeof(PROXY_CONNECTION)-1
+ || c != PROXY_CONNECTION[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(PROXY_CONNECTION)-2) {
+ parser->header_state = h_connection;
+ }
+ break;
+
+ /* content-length */
+
+ case h_matching_content_length:
+ parser->index++;
+ if (parser->index > sizeof(CONTENT_LENGTH)-1
+ || c != CONTENT_LENGTH[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(CONTENT_LENGTH)-2) {
+ parser->header_state = h_content_length;
+ }
+ break;
+
+ /* transfer-encoding */
+
+ case h_matching_transfer_encoding:
+ parser->index++;
+ if (parser->index > sizeof(TRANSFER_ENCODING)-1
+ || c != TRANSFER_ENCODING[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) {
+ parser->header_state = h_transfer_encoding;
+ }
+ break;
+
+ /* upgrade */
+
+ case h_matching_upgrade:
+ parser->index++;
+ if (parser->index > sizeof(UPGRADE)-1
+ || c != UPGRADE[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(UPGRADE)-2) {
+ parser->header_state = h_upgrade;
+ }
+ break;
+
+ case h_connection:
+ case h_content_length:
+ case h_transfer_encoding:
+ case h_upgrade:
+ if (ch != ' ') parser->header_state = h_general;
+ break;
+
+ default:
+ assert(0 && "Unknown header_state");
+ break;
+ }
+ break;
+ }
+
+ if (ch == ':') {
+ parser->state = s_header_value_start;
+ CALLBACK_DATA(header_field);
+ break;
+ }
+
+ if (ch == CR) {
+ parser->state = s_header_almost_done;
+ CALLBACK_DATA(header_field);
+ break;
+ }
+
+ if (ch == LF) {
+ parser->state = s_header_field_start;
+ CALLBACK_DATA(header_field);
+ break;
+ }
+
+ SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
+ goto error;
+ }
+
+ case s_header_value_start:
+ {
+ if (ch == ' ' || ch == '\t') break;
+
+ MARK(header_value);
+
+ parser->state = s_header_value;
+ parser->index = 0;
+
+ if (ch == CR) {
+ parser->header_state = h_general;
+ parser->state = s_header_almost_done;
+ CALLBACK_DATA(header_value);
+ break;
+ }
+
+ if (ch == LF) {
+ parser->state = s_header_field_start;
+ CALLBACK_DATA(header_value);
+ break;
+ }
+
+ c = LOWER(ch);
+
+ switch (parser->header_state) {
+ case h_upgrade:
+ parser->flags |= F_UPGRADE;
+ parser->header_state = h_general;
+ break;
+
+ case h_transfer_encoding:
+ /* looking for 'Transfer-Encoding: chunked' */
+ if ('c' == c) {
+ parser->header_state = h_matching_transfer_encoding_chunked;
+ } else {
+ parser->header_state = h_general;
+ }
+ break;
+
+ case h_content_length:
+ if (!IS_NUM(ch)) {
+ SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+ goto error;
+ }
+
+ parser->content_length = ch - '0';
+ break;
+
+ case h_connection:
+ /* looking for 'Connection: keep-alive' */
+ if (c == 'k') {
+ parser->header_state = h_matching_connection_keep_alive;
+ /* looking for 'Connection: close' */
+ } else if (c == 'c') {
+ parser->header_state = h_matching_connection_close;
+ } else {
+ parser->header_state = h_general;
+ }
+ break;
+
+ default:
+ parser->header_state = h_general;
+ break;
+ }
+ break;
+ }
+
+ case s_header_value:
+ {
+
+ if (ch == CR) {
+ parser->state = s_header_almost_done;
+ CALLBACK_DATA(header_value);
+ break;
+ }
+
+ if (ch == LF) {
+ parser->state = s_header_almost_done;
+ CALLBACK_DATA_NOADVANCE(header_value);
+ goto reexecute_byte;
+ }
+
+ c = LOWER(ch);
+
+ switch (parser->header_state) {
+ case h_general:
+ break;
+
+ case h_connection:
+ case h_transfer_encoding:
+ assert(0 && "Shouldn't get here.");
+ break;
+
+ case h_content_length:
+ {
+ uint64_t t;
+
+ if (ch == ' ') break;
+
+ if (!IS_NUM(ch)) {
+ SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+ goto error;
+ }
+
+ t = parser->content_length;
+ t *= 10;
+ t += ch - '0';
+
+ /* Overflow? */
+ if (t < parser->content_length || t == ULLONG_MAX) {
+ SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+ goto error;
+ }
+
+ parser->content_length = t;
+ break;
+ }
+
+ /* Transfer-Encoding: chunked */
+ case h_matching_transfer_encoding_chunked:
+ parser->index++;
+ if (parser->index > sizeof(CHUNKED)-1
+ || c != CHUNKED[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(CHUNKED)-2) {
+ parser->header_state = h_transfer_encoding_chunked;
+ }
+ break;
+
+ /* looking for 'Connection: keep-alive' */
+ case h_matching_connection_keep_alive:
+ parser->index++;
+ if (parser->index > sizeof(KEEP_ALIVE)-1
+ || c != KEEP_ALIVE[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(KEEP_ALIVE)-2) {
+ parser->header_state = h_connection_keep_alive;
+ }
+ break;
+
+ /* looking for 'Connection: close' */
+ case h_matching_connection_close:
+ parser->index++;
+ if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(CLOSE)-2) {
+ parser->header_state = h_connection_close;
+ }
+ break;
+
+ case h_transfer_encoding_chunked:
+ case h_connection_keep_alive:
+ case h_connection_close:
+ if (ch != ' ') parser->header_state = h_general;
+ break;
+
+ default:
+ parser->state = s_header_value;
+ parser->header_state = h_general;
+ break;
+ }
+ break;
+ }
+
+ case s_header_almost_done:
+ {
+ STRICT_CHECK(ch != LF);
+
+ parser->state = s_header_value_lws;
+
+ switch (parser->header_state) {
+ case h_connection_keep_alive:
+ parser->flags |= F_CONNECTION_KEEP_ALIVE;
+ break;
+ case h_connection_close:
+ parser->flags |= F_CONNECTION_CLOSE;
+ break;
+ case h_transfer_encoding_chunked:
+ parser->flags |= F_CHUNKED;
+ break;
+ default:
+ break;
+ }
+
+ break;
+ }
+
+ case s_header_value_lws:
+ {
+ if (ch == ' ' || ch == '\t')
+ parser->state = s_header_value_start;
+ else
+ {
+ parser->state = s_header_field_start;
+ goto reexecute_byte;
+ }
+ break;
+ }
+
+ case s_headers_almost_done:
+ {
+ STRICT_CHECK(ch != LF);
+
+ if (parser->flags & F_TRAILING) {
+ /* End of a chunked request */
+ parser->state = NEW_MESSAGE();
+ CALLBACK_NOTIFY(message_complete);
+ break;
+ }
+
+ parser->state = s_headers_done;
+
+ /* Set this here so that on_headers_complete() callbacks can see it */
+ parser->upgrade =
+ (parser->flags & F_UPGRADE || parser->method == HTTP_CONNECT);
+
+ /* Here we call the headers_complete callback. This is somewhat
+ * different than other callbacks because if the user returns 1, we
+ * will interpret that as saying that this message has no body. This
+ * is needed for the annoying case of recieving a response to a HEAD
+ * request.
+ *
+ * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so
+ * we have to simulate it by handling a change in errno below.
+ */
+ if (settings->on_headers_complete) {
+ switch (settings->on_headers_complete(parser)) {
+ case 0:
+ break;
+
+ case 1:
+ parser->flags |= F_SKIPBODY;
+ break;
+
+ default:
+ SET_ERRNO(HPE_CB_headers_complete);
+ return p - data; /* Error */
+ }
+ }
+
+ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
+ return p - data;
+ }
+
+ goto reexecute_byte;
+ }
+
+ case s_headers_done:
+ {
+ STRICT_CHECK(ch != LF);
+
+ parser->nread = 0;
+
+ /* Exit, the rest of the connect is in a different protocol. */
+ if (parser->upgrade) {
+ parser->state = NEW_MESSAGE();
+ CALLBACK_NOTIFY(message_complete);
+ return (p - data) + 1;
+ }
+
+ if (parser->flags & F_SKIPBODY) {
+ parser->state = NEW_MESSAGE();
+ CALLBACK_NOTIFY(message_complete);
+ } else if (parser->flags & F_CHUNKED) {
+ /* chunked encoding - ignore Content-Length header */
+ parser->state = s_chunk_size_start;
+ } else {
+ if (parser->content_length == 0) {
+ /* Content-Length header given but zero: Content-Length: 0\r\n */
+ parser->state = NEW_MESSAGE();
+ CALLBACK_NOTIFY(message_complete);
+ } else if (parser->content_length != ULLONG_MAX) {
+ /* Content-Length header given and non-zero */
+ parser->state = s_body_identity;
+ } else {
+ if (parser->type == HTTP_REQUEST ||
+ !http_message_needs_eof(parser)) {
+ /* Assume content-length 0 - read the next */
+ parser->state = NEW_MESSAGE();
+ CALLBACK_NOTIFY(message_complete);
+ } else {
+ /* Read body until EOF */
+ parser->state = s_body_identity_eof;
+ }
+ }
+ }
+
+ break;
+ }
+
+ case s_body_identity:
+ {
+ uint64_t to_read = MIN(parser->content_length,
+ (uint64_t) ((data + len) - p));
+
+ assert(parser->content_length != 0
+ && parser->content_length != ULLONG_MAX);
+
+ /* The difference between advancing content_length and p is because
+ * the latter will automaticaly advance on the next loop iteration.
+ * Further, if content_length ends up at 0, we want to see the last
+ * byte again for our message complete callback.
+ */
+ MARK(body);
+ parser->content_length -= to_read;
+ p += to_read - 1;
+
+ if (parser->content_length == 0) {
+ parser->state = s_message_done;
+
+ /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte.
+ *
+ * The alternative to doing this is to wait for the next byte to
+ * trigger the data callback, just as in every other case. The
+ * problem with this is that this makes it difficult for the test
+ * harness to distinguish between complete-on-EOF and
+ * complete-on-length. It's not clear that this distinction is
+ * important for applications, but let's keep it for now.
+ */
+ CALLBACK_DATA_(body, p - body_mark + 1, p - data);
+ goto reexecute_byte;
+ }
+
+ break;
+ }
+
+ /* read until EOF */
+ case s_body_identity_eof:
+ MARK(body);
+ p = data + len - 1;
+
+ break;
+
+ case s_message_done:
+ parser->state = NEW_MESSAGE();
+ CALLBACK_NOTIFY(message_complete);
+ break;
+
+ case s_chunk_size_start:
+ {
+ assert(parser->nread == 1);
+ assert(parser->flags & F_CHUNKED);
+
+ unhex_val = unhex[(unsigned char)ch];
+ if (unhex_val == -1) {
+ SET_ERRNO(HPE_INVALID_CHUNK_SIZE);
+ goto error;
+ }
+
+ parser->content_length = unhex_val;
+ parser->state = s_chunk_size;
+ break;
+ }
+
+ case s_chunk_size:
+ {
+ uint64_t t;
+
+ assert(parser->flags & F_CHUNKED);
+
+ if (ch == CR) {
+ parser->state = s_chunk_size_almost_done;
+ break;
+ }
+
+ unhex_val = unhex[(unsigned char)ch];
+
+ if (unhex_val == -1) {
+ if (ch == ';' || ch == ' ') {
+ parser->state = s_chunk_parameters;
+ break;
+ }
+
+ SET_ERRNO(HPE_INVALID_CHUNK_SIZE);
+ goto error;
+ }
+
+ t = parser->content_length;
+ t *= 16;
+ t += unhex_val;
+
+ /* Overflow? */
+ if (t < parser->content_length || t == ULLONG_MAX) {
+ SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+ goto error;
+ }
+
+ parser->content_length = t;
+ break;
+ }
+
+ case s_chunk_parameters:
+ {
+ assert(parser->flags & F_CHUNKED);
+ /* just ignore this. TODO check for overflow */
+ if (ch == CR) {
+ parser->state = s_chunk_size_almost_done;
+ break;
+ }
+ break;
+ }
+
+ case s_chunk_size_almost_done:
+ {
+ assert(parser->flags & F_CHUNKED);
+ STRICT_CHECK(ch != LF);
+
+ parser->nread = 0;
+
+ if (parser->content_length == 0) {
+ parser->flags |= F_TRAILING;
+ parser->state = s_header_field_start;
+ } else {
+ parser->state = s_chunk_data;
+ }
+ break;
+ }
+
+ case s_chunk_data:
+ {
+ uint64_t to_read = MIN(parser->content_length,
+ (uint64_t) ((data + len) - p));
+
+ assert(parser->flags & F_CHUNKED);
+ assert(parser->content_length != 0
+ && parser->content_length != ULLONG_MAX);
+
+ /* See the explanation in s_body_identity for why the content
+ * length and data pointers are managed this way.
+ */
+ MARK(body);
+ parser->content_length -= to_read;
+ p += to_read - 1;
+
+ if (parser->content_length == 0) {
+ parser->state = s_chunk_data_almost_done;
+ }
+
+ break;
+ }
+
+ case s_chunk_data_almost_done:
+ assert(parser->flags & F_CHUNKED);
+ assert(parser->content_length == 0);
+ STRICT_CHECK(ch != CR);
+ parser->state = s_chunk_data_done;
+ CALLBACK_DATA(body);
+ break;
+
+ case s_chunk_data_done:
+ assert(parser->flags & F_CHUNKED);
+ STRICT_CHECK(ch != LF);
+ parser->nread = 0;
+ parser->state = s_chunk_size_start;
+ break;
+
+ default:
+ assert(0 && "unhandled state");
+ SET_ERRNO(HPE_INVALID_INTERNAL_STATE);
+ goto error;
+ }
+ }
+
+ /* Run callbacks for any marks that we have leftover after we ran our of
+ * bytes. There should be at most one of these set, so it's OK to invoke
+ * them in series (unset marks will not result in callbacks).
+ *
+ * We use the NOADVANCE() variety of callbacks here because 'p' has already
+ * overflowed 'data' and this allows us to correct for the off-by-one that
+ * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p'
+ * value that's in-bounds).
+ */
+
+ assert(((header_field_mark ? 1 : 0) +
+ (header_value_mark ? 1 : 0) +
+ (url_mark ? 1 : 0) +
+ (body_mark ? 1 : 0)) <= 1);
+
+ CALLBACK_DATA_NOADVANCE(header_field);
+ CALLBACK_DATA_NOADVANCE(header_value);
+ CALLBACK_DATA_NOADVANCE(url);
+ CALLBACK_DATA_NOADVANCE(body);
+
+ return len;
+
+error:
+ if (HTTP_PARSER_ERRNO(parser) == HPE_OK) {
+ SET_ERRNO(HPE_UNKNOWN);
+ }
+
+ return (p - data);
+}
+
+
+/* Does the parser need to see an EOF to find the end of the message? */
+int
+http_message_needs_eof (const http_parser *parser)
+{
+ if (parser->type == HTTP_REQUEST) {
+ return 0;
+ }
+
+ /* See RFC 2616 section 4.4 */
+ if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */
+ parser->status_code == 204 || /* No Content */
+ parser->status_code == 304 || /* Not Modified */
+ parser->flags & F_SKIPBODY) { /* response to a HEAD request */
+ return 0;
+ }
+
+ if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) {
+ return 0;
+ }
+
+ return 1;
+}
+
+
+int
+http_should_keep_alive (const http_parser *parser)
+{
+ if (parser->http_major > 0 && parser->http_minor > 0) {
+ /* HTTP/1.1 */
+ if (parser->flags & F_CONNECTION_CLOSE) {
+ return 0;
+ }
+ } else {
+ /* HTTP/1.0 or earlier */
+ if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) {
+ return 0;
+ }
+ }
+
+ return !http_message_needs_eof(parser);
+}
+
+
+const char *
+http_method_str (enum http_method m)
+{
+ return ELEM_AT(method_strings, m, "<unknown>");
+}
+
+
+void
+http_parser_init (http_parser *parser, enum http_parser_type t)
+{
+ void *data = parser->data; /* preserve application data */
+ memset(parser, 0, sizeof(*parser));
+ parser->data = data;
+ parser->type = t;
+ parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res));
+ parser->http_errno = HPE_OK;
+}
+
+const char *
+http_errno_name(enum http_errno err) {
+ assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0])));
+ return http_strerror_tab[err].name;
+}
+
+const char *
+http_errno_description(enum http_errno err) {
+ assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0])));
+ return http_strerror_tab[err].description;
+}
+
+static enum http_host_state
+http_parse_host_char(enum http_host_state s, const char ch) {
+ switch(s) {
+ case s_http_userinfo:
+ case s_http_userinfo_start:
+ if (ch == '@') {
+ return s_http_host_start;
+ }
+
+ if (IS_USERINFO_CHAR(ch)) {
+ return s_http_userinfo;
+ }
+ break;
+
+ case s_http_host_start:
+ if (ch == '[') {
+ return s_http_host_v6_start;
+ }
+
+ if (IS_HOST_CHAR(ch)) {
+ return s_http_host;
+ }
+
+ break;
+
+ case s_http_host:
+ if (IS_HOST_CHAR(ch)) {
+ return s_http_host;
+ }
+
+ /* FALLTHROUGH */
+ case s_http_host_v6_end:
+ if (ch == ':') {
+ return s_http_host_port_start;
+ }
+
+ break;
+
+ case s_http_host_v6:
+ if (ch == ']') {
+ return s_http_host_v6_end;
+ }
+
+ /* FALLTHROUGH */
+ case s_http_host_v6_start:
+ if (IS_HEX(ch) || ch == ':') {
+ return s_http_host_v6;
+ }
+
+ break;
+
+ case s_http_host_port:
+ case s_http_host_port_start:
+ if (IS_NUM(ch)) {
+ return s_http_host_port;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+ return s_http_host_dead;
+}
+
+static int
+http_parse_host(const char * buf, struct http_parser_url *u, int found_at) {
+ enum http_host_state s;
+
+ const char *p;
+ size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len;
+
+ u->field_data[UF_HOST].len = 0;
+
+ s = found_at ? s_http_userinfo_start : s_http_host_start;
+
+ for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) {
+ enum http_host_state new_s = http_parse_host_char(s, *p);
+
+ if (new_s == s_http_host_dead) {
+ return 1;
+ }
+
+ switch(new_s) {
+ case s_http_host:
+ if (s != s_http_host) {
+ u->field_data[UF_HOST].off = p - buf;
+ }
+ u->field_data[UF_HOST].len++;
+ break;
+
+ case s_http_host_v6:
+ if (s != s_http_host_v6) {
+ u->field_data[UF_HOST].off = p - buf;
+ }
+ u->field_data[UF_HOST].len++;
+ break;
+
+ case s_http_host_port:
+ if (s != s_http_host_port) {
+ u->field_data[UF_PORT].off = p - buf;
+ u->field_data[UF_PORT].len = 0;
+ u->field_set |= (1 << UF_PORT);
+ }
+ u->field_data[UF_PORT].len++;
+ break;
+
+ case s_http_userinfo:
+ if (s != s_http_userinfo) {
+ u->field_data[UF_USERINFO].off = p - buf ;
+ u->field_data[UF_USERINFO].len = 0;
+ u->field_set |= (1 << UF_USERINFO);
+ }
+ u->field_data[UF_USERINFO].len++;
+ break;
+
+ default:
+ break;
+ }
+ s = new_s;
+ }
+
+ /* Make sure we don't end somewhere unexpected */
+ switch (s) {
+ case s_http_host_start:
+ case s_http_host_v6_start:
+ case s_http_host_v6:
+ case s_http_host_port_start:
+ case s_http_userinfo:
+ case s_http_userinfo_start:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+int
+http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
+ struct http_parser_url *u)
+{
+ enum state s;
+ const char *p;
+ enum http_parser_url_fields uf, old_uf;
+ int found_at = 0;
+
+ u->port = u->field_set = 0;
+ s = is_connect ? s_req_server_start : s_req_spaces_before_url;
+ uf = old_uf = UF_MAX;
+
+ for (p = buf; p < buf + buflen; p++) {
+ s = parse_url_char(s, *p);
+
+ /* Figure out the next field that we're operating on */
+ switch (s) {
+ case s_dead:
+ return 1;
+
+ /* Skip delimeters */
+ case s_req_schema_slash:
+ case s_req_schema_slash_slash:
+ case s_req_server_start:
+ case s_req_query_string_start:
+ case s_req_fragment_start:
+ continue;
+
+ case s_req_schema:
+ uf = UF_SCHEMA;
+ break;
+
+ case s_req_server_with_at:
+ found_at = 1;
+
+ /* FALLTROUGH */
+ case s_req_server:
+ uf = UF_HOST;
+ break;
+
+ case s_req_path:
+ uf = UF_PATH;
+ break;
+
+ case s_req_query_string:
+ uf = UF_QUERY;
+ break;
+
+ case s_req_fragment:
+ uf = UF_FRAGMENT;
+ break;
+
+ default:
+ assert(!"Unexpected state");
+ return 1;
+ }
+
+ /* Nothing's changed; soldier on */
+ if (uf == old_uf) {
+ u->field_data[uf].len++;
+ continue;
+ }
+
+ u->field_data[uf].off = p - buf;
+ u->field_data[uf].len = 1;
+
+ u->field_set |= (1 << uf);
+ old_uf = uf;
+ }
+
+ /* host must be present if there is a schema */
+ /* parsing http:///toto will fail */
+ if ((u->field_set & ((1 << UF_SCHEMA) | (1 << UF_HOST))) != 0) {
+ if (http_parse_host(buf, u, found_at) != 0) {
+ return 1;
+ }
+ }
+
+ /* CONNECT requests can only contain "hostname:port" */
+ if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) {
+ return 1;
+ }
+
+ if (u->field_set & (1 << UF_PORT)) {
+ /* Don't bother with endp; we've already validated the string */
+ unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10);
+
+ /* Ports have a max value of 2^16 */
+ if (v > 0xffff) {
+ return 1;
+ }
+
+ u->port = (uint16_t) v;
+ }
+
+ return 0;
+}
+
+void
+http_parser_pause(http_parser *parser, int paused) {
+ /* Users should only be pausing/unpausing a parser that is not in an error
+ * state. In non-debug builds, there's not much that we can do about this
+ * other than ignore it.
+ */
+ if (HTTP_PARSER_ERRNO(parser) == HPE_OK ||
+ HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) {
+ SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK);
+ } else {
+ assert(0 && "Attempting to pause parser in error state");
+ }
+}
+
+int
+http_body_is_final(const struct http_parser *parser) {
+ return parser->state == s_message_done;
+}
diff --git a/deps/http-parser/http_parser.h b/deps/http-parser/http_parser.h
new file mode 100644
index 000000000..4f20396c6
--- /dev/null
+++ b/deps/http-parser/http_parser.h
@@ -0,0 +1,303 @@
+/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#ifndef http_parser_h
+#define http_parser_h
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define HTTP_PARSER_VERSION_MAJOR 2
+#define HTTP_PARSER_VERSION_MINOR 0
+
+#include <sys/types.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
+ */
+#ifndef HTTP_PARSER_STRICT
+# define HTTP_PARSER_STRICT 1
+#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;
+
+
+/* Callbacks should return non-zero to indicate an error. The parser will
+ * then halt execution.
+ *
+ * The one exception is on_headers_complete. In a HTTP_RESPONSE parser
+ * returning '1' from on_headers_complete will tell the parser that it
+ * should not expect a body. This is used when receiving a response to a
+ * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:
+ * 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_url"
+ * each providing just a few characters more data.
+ */
+typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
+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
+ {
+#define XX(num, name, string) HTTP_##name = num,
+ HTTP_METHOD_MAP(XX)
+#undef XX
+ };
+
+
+enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };
+
+
+/* Flag values for http_parser.flags field */
+enum flags
+ { F_CHUNKED = 1 << 0
+ , F_CONNECTION_KEEP_ALIVE = 1 << 1
+ , F_CONNECTION_CLOSE = 1 << 2
+ , F_TRAILING = 1 << 3
+ , F_UPGRADE = 1 << 4
+ , F_SKIPBODY = 1 << 5
+ };
+
+
+/* Map for errno-related constants
+ *
+ * The provided argument should be a macro that takes 2 arguments.
+ */
+#define HTTP_ERRNO_MAP(XX) \
+ /* No error */ \
+ XX(OK, "success") \
+ \
+ /* Callback-related errors */ \
+ XX(CB_message_begin, "the on_message_begin callback failed") \
+ XX(CB_url, "the on_url 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") \
+ XX(CB_body, "the on_body callback failed") \
+ XX(CB_message_complete, "the on_message_complete callback failed") \
+ \
+ /* Parsing-related errors */ \
+ XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \
+ XX(HEADER_OVERFLOW, \
+ "too many header bytes seen; overflow detected") \
+ XX(CLOSED_CONNECTION, \
+ "data received after completed connection: close message") \
+ XX(INVALID_VERSION, "invalid HTTP version") \
+ XX(INVALID_STATUS, "invalid HTTP status code") \
+ XX(INVALID_METHOD, "invalid HTTP method") \
+ XX(INVALID_URL, "invalid URL") \
+ XX(INVALID_HOST, "invalid host") \
+ XX(INVALID_PORT, "invalid port") \
+ XX(INVALID_PATH, "invalid path") \
+ XX(INVALID_QUERY_STRING, "invalid query string") \
+ XX(INVALID_FRAGMENT, "invalid fragment") \
+ XX(LF_EXPECTED, "LF character expected") \
+ XX(INVALID_HEADER_TOKEN, "invalid character in header") \
+ XX(INVALID_CONTENT_LENGTH, \
+ "invalid character in content-length header") \
+ XX(INVALID_CHUNK_SIZE, \
+ "invalid character in chunk size header") \
+ 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")
+
+
+/* Define HPE_* values for each errno value above */
+#define HTTP_ERRNO_GEN(n, s) HPE_##n,
+enum http_errno {
+ HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
+};
+#undef HTTP_ERRNO_GEN
+
+
+/* Get an http_errno value from an http_parser */
+#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno)
+
+
+struct http_parser {
+ /** PRIVATE **/
+ 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 */
+
+ 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 http_errno : 7;
+
+ /* 1 = Upgrade header was present and the parser has exited because of that.
+ * 0 = No upgrade header present.
+ * Should be checked when http_parser_execute() returns in addition to
+ * error checking.
+ */
+ unsigned char upgrade : 1;
+
+ /** PUBLIC **/
+ void *data; /* A pointer to get hook to the "connection" or "socket" object */
+};
+
+
+struct http_parser_settings {
+ http_cb on_message_begin;
+ http_data_cb on_url;
+ http_data_cb on_header_field;
+ http_data_cb on_header_value;
+ http_cb on_headers_complete;
+ http_data_cb on_body;
+ http_cb on_message_complete;
+};
+
+
+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);
+
+
+size_t http_parser_execute(http_parser *parser,
+ const http_parser_settings *settings,
+ const char *data,
+ size_t len);
+
+
+/* If http_should_keep_alive() in the on_headers_complete or
+ * 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(const http_parser *parser);
+
+/* Returns a string version of the HTTP method. */
+const char *http_method_str(enum http_method m);
+
+/* Return a string name of the given error */
+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
+#endif
diff --git a/deps/regex/config.h b/deps/regex/config.h
new file mode 100644
index 000000000..95370690e
--- /dev/null
+++ b/deps/regex/config.h
@@ -0,0 +1,7 @@
+#ifndef _REGEX_CONFIG_H_
+#define _REGEX_CONFIG_H_
+
+# define GAWK
+# define NO_MBSUPPORT
+
+#endif
diff --git a/deps/regex/regcomp.c b/deps/regex/regcomp.c
new file mode 100644
index 000000000..43bffbc21
--- /dev/null
+++ b/deps/regex/regcomp.c
@@ -0,0 +1,3857 @@
+/* Extended regular expression matching and search library.
+ Copyright (C) 2002-2007,2009,2010 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Isamu Hasegawa <isamu@yamato.ibm.com>.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA. */
+
+static reg_errcode_t re_compile_internal (regex_t *preg, const char * pattern,
+ size_t length, reg_syntax_t syntax);
+static void re_compile_fastmap_iter (regex_t *bufp,
+ const re_dfastate_t *init_state,
+ char *fastmap);
+static reg_errcode_t init_dfa (re_dfa_t *dfa, size_t pat_len);
+#ifdef RE_ENABLE_I18N
+static void free_charset (re_charset_t *cset);
+#endif /* RE_ENABLE_I18N */
+static void free_workarea_compile (regex_t *preg);
+static reg_errcode_t create_initial_state (re_dfa_t *dfa);
+#ifdef RE_ENABLE_I18N
+static void optimize_utf8 (re_dfa_t *dfa);
+#endif
+static reg_errcode_t analyze (regex_t *preg);
+static reg_errcode_t preorder (bin_tree_t *root,
+ reg_errcode_t (fn (void *, bin_tree_t *)),
+ void *extra);
+static reg_errcode_t postorder (bin_tree_t *root,
+ reg_errcode_t (fn (void *, bin_tree_t *)),
+ void *extra);
+static reg_errcode_t optimize_subexps (void *extra, bin_tree_t *node);
+static reg_errcode_t lower_subexps (void *extra, bin_tree_t *node);
+static bin_tree_t *lower_subexp (reg_errcode_t *err, regex_t *preg,
+ bin_tree_t *node);
+static reg_errcode_t calc_first (void *extra, bin_tree_t *node);
+static reg_errcode_t calc_next (void *extra, bin_tree_t *node);
+static reg_errcode_t link_nfa_nodes (void *extra, bin_tree_t *node);
+static int duplicate_node (re_dfa_t *dfa, int org_idx, unsigned int constraint);
+static int search_duplicated_node (const re_dfa_t *dfa, int org_node,
+ unsigned int constraint);
+static reg_errcode_t calc_eclosure (re_dfa_t *dfa);
+static reg_errcode_t calc_eclosure_iter (re_node_set *new_set, re_dfa_t *dfa,
+ int node, int root);
+static reg_errcode_t calc_inveclosure (re_dfa_t *dfa);
+static int fetch_number (re_string_t *input, re_token_t *token,
+ reg_syntax_t syntax);
+static int peek_token (re_token_t *token, re_string_t *input,
+ reg_syntax_t syntax) internal_function;
+static bin_tree_t *parse (re_string_t *regexp, regex_t *preg,
+ reg_syntax_t syntax, reg_errcode_t *err);
+static bin_tree_t *parse_reg_exp (re_string_t *regexp, regex_t *preg,
+ re_token_t *token, reg_syntax_t syntax,
+ int nest, reg_errcode_t *err);
+static bin_tree_t *parse_branch (re_string_t *regexp, regex_t *preg,
+ re_token_t *token, reg_syntax_t syntax,
+ int nest, reg_errcode_t *err);
+static bin_tree_t *parse_expression (re_string_t *regexp, regex_t *preg,
+ re_token_t *token, reg_syntax_t syntax,
+ int nest, reg_errcode_t *err);
+static bin_tree_t *parse_sub_exp (re_string_t *regexp, regex_t *preg,
+ re_token_t *token, reg_syntax_t syntax,
+ int nest, reg_errcode_t *err);
+static bin_tree_t *parse_dup_op (bin_tree_t *dup_elem, re_string_t *regexp,
+ re_dfa_t *dfa, re_token_t *token,
+ reg_syntax_t syntax, reg_errcode_t *err);
+static bin_tree_t *parse_bracket_exp (re_string_t *regexp, re_dfa_t *dfa,
+ re_token_t *token, reg_syntax_t syntax,
+ reg_errcode_t *err);
+static reg_errcode_t parse_bracket_element (bracket_elem_t *elem,
+ re_string_t *regexp,
+ re_token_t *token, int token_len,
+ re_dfa_t *dfa,
+ reg_syntax_t syntax,
+ int accept_hyphen);
+static reg_errcode_t parse_bracket_symbol (bracket_elem_t *elem,
+ re_string_t *regexp,
+ re_token_t *token);
+#ifdef RE_ENABLE_I18N
+static reg_errcode_t build_equiv_class (bitset_t sbcset,
+ re_charset_t *mbcset,
+ int *equiv_class_alloc,
+ const unsigned char *name);
+static reg_errcode_t build_charclass (RE_TRANSLATE_TYPE trans,
+ bitset_t sbcset,
+ re_charset_t *mbcset,
+ int *char_class_alloc,
+ const char *class_name,
+ reg_syntax_t syntax);
+#else /* not RE_ENABLE_I18N */
+static reg_errcode_t build_equiv_class (bitset_t sbcset,
+ const unsigned char *name);
+static reg_errcode_t build_charclass (RE_TRANSLATE_TYPE trans,
+ bitset_t sbcset,
+ const char *class_name,
+ reg_syntax_t syntax);
+#endif /* not RE_ENABLE_I18N */
+static bin_tree_t *build_charclass_op (re_dfa_t *dfa,
+ RE_TRANSLATE_TYPE trans,
+ const char *class_name,
+ const char *extra,
+ int non_match, reg_errcode_t *err);
+static bin_tree_t *create_tree (re_dfa_t *dfa,
+ bin_tree_t *left, bin_tree_t *right,
+ re_token_type_t type);
+static bin_tree_t *create_token_tree (re_dfa_t *dfa,
+ bin_tree_t *left, bin_tree_t *right,
+ const re_token_t *token);
+static bin_tree_t *duplicate_tree (const bin_tree_t *src, re_dfa_t *dfa);
+static void free_token (re_token_t *node);
+static reg_errcode_t free_tree (void *extra, bin_tree_t *node);
+static reg_errcode_t mark_opt_subexp (void *extra, bin_tree_t *node);
+
+/* This table gives an error message for each of the error codes listed
+ in regex.h. Obviously the order here has to be same as there.
+ POSIX doesn't require that we do anything for REG_NOERROR,
+ but why not be nice? */
+
+const char __re_error_msgid[] attribute_hidden =
+ {
+#define REG_NOERROR_IDX 0
+ gettext_noop ("Success") /* REG_NOERROR */
+ "\0"
+#define REG_NOMATCH_IDX (REG_NOERROR_IDX + sizeof "Success")
+ gettext_noop ("No match") /* REG_NOMATCH */
+ "\0"
+#define REG_BADPAT_IDX (REG_NOMATCH_IDX + sizeof "No match")
+ gettext_noop ("Invalid regular expression") /* REG_BADPAT */
+ "\0"
+#define REG_ECOLLATE_IDX (REG_BADPAT_IDX + sizeof "Invalid regular expression")
+ gettext_noop ("Invalid collation character") /* REG_ECOLLATE */
+ "\0"
+#define REG_ECTYPE_IDX (REG_ECOLLATE_IDX + sizeof "Invalid collation character")
+ gettext_noop ("Invalid character class name") /* REG_ECTYPE */
+ "\0"
+#define REG_EESCAPE_IDX (REG_ECTYPE_IDX + sizeof "Invalid character class name")
+ gettext_noop ("Trailing backslash") /* REG_EESCAPE */
+ "\0"
+#define REG_ESUBREG_IDX (REG_EESCAPE_IDX + sizeof "Trailing backslash")
+ gettext_noop ("Invalid back reference") /* REG_ESUBREG */
+ "\0"
+#define REG_EBRACK_IDX (REG_ESUBREG_IDX + sizeof "Invalid back reference")
+ gettext_noop ("Unmatched [ or [^") /* REG_EBRACK */
+ "\0"
+#define REG_EPAREN_IDX (REG_EBRACK_IDX + sizeof "Unmatched [ or [^")
+ gettext_noop ("Unmatched ( or \\(") /* REG_EPAREN */
+ "\0"
+#define REG_EBRACE_IDX (REG_EPAREN_IDX + sizeof "Unmatched ( or \\(")
+ gettext_noop ("Unmatched \\{") /* REG_EBRACE */
+ "\0"
+#define REG_BADBR_IDX (REG_EBRACE_IDX + sizeof "Unmatched \\{")
+ gettext_noop ("Invalid content of \\{\\}") /* REG_BADBR */
+ "\0"
+#define REG_ERANGE_IDX (REG_BADBR_IDX + sizeof "Invalid content of \\{\\}")
+ gettext_noop ("Invalid range end") /* REG_ERANGE */
+ "\0"
+#define REG_ESPACE_IDX (REG_ERANGE_IDX + sizeof "Invalid range end")
+ gettext_noop ("Memory exhausted") /* REG_ESPACE */
+ "\0"
+#define REG_BADRPT_IDX (REG_ESPACE_IDX + sizeof "Memory exhausted")
+ gettext_noop ("Invalid preceding regular expression") /* REG_BADRPT */
+ "\0"
+#define REG_EEND_IDX (REG_BADRPT_IDX + sizeof "Invalid preceding regular expression")
+ gettext_noop ("Premature end of regular expression") /* REG_EEND */
+ "\0"
+#define REG_ESIZE_IDX (REG_EEND_IDX + sizeof "Premature end of regular expression")
+ gettext_noop ("Regular expression too big") /* REG_ESIZE */
+ "\0"
+#define REG_ERPAREN_IDX (REG_ESIZE_IDX + sizeof "Regular expression too big")
+ gettext_noop ("Unmatched ) or \\)") /* REG_ERPAREN */
+ };
+
+const size_t __re_error_msgid_idx[] attribute_hidden =
+ {
+ REG_NOERROR_IDX,
+ REG_NOMATCH_IDX,
+ REG_BADPAT_IDX,
+ REG_ECOLLATE_IDX,
+ REG_ECTYPE_IDX,
+ REG_EESCAPE_IDX,
+ REG_ESUBREG_IDX,
+ REG_EBRACK_IDX,
+ REG_EPAREN_IDX,
+ REG_EBRACE_IDX,
+ REG_BADBR_IDX,
+ REG_ERANGE_IDX,
+ REG_ESPACE_IDX,
+ REG_BADRPT_IDX,
+ REG_EEND_IDX,
+ REG_ESIZE_IDX,
+ REG_ERPAREN_IDX
+ };
+
+/* Entry points for GNU code. */
+
+
+#ifdef ZOS_USS
+
+/* For ZOS USS we must define btowc */
+
+wchar_t
+btowc (int c)
+{
+ wchar_t wtmp[2];
+ char tmp[2];
+
+ tmp[0] = c;
+ tmp[1] = 0;
+
+ mbtowc (wtmp, tmp, 1);
+ return wtmp[0];
+}
+#endif
+
+/* re_compile_pattern is the GNU regular expression compiler: it
+ compiles PATTERN (of length LENGTH) and puts the result in BUFP.
+ Returns 0 if the pattern was valid, otherwise an error string.
+
+ Assumes the `allocated' (and perhaps `buffer') and `translate' fields
+ are set in BUFP on entry. */
+
+const char *
+re_compile_pattern (const char *pattern,
+ size_t length,
+ struct re_pattern_buffer *bufp)
+{
+ reg_errcode_t ret;
+
+ /* And GNU code determines whether or not to get register information
+ by passing null for the REGS argument to re_match, etc., not by
+ setting no_sub, unless RE_NO_SUB is set. */
+ bufp->no_sub = !!(re_syntax_options & RE_NO_SUB);
+
+ /* Match anchors at newline. */
+ bufp->newline_anchor = 1;
+
+ ret = re_compile_internal (bufp, pattern, length, re_syntax_options);
+
+ if (!ret)
+ return NULL;
+ return gettext (__re_error_msgid + __re_error_msgid_idx[(int) ret]);
+}
+#ifdef _LIBC
+weak_alias (__re_compile_pattern, re_compile_pattern)
+#endif
+
+/* Set by `re_set_syntax' to the current regexp syntax to recognize. Can
+ also be assigned to arbitrarily: each pattern buffer stores its own
+ syntax, so it can be changed between regex compilations. */
+/* This has no initializer because initialized variables in Emacs
+ become read-only after dumping. */
+reg_syntax_t re_syntax_options;
+
+
+/* Specify the precise syntax of regexps for compilation. This provides
+ for compatibility for various utilities which historically have
+ different, incompatible syntaxes.
+
+ The argument SYNTAX is a bit mask comprised of the various bits
+ defined in regex.h. We return the old syntax. */
+
+reg_syntax_t
+re_set_syntax (reg_syntax_t syntax)
+{
+ reg_syntax_t ret = re_syntax_options;
+
+ re_syntax_options = syntax;
+ return ret;
+}
+#ifdef _LIBC
+weak_alias (__re_set_syntax, re_set_syntax)
+#endif
+
+int
+re_compile_fastmap (struct re_pattern_buffer *bufp)
+{
+ re_dfa_t *dfa = (re_dfa_t *) bufp->buffer;
+ char *fastmap = bufp->fastmap;
+
+ memset (fastmap, '\0', sizeof (char) * SBC_MAX);
+ re_compile_fastmap_iter (bufp, dfa->init_state, fastmap);
+ if (dfa->init_state != dfa->init_state_word)
+ re_compile_fastmap_iter (bufp, dfa->init_state_word, fastmap);
+ if (dfa->init_state != dfa->init_state_nl)
+ re_compile_fastmap_iter (bufp, dfa->init_state_nl, fastmap);
+ if (dfa->init_state != dfa->init_state_begbuf)
+ re_compile_fastmap_iter (bufp, dfa->init_state_begbuf, fastmap);
+ bufp->fastmap_accurate = 1;
+ return 0;
+}
+#ifdef _LIBC
+weak_alias (__re_compile_fastmap, re_compile_fastmap)
+#endif
+
+static inline void
+__attribute ((always_inline))
+re_set_fastmap (char *fastmap, int icase, int ch)
+{
+ fastmap[ch] = 1;
+ if (icase)
+ fastmap[tolower (ch)] = 1;
+}
+
+/* Helper function for re_compile_fastmap.
+ Compile fastmap for the initial_state INIT_STATE. */
+
+static void
+re_compile_fastmap_iter (regex_t *bufp, const re_dfastate_t *init_state,
+ char *fastmap)
+{
+ volatile re_dfa_t *dfa = (re_dfa_t *) bufp->buffer;
+ int node_cnt;
+ int icase = (dfa->mb_cur_max == 1 && (bufp->syntax & RE_ICASE));
+ for (node_cnt = 0; node_cnt < init_state->nodes.nelem; ++node_cnt)
+ {
+ int node = init_state->nodes.elems[node_cnt];
+ re_token_type_t type = dfa->nodes[node].type;
+
+ if (type == CHARACTER)
+ {
+ re_set_fastmap (fastmap, icase, dfa->nodes[node].opr.c);
+#ifdef RE_ENABLE_I18N
+ if ((bufp->syntax & RE_ICASE) && dfa->mb_cur_max > 1)
+ {
+ unsigned char *buf = re_malloc (unsigned char, dfa->mb_cur_max), *p;
+ wchar_t wc;
+ mbstate_t state;
+
+ p = buf;
+ *p++ = dfa->nodes[node].opr.c;
+ while (++node < dfa->nodes_len
+ && dfa->nodes[node].type == CHARACTER
+ && dfa->nodes[node].mb_partial)
+ *p++ = dfa->nodes[node].opr.c;
+ memset (&state, '\0', sizeof (state));
+ if (__mbrtowc (&wc, (const char *) buf, p - buf,
+ &state) == p - buf
+ && (__wcrtomb ((char *) buf, towlower (wc), &state)
+ != (size_t) -1))
+ re_set_fastmap (fastmap, 0, buf[0]);
+ re_free (buf);
+ }
+#endif
+ }
+ else if (type == SIMPLE_BRACKET)
+ {
+ int i, ch;
+ for (i = 0, ch = 0; i < BITSET_WORDS; ++i)
+ {
+ int j;
+ bitset_word_t w = dfa->nodes[node].opr.sbcset[i];
+ for (j = 0; j < BITSET_WORD_BITS; ++j, ++ch)
+ if (w & ((bitset_word_t) 1 << j))
+ re_set_fastmap (fastmap, icase, ch);
+ }
+ }
+#ifdef RE_ENABLE_I18N
+ else if (type == COMPLEX_BRACKET)
+ {
+ re_charset_t *cset = dfa->nodes[node].opr.mbcset;
+ int i;
+
+# ifdef _LIBC
+ /* See if we have to try all bytes which start multiple collation
+ elements.
+ e.g. In da_DK, we want to catch 'a' since "aa" is a valid
+ collation element, and don't catch 'b' since 'b' is
+ the only collation element which starts from 'b' (and
+ it is caught by SIMPLE_BRACKET). */
+ if (_NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES) != 0
+ && (cset->ncoll_syms || cset->nranges))
+ {
+ const int32_t *table = (const int32_t *)
+ _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB);
+ for (i = 0; i < SBC_MAX; ++i)
+ if (table[i] < 0)
+ re_set_fastmap (fastmap, icase, i);
+ }
+# endif /* _LIBC */
+
+ /* See if we have to start the match at all multibyte characters,
+ i.e. where we would not find an invalid sequence. This only
+ applies to multibyte character sets; for single byte character
+ sets, the SIMPLE_BRACKET again suffices. */
+ if (dfa->mb_cur_max > 1
+ && (cset->nchar_classes || cset->non_match || cset->nranges
+# ifdef _LIBC
+ || cset->nequiv_classes
+# endif /* _LIBC */
+ ))
+ {
+ unsigned char c = 0;
+ do
+ {
+ mbstate_t mbs;
+ memset (&mbs, 0, sizeof (mbs));
+ if (__mbrtowc (NULL, (char *) &c, 1, &mbs) == (size_t) -2)
+ re_set_fastmap (fastmap, false, (int) c);
+ }
+ while (++c != 0);
+ }
+
+ else
+ {
+ /* ... Else catch all bytes which can start the mbchars. */
+ for (i = 0; i < cset->nmbchars; ++i)
+ {
+ char buf[256];
+ mbstate_t state;
+ memset (&state, '\0', sizeof (state));
+ if (__wcrtomb (buf, cset->mbchars[i], &state) != (size_t) -1)
+ re_set_fastmap (fastmap, icase, *(unsigned char *) buf);
+ if ((bufp->syntax & RE_ICASE) && dfa->mb_cur_max > 1)
+ {
+ if (__wcrtomb (buf, towlower (cset->mbchars[i]), &state)
+ != (size_t) -1)
+ re_set_fastmap (fastmap, false, *(unsigned char *) buf);
+ }
+ }
+ }
+ }
+#endif /* RE_ENABLE_I18N */
+ else if (type == OP_PERIOD
+#ifdef RE_ENABLE_I18N
+ || type == OP_UTF8_PERIOD
+#endif /* RE_ENABLE_I18N */
+ || type == END_OF_RE)
+ {
+ memset (fastmap, '\1', sizeof (char) * SBC_MAX);
+ if (type == END_OF_RE)
+ bufp->can_be_null = 1;
+ return;
+ }
+ }
+}
+
+/* Entry point for POSIX code. */
+/* regcomp takes a regular expression as a string and compiles it.
+
+ PREG is a regex_t *. We do not expect any fields to be initialized,
+ since POSIX says we shouldn't. Thus, we set
+
+ `buffer' to the compiled pattern;
+ `used' to the length of the compiled pattern;
+ `syntax' to RE_SYNTAX_POSIX_EXTENDED if the
+ REG_EXTENDED bit in CFLAGS is set; otherwise, to
+ RE_SYNTAX_POSIX_BASIC;
+ `newline_anchor' to REG_NEWLINE being set in CFLAGS;
+ `fastmap' to an allocated space for the fastmap;
+ `fastmap_accurate' to zero;
+ `re_nsub' to the number of subexpressions in PATTERN.
+
+ PATTERN is the address of the pattern string.
+
+ CFLAGS is a series of bits which affect compilation.
+
+ If REG_EXTENDED is set, we use POSIX extended syntax; otherwise, we
+ use POSIX basic syntax.
+
+ If REG_NEWLINE is set, then . and [^...] don't match newline.
+ Also, regexec will try a match beginning after every newline.
+
+ If REG_ICASE is set, then we considers upper- and lowercase
+ versions of letters to be equivalent when matching.
+
+ If REG_NOSUB is set, then when PREG is passed to regexec, that
+ routine will report only success or failure, and nothing about the
+ registers.
+
+ It returns 0 if it succeeds, nonzero if it doesn't. (See regex.h for
+ the return codes and their meanings.) */
+
+int
+regcomp (regex_t *__restrict preg,
+ const char *__restrict pattern,
+ int cflags)
+{
+ reg_errcode_t ret;
+ reg_syntax_t syntax = ((cflags & REG_EXTENDED) ? RE_SYNTAX_POSIX_EXTENDED
+ : RE_SYNTAX_POSIX_BASIC);
+
+ preg->buffer = NULL;
+ preg->allocated = 0;
+ preg->used = 0;
+
+ /* Try to allocate space for the fastmap. */
+ preg->fastmap = re_malloc (char, SBC_MAX);
+ if (BE (preg->fastmap == NULL, 0))
+ return REG_ESPACE;
+
+ syntax |= (cflags & REG_ICASE) ? RE_ICASE : 0;
+
+ /* If REG_NEWLINE is set, newlines are treated differently. */
+ if (cflags & REG_NEWLINE)
+ { /* REG_NEWLINE implies neither . nor [^...] match newline. */
+ syntax &= ~RE_DOT_NEWLINE;
+ syntax |= RE_HAT_LISTS_NOT_NEWLINE;
+ /* It also changes the matching behavior. */
+ preg->newline_anchor = 1;
+ }
+ else
+ preg->newline_anchor = 0;
+ preg->no_sub = !!(cflags & REG_NOSUB);
+ preg->translate = NULL;
+
+ ret = re_compile_internal (preg, pattern, strlen (pattern), syntax);
+
+ /* POSIX doesn't distinguish between an unmatched open-group and an
+ unmatched close-group: both are REG_EPAREN. */
+ if (ret == REG_ERPAREN)
+ ret = REG_EPAREN;
+
+ /* We have already checked preg->fastmap != NULL. */
+ if (BE (ret == REG_NOERROR, 1))
+ /* Compute the fastmap now, since regexec cannot modify the pattern
+ buffer. This function never fails in this implementation. */
+ (void) re_compile_fastmap (preg);
+ else
+ {
+ /* Some error occurred while compiling the expression. */
+ re_free (preg->fastmap);
+ preg->fastmap = NULL;
+ }
+
+ return (int) ret;
+}
+#ifdef _LIBC
+weak_alias (__regcomp, regcomp)
+#endif
+
+/* Returns a message corresponding to an error code, ERRCODE, returned
+ from either regcomp or regexec. We don't use PREG here. */
+
+size_t
+regerror(int errcode, UNUSED const regex_t *__restrict preg,
+ char *__restrict errbuf, size_t errbuf_size)
+{
+ const char *msg;
+ size_t msg_size;
+
+ if (BE (errcode < 0
+ || errcode >= (int) (sizeof (__re_error_msgid_idx)
+ / sizeof (__re_error_msgid_idx[0])), 0))
+ /* Only error codes returned by the rest of the code should be passed
+ to this routine. If we are given anything else, or if other regex
+ code generates an invalid error code, then the program has a bug.
+ Dump core so we can fix it. */
+ abort ();
+
+ msg = gettext (__re_error_msgid + __re_error_msgid_idx[errcode]);
+
+ msg_size = strlen (msg) + 1; /* Includes the null. */
+
+ if (BE (errbuf_size != 0, 1))
+ {
+ if (BE (msg_size > errbuf_size, 0))
+ {
+ memcpy (errbuf, msg, errbuf_size - 1);
+ errbuf[errbuf_size - 1] = 0;
+ }
+ else
+ memcpy (errbuf, msg, msg_size);
+ }
+
+ return msg_size;
+}
+#ifdef _LIBC
+weak_alias (__regerror, regerror)
+#endif
+
+
+#ifdef RE_ENABLE_I18N
+/* This static array is used for the map to single-byte characters when
+ UTF-8 is used. Otherwise we would allocate memory just to initialize
+ it the same all the time. UTF-8 is the preferred encoding so this is
+ a worthwhile optimization. */
+#if __GNUC__ >= 3
+static const bitset_t utf8_sb_map = {
+ /* Set the first 128 bits. */
+ [0 ... 0x80 / BITSET_WORD_BITS - 1] = BITSET_WORD_MAX
+};
+#else /* ! (__GNUC__ >= 3) */
+static bitset_t utf8_sb_map;
+#endif /* __GNUC__ >= 3 */
+#endif /* RE_ENABLE_I18N */
+
+
+static void
+free_dfa_content (re_dfa_t *dfa)
+{
+ unsigned int i;
+ int j;
+
+ if (dfa->nodes)
+ for (i = 0; i < dfa->nodes_len; ++i)
+ free_token (dfa->nodes + i);
+ re_free (dfa->nexts);
+ for (i = 0; i < dfa->nodes_len; ++i)
+ {
+ if (dfa->eclosures != NULL)
+ re_node_set_free (dfa->eclosures + i);
+ if (dfa->inveclosures != NULL)
+ re_node_set_free (dfa->inveclosures + i);
+ if (dfa->edests != NULL)
+ re_node_set_free (dfa->edests + i);
+ }
+ re_free (dfa->edests);
+ re_free (dfa->eclosures);
+ re_free (dfa->inveclosures);
+ re_free (dfa->nodes);
+
+ if (dfa->state_table)
+ for (i = 0; i <= dfa->state_hash_mask; ++i)
+ {
+ struct re_state_table_entry *entry = dfa->state_table + i;
+ for (j = 0; j < entry->num; ++j)
+ {
+ re_dfastate_t *state = entry->array[j];
+ free_state (state);
+ }
+ re_free (entry->array);
+ }
+ re_free (dfa->state_table);
+#ifdef RE_ENABLE_I18N
+ if (dfa->sb_char != utf8_sb_map)
+ re_free (dfa->sb_char);
+#endif
+ re_free (dfa->subexp_map);
+#ifdef DEBUG
+ re_free (dfa->re_str);
+#endif
+
+ re_free (dfa);
+}
+
+
+/* Free dynamically allocated space used by PREG. */
+
+void
+regfree (regex_t *preg)
+{
+ re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+ if (BE (dfa != NULL, 1))
+ free_dfa_content (dfa);
+ preg->buffer = NULL;
+ preg->allocated = 0;
+
+ re_free (preg->fastmap);
+ preg->fastmap = NULL;
+
+ re_free (preg->translate);
+ preg->translate = NULL;
+}
+#ifdef _LIBC
+weak_alias (__regfree, regfree)
+#endif
+
+/* Entry points compatible with 4.2 BSD regex library. We don't define
+ them unless specifically requested. */
+
+#if defined _REGEX_RE_COMP || defined _LIBC
+
+/* BSD has one and only one pattern buffer. */
+static struct re_pattern_buffer re_comp_buf;
+
+char *
+# ifdef _LIBC
+/* Make these definitions weak in libc, so POSIX programs can redefine
+ these names if they don't use our functions, and still use
+ regcomp/regexec above without link errors. */
+weak_function
+# endif
+re_comp (s)
+ const char *s;
+{
+ reg_errcode_t ret;
+ char *fastmap;
+
+ if (!s)
+ {
+ if (!re_comp_buf.buffer)
+ return gettext ("No previous regular expression");
+ return 0;
+ }
+
+ if (re_comp_buf.buffer)
+ {
+ fastmap = re_comp_buf.fastmap;
+ re_comp_buf.fastmap = NULL;
+ __regfree (&re_comp_buf);
+ memset (&re_comp_buf, '\0', sizeof (re_comp_buf));
+ re_comp_buf.fastmap = fastmap;
+ }
+
+ if (re_comp_buf.fastmap == NULL)
+ {
+ re_comp_buf.fastmap = (char *) malloc (SBC_MAX);
+ if (re_comp_buf.fastmap == NULL)
+ return (char *) gettext (__re_error_msgid
+ + __re_error_msgid_idx[(int) REG_ESPACE]);
+ }
+
+ /* Since `re_exec' always passes NULL for the `regs' argument, we
+ don't need to initialize the pattern buffer fields which affect it. */
+
+ /* Match anchors at newlines. */
+ re_comp_buf.newline_anchor = 1;
+
+ ret = re_compile_internal (&re_comp_buf, s, strlen (s), re_syntax_options);
+
+ if (!ret)
+ return NULL;
+
+ /* Yes, we're discarding `const' here if !HAVE_LIBINTL. */
+ return (char *) gettext (__re_error_msgid + __re_error_msgid_idx[(int) ret]);
+}
+
+#ifdef _LIBC
+libc_freeres_fn (free_mem)
+{
+ __regfree (&re_comp_buf);
+}
+#endif
+
+#endif /* _REGEX_RE_COMP */
+
+/* Internal entry point.
+ Compile the regular expression PATTERN, whose length is LENGTH.
+ SYNTAX indicate regular expression's syntax. */
+
+static reg_errcode_t
+re_compile_internal (regex_t *preg, const char * pattern, size_t length,
+ reg_syntax_t syntax)
+{
+ reg_errcode_t err = REG_NOERROR;
+ re_dfa_t *dfa;
+ re_string_t regexp;
+
+ /* Initialize the pattern buffer. */
+ preg->fastmap_accurate = 0;
+ preg->syntax = syntax;
+ preg->not_bol = preg->not_eol = 0;
+ preg->used = 0;
+ preg->re_nsub = 0;
+ preg->can_be_null = 0;
+ preg->regs_allocated = REGS_UNALLOCATED;
+
+ /* Initialize the dfa. */
+ dfa = (re_dfa_t *) preg->buffer;
+ if (BE (preg->allocated < sizeof (re_dfa_t), 0))
+ {
+ /* If zero allocated, but buffer is non-null, try to realloc
+ enough space. This loses if buffer's address is bogus, but
+ that is the user's responsibility. If ->buffer is NULL this
+ is a simple allocation. */
+ dfa = re_realloc (preg->buffer, re_dfa_t, 1);
+ if (dfa == NULL)
+ return REG_ESPACE;
+ preg->allocated = sizeof (re_dfa_t);
+ preg->buffer = (unsigned char *) dfa;
+ }
+ preg->used = sizeof (re_dfa_t);
+
+ err = init_dfa (dfa, length);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ free_dfa_content (dfa);
+ preg->buffer = NULL;
+ preg->allocated = 0;
+ return err;
+ }
+#ifdef DEBUG
+ /* Note: length+1 will not overflow since it is checked in init_dfa. */
+ dfa->re_str = re_malloc (char, length + 1);
+ strncpy (dfa->re_str, pattern, length + 1);
+#endif
+
+ __libc_lock_init (dfa->lock);
+
+ err = re_string_construct (&regexp, pattern, length, preg->translate,
+ syntax & RE_ICASE, dfa);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_compile_internal_free_return:
+ free_workarea_compile (preg);
+ re_string_destruct (&regexp);
+ free_dfa_content (dfa);
+ preg->buffer = NULL;
+ preg->allocated = 0;
+ return err;
+ }
+
+ /* Parse the regular expression, and build a structure tree. */
+ preg->re_nsub = 0;
+ dfa->str_tree = parse (&regexp, preg, syntax, &err);
+ if (BE (dfa->str_tree == NULL, 0))
+ goto re_compile_internal_free_return;
+
+ /* Analyze the tree and create the nfa. */
+ err = analyze (preg);
+ if (BE (err != REG_NOERROR, 0))
+ goto re_compile_internal_free_return;
+
+#ifdef RE_ENABLE_I18N
+ /* If possible, do searching in single byte encoding to speed things up. */
+ if (dfa->is_utf8 && !(syntax & RE_ICASE) && preg->translate == NULL)
+ optimize_utf8 (dfa);
+#endif
+
+ /* Then create the initial state of the dfa. */
+ err = create_initial_state (dfa);
+
+ /* Release work areas. */
+ free_workarea_compile (preg);
+ re_string_destruct (&regexp);
+
+ if (BE (err != REG_NOERROR, 0))
+ {
+ free_dfa_content (dfa);
+ preg->buffer = NULL;
+ preg->allocated = 0;
+ }
+
+ return err;
+}
+
+/* Initialize DFA. We use the length of the regular expression PAT_LEN
+ as the initial length of some arrays. */
+
+static reg_errcode_t
+init_dfa (re_dfa_t *dfa, size_t pat_len)
+{
+ unsigned int table_size;
+
+ memset (dfa, '\0', sizeof (re_dfa_t));
+
+ /* Force allocation of str_tree_storage the first time. */
+ dfa->str_tree_storage_idx = BIN_TREE_STORAGE_SIZE;
+
+ /* Avoid overflows. */
+ if (pat_len == SIZE_MAX)
+ return REG_ESPACE;
+
+ dfa->nodes_alloc = pat_len + 1;
+ dfa->nodes = re_malloc (re_token_t, dfa->nodes_alloc);
+
+ /* table_size = 2 ^ ceil(log pat_len) */
+ for (table_size = 1; ; table_size <<= 1)
+ if (table_size > pat_len)
+ break;
+
+ dfa->state_table = calloc (sizeof (struct re_state_table_entry), table_size);
+ dfa->state_hash_mask = table_size - 1;
+
+ dfa->mb_cur_max = MB_CUR_MAX;
+#ifdef _LIBC
+ if (dfa->mb_cur_max == 6
+ && strcmp (_NL_CURRENT (LC_CTYPE, _NL_CTYPE_CODESET_NAME), "UTF-8") == 0)
+ dfa->is_utf8 = 1;
+ dfa->map_notascii = (_NL_CURRENT_WORD (LC_CTYPE, _NL_CTYPE_MAP_TO_NONASCII)
+ != 0);
+#else
+ dfa->is_utf8 = 1;
+ /* We check exhaustively in the loop below if this charset is a
+ superset of ASCII. */
+ dfa->map_notascii = 0;
+#endif
+
+#ifdef RE_ENABLE_I18N
+ if (dfa->mb_cur_max > 1)
+ {
+ if (dfa->is_utf8)
+ {
+#if !defined(__GNUC__) || __GNUC__ < 3
+ static short utf8_sb_map_inited = 0;
+
+ if (! utf8_sb_map_inited)
+ {
+ int i;
+
+ utf8_sb_map_inited = 0;
+ for (i = 0; i <= 0x80 / BITSET_WORD_BITS - 1; i++)
+ utf8_sb_map[i] = BITSET_WORD_MAX;
+ }
+#endif
+ dfa->sb_char = (re_bitset_ptr_t) utf8_sb_map;
+ }
+ else
+ {
+ int i, j, ch;
+
+ dfa->sb_char = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1);
+ if (BE (dfa->sb_char == NULL, 0))
+ return REG_ESPACE;
+
+ /* Set the bits corresponding to single byte chars. */
+ for (i = 0, ch = 0; i < BITSET_WORDS; ++i)
+ for (j = 0; j < BITSET_WORD_BITS; ++j, ++ch)
+ {
+ wint_t wch = __btowc (ch);
+ if (wch != WEOF)
+ dfa->sb_char[i] |= (bitset_word_t) 1 << j;
+# ifndef _LIBC
+ if (isascii (ch) && wch != ch)
+ dfa->map_notascii = 1;
+# endif
+ }
+ }
+ }
+#endif
+
+ if (BE (dfa->nodes == NULL || dfa->state_table == NULL, 0))
+ return REG_ESPACE;
+ return REG_NOERROR;
+}
+
+/* Initialize WORD_CHAR table, which indicate which character is
+ "word". In this case "word" means that it is the word construction
+ character used by some operators like "\<", "\>", etc. */
+
+static void
+internal_function
+init_word_char (re_dfa_t *dfa)
+{
+ int i, j, ch;
+ dfa->word_ops_used = 1;
+ for (i = 0, ch = 0; i < BITSET_WORDS; ++i)
+ for (j = 0; j < BITSET_WORD_BITS; ++j, ++ch)
+ if (isalnum (ch) || ch == '_')
+ dfa->word_char[i] |= (bitset_word_t) 1 << j;
+}
+
+/* Free the work area which are only used while compiling. */
+
+static void
+free_workarea_compile (regex_t *preg)
+{
+ re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+ bin_tree_storage_t *storage, *next;
+ for (storage = dfa->str_tree_storage; storage; storage = next)
+ {
+ next = storage->next;
+ re_free (storage);
+ }
+ dfa->str_tree_storage = NULL;
+ dfa->str_tree_storage_idx = BIN_TREE_STORAGE_SIZE;
+ dfa->str_tree = NULL;
+ re_free (dfa->org_indices);
+ dfa->org_indices = NULL;
+}
+
+/* Create initial states for all contexts. */
+
+static reg_errcode_t
+create_initial_state (re_dfa_t *dfa)
+{
+ int first, i;
+ reg_errcode_t err;
+ re_node_set init_nodes;
+
+ /* Initial states have the epsilon closure of the node which is
+ the first node of the regular expression. */
+ first = dfa->str_tree->first->node_idx;
+ dfa->init_node = first;
+ err = re_node_set_init_copy (&init_nodes, dfa->eclosures + first);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+
+ /* The back-references which are in initial states can epsilon transit,
+ since in this case all of the subexpressions can be null.
+ Then we add epsilon closures of the nodes which are the next nodes of
+ the back-references. */
+ if (dfa->nbackref > 0)
+ for (i = 0; i < init_nodes.nelem; ++i)
+ {
+ int node_idx = init_nodes.elems[i];
+ re_token_type_t type = dfa->nodes[node_idx].type;
+
+ int clexp_idx;
+ if (type != OP_BACK_REF)
+ continue;
+ for (clexp_idx = 0; clexp_idx < init_nodes.nelem; ++clexp_idx)
+ {
+ re_token_t *clexp_node;
+ clexp_node = dfa->nodes + init_nodes.elems[clexp_idx];
+ if (clexp_node->type == OP_CLOSE_SUBEXP
+ && clexp_node->opr.idx == dfa->nodes[node_idx].opr.idx)
+ break;
+ }
+ if (clexp_idx == init_nodes.nelem)
+ continue;
+
+ if (type == OP_BACK_REF)
+ {
+ int dest_idx = dfa->edests[node_idx].elems[0];
+ if (!re_node_set_contains (&init_nodes, dest_idx))
+ {
+ reg_errcode_t err = re_node_set_merge (&init_nodes,
+ dfa->eclosures
+ + dest_idx);
+ if (err != REG_NOERROR)
+ return err;
+ i = 0;
+ }
+ }
+ }
+
+ /* It must be the first time to invoke acquire_state. */
+ dfa->init_state = re_acquire_state_context (&err, dfa, &init_nodes, 0);
+ /* We don't check ERR here, since the initial state must not be NULL. */
+ if (BE (dfa->init_state == NULL, 0))
+ return err;
+ if (dfa->init_state->has_constraint)
+ {
+ dfa->init_state_word = re_acquire_state_context (&err, dfa, &init_nodes,
+ CONTEXT_WORD);
+ dfa->init_state_nl = re_acquire_state_context (&err, dfa, &init_nodes,
+ CONTEXT_NEWLINE);
+ dfa->init_state_begbuf = re_acquire_state_context (&err, dfa,
+ &init_nodes,
+ CONTEXT_NEWLINE
+ | CONTEXT_BEGBUF);
+ if (BE (dfa->init_state_word == NULL || dfa->init_state_nl == NULL
+ || dfa->init_state_begbuf == NULL, 0))
+ return err;
+ }
+ else
+ dfa->init_state_word = dfa->init_state_nl
+ = dfa->init_state_begbuf = dfa->init_state;
+
+ re_node_set_free (&init_nodes);
+ return REG_NOERROR;
+}
+
+#ifdef RE_ENABLE_I18N
+/* If it is possible to do searching in single byte encoding instead of UTF-8
+ to speed things up, set dfa->mb_cur_max to 1, clear is_utf8 and change
+ DFA nodes where needed. */
+
+static void
+optimize_utf8 (re_dfa_t *dfa)
+{
+ int node, i, mb_chars = 0, has_period = 0;
+
+ for (node = 0; node < dfa->nodes_len; ++node)
+ switch (dfa->nodes[node].type)
+ {
+ case CHARACTER:
+ if (dfa->nodes[node].opr.c >= 0x80)
+ mb_chars = 1;
+ break;
+ case ANCHOR:
+ switch (dfa->nodes[node].opr.ctx_type)
+ {
+ case LINE_FIRST:
+ case LINE_LAST:
+ case BUF_FIRST:
+ case BUF_LAST:
+ break;
+ default:
+ /* Word anchors etc. cannot be handled. It's okay to test
+ opr.ctx_type since constraints (for all DFA nodes) are
+ created by ORing one or more opr.ctx_type values. */
+ return;
+ }
+ break;
+ case OP_PERIOD:
+ has_period = 1;
+ break;
+ case OP_BACK_REF:
+ case OP_ALT:
+ case END_OF_RE:
+ case OP_DUP_ASTERISK:
+ case OP_OPEN_SUBEXP:
+ case OP_CLOSE_SUBEXP:
+ break;
+ case COMPLEX_BRACKET:
+ return;
+ case SIMPLE_BRACKET:
+ /* Just double check. The non-ASCII range starts at 0x80. */
+ assert (0x80 % BITSET_WORD_BITS == 0);
+ for (i = 0x80 / BITSET_WORD_BITS; i < BITSET_WORDS; ++i)
+ if (dfa->nodes[node].opr.sbcset[i])
+ return;
+ break;
+ default:
+ abort ();
+ }
+
+ if (mb_chars || has_period)
+ for (node = 0; node < dfa->nodes_len; ++node)
+ {
+ if (dfa->nodes[node].type == CHARACTER
+ && dfa->nodes[node].opr.c >= 0x80)
+ dfa->nodes[node].mb_partial = 0;
+ else if (dfa->nodes[node].type == OP_PERIOD)
+ dfa->nodes[node].type = OP_UTF8_PERIOD;
+ }
+
+ /* The search can be in single byte locale. */
+ dfa->mb_cur_max = 1;
+ dfa->is_utf8 = 0;
+ dfa->has_mb_node = dfa->nbackref > 0 || has_period;
+}
+#endif
+
+/* Analyze the structure tree, and calculate "first", "next", "edest",
+ "eclosure", and "inveclosure". */
+
+static reg_errcode_t
+analyze (regex_t *preg)
+{
+ re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+ reg_errcode_t ret;
+
+ /* Allocate arrays. */
+ dfa->nexts = re_malloc (int, dfa->nodes_alloc);
+ dfa->org_indices = re_malloc (int, dfa->nodes_alloc);
+ dfa->edests = re_malloc (re_node_set, dfa->nodes_alloc);
+ dfa->eclosures = re_malloc (re_node_set, dfa->nodes_alloc);
+ if (BE (dfa->nexts == NULL || dfa->org_indices == NULL || dfa->edests == NULL
+ || dfa->eclosures == NULL, 0))
+ return REG_ESPACE;
+
+ dfa->subexp_map = re_malloc (int, preg->re_nsub);
+ if (dfa->subexp_map != NULL)
+ {
+ unsigned int i;
+ for (i = 0; i < preg->re_nsub; i++)
+ dfa->subexp_map[i] = i;
+ preorder (dfa->str_tree, optimize_subexps, dfa);
+ for (i = 0; i < preg->re_nsub; i++)
+ if (dfa->subexp_map[i] != (int)i)
+ break;
+ if (i == preg->re_nsub)
+ {
+ free (dfa->subexp_map);
+ dfa->subexp_map = NULL;
+ }
+ }
+
+ ret = postorder (dfa->str_tree, lower_subexps, preg);
+ if (BE (ret != REG_NOERROR, 0))
+ return ret;
+ ret = postorder (dfa->str_tree, calc_first, dfa);
+ if (BE (ret != REG_NOERROR, 0))
+ return ret;
+ preorder (dfa->str_tree, calc_next, dfa);
+ ret = preorder (dfa->str_tree, link_nfa_nodes, dfa);
+ if (BE (ret != REG_NOERROR, 0))
+ return ret;
+ ret = calc_eclosure (dfa);
+ if (BE (ret != REG_NOERROR, 0))
+ return ret;
+
+ /* We only need this during the prune_impossible_nodes pass in regexec.c;
+ skip it if p_i_n will not run, as calc_inveclosure can be quadratic. */
+ if ((!preg->no_sub && preg->re_nsub > 0 && dfa->has_plural_match)
+ || dfa->nbackref)
+ {
+ dfa->inveclosures = re_malloc (re_node_set, dfa->nodes_len);
+ if (BE (dfa->inveclosures == NULL, 0))
+ return REG_ESPACE;
+ ret = calc_inveclosure (dfa);
+ }
+
+ return ret;
+}
+
+/* Our parse trees are very unbalanced, so we cannot use a stack to
+ implement parse tree visits. Instead, we use parent pointers and
+ some hairy code in these two functions. */
+static reg_errcode_t
+postorder (bin_tree_t *root, reg_errcode_t (fn (void *, bin_tree_t *)),
+ void *extra)
+{
+ bin_tree_t *node, *prev;
+
+ for (node = root; ; )
+ {
+ /* Descend down the tree, preferably to the left (or to the right
+ if that's the only child). */
+ while (node->left || node->right)
+ if (node->left)
+ node = node->left;
+ else
+ node = node->right;
+
+ do
+ {
+ reg_errcode_t err = fn (extra, node);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ if (node->parent == NULL)
+ return REG_NOERROR;
+ prev = node;
+ node = node->parent;
+ }
+ /* Go up while we have a node that is reached from the right. */
+ while (node->right == prev || node->right == NULL);
+ node = node->right;
+ }
+}
+
+static reg_errcode_t
+preorder (bin_tree_t *root, reg_errcode_t (fn (void *, bin_tree_t *)),
+ void *extra)
+{
+ bin_tree_t *node;
+
+ for (node = root; ; )
+ {
+ reg_errcode_t err = fn (extra, node);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+
+ /* Go to the left node, or up and to the right. */
+ if (node->left)
+ node = node->left;
+ else
+ {
+ bin_tree_t *prev = NULL;
+ while (node->right == prev || node->right == NULL)
+ {
+ prev = node;
+ node = node->parent;
+ if (!node)
+ return REG_NOERROR;
+ }
+ node = node->right;
+ }
+ }
+}
+
+/* Optimization pass: if a SUBEXP is entirely contained, strip it and tell
+ re_search_internal to map the inner one's opr.idx to this one's. Adjust
+ backreferences as well. Requires a preorder visit. */
+static reg_errcode_t
+optimize_subexps (void *extra, bin_tree_t *node)
+{
+ re_dfa_t *dfa = (re_dfa_t *) extra;
+
+ if (node->token.type == OP_BACK_REF && dfa->subexp_map)
+ {
+ int idx = node->token.opr.idx;
+ node->token.opr.idx = dfa->subexp_map[idx];
+ dfa->used_bkref_map |= 1 << node->token.opr.idx;
+ }
+
+ else if (node->token.type == SUBEXP
+ && node->left && node->left->token.type == SUBEXP)
+ {
+ int other_idx = node->left->token.opr.idx;
+
+ node->left = node->left->left;
+ if (node->left)
+ node->left->parent = node;
+
+ dfa->subexp_map[other_idx] = dfa->subexp_map[node->token.opr.idx];
+ if (other_idx < BITSET_WORD_BITS)
+ dfa->used_bkref_map &= ~((bitset_word_t) 1 << other_idx);
+ }
+
+ return REG_NOERROR;
+}
+
+/* Lowering pass: Turn each SUBEXP node into the appropriate concatenation
+ of OP_OPEN_SUBEXP, the body of the SUBEXP (if any) and OP_CLOSE_SUBEXP. */
+static reg_errcode_t
+lower_subexps (void *extra, bin_tree_t *node)
+{
+ regex_t *preg = (regex_t *) extra;
+ reg_errcode_t err = REG_NOERROR;
+
+ if (node->left && node->left->token.type == SUBEXP)
+ {
+ node->left = lower_subexp (&err, preg, node->left);
+ if (node->left)
+ node->left->parent = node;
+ }
+ if (node->right && node->right->token.type == SUBEXP)
+ {
+ node->right = lower_subexp (&err, preg, node->right);
+ if (node->right)
+ node->right->parent = node;
+ }
+
+ return err;
+}
+
+static bin_tree_t *
+lower_subexp (reg_errcode_t *err, regex_t *preg, bin_tree_t *node)
+{
+ re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+ bin_tree_t *body = node->left;
+ bin_tree_t *op, *cls, *tree1, *tree;
+
+ if (preg->no_sub
+ /* We do not optimize empty subexpressions, because otherwise we may
+ have bad CONCAT nodes with NULL children. This is obviously not
+ very common, so we do not lose much. An example that triggers
+ this case is the sed "script" /\(\)/x. */
+ && node->left != NULL
+ && (node->token.opr.idx >= BITSET_WORD_BITS
+ || !(dfa->used_bkref_map
+ & ((bitset_word_t) 1 << node->token.opr.idx))))
+ return node->left;
+
+ /* Convert the SUBEXP node to the concatenation of an
+ OP_OPEN_SUBEXP, the contents, and an OP_CLOSE_SUBEXP. */
+ op = create_tree (dfa, NULL, NULL, OP_OPEN_SUBEXP);
+ cls = create_tree (dfa, NULL, NULL, OP_CLOSE_SUBEXP);
+ tree1 = body ? create_tree (dfa, body, cls, CONCAT) : cls;
+ tree = create_tree (dfa, op, tree1, CONCAT);
+ if (BE (tree == NULL || tree1 == NULL || op == NULL || cls == NULL, 0))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+
+ op->token.opr.idx = cls->token.opr.idx = node->token.opr.idx;
+ op->token.opt_subexp = cls->token.opt_subexp = node->token.opt_subexp;
+ return tree;
+}
+
+/* Pass 1 in building the NFA: compute FIRST and create unlinked automaton
+ nodes. Requires a postorder visit. */
+static reg_errcode_t
+calc_first (void *extra, bin_tree_t *node)
+{
+ re_dfa_t *dfa = (re_dfa_t *) extra;
+ if (node->token.type == CONCAT)
+ {
+ node->first = node->left->first;
+ node->node_idx = node->left->node_idx;
+ }
+ else
+ {
+ node->first = node;
+ node->node_idx = re_dfa_add_node (dfa, node->token);
+ if (BE (node->node_idx == -1, 0))
+ return REG_ESPACE;
+ if (node->token.type == ANCHOR)
+ dfa->nodes[node->node_idx].constraint = node->token.opr.ctx_type;
+ }
+ return REG_NOERROR;
+}
+
+/* Pass 2: compute NEXT on the tree. Preorder visit. */
+static reg_errcode_t
+calc_next (UNUSED void *extra, bin_tree_t *node)
+{
+ switch (node->token.type)
+ {
+ case OP_DUP_ASTERISK:
+ node->left->next = node;
+ break;
+ case CONCAT:
+ node->left->next = node->right->first;
+ node->right->next = node->next;
+ break;
+ default:
+ if (node->left)
+ node->left->next = node->next;
+ if (node->right)
+ node->right->next = node->next;
+ break;
+ }
+ return REG_NOERROR;
+}
+
+/* Pass 3: link all DFA nodes to their NEXT node (any order will do). */
+static reg_errcode_t
+link_nfa_nodes (void *extra, bin_tree_t *node)
+{
+ re_dfa_t *dfa = (re_dfa_t *) extra;
+ int idx = node->node_idx;
+ reg_errcode_t err = REG_NOERROR;
+
+ switch (node->token.type)
+ {
+ case CONCAT:
+ break;
+
+ case END_OF_RE:
+ assert (node->next == NULL);
+ break;
+
+ case OP_DUP_ASTERISK:
+ case OP_ALT:
+ {
+ int left, right;
+ dfa->has_plural_match = 1;
+ if (node->left != NULL)
+ left = node->left->first->node_idx;
+ else
+ left = node->next->node_idx;
+ if (node->right != NULL)
+ right = node->right->first->node_idx;
+ else
+ right = node->next->node_idx;
+ assert (left > -1);
+ assert (right > -1);
+ err = re_node_set_init_2 (dfa->edests + idx, left, right);
+ }
+ break;
+
+ case ANCHOR:
+ case OP_OPEN_SUBEXP:
+ case OP_CLOSE_SUBEXP:
+ err = re_node_set_init_1 (dfa->edests + idx, node->next->node_idx);
+ break;
+
+ case OP_BACK_REF:
+ dfa->nexts[idx] = node->next->node_idx;
+ if (node->token.type == OP_BACK_REF)
+ err = re_node_set_init_1 (dfa->edests + idx, dfa->nexts[idx]);
+ break;
+
+ default:
+ assert (!IS_EPSILON_NODE (node->token.type));
+ dfa->nexts[idx] = node->next->node_idx;
+ break;
+ }
+
+ return err;
+}
+
+/* Duplicate the epsilon closure of the node ROOT_NODE.
+ Note that duplicated nodes have constraint INIT_CONSTRAINT in addition
+ to their own constraint. */
+
+static reg_errcode_t
+internal_function
+duplicate_node_closure (re_dfa_t *dfa, int top_org_node, int top_clone_node,
+ int root_node, unsigned int init_constraint)
+{
+ int org_node, clone_node, ret;
+ unsigned int constraint = init_constraint;
+ for (org_node = top_org_node, clone_node = top_clone_node;;)
+ {
+ int org_dest, clone_dest;
+ if (dfa->nodes[org_node].type == OP_BACK_REF)
+ {
+ /* If the back reference epsilon-transit, its destination must
+ also have the constraint. Then duplicate the epsilon closure
+ of the destination of the back reference, and store it in
+ edests of the back reference. */
+ org_dest = dfa->nexts[org_node];
+ re_node_set_empty (dfa->edests + clone_node);
+ clone_dest = duplicate_node (dfa, org_dest, constraint);
+ if (BE (clone_dest == -1, 0))
+ return REG_ESPACE;
+ dfa->nexts[clone_node] = dfa->nexts[org_node];
+ ret = re_node_set_insert (dfa->edests + clone_node, clone_dest);
+ if (BE (ret < 0, 0))
+ return REG_ESPACE;
+ }
+ else if (dfa->edests[org_node].nelem == 0)
+ {
+ /* In case of the node can't epsilon-transit, don't duplicate the
+ destination and store the original destination as the
+ destination of the node. */
+ dfa->nexts[clone_node] = dfa->nexts[org_node];
+ break;
+ }
+ else if (dfa->edests[org_node].nelem == 1)
+ {
+ /* In case of the node can epsilon-transit, and it has only one
+ destination. */
+ org_dest = dfa->edests[org_node].elems[0];
+ re_node_set_empty (dfa->edests + clone_node);
+ /* If the node is root_node itself, it means the epsilon clsoure
+ has a loop. Then tie it to the destination of the root_node. */
+ if (org_node == root_node && clone_node != org_node)
+ {
+ ret = re_node_set_insert (dfa->edests + clone_node, org_dest);
+ if (BE (ret < 0, 0))
+ return REG_ESPACE;
+ break;
+ }
+ /* In case of the node has another constraint, add it. */
+ constraint |= dfa->nodes[org_node].constraint;
+ clone_dest = duplicate_node (dfa, org_dest, constraint);
+ if (BE (clone_dest == -1, 0))
+ return REG_ESPACE;
+ ret = re_node_set_insert (dfa->edests + clone_node, clone_dest);
+ if (BE (ret < 0, 0))
+ return REG_ESPACE;
+ }
+ else /* dfa->edests[org_node].nelem == 2 */
+ {
+ /* In case of the node can epsilon-transit, and it has two
+ destinations. In the bin_tree_t and DFA, that's '|' and '*'. */
+ org_dest = dfa->edests[org_node].elems[0];
+ re_node_set_empty (dfa->edests + clone_node);
+ /* Search for a duplicated node which satisfies the constraint. */
+ clone_dest = search_duplicated_node (dfa, org_dest, constraint);
+ if (clone_dest == -1)
+ {
+ /* There is no such duplicated node, create a new one. */
+ reg_errcode_t err;
+ clone_dest = duplicate_node (dfa, org_dest, constraint);
+ if (BE (clone_dest == -1, 0))
+ return REG_ESPACE;
+ ret = re_node_set_insert (dfa->edests + clone_node, clone_dest);
+ if (BE (ret < 0, 0))
+ return REG_ESPACE;
+ err = duplicate_node_closure (dfa, org_dest, clone_dest,
+ root_node, constraint);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ else
+ {
+ /* There is a duplicated node which satisfies the constraint,
+ use it to avoid infinite loop. */
+ ret = re_node_set_insert (dfa->edests + clone_node, clone_dest);
+ if (BE (ret < 0, 0))
+ return REG_ESPACE;
+ }
+
+ org_dest = dfa->edests[org_node].elems[1];
+ clone_dest = duplicate_node (dfa, org_dest, constraint);
+ if (BE (clone_dest == -1, 0))
+ return REG_ESPACE;
+ ret = re_node_set_insert (dfa->edests + clone_node, clone_dest);
+ if (BE (ret < 0, 0))
+ return REG_ESPACE;
+ }
+ org_node = org_dest;
+ clone_node = clone_dest;
+ }
+ return REG_NOERROR;
+}
+
+/* Search for a node which is duplicated from the node ORG_NODE, and
+ satisfies the constraint CONSTRAINT. */
+
+static int
+search_duplicated_node (const re_dfa_t *dfa, int org_node,
+ unsigned int constraint)
+{
+ int idx;
+ for (idx = dfa->nodes_len - 1; dfa->nodes[idx].duplicated && idx > 0; --idx)
+ {
+ if (org_node == dfa->org_indices[idx]
+ && constraint == dfa->nodes[idx].constraint)
+ return idx; /* Found. */
+ }
+ return -1; /* Not found. */
+}
+
+/* Duplicate the node whose index is ORG_IDX and set the constraint CONSTRAINT.
+ Return the index of the new node, or -1 if insufficient storage is
+ available. */
+
+static int
+duplicate_node (re_dfa_t *dfa, int org_idx, unsigned int constraint)
+{
+ int dup_idx = re_dfa_add_node (dfa, dfa->nodes[org_idx]);
+ if (BE (dup_idx != -1, 1))
+ {
+ dfa->nodes[dup_idx].constraint = constraint;
+ dfa->nodes[dup_idx].constraint |= dfa->nodes[org_idx].constraint;
+ dfa->nodes[dup_idx].duplicated = 1;
+
+ /* Store the index of the original node. */
+ dfa->org_indices[dup_idx] = org_idx;
+ }
+ return dup_idx;
+}
+
+static reg_errcode_t
+calc_inveclosure (re_dfa_t *dfa)
+{
+ int ret;
+ unsigned int src, idx;
+ for (idx = 0; idx < dfa->nodes_len; ++idx)
+ re_node_set_init_empty (dfa->inveclosures + idx);
+
+ for (src = 0; src < dfa->nodes_len; ++src)
+ {
+ int *elems = dfa->eclosures[src].elems;
+ int idx;
+ for (idx = 0; idx < dfa->eclosures[src].nelem; ++idx)
+ {
+ ret = re_node_set_insert_last (dfa->inveclosures + elems[idx], src);
+ if (BE (ret == -1, 0))
+ return REG_ESPACE;
+ }
+ }
+
+ return REG_NOERROR;
+}
+
+/* Calculate "eclosure" for all the node in DFA. */
+
+static reg_errcode_t
+calc_eclosure (re_dfa_t *dfa)
+{
+ size_t node_idx;
+ int incomplete;
+#ifdef DEBUG
+ assert (dfa->nodes_len > 0);
+#endif
+ incomplete = 0;
+ /* For each nodes, calculate epsilon closure. */
+ for (node_idx = 0; ; ++node_idx)
+ {
+ reg_errcode_t err;
+ re_node_set eclosure_elem;
+ if (node_idx == dfa->nodes_len)
+ {
+ if (!incomplete)
+ break;
+ incomplete = 0;
+ node_idx = 0;
+ }
+
+#ifdef DEBUG
+ assert (dfa->eclosures[node_idx].nelem != -1);
+#endif
+
+ /* If we have already calculated, skip it. */
+ if (dfa->eclosures[node_idx].nelem != 0)
+ continue;
+ /* Calculate epsilon closure of `node_idx'. */
+ err = calc_eclosure_iter (&eclosure_elem, dfa, node_idx, 1);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+
+ if (dfa->eclosures[node_idx].nelem == 0)
+ {
+ incomplete = 1;
+ re_node_set_free (&eclosure_elem);
+ }
+ }
+ return REG_NOERROR;
+}
+
+/* Calculate epsilon closure of NODE. */
+
+static reg_errcode_t
+calc_eclosure_iter (re_node_set *new_set, re_dfa_t *dfa, int node, int root)
+{
+ reg_errcode_t err;
+ int i;
+ re_node_set eclosure;
+ int ret;
+ int incomplete = 0;
+ err = re_node_set_alloc (&eclosure, dfa->edests[node].nelem + 1);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+
+ /* This indicates that we are calculating this node now.
+ We reference this value to avoid infinite loop. */
+ dfa->eclosures[node].nelem = -1;
+
+ /* If the current node has constraints, duplicate all nodes
+ since they must inherit the constraints. */
+ if (dfa->nodes[node].constraint
+ && dfa->edests[node].nelem
+ && !dfa->nodes[dfa->edests[node].elems[0]].duplicated)
+ {
+ err = duplicate_node_closure (dfa, node, node, node,
+ dfa->nodes[node].constraint);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+
+ /* Expand each epsilon destination nodes. */
+ if (IS_EPSILON_NODE(dfa->nodes[node].type))
+ for (i = 0; i < dfa->edests[node].nelem; ++i)
+ {
+ re_node_set eclosure_elem;
+ int edest = dfa->edests[node].elems[i];
+ /* If calculating the epsilon closure of `edest' is in progress,
+ return intermediate result. */
+ if (dfa->eclosures[edest].nelem == -1)
+ {
+ incomplete = 1;
+ continue;
+ }
+ /* If we haven't calculated the epsilon closure of `edest' yet,
+ calculate now. Otherwise use calculated epsilon closure. */
+ if (dfa->eclosures[edest].nelem == 0)
+ {
+ err = calc_eclosure_iter (&eclosure_elem, dfa, edest, 0);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ else
+ eclosure_elem = dfa->eclosures[edest];
+ /* Merge the epsilon closure of `edest'. */
+ err = re_node_set_merge (&eclosure, &eclosure_elem);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ /* If the epsilon closure of `edest' is incomplete,
+ the epsilon closure of this node is also incomplete. */
+ if (dfa->eclosures[edest].nelem == 0)
+ {
+ incomplete = 1;
+ re_node_set_free (&eclosure_elem);
+ }
+ }
+
+ /* An epsilon closure includes itself. */
+ ret = re_node_set_insert (&eclosure, node);
+ if (BE (ret < 0, 0))
+ return REG_ESPACE;
+ if (incomplete && !root)
+ dfa->eclosures[node].nelem = 0;
+ else
+ dfa->eclosures[node] = eclosure;
+ *new_set = eclosure;
+ return REG_NOERROR;
+}
+
+/* Functions for token which are used in the parser. */
+
+/* Fetch a token from INPUT.
+ We must not use this function inside bracket expressions. */
+
+static void
+internal_function
+fetch_token (re_token_t *result, re_string_t *input, reg_syntax_t syntax)
+{
+ re_string_skip_bytes (input, peek_token (result, input, syntax));
+}
+
+/* Peek a token from INPUT, and return the length of the token.
+ We must not use this function inside bracket expressions. */
+
+static int
+internal_function
+peek_token (re_token_t *token, re_string_t *input, reg_syntax_t syntax)
+{
+ unsigned char c;
+
+ if (re_string_eoi (input))
+ {
+ token->type = END_OF_RE;
+ return 0;
+ }
+
+ c = re_string_peek_byte (input, 0);
+ token->opr.c = c;
+
+ token->word_char = 0;
+#ifdef RE_ENABLE_I18N
+ token->mb_partial = 0;
+ if (input->mb_cur_max > 1 &&
+ !re_string_first_byte (input, re_string_cur_idx (input)))
+ {
+ token->type = CHARACTER;
+ token->mb_partial = 1;
+ return 1;
+ }
+#endif
+ if (c == '\\')
+ {
+ unsigned char c2;
+ if (re_string_cur_idx (input) + 1 >= re_string_length (input))
+ {
+ token->type = BACK_SLASH;
+ return 1;
+ }
+
+ c2 = re_string_peek_byte_case (input, 1);
+ token->opr.c = c2;
+ token->type = CHARACTER;
+#ifdef RE_ENABLE_I18N
+ if (input->mb_cur_max > 1)
+ {
+ wint_t wc = re_string_wchar_at (input,
+ re_string_cur_idx (input) + 1);
+ token->word_char = IS_WIDE_WORD_CHAR (wc) != 0;
+ }
+ else
+#endif
+ token->word_char = IS_WORD_CHAR (c2) != 0;
+
+ switch (c2)
+ {
+ case '|':
+ if (!(syntax & RE_LIMITED_OPS) && !(syntax & RE_NO_BK_VBAR))
+ token->type = OP_ALT;
+ break;
+ case '1': case '2': case '3': case '4': case '5':
+ case '6': case '7': case '8': case '9':
+ if (!(syntax & RE_NO_BK_REFS))
+ {
+ token->type = OP_BACK_REF;
+ token->opr.idx = c2 - '1';
+ }
+ break;
+ case '<':
+ if (!(syntax & RE_NO_GNU_OPS))
+ {
+ token->type = ANCHOR;
+ token->opr.ctx_type = WORD_FIRST;
+ }
+ break;
+ case '>':
+ if (!(syntax & RE_NO_GNU_OPS))
+ {
+ token->type = ANCHOR;
+ token->opr.ctx_type = WORD_LAST;
+ }
+ break;
+ case 'b':
+ if (!(syntax & RE_NO_GNU_OPS))
+ {
+ token->type = ANCHOR;
+ token->opr.ctx_type = WORD_DELIM;
+ }
+ break;
+ case 'B':
+ if (!(syntax & RE_NO_GNU_OPS))
+ {
+ token->type = ANCHOR;
+ token->opr.ctx_type = NOT_WORD_DELIM;
+ }
+ break;
+ case 'w':
+ if (!(syntax & RE_NO_GNU_OPS))
+ token->type = OP_WORD;
+ break;
+ case 'W':
+ if (!(syntax & RE_NO_GNU_OPS))
+ token->type = OP_NOTWORD;
+ break;
+ case 's':
+ if (!(syntax & RE_NO_GNU_OPS))
+ token->type = OP_SPACE;
+ break;
+ case 'S':
+ if (!(syntax & RE_NO_GNU_OPS))
+ token->type = OP_NOTSPACE;
+ break;
+ case '`':
+ if (!(syntax & RE_NO_GNU_OPS))
+ {
+ token->type = ANCHOR;
+ token->opr.ctx_type = BUF_FIRST;
+ }
+ break;
+ case '\'':
+ if (!(syntax & RE_NO_GNU_OPS))
+ {
+ token->type = ANCHOR;
+ token->opr.ctx_type = BUF_LAST;
+ }
+ break;
+ case '(':
+ if (!(syntax & RE_NO_BK_PARENS))
+ token->type = OP_OPEN_SUBEXP;
+ break;
+ case ')':
+ if (!(syntax & RE_NO_BK_PARENS))
+ token->type = OP_CLOSE_SUBEXP;
+ break;
+ case '+':
+ if (!(syntax & RE_LIMITED_OPS) && (syntax & RE_BK_PLUS_QM))
+ token->type = OP_DUP_PLUS;
+ break;
+ case '?':
+ if (!(syntax & RE_LIMITED_OPS) && (syntax & RE_BK_PLUS_QM))
+ token->type = OP_DUP_QUESTION;
+ break;
+ case '{':
+ if ((syntax & RE_INTERVALS) && (!(syntax & RE_NO_BK_BRACES)))
+ token->type = OP_OPEN_DUP_NUM;
+ break;
+ case '}':
+ if ((syntax & RE_INTERVALS) && (!(syntax & RE_NO_BK_BRACES)))
+ token->type = OP_CLOSE_DUP_NUM;
+ break;
+ default:
+ break;
+ }
+ return 2;
+ }
+
+ token->type = CHARACTER;
+#ifdef RE_ENABLE_I18N
+ if (input->mb_cur_max > 1)
+ {
+ wint_t wc = re_string_wchar_at (input, re_string_cur_idx (input));
+ token->word_char = IS_WIDE_WORD_CHAR (wc) != 0;
+ }
+ else
+#endif
+ token->word_char = IS_WORD_CHAR (token->opr.c);
+
+ switch (c)
+ {
+ case '\n':
+ if (syntax & RE_NEWLINE_ALT)
+ token->type = OP_ALT;
+ break;
+ case '|':
+ if (!(syntax & RE_LIMITED_OPS) && (syntax & RE_NO_BK_VBAR))
+ token->type = OP_ALT;
+ break;
+ case '*':
+ token->type = OP_DUP_ASTERISK;
+ break;
+ case '+':
+ if (!(syntax & RE_LIMITED_OPS) && !(syntax & RE_BK_PLUS_QM))
+ token->type = OP_DUP_PLUS;
+ break;
+ case '?':
+ if (!(syntax & RE_LIMITED_OPS) && !(syntax & RE_BK_PLUS_QM))
+ token->type = OP_DUP_QUESTION;
+ break;
+ case '{':
+ if ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES))
+ token->type = OP_OPEN_DUP_NUM;
+ break;
+ case '}':
+ if ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES))
+ token->type = OP_CLOSE_DUP_NUM;
+ break;
+ case '(':
+ if (syntax & RE_NO_BK_PARENS)
+ token->type = OP_OPEN_SUBEXP;
+ break;
+ case ')':
+ if (syntax & RE_NO_BK_PARENS)
+ token->type = OP_CLOSE_SUBEXP;
+ break;
+ case '[':
+ token->type = OP_OPEN_BRACKET;
+ break;
+ case '.':
+ token->type = OP_PERIOD;
+ break;
+ case '^':
+ if (!(syntax & (RE_CONTEXT_INDEP_ANCHORS | RE_CARET_ANCHORS_HERE)) &&
+ re_string_cur_idx (input) != 0)
+ {
+ char prev = re_string_peek_byte (input, -1);
+ if (!(syntax & RE_NEWLINE_ALT) || prev != '\n')
+ break;
+ }
+ token->type = ANCHOR;
+ token->opr.ctx_type = LINE_FIRST;
+ break;
+ case '$':
+ if (!(syntax & RE_CONTEXT_INDEP_ANCHORS) &&
+ re_string_cur_idx (input) + 1 != re_string_length (input))
+ {
+ re_token_t next;
+ re_string_skip_bytes (input, 1);
+ peek_token (&next, input, syntax);
+ re_string_skip_bytes (input, -1);
+ if (next.type != OP_ALT && next.type != OP_CLOSE_SUBEXP)
+ break;
+ }
+ token->type = ANCHOR;
+ token->opr.ctx_type = LINE_LAST;
+ break;
+ default:
+ break;
+ }
+ return 1;
+}
+
+/* Peek a token from INPUT, and return the length of the token.
+ We must not use this function out of bracket expressions. */
+
+static int
+internal_function
+peek_token_bracket (re_token_t *token, re_string_t *input, reg_syntax_t syntax)
+{
+ unsigned char c;
+ if (re_string_eoi (input))
+ {
+ token->type = END_OF_RE;
+ return 0;
+ }
+ c = re_string_peek_byte (input, 0);
+ token->opr.c = c;
+
+#ifdef RE_ENABLE_I18N
+ if (input->mb_cur_max > 1 &&
+ !re_string_first_byte (input, re_string_cur_idx (input)))
+ {
+ token->type = CHARACTER;
+ return 1;
+ }
+#endif /* RE_ENABLE_I18N */
+
+ if (c == '\\' && (syntax & RE_BACKSLASH_ESCAPE_IN_LISTS)
+ && re_string_cur_idx (input) + 1 < re_string_length (input))
+ {
+ /* In this case, '\' escape a character. */
+ unsigned char c2;
+ re_string_skip_bytes (input, 1);
+ c2 = re_string_peek_byte (input, 0);
+ token->opr.c = c2;
+ token->type = CHARACTER;
+ return 1;
+ }
+ if (c == '[') /* '[' is a special char in a bracket exps. */
+ {
+ unsigned char c2;
+ int token_len;
+ if (re_string_cur_idx (input) + 1 < re_string_length (input))
+ c2 = re_string_peek_byte (input, 1);
+ else
+ c2 = 0;
+ token->opr.c = c2;
+ token_len = 2;
+ switch (c2)
+ {
+ case '.':
+ token->type = OP_OPEN_COLL_ELEM;
+ break;
+ case '=':
+ token->type = OP_OPEN_EQUIV_CLASS;
+ break;
+ case ':':
+ if (syntax & RE_CHAR_CLASSES)
+ {
+ token->type = OP_OPEN_CHAR_CLASS;
+ break;
+ }
+ /* else fall through. */
+ default:
+ token->type = CHARACTER;
+ token->opr.c = c;
+ token_len = 1;
+ break;
+ }
+ return token_len;
+ }
+ switch (c)
+ {
+ case '-':
+ token->type = OP_CHARSET_RANGE;
+ break;
+ case ']':
+ token->type = OP_CLOSE_BRACKET;
+ break;
+ case '^':
+ token->type = OP_NON_MATCH_LIST;
+ break;
+ default:
+ token->type = CHARACTER;
+ }
+ return 1;
+}
+
+/* Functions for parser. */
+
+/* Entry point of the parser.
+ Parse the regular expression REGEXP and return the structure tree.
+ If an error is occured, ERR is set by error code, and return NULL.
+ This function build the following tree, from regular expression <reg_exp>:
+ CAT
+ / \
+ / \
+ <reg_exp> EOR
+
+ CAT means concatenation.
+ EOR means end of regular expression. */
+
+static bin_tree_t *
+parse (re_string_t *regexp, regex_t *preg, reg_syntax_t syntax,
+ reg_errcode_t *err)
+{
+ re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+ bin_tree_t *tree, *eor, *root;
+ re_token_t current_token;
+ dfa->syntax = syntax;
+ fetch_token (&current_token, regexp, syntax | RE_CARET_ANCHORS_HERE);
+ tree = parse_reg_exp (regexp, preg, &current_token, syntax, 0, err);
+ if (BE (*err != REG_NOERROR && tree == NULL, 0))
+ return NULL;
+ eor = create_tree (dfa, NULL, NULL, END_OF_RE);
+ if (tree != NULL)
+ root = create_tree (dfa, tree, eor, CONCAT);
+ else
+ root = eor;
+ if (BE (eor == NULL || root == NULL, 0))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+ return root;
+}
+
+/* This function build the following tree, from regular expression
+ <branch1>|<branch2>:
+ ALT
+ / \
+ / \
+ <branch1> <branch2>
+
+ ALT means alternative, which represents the operator `|'. */
+
+static bin_tree_t *
+parse_reg_exp (re_string_t *regexp, regex_t *preg, re_token_t *token,
+ reg_syntax_t syntax, int nest, reg_errcode_t *err)
+{
+ re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+ bin_tree_t *tree, *branch = NULL;
+ tree = parse_branch (regexp, preg, token, syntax, nest, err);
+ if (BE (*err != REG_NOERROR && tree == NULL, 0))
+ return NULL;
+
+ while (token->type == OP_ALT)
+ {
+ fetch_token (token, regexp, syntax | RE_CARET_ANCHORS_HERE);
+ if (token->type != OP_ALT && token->type != END_OF_RE
+ && (nest == 0 || token->type != OP_CLOSE_SUBEXP))
+ {
+ branch = parse_branch (regexp, preg, token, syntax, nest, err);
+ if (BE (*err != REG_NOERROR && branch == NULL, 0))
+ return NULL;
+ }
+ else
+ branch = NULL;
+ tree = create_tree (dfa, tree, branch, OP_ALT);
+ if (BE (tree == NULL, 0))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+ }
+ return tree;
+}
+
+/* This function build the following tree, from regular expression
+ <exp1><exp2>:
+ CAT
+ / \
+ / \
+ <exp1> <exp2>
+
+ CAT means concatenation. */
+
+static bin_tree_t *
+parse_branch (re_string_t *regexp, regex_t *preg, re_token_t *token,
+ reg_syntax_t syntax, int nest, reg_errcode_t *err)
+{
+ bin_tree_t *tree, *exp;
+ re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+ tree = parse_expression (regexp, preg, token, syntax, nest, err);
+ if (BE (*err != REG_NOERROR && tree == NULL, 0))
+ return NULL;
+
+ while (token->type != OP_ALT && token->type != END_OF_RE
+ && (nest == 0 || token->type != OP_CLOSE_SUBEXP))
+ {
+ exp = parse_expression (regexp, preg, token, syntax, nest, err);
+ if (BE (*err != REG_NOERROR && exp == NULL, 0))
+ {
+ return NULL;
+ }
+ if (tree != NULL && exp != NULL)
+ {
+ tree = create_tree (dfa, tree, exp, CONCAT);
+ if (tree == NULL)
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+ }
+ else if (tree == NULL)
+ tree = exp;
+ /* Otherwise exp == NULL, we don't need to create new tree. */
+ }
+ return tree;
+}
+
+/* This function build the following tree, from regular expression a*:
+ *
+ |
+ a
+*/
+
+static bin_tree_t *
+parse_expression (re_string_t *regexp, regex_t *preg, re_token_t *token,
+ reg_syntax_t syntax, int nest, reg_errcode_t *err)
+{
+ re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+ bin_tree_t *tree;
+ switch (token->type)
+ {
+ case CHARACTER:
+ tree = create_token_tree (dfa, NULL, NULL, token);
+ if (BE (tree == NULL, 0))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+#ifdef RE_ENABLE_I18N
+ if (dfa->mb_cur_max > 1)
+ {
+ while (!re_string_eoi (regexp)
+ && !re_string_first_byte (regexp, re_string_cur_idx (regexp)))
+ {
+ bin_tree_t *mbc_remain;
+ fetch_token (token, regexp, syntax);
+ mbc_remain = create_token_tree (dfa, NULL, NULL, token);
+ tree = create_tree (dfa, tree, mbc_remain, CONCAT);
+ if (BE (mbc_remain == NULL || tree == NULL, 0))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+ }
+ }
+#endif
+ break;
+ case OP_OPEN_SUBEXP:
+ tree = parse_sub_exp (regexp, preg, token, syntax, nest + 1, err);
+ if (BE (*err != REG_NOERROR && tree == NULL, 0))
+ return NULL;
+ break;
+ case OP_OPEN_BRACKET:
+ tree = parse_bracket_exp (regexp, dfa, token, syntax, err);
+ if (BE (*err != REG_NOERROR && tree == NULL, 0))
+ return NULL;
+ break;
+ case OP_BACK_REF:
+ if (!BE (dfa->completed_bkref_map & (1 << token->opr.idx), 1))
+ {
+ *err = REG_ESUBREG;
+ return NULL;
+ }
+ dfa->used_bkref_map |= 1 << token->opr.idx;
+ tree = create_token_tree (dfa, NULL, NULL, token);
+ if (BE (tree == NULL, 0))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+ ++dfa->nbackref;
+ dfa->has_mb_node = 1;
+ break;
+ case OP_OPEN_DUP_NUM:
+ if (syntax & RE_CONTEXT_INVALID_DUP)
+ {
+ *err = REG_BADRPT;
+ return NULL;
+ }
+ /* FALLTHROUGH */
+ case OP_DUP_ASTERISK:
+ case OP_DUP_PLUS:
+ case OP_DUP_QUESTION:
+ if (syntax & RE_CONTEXT_INVALID_OPS)
+ {
+ *err = REG_BADRPT;
+ return NULL;
+ }
+ else if (syntax & RE_CONTEXT_INDEP_OPS)
+ {
+ fetch_token (token, regexp, syntax);
+ return parse_expression (regexp, preg, token, syntax, nest, err);
+ }
+ /* else fall through */
+ case OP_CLOSE_SUBEXP:
+ if ((token->type == OP_CLOSE_SUBEXP) &&
+ !(syntax & RE_UNMATCHED_RIGHT_PAREN_ORD))
+ {
+ *err = REG_ERPAREN;
+ return NULL;
+ }
+ /* else fall through */
+ case OP_CLOSE_DUP_NUM:
+ /* We treat it as a normal character. */
+
+ /* Then we can these characters as normal characters. */
+ token->type = CHARACTER;
+ /* mb_partial and word_char bits should be initialized already
+ by peek_token. */
+ tree = create_token_tree (dfa, NULL, NULL, token);
+ if (BE (tree == NULL, 0))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+ break;
+ case ANCHOR:
+ if ((token->opr.ctx_type
+ & (WORD_DELIM | NOT_WORD_DELIM | WORD_FIRST | WORD_LAST))
+ && dfa->word_ops_used == 0)
+ init_word_char (dfa);
+ if (token->opr.ctx_type == WORD_DELIM
+ || token->opr.ctx_type == NOT_WORD_DELIM)
+ {
+ bin_tree_t *tree_first, *tree_last;
+ if (token->opr.ctx_type == WORD_DELIM)
+ {
+ token->opr.ctx_type = WORD_FIRST;
+ tree_first = create_token_tree (dfa, NULL, NULL, token);
+ token->opr.ctx_type = WORD_LAST;
+ }
+ else
+ {
+ token->opr.ctx_type = INSIDE_WORD;
+ tree_first = create_token_tree (dfa, NULL, NULL, token);
+ token->opr.ctx_type = INSIDE_NOTWORD;
+ }
+ tree_last = create_token_tree (dfa, NULL, NULL, token);
+ tree = create_tree (dfa, tree_first, tree_last, OP_ALT);
+ if (BE (tree_first == NULL || tree_last == NULL || tree == NULL, 0))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+ }
+ else
+ {
+ tree = create_token_tree (dfa, NULL, NULL, token);
+ if (BE (tree == NULL, 0))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+ }
+ /* We must return here, since ANCHORs can't be followed
+ by repetition operators.
+ eg. RE"^*" is invalid or "<ANCHOR(^)><CHAR(*)>",
+ it must not be "<ANCHOR(^)><REPEAT(*)>". */
+ fetch_token (token, regexp, syntax);
+ return tree;
+ case OP_PERIOD:
+ tree = create_token_tree (dfa, NULL, NULL, token);
+ if (BE (tree == NULL, 0))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+ if (dfa->mb_cur_max > 1)
+ dfa->has_mb_node = 1;
+ break;
+ case OP_WORD:
+ case OP_NOTWORD:
+ tree = build_charclass_op (dfa, regexp->trans,
+ "alnum",
+ "_",
+ token->type == OP_NOTWORD, err);
+ if (BE (*err != REG_NOERROR && tree == NULL, 0))
+ return NULL;
+ break;
+ case OP_SPACE:
+ case OP_NOTSPACE:
+ tree = build_charclass_op (dfa, regexp->trans,
+ "space",
+ "",
+ token->type == OP_NOTSPACE, err);
+ if (BE (*err != REG_NOERROR && tree == NULL, 0))
+ return NULL;
+ break;
+ case OP_ALT:
+ case END_OF_RE:
+ return NULL;
+ case BACK_SLASH:
+ *err = REG_EESCAPE;
+ return NULL;
+ default:
+ /* Must not happen? */
+#ifdef DEBUG
+ assert (0);
+#endif
+ return NULL;
+ }
+ fetch_token (token, regexp, syntax);
+
+ while (token->type == OP_DUP_ASTERISK || token->type == OP_DUP_PLUS
+ || token->type == OP_DUP_QUESTION || token->type == OP_OPEN_DUP_NUM)
+ {
+ tree = parse_dup_op (tree, regexp, dfa, token, syntax, err);
+ if (BE (*err != REG_NOERROR && tree == NULL, 0))
+ return NULL;
+ /* In BRE consecutive duplications are not allowed. */
+ if ((syntax & RE_CONTEXT_INVALID_DUP)
+ && (token->type == OP_DUP_ASTERISK
+ || token->type == OP_OPEN_DUP_NUM))
+ {
+ *err = REG_BADRPT;
+ return NULL;
+ }
+ }
+
+ return tree;
+}
+
+/* This function build the following tree, from regular expression
+ (<reg_exp>):
+ SUBEXP
+ |
+ <reg_exp>
+*/
+
+static bin_tree_t *
+parse_sub_exp (re_string_t *regexp, regex_t *preg, re_token_t *token,
+ reg_syntax_t syntax, int nest, reg_errcode_t *err)
+{
+ re_dfa_t *dfa = (re_dfa_t *) preg->buffer;
+ bin_tree_t *tree;
+ size_t cur_nsub;
+ cur_nsub = preg->re_nsub++;
+
+ fetch_token (token, regexp, syntax | RE_CARET_ANCHORS_HERE);
+
+ /* The subexpression may be a null string. */
+ if (token->type == OP_CLOSE_SUBEXP)
+ tree = NULL;
+ else
+ {
+ tree = parse_reg_exp (regexp, preg, token, syntax, nest, err);
+ if (BE (*err == REG_NOERROR && token->type != OP_CLOSE_SUBEXP, 0))
+ *err = REG_EPAREN;
+ if (BE (*err != REG_NOERROR, 0))
+ return NULL;
+ }
+
+ if (cur_nsub <= '9' - '1')
+ dfa->completed_bkref_map |= 1 << cur_nsub;
+
+ tree = create_tree (dfa, tree, NULL, SUBEXP);
+ if (BE (tree == NULL, 0))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+ tree->token.opr.idx = cur_nsub;
+ return tree;
+}
+
+/* This function parse repetition operators like "*", "+", "{1,3}" etc. */
+
+static bin_tree_t *
+parse_dup_op (bin_tree_t *elem, re_string_t *regexp, re_dfa_t *dfa,
+ re_token_t *token, reg_syntax_t syntax, reg_errcode_t *err)
+{
+ bin_tree_t *tree = NULL, *old_tree = NULL;
+ int i, start, end, start_idx = re_string_cur_idx (regexp);
+#ifndef RE_TOKEN_INIT_BUG
+ re_token_t start_token = *token;
+#else
+ re_token_t start_token;
+
+ memcpy ((void *) &start_token, (void *) token, sizeof start_token);
+#endif
+
+ if (token->type == OP_OPEN_DUP_NUM)
+ {
+ end = 0;
+ start = fetch_number (regexp, token, syntax);
+ if (start == -1)
+ {
+ if (token->type == CHARACTER && token->opr.c == ',')
+ start = 0; /* We treat "{,m}" as "{0,m}". */
+ else
+ {
+ *err = REG_BADBR; /* <re>{} is invalid. */
+ return NULL;
+ }
+ }
+ if (BE (start != -2, 1))
+ {
+ /* We treat "{n}" as "{n,n}". */
+ end = ((token->type == OP_CLOSE_DUP_NUM) ? start
+ : ((token->type == CHARACTER && token->opr.c == ',')
+ ? fetch_number (regexp, token, syntax) : -2));
+ }
+ if (BE (start == -2 || end == -2, 0))
+ {
+ /* Invalid sequence. */
+ if (BE (!(syntax & RE_INVALID_INTERVAL_ORD), 0))
+ {
+ if (token->type == END_OF_RE)
+ *err = REG_EBRACE;
+ else
+ *err = REG_BADBR;
+
+ return NULL;
+ }
+
+ /* If the syntax bit is set, rollback. */
+ re_string_set_index (regexp, start_idx);
+ *token = start_token;
+ token->type = CHARACTER;
+ /* mb_partial and word_char bits should be already initialized by
+ peek_token. */
+ return elem;
+ }
+
+ if (BE ((end != -1 && start > end) || token->type != OP_CLOSE_DUP_NUM, 0))
+ {
+ /* First number greater than second. */
+ *err = REG_BADBR;
+ return NULL;
+ }
+ }
+ else
+ {
+ start = (token->type == OP_DUP_PLUS) ? 1 : 0;
+ end = (token->type == OP_DUP_QUESTION) ? 1 : -1;
+ }
+
+ fetch_token (token, regexp, syntax);
+
+ if (BE (elem == NULL, 0))
+ return NULL;
+ if (BE (start == 0 && end == 0, 0))
+ {
+ postorder (elem, free_tree, NULL);
+ return NULL;
+ }
+
+ /* Extract "<re>{n,m}" to "<re><re>...<re><re>{0,<m-n>}". */
+ if (BE (start > 0, 0))
+ {
+ tree = elem;
+ for (i = 2; i <= start; ++i)
+ {
+ elem = duplicate_tree (elem, dfa);
+ tree = create_tree (dfa, tree, elem, CONCAT);
+ if (BE (elem == NULL || tree == NULL, 0))
+ goto parse_dup_op_espace;
+ }
+
+ if (start == end)
+ return tree;
+
+ /* Duplicate ELEM before it is marked optional. */
+ elem = duplicate_tree (elem, dfa);
+ old_tree = tree;
+ }
+ else
+ old_tree = NULL;
+
+ if (elem->token.type == SUBEXP)
+ postorder (elem, mark_opt_subexp, (void *) (long) elem->token.opr.idx);
+
+ tree = create_tree (dfa, elem, NULL, (end == -1 ? OP_DUP_ASTERISK : OP_ALT));
+ if (BE (tree == NULL, 0))
+ goto parse_dup_op_espace;
+
+ /* This loop is actually executed only when end != -1,
+ to rewrite <re>{0,n} as (<re>(<re>...<re>?)?)?... We have
+ already created the start+1-th copy. */
+ for (i = start + 2; i <= end; ++i)
+ {
+ elem = duplicate_tree (elem, dfa);
+ tree = create_tree (dfa, tree, elem, CONCAT);
+ if (BE (elem == NULL || tree == NULL, 0))
+ goto parse_dup_op_espace;
+
+ tree = create_tree (dfa, tree, NULL, OP_ALT);
+ if (BE (tree == NULL, 0))
+ goto parse_dup_op_espace;
+ }
+
+ if (old_tree)
+ tree = create_tree (dfa, old_tree, tree, CONCAT);
+
+ return tree;
+
+ parse_dup_op_espace:
+ *err = REG_ESPACE;
+ return NULL;
+}
+
+/* Size of the names for collating symbol/equivalence_class/character_class.
+ I'm not sure, but maybe enough. */
+#define BRACKET_NAME_BUF_SIZE 32
+
+#ifndef _LIBC
+ /* Local function for parse_bracket_exp only used in case of NOT _LIBC.
+ Build the range expression which starts from START_ELEM, and ends
+ at END_ELEM. The result are written to MBCSET and SBCSET.
+ RANGE_ALLOC is the allocated size of mbcset->range_starts, and
+ mbcset->range_ends, is a pointer argument sinse we may
+ update it. */
+
+static reg_errcode_t
+internal_function
+# ifdef RE_ENABLE_I18N
+build_range_exp (bitset_t sbcset, re_charset_t *mbcset, int *range_alloc,
+ bracket_elem_t *start_elem, bracket_elem_t *end_elem)
+# else /* not RE_ENABLE_I18N */
+build_range_exp (bitset_t sbcset, bracket_elem_t *start_elem,
+ bracket_elem_t *end_elem)
+# endif /* not RE_ENABLE_I18N */
+{
+ unsigned int start_ch, end_ch;
+ /* Equivalence Classes and Character Classes can't be a range start/end. */
+ if (BE (start_elem->type == EQUIV_CLASS || start_elem->type == CHAR_CLASS
+ || end_elem->type == EQUIV_CLASS || end_elem->type == CHAR_CLASS,
+ 0))
+ return REG_ERANGE;
+
+ /* We can handle no multi character collating elements without libc
+ support. */
+ if (BE ((start_elem->type == COLL_SYM
+ && strlen ((char *) start_elem->opr.name) > 1)
+ || (end_elem->type == COLL_SYM
+ && strlen ((char *) end_elem->opr.name) > 1), 0))
+ return REG_ECOLLATE;
+
+# ifdef RE_ENABLE_I18N
+ {
+ wchar_t wc;
+ wint_t start_wc;
+ wint_t end_wc;
+ wchar_t cmp_buf[6] = {L'\0', L'\0', L'\0', L'\0', L'\0', L'\0'};
+
+ start_ch = ((start_elem->type == SB_CHAR) ? start_elem->opr.ch
+ : ((start_elem->type == COLL_SYM) ? start_elem->opr.name[0]
+ : 0));
+ end_ch = ((end_elem->type == SB_CHAR) ? end_elem->opr.ch
+ : ((end_elem->type == COLL_SYM) ? end_elem->opr.name[0]
+ : 0));
+#ifdef GAWK
+ /*
+ * Fedora Core 2, maybe others, have broken `btowc' that returns -1
+ * for any value > 127. Sigh. Note that `start_ch' and `end_ch' are
+ * unsigned, so we don't have sign extension problems.
+ */
+ start_wc = ((start_elem->type == SB_CHAR || start_elem->type == COLL_SYM)
+ ? start_ch : start_elem->opr.wch);
+ end_wc = ((end_elem->type == SB_CHAR || end_elem->type == COLL_SYM)
+ ? end_ch : end_elem->opr.wch);
+#else
+ start_wc = ((start_elem->type == SB_CHAR || start_elem->type == COLL_SYM)
+ ? __btowc (start_ch) : start_elem->opr.wch);
+ end_wc = ((end_elem->type == SB_CHAR || end_elem->type == COLL_SYM)
+ ? __btowc (end_ch) : end_elem->opr.wch);
+#endif
+ if (start_wc == WEOF || end_wc == WEOF)
+ return REG_ECOLLATE;
+ cmp_buf[0] = start_wc;
+ cmp_buf[4] = end_wc;
+ if (wcscoll (cmp_buf, cmp_buf + 4) > 0)
+ return REG_ERANGE;
+
+ /* Got valid collation sequence values, add them as a new entry.
+ However, for !_LIBC we have no collation elements: if the
+ character set is single byte, the single byte character set
+ that we build below suffices. parse_bracket_exp passes
+ no MBCSET if dfa->mb_cur_max == 1. */
+ if (mbcset)
+ {
+ /* Check the space of the arrays. */
+ if (BE (*range_alloc == mbcset->nranges, 0))
+ {
+ /* There is not enough space, need realloc. */
+ wchar_t *new_array_start, *new_array_end;
+ int new_nranges;
+
+ /* +1 in case of mbcset->nranges is 0. */
+ new_nranges = 2 * mbcset->nranges + 1;
+ /* Use realloc since mbcset->range_starts and mbcset->range_ends
+ are NULL if *range_alloc == 0. */
+ new_array_start = re_realloc (mbcset->range_starts, wchar_t,
+ new_nranges);
+ new_array_end = re_realloc (mbcset->range_ends, wchar_t,
+ new_nranges);
+
+ if (BE (new_array_start == NULL || new_array_end == NULL, 0))
+ return REG_ESPACE;
+
+ mbcset->range_starts = new_array_start;
+ mbcset->range_ends = new_array_end;
+ *range_alloc = new_nranges;
+ }
+
+ mbcset->range_starts[mbcset->nranges] = start_wc;
+ mbcset->range_ends[mbcset->nranges++] = end_wc;
+ }
+
+ /* Build the table for single byte characters. */
+ for (wc = 0; wc < SBC_MAX; ++wc)
+ {
+ cmp_buf[2] = wc;
+ if (wcscoll (cmp_buf, cmp_buf + 2) <= 0
+ && wcscoll (cmp_buf + 2, cmp_buf + 4) <= 0)
+ bitset_set (sbcset, wc);
+ }
+ }
+# else /* not RE_ENABLE_I18N */
+ {
+ unsigned int ch;
+ start_ch = ((start_elem->type == SB_CHAR ) ? start_elem->opr.ch
+ : ((start_elem->type == COLL_SYM) ? start_elem->opr.name[0]
+ : 0));
+ end_ch = ((end_elem->type == SB_CHAR ) ? end_elem->opr.ch
+ : ((end_elem->type == COLL_SYM) ? end_elem->opr.name[0]
+ : 0));
+ if (start_ch > end_ch)
+ return REG_ERANGE;
+ /* Build the table for single byte characters. */
+ for (ch = 0; ch < SBC_MAX; ++ch)
+ if (start_ch <= ch && ch <= end_ch)
+ bitset_set (sbcset, ch);
+ }
+# endif /* not RE_ENABLE_I18N */
+ return REG_NOERROR;
+}
+#endif /* not _LIBC */
+
+#ifndef _LIBC
+/* Helper function for parse_bracket_exp only used in case of NOT _LIBC..
+ Build the collating element which is represented by NAME.
+ The result are written to MBCSET and SBCSET.
+ COLL_SYM_ALLOC is the allocated size of mbcset->coll_sym, is a
+ pointer argument since we may update it. */
+
+static reg_errcode_t
+internal_function
+# ifdef RE_ENABLE_I18N
+build_collating_symbol (bitset_t sbcset, re_charset_t *mbcset,
+ int *coll_sym_alloc, const unsigned char *name)
+# else /* not RE_ENABLE_I18N */
+build_collating_symbol (bitset_t sbcset, const unsigned char *name)
+# endif /* not RE_ENABLE_I18N */
+{
+ size_t name_len = strlen ((const char *) name);
+ if (BE (name_len != 1, 0))
+ return REG_ECOLLATE;
+ else
+ {
+ bitset_set (sbcset, name[0]);
+ return REG_NOERROR;
+ }
+}
+#endif /* not _LIBC */
+
+/* This function parse bracket expression like "[abc]", "[a-c]",
+ "[[.a-a.]]" etc. */
+
+static bin_tree_t *
+parse_bracket_exp (re_string_t *regexp, re_dfa_t *dfa, re_token_t *token,
+ reg_syntax_t syntax, reg_errcode_t *err)
+{
+#ifdef _LIBC
+ const unsigned char *collseqmb;
+ const char *collseqwc;
+ uint32_t nrules;
+ int32_t table_size;
+ const int32_t *symb_table;
+ const unsigned char *extra;
+
+ /* Local function for parse_bracket_exp used in _LIBC environement.
+ Seek the collating symbol entry correspondings to NAME.
+ Return the index of the symbol in the SYMB_TABLE. */
+
+ auto inline int32_t
+ __attribute ((always_inline))
+ seek_collating_symbol_entry (name, name_len)
+ const unsigned char *name;
+ size_t name_len;
+ {
+ int32_t hash = elem_hash ((const char *) name, name_len);
+ int32_t elem = hash % table_size;
+ if (symb_table[2 * elem] != 0)
+ {
+ int32_t second = hash % (table_size - 2) + 1;
+
+ do
+ {
+ /* First compare the hashing value. */
+ if (symb_table[2 * elem] == hash
+ /* Compare the length of the name. */
+ && name_len == extra[symb_table[2 * elem + 1]]
+ /* Compare the name. */
+ && memcmp (name, &extra[symb_table[2 * elem + 1] + 1],
+ name_len) == 0)
+ {
+ /* Yep, this is the entry. */
+ break;
+ }
+
+ /* Next entry. */
+ elem += second;
+ }
+ while (symb_table[2 * elem] != 0);
+ }
+ return elem;
+ }
+
+ /* Local function for parse_bracket_exp used in _LIBC environment.
+ Look up the collation sequence value of BR_ELEM.
+ Return the value if succeeded, UINT_MAX otherwise. */
+
+ auto inline unsigned int
+ __attribute ((always_inline))
+ lookup_collation_sequence_value (br_elem)
+ bracket_elem_t *br_elem;
+ {
+ if (br_elem->type == SB_CHAR)
+ {
+ /*
+ if (MB_CUR_MAX == 1)
+ */
+ if (nrules == 0)
+ return collseqmb[br_elem->opr.ch];
+ else
+ {
+ wint_t wc = __btowc (br_elem->opr.ch);
+ return __collseq_table_lookup (collseqwc, wc);
+ }
+ }
+ else if (br_elem->type == MB_CHAR)
+ {
+ if (nrules != 0)
+ return __collseq_table_lookup (collseqwc, br_elem->opr.wch);
+ }
+ else if (br_elem->type == COLL_SYM)
+ {
+ size_t sym_name_len = strlen ((char *) br_elem->opr.name);
+ if (nrules != 0)
+ {
+ int32_t elem, idx;
+ elem = seek_collating_symbol_entry (br_elem->opr.name,
+ sym_name_len);
+ if (symb_table[2 * elem] != 0)
+ {
+ /* We found the entry. */
+ idx = symb_table[2 * elem + 1];
+ /* Skip the name of collating element name. */
+ idx += 1 + extra[idx];
+ /* Skip the byte sequence of the collating element. */
+ idx += 1 + extra[idx];
+ /* Adjust for the alignment. */
+ idx = (idx + 3) & ~3;
+ /* Skip the multibyte collation sequence value. */
+ idx += sizeof (unsigned int);
+ /* Skip the wide char sequence of the collating element. */
+ idx += sizeof (unsigned int) *
+ (1 + *(unsigned int *) (extra + idx));
+ /* Return the collation sequence value. */
+ return *(unsigned int *) (extra + idx);
+ }
+ else if (symb_table[2 * elem] == 0 && sym_name_len == 1)
+ {
+ /* No valid character. Match it as a single byte
+ character. */
+ return collseqmb[br_elem->opr.name[0]];
+ }
+ }
+ else if (sym_name_len == 1)
+ return collseqmb[br_elem->opr.name[0]];
+ }
+ return UINT_MAX;
+ }
+
+ /* Local function for parse_bracket_exp used in _LIBC environement.
+ Build the range expression which starts from START_ELEM, and ends
+ at END_ELEM. The result are written to MBCSET and SBCSET.
+ RANGE_ALLOC is the allocated size of mbcset->range_starts, and
+ mbcset->range_ends, is a pointer argument sinse we may
+ update it. */
+
+ auto inline reg_errcode_t
+ __attribute ((always_inline))
+ build_range_exp (sbcset, mbcset, range_alloc, start_elem, end_elem)
+ re_charset_t *mbcset;
+ int *range_alloc;
+ bitset_t sbcset;
+ bracket_elem_t *start_elem, *end_elem;
+ {
+ unsigned int ch;
+ uint32_t start_collseq;
+ uint32_t end_collseq;
+
+ /* Equivalence Classes and Character Classes can't be a range
+ start/end. */
+ if (BE (start_elem->type == EQUIV_CLASS || start_elem->type == CHAR_CLASS
+ || end_elem->type == EQUIV_CLASS || end_elem->type == CHAR_CLASS,
+ 0))
+ return REG_ERANGE;
+
+ start_collseq = lookup_collation_sequence_value (start_elem);
+ end_collseq = lookup_collation_sequence_value (end_elem);
+ /* Check start/end collation sequence values. */
+ if (BE (start_collseq == UINT_MAX || end_collseq == UINT_MAX, 0))
+ return REG_ECOLLATE;
+ if (BE ((syntax & RE_NO_EMPTY_RANGES) && start_collseq > end_collseq, 0))
+ return REG_ERANGE;
+
+ /* Got valid collation sequence values, add them as a new entry.
+ However, if we have no collation elements, and the character set
+ is single byte, the single byte character set that we
+ build below suffices. */
+ if (nrules > 0 || dfa->mb_cur_max > 1)
+ {
+ /* Check the space of the arrays. */
+ if (BE (*range_alloc == mbcset->nranges, 0))
+ {
+ /* There is not enough space, need realloc. */
+ uint32_t *new_array_start;
+ uint32_t *new_array_end;
+ int new_nranges;
+
+ /* +1 in case of mbcset->nranges is 0. */
+ new_nranges = 2 * mbcset->nranges + 1;
+ new_array_start = re_realloc (mbcset->range_starts, uint32_t,
+ new_nranges);
+ new_array_end = re_realloc (mbcset->range_ends, uint32_t,
+ new_nranges);
+
+ if (BE (new_array_start == NULL || new_array_end == NULL, 0))
+ return REG_ESPACE;
+
+ mbcset->range_starts = new_array_start;
+ mbcset->range_ends = new_array_end;
+ *range_alloc = new_nranges;
+ }
+
+ mbcset->range_starts[mbcset->nranges] = start_collseq;
+ mbcset->range_ends[mbcset->nranges++] = end_collseq;
+ }
+
+ /* Build the table for single byte characters. */
+ for (ch = 0; ch < SBC_MAX; ch++)
+ {
+ uint32_t ch_collseq;
+ /*
+ if (MB_CUR_MAX == 1)
+ */
+ if (nrules == 0)
+ ch_collseq = collseqmb[ch];
+ else
+ ch_collseq = __collseq_table_lookup (collseqwc, __btowc (ch));
+ if (start_collseq <= ch_collseq && ch_collseq <= end_collseq)
+ bitset_set (sbcset, ch);
+ }
+ return REG_NOERROR;
+ }
+
+ /* Local function for parse_bracket_exp used in _LIBC environement.
+ Build the collating element which is represented by NAME.
+ The result are written to MBCSET and SBCSET.
+ COLL_SYM_ALLOC is the allocated size of mbcset->coll_sym, is a
+ pointer argument sinse we may update it. */
+
+ auto inline reg_errcode_t
+ __attribute ((always_inline))
+ build_collating_symbol (sbcset, mbcset, coll_sym_alloc, name)
+ re_charset_t *mbcset;
+ int *coll_sym_alloc;
+ bitset_t sbcset;
+ const unsigned char *name;
+ {
+ int32_t elem, idx;
+ size_t name_len = strlen ((const char *) name);
+ if (nrules != 0)
+ {
+ elem = seek_collating_symbol_entry (name, name_len);
+ if (symb_table[2 * elem] != 0)
+ {
+ /* We found the entry. */
+ idx = symb_table[2 * elem + 1];
+ /* Skip the name of collating element name. */
+ idx += 1 + extra[idx];
+ }
+ else if (symb_table[2 * elem] == 0 && name_len == 1)
+ {
+ /* No valid character, treat it as a normal
+ character. */
+ bitset_set (sbcset, name[0]);
+ return REG_NOERROR;
+ }
+ else
+ return REG_ECOLLATE;
+
+ /* Got valid collation sequence, add it as a new entry. */
+ /* Check the space of the arrays. */
+ if (BE (*coll_sym_alloc == mbcset->ncoll_syms, 0))
+ {
+ /* Not enough, realloc it. */
+ /* +1 in case of mbcset->ncoll_syms is 0. */
+ int new_coll_sym_alloc = 2 * mbcset->ncoll_syms + 1;
+ /* Use realloc since mbcset->coll_syms is NULL
+ if *alloc == 0. */
+ int32_t *new_coll_syms = re_realloc (mbcset->coll_syms, int32_t,
+ new_coll_sym_alloc);
+ if (BE (new_coll_syms == NULL, 0))
+ return REG_ESPACE;
+ mbcset->coll_syms = new_coll_syms;
+ *coll_sym_alloc = new_coll_sym_alloc;
+ }
+ mbcset->coll_syms[mbcset->ncoll_syms++] = idx;
+ return REG_NOERROR;
+ }
+ else
+ {
+ if (BE (name_len != 1, 0))
+ return REG_ECOLLATE;
+ else
+ {
+ bitset_set (sbcset, name[0]);
+ return REG_NOERROR;
+ }
+ }
+ }
+#endif
+
+ re_token_t br_token;
+ re_bitset_ptr_t sbcset;
+#ifdef RE_ENABLE_I18N
+ re_charset_t *mbcset;
+ int coll_sym_alloc = 0, range_alloc = 0, mbchar_alloc = 0;
+ int equiv_class_alloc = 0, char_class_alloc = 0;
+#endif /* not RE_ENABLE_I18N */
+ int non_match = 0;
+ bin_tree_t *work_tree;
+ int token_len;
+ int first_round = 1;
+#ifdef _LIBC
+ collseqmb = (const unsigned char *)
+ _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQMB);
+ nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES);
+ if (nrules)
+ {
+ /*
+ if (MB_CUR_MAX > 1)
+ */
+ collseqwc = _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQWC);
+ table_size = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_SYMB_HASH_SIZEMB);
+ symb_table = (const int32_t *) _NL_CURRENT (LC_COLLATE,
+ _NL_COLLATE_SYMB_TABLEMB);
+ extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE,
+ _NL_COLLATE_SYMB_EXTRAMB);
+ }
+#endif
+ sbcset = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1);
+#ifdef RE_ENABLE_I18N
+ mbcset = (re_charset_t *) calloc (sizeof (re_charset_t), 1);
+#endif /* RE_ENABLE_I18N */
+#ifdef RE_ENABLE_I18N
+ if (BE (sbcset == NULL || mbcset == NULL, 0))
+#else
+ if (BE (sbcset == NULL, 0))
+#endif /* RE_ENABLE_I18N */
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+
+ token_len = peek_token_bracket (token, regexp, syntax);
+ if (BE (token->type == END_OF_RE, 0))
+ {
+ *err = REG_BADPAT;
+ goto parse_bracket_exp_free_return;
+ }
+ if (token->type == OP_NON_MATCH_LIST)
+ {
+#ifdef RE_ENABLE_I18N
+ mbcset->non_match = 1;
+#endif /* not RE_ENABLE_I18N */
+ non_match = 1;
+ if (syntax & RE_HAT_LISTS_NOT_NEWLINE)
+ bitset_set (sbcset, '\n');
+ re_string_skip_bytes (regexp, token_len); /* Skip a token. */
+ token_len = peek_token_bracket (token, regexp, syntax);
+ if (BE (token->type == END_OF_RE, 0))
+ {
+ *err = REG_BADPAT;
+ goto parse_bracket_exp_free_return;
+ }
+ }
+
+ /* We treat the first ']' as a normal character. */
+ if (token->type == OP_CLOSE_BRACKET)
+ token->type = CHARACTER;
+
+ while (1)
+ {
+ bracket_elem_t start_elem, end_elem;
+ unsigned char start_name_buf[BRACKET_NAME_BUF_SIZE];
+ unsigned char end_name_buf[BRACKET_NAME_BUF_SIZE];
+ reg_errcode_t ret;
+ int token_len2 = 0, is_range_exp = 0;
+ re_token_t token2;
+
+ start_elem.opr.name = start_name_buf;
+ ret = parse_bracket_element (&start_elem, regexp, token, token_len, dfa,
+ syntax, first_round);
+ if (BE (ret != REG_NOERROR, 0))
+ {
+ *err = ret;
+ goto parse_bracket_exp_free_return;
+ }
+ first_round = 0;
+
+ /* Get information about the next token. We need it in any case. */
+ token_len = peek_token_bracket (token, regexp, syntax);
+
+ /* Do not check for ranges if we know they are not allowed. */
+ if (start_elem.type != CHAR_CLASS && start_elem.type != EQUIV_CLASS)
+ {
+ if (BE (token->type == END_OF_RE, 0))
+ {
+ *err = REG_EBRACK;
+ goto parse_bracket_exp_free_return;
+ }
+ if (token->type == OP_CHARSET_RANGE)
+ {
+ re_string_skip_bytes (regexp, token_len); /* Skip '-'. */
+ token_len2 = peek_token_bracket (&token2, regexp, syntax);
+ if (BE (token2.type == END_OF_RE, 0))
+ {
+ *err = REG_EBRACK;
+ goto parse_bracket_exp_free_return;
+ }
+ if (token2.type == OP_CLOSE_BRACKET)
+ {
+ /* We treat the last '-' as a normal character. */
+ re_string_skip_bytes (regexp, -token_len);
+ token->type = CHARACTER;
+ }
+ else
+ is_range_exp = 1;
+ }
+ }
+
+ if (is_range_exp == 1)
+ {
+ end_elem.opr.name = end_name_buf;
+ ret = parse_bracket_element (&end_elem, regexp, &token2, token_len2,
+ dfa, syntax, 1);
+ if (BE (ret != REG_NOERROR, 0))
+ {
+ *err = ret;
+ goto parse_bracket_exp_free_return;
+ }
+
+ token_len = peek_token_bracket (token, regexp, syntax);
+
+#ifdef _LIBC
+ *err = build_range_exp (sbcset, mbcset, &range_alloc,
+ &start_elem, &end_elem);
+#else
+# ifdef RE_ENABLE_I18N
+ *err = build_range_exp (sbcset,
+ dfa->mb_cur_max > 1 ? mbcset : NULL,
+ &range_alloc, &start_elem, &end_elem);
+# else
+ *err = build_range_exp (sbcset, &start_elem, &end_elem);
+# endif
+#endif /* RE_ENABLE_I18N */
+ if (BE (*err != REG_NOERROR, 0))
+ goto parse_bracket_exp_free_return;
+ }
+ else
+ {
+ switch (start_elem.type)
+ {
+ case SB_CHAR:
+ bitset_set (sbcset, start_elem.opr.ch);
+ break;
+#ifdef RE_ENABLE_I18N
+ case MB_CHAR:
+ /* Check whether the array has enough space. */
+ if (BE (mbchar_alloc == mbcset->nmbchars, 0))
+ {
+ wchar_t *new_mbchars;
+ /* Not enough, realloc it. */
+ /* +1 in case of mbcset->nmbchars is 0. */
+ mbchar_alloc = 2 * mbcset->nmbchars + 1;
+ /* Use realloc since array is NULL if *alloc == 0. */
+ new_mbchars = re_realloc (mbcset->mbchars, wchar_t,
+ mbchar_alloc);
+ if (BE (new_mbchars == NULL, 0))
+ goto parse_bracket_exp_espace;
+ mbcset->mbchars = new_mbchars;
+ }
+ mbcset->mbchars[mbcset->nmbchars++] = start_elem.opr.wch;
+ break;
+#endif /* RE_ENABLE_I18N */
+ case EQUIV_CLASS:
+ *err = build_equiv_class (sbcset,
+#ifdef RE_ENABLE_I18N
+ mbcset, &equiv_class_alloc,
+#endif /* RE_ENABLE_I18N */
+ start_elem.opr.name);
+ if (BE (*err != REG_NOERROR, 0))
+ goto parse_bracket_exp_free_return;
+ break;
+ case COLL_SYM:
+ *err = build_collating_symbol (sbcset,
+#ifdef RE_ENABLE_I18N
+ mbcset, &coll_sym_alloc,
+#endif /* RE_ENABLE_I18N */
+ start_elem.opr.name);
+ if (BE (*err != REG_NOERROR, 0))
+ goto parse_bracket_exp_free_return;
+ break;
+ case CHAR_CLASS:
+ *err = build_charclass (regexp->trans, sbcset,
+#ifdef RE_ENABLE_I18N
+ mbcset, &char_class_alloc,
+#endif /* RE_ENABLE_I18N */
+ (const char *) start_elem.opr.name, syntax);
+ if (BE (*err != REG_NOERROR, 0))
+ goto parse_bracket_exp_free_return;
+ break;
+ default:
+ assert (0);
+ break;
+ }
+ }
+ if (BE (token->type == END_OF_RE, 0))
+ {
+ *err = REG_EBRACK;
+ goto parse_bracket_exp_free_return;
+ }
+ if (token->type == OP_CLOSE_BRACKET)
+ break;
+ }
+
+ re_string_skip_bytes (regexp, token_len); /* Skip a token. */
+
+ /* If it is non-matching list. */
+ if (non_match)
+ bitset_not (sbcset);
+
+#ifdef RE_ENABLE_I18N
+ /* Ensure only single byte characters are set. */
+ if (dfa->mb_cur_max > 1)
+ bitset_mask (sbcset, dfa->sb_char);
+
+ if (mbcset->nmbchars || mbcset->ncoll_syms || mbcset->nequiv_classes
+ || mbcset->nranges || (dfa->mb_cur_max > 1 && (mbcset->nchar_classes
+ || mbcset->non_match)))
+ {
+ bin_tree_t *mbc_tree;
+ int sbc_idx;
+ /* Build a tree for complex bracket. */
+ dfa->has_mb_node = 1;
+ br_token.type = COMPLEX_BRACKET;
+ br_token.opr.mbcset = mbcset;
+ mbc_tree = create_token_tree (dfa, NULL, NULL, &br_token);
+ if (BE (mbc_tree == NULL, 0))
+ goto parse_bracket_exp_espace;
+ for (sbc_idx = 0; sbc_idx < BITSET_WORDS; ++sbc_idx)
+ if (sbcset[sbc_idx])
+ break;
+ /* If there are no bits set in sbcset, there is no point
+ of having both SIMPLE_BRACKET and COMPLEX_BRACKET. */
+ if (sbc_idx < BITSET_WORDS)
+ {
+ /* Build a tree for simple bracket. */
+ br_token.type = SIMPLE_BRACKET;
+ br_token.opr.sbcset = sbcset;
+ work_tree = create_token_tree (dfa, NULL, NULL, &br_token);
+ if (BE (work_tree == NULL, 0))
+ goto parse_bracket_exp_espace;
+
+ /* Then join them by ALT node. */
+ work_tree = create_tree (dfa, work_tree, mbc_tree, OP_ALT);
+ if (BE (work_tree == NULL, 0))
+ goto parse_bracket_exp_espace;
+ }
+ else
+ {
+ re_free (sbcset);
+ work_tree = mbc_tree;
+ }
+ }
+ else
+#endif /* not RE_ENABLE_I18N */
+ {
+#ifdef RE_ENABLE_I18N
+ free_charset (mbcset);
+#endif
+ /* Build a tree for simple bracket. */
+ br_token.type = SIMPLE_BRACKET;
+ br_token.opr.sbcset = sbcset;
+ work_tree = create_token_tree (dfa, NULL, NULL, &br_token);
+ if (BE (work_tree == NULL, 0))
+ goto parse_bracket_exp_espace;
+ }
+ return work_tree;
+
+ parse_bracket_exp_espace:
+ *err = REG_ESPACE;
+ parse_bracket_exp_free_return:
+ re_free (sbcset);
+#ifdef RE_ENABLE_I18N
+ free_charset (mbcset);
+#endif /* RE_ENABLE_I18N */
+ return NULL;
+}
+
+/* Parse an element in the bracket expression. */
+
+static reg_errcode_t
+parse_bracket_element (bracket_elem_t *elem, re_string_t *regexp,
+ re_token_t *token, int token_len, UNUSED re_dfa_t *dfa,
+ reg_syntax_t syntax, int accept_hyphen)
+{
+#ifdef RE_ENABLE_I18N
+ int cur_char_size;
+ cur_char_size = re_string_char_size_at (regexp, re_string_cur_idx (regexp));
+ if (cur_char_size > 1)
+ {
+ elem->type = MB_CHAR;
+ elem->opr.wch = re_string_wchar_at (regexp, re_string_cur_idx (regexp));
+ re_string_skip_bytes (regexp, cur_char_size);
+ return REG_NOERROR;
+ }
+#endif /* RE_ENABLE_I18N */
+ re_string_skip_bytes (regexp, token_len); /* Skip a token. */
+ if (token->type == OP_OPEN_COLL_ELEM || token->type == OP_OPEN_CHAR_CLASS
+ || token->type == OP_OPEN_EQUIV_CLASS)
+ return parse_bracket_symbol (elem, regexp, token);
+ if (BE (token->type == OP_CHARSET_RANGE, 0) && !accept_hyphen)
+ {
+ /* A '-' must only appear as anything but a range indicator before
+ the closing bracket. Everything else is an error. */
+ re_token_t token2;
+ (void) peek_token_bracket (&token2, regexp, syntax);
+ if (token2.type != OP_CLOSE_BRACKET)
+ /* The actual error value is not standardized since this whole
+ case is undefined. But ERANGE makes good sense. */
+ return REG_ERANGE;
+ }
+ elem->type = SB_CHAR;
+ elem->opr.ch = token->opr.c;
+ return REG_NOERROR;
+}
+
+/* Parse a bracket symbol in the bracket expression. Bracket symbols are
+ such as [:<character_class>:], [.<collating_element>.], and
+ [=<equivalent_class>=]. */
+
+static reg_errcode_t
+parse_bracket_symbol (bracket_elem_t *elem, re_string_t *regexp,
+ re_token_t *token)
+{
+ unsigned char ch, delim = token->opr.c;
+ int i = 0;
+ if (re_string_eoi(regexp))
+ return REG_EBRACK;
+ for (;; ++i)
+ {
+ if (i >= BRACKET_NAME_BUF_SIZE)
+ return REG_EBRACK;
+ if (token->type == OP_OPEN_CHAR_CLASS)
+ ch = re_string_fetch_byte_case (regexp);
+ else
+ ch = re_string_fetch_byte (regexp);
+ if (re_string_eoi(regexp))
+ return REG_EBRACK;
+ if (ch == delim && re_string_peek_byte (regexp, 0) == ']')
+ break;
+ elem->opr.name[i] = ch;
+ }
+ re_string_skip_bytes (regexp, 1);
+ elem->opr.name[i] = '\0';
+ switch (token->type)
+ {
+ case OP_OPEN_COLL_ELEM:
+ elem->type = COLL_SYM;
+ break;
+ case OP_OPEN_EQUIV_CLASS:
+ elem->type = EQUIV_CLASS;
+ break;
+ case OP_OPEN_CHAR_CLASS:
+ elem->type = CHAR_CLASS;
+ break;
+ default:
+ break;
+ }
+ return REG_NOERROR;
+}
+
+ /* Helper function for parse_bracket_exp.
+ Build the equivalence class which is represented by NAME.
+ The result are written to MBCSET and SBCSET.
+ EQUIV_CLASS_ALLOC is the allocated size of mbcset->equiv_classes,
+ is a pointer argument sinse we may update it. */
+
+static reg_errcode_t
+#ifdef RE_ENABLE_I18N
+build_equiv_class (bitset_t sbcset, re_charset_t *mbcset,
+ int *equiv_class_alloc, const unsigned char *name)
+#else /* not RE_ENABLE_I18N */
+build_equiv_class (bitset_t sbcset, const unsigned char *name)
+#endif /* not RE_ENABLE_I18N */
+{
+#ifdef _LIBC
+ uint32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES);
+ if (nrules != 0)
+ {
+ const int32_t *table, *indirect;
+ const unsigned char *weights, *extra, *cp;
+ unsigned char char_buf[2];
+ int32_t idx1, idx2;
+ unsigned int ch;
+ size_t len;
+ /* This #include defines a local function! */
+# include <locale/weight.h>
+ /* Calculate the index for equivalence class. */
+ cp = name;
+ table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB);
+ weights = (const unsigned char *) _NL_CURRENT (LC_COLLATE,
+ _NL_COLLATE_WEIGHTMB);
+ extra = (const unsigned char *) _NL_CURRENT (LC_COLLATE,
+ _NL_COLLATE_EXTRAMB);
+ indirect = (const int32_t *) _NL_CURRENT (LC_COLLATE,
+ _NL_COLLATE_INDIRECTMB);
+ idx1 = findidx (&cp);
+ if (BE (idx1 == 0 || cp < name + strlen ((const char *) name), 0))
+ /* This isn't a valid character. */
+ return REG_ECOLLATE;
+
+ /* Build single byte matcing table for this equivalence class. */
+ char_buf[1] = (unsigned char) '\0';
+ len = weights[idx1 & 0xffffff];
+ for (ch = 0; ch < SBC_MAX; ++ch)
+ {
+ char_buf[0] = ch;
+ cp = char_buf;
+ idx2 = findidx (&cp);
+/*
+ idx2 = table[ch];
+*/
+ if (idx2 == 0)
+ /* This isn't a valid character. */
+ continue;
+ /* Compare only if the length matches and the collation rule
+ index is the same. */
+ if (len == weights[idx2 & 0xffffff] && (idx1 >> 24) == (idx2 >> 24))
+ {
+ int cnt = 0;
+
+ while (cnt <= len &&
+ weights[(idx1 & 0xffffff) + 1 + cnt]
+ == weights[(idx2 & 0xffffff) + 1 + cnt])
+ ++cnt;
+
+ if (cnt > len)
+ bitset_set (sbcset, ch);
+ }
+ }
+ /* Check whether the array has enough space. */
+ if (BE (*equiv_class_alloc == mbcset->nequiv_classes, 0))
+ {
+ /* Not enough, realloc it. */
+ /* +1 in case of mbcset->nequiv_classes is 0. */
+ int new_equiv_class_alloc = 2 * mbcset->nequiv_classes + 1;
+ /* Use realloc since the array is NULL if *alloc == 0. */
+ int32_t *new_equiv_classes = re_realloc (mbcset->equiv_classes,
+ int32_t,
+ new_equiv_class_alloc);
+ if (BE (new_equiv_classes == NULL, 0))
+ return REG_ESPACE;
+ mbcset->equiv_classes = new_equiv_classes;
+ *equiv_class_alloc = new_equiv_class_alloc;
+ }
+ mbcset->equiv_classes[mbcset->nequiv_classes++] = idx1;
+ }
+ else
+#endif /* _LIBC */
+ {
+ if (BE (strlen ((const char *) name) != 1, 0))
+ return REG_ECOLLATE;
+ bitset_set (sbcset, *name);
+ }
+ return REG_NOERROR;
+}
+
+ /* Helper function for parse_bracket_exp.
+ Build the character class which is represented by NAME.
+ The result are written to MBCSET and SBCSET.
+ CHAR_CLASS_ALLOC is the allocated size of mbcset->char_classes,
+ is a pointer argument sinse we may update it. */
+
+static reg_errcode_t
+#ifdef RE_ENABLE_I18N
+build_charclass (RE_TRANSLATE_TYPE trans, bitset_t sbcset,
+ re_charset_t *mbcset, int *char_class_alloc,
+ const char *class_name, reg_syntax_t syntax)
+#else /* not RE_ENABLE_I18N */
+build_charclass (RE_TRANSLATE_TYPE trans, bitset_t sbcset,
+ const char *class_name, reg_syntax_t syntax)
+#endif /* not RE_ENABLE_I18N */
+{
+ int i;
+
+ /* In case of REG_ICASE "upper" and "lower" match the both of
+ upper and lower cases. */
+ if ((syntax & RE_ICASE)
+ && (strcmp (class_name, "upper") == 0 || strcmp (class_name, "lower") == 0))
+ class_name = "alpha";
+
+#ifdef RE_ENABLE_I18N
+ /* Check the space of the arrays. */
+ if (BE (*char_class_alloc == mbcset->nchar_classes, 0))
+ {
+ /* Not enough, realloc it. */
+ /* +1 in case of mbcset->nchar_classes is 0. */
+ int new_char_class_alloc = 2 * mbcset->nchar_classes + 1;
+ /* Use realloc since array is NULL if *alloc == 0. */
+ wctype_t *new_char_classes = re_realloc (mbcset->char_classes, wctype_t,
+ new_char_class_alloc);
+ if (BE (new_char_classes == NULL, 0))
+ return REG_ESPACE;
+ mbcset->char_classes = new_char_classes;
+ *char_class_alloc = new_char_class_alloc;
+ }
+ mbcset->char_classes[mbcset->nchar_classes++] = __wctype (class_name);
+#endif /* RE_ENABLE_I18N */
+
+#define BUILD_CHARCLASS_LOOP(ctype_func) \
+ do { \
+ if (BE (trans != NULL, 0)) \
+ { \
+ for (i = 0; i < SBC_MAX; ++i) \
+ if (ctype_func (i)) \
+ bitset_set (sbcset, trans[i]); \
+ } \
+ else \
+ { \
+ for (i = 0; i < SBC_MAX; ++i) \
+ if (ctype_func (i)) \
+ bitset_set (sbcset, i); \
+ } \
+ } while (0)
+
+ if (strcmp (class_name, "alnum") == 0)
+ BUILD_CHARCLASS_LOOP (isalnum);
+ else if (strcmp (class_name, "cntrl") == 0)
+ BUILD_CHARCLASS_LOOP (iscntrl);
+ else if (strcmp (class_name, "lower") == 0)
+ BUILD_CHARCLASS_LOOP (islower);
+ else if (strcmp (class_name, "space") == 0)
+ BUILD_CHARCLASS_LOOP (isspace);
+ else if (strcmp (class_name, "alpha") == 0)
+ BUILD_CHARCLASS_LOOP (isalpha);
+ else if (strcmp (class_name, "digit") == 0)
+ BUILD_CHARCLASS_LOOP (isdigit);
+ else if (strcmp (class_name, "print") == 0)
+ BUILD_CHARCLASS_LOOP (isprint);
+ else if (strcmp (class_name, "upper") == 0)
+ BUILD_CHARCLASS_LOOP (isupper);
+ else if (strcmp (class_name, "blank") == 0)
+#ifndef GAWK
+ BUILD_CHARCLASS_LOOP (isblank);
+#else
+ /* see comments above */
+ BUILD_CHARCLASS_LOOP (is_blank);
+#endif
+ else if (strcmp (class_name, "graph") == 0)
+ BUILD_CHARCLASS_LOOP (isgraph);
+ else if (strcmp (class_name, "punct") == 0)
+ BUILD_CHARCLASS_LOOP (ispunct);
+ else if (strcmp (class_name, "xdigit") == 0)
+ BUILD_CHARCLASS_LOOP (isxdigit);
+ else
+ return REG_ECTYPE;
+
+ return REG_NOERROR;
+}
+
+static bin_tree_t *
+build_charclass_op (re_dfa_t *dfa, RE_TRANSLATE_TYPE trans,
+ const char *class_name,
+ const char *extra, int non_match,
+ reg_errcode_t *err)
+{
+ re_bitset_ptr_t sbcset;
+#ifdef RE_ENABLE_I18N
+ re_charset_t *mbcset;
+ int alloc = 0;
+#endif /* not RE_ENABLE_I18N */
+ reg_errcode_t ret;
+ re_token_t br_token;
+ bin_tree_t *tree;
+
+ sbcset = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1);
+#ifdef RE_ENABLE_I18N
+ mbcset = (re_charset_t *) calloc (sizeof (re_charset_t), 1);
+#endif /* RE_ENABLE_I18N */
+
+#ifdef RE_ENABLE_I18N
+ if (BE (sbcset == NULL || mbcset == NULL, 0))
+#else /* not RE_ENABLE_I18N */
+ if (BE (sbcset == NULL, 0))
+#endif /* not RE_ENABLE_I18N */
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+
+ if (non_match)
+ {
+#ifdef RE_ENABLE_I18N
+ mbcset->non_match = 1;
+#endif /* not RE_ENABLE_I18N */
+ }
+
+ /* We don't care the syntax in this case. */
+ ret = build_charclass (trans, sbcset,
+#ifdef RE_ENABLE_I18N
+ mbcset, &alloc,
+#endif /* RE_ENABLE_I18N */
+ class_name, 0);
+
+ if (BE (ret != REG_NOERROR, 0))
+ {
+ re_free (sbcset);
+#ifdef RE_ENABLE_I18N
+ free_charset (mbcset);
+#endif /* RE_ENABLE_I18N */
+ *err = ret;
+ return NULL;
+ }
+ /* \w match '_' also. */
+ for (; *extra; extra++)
+ bitset_set (sbcset, *extra);
+
+ /* If it is non-matching list. */
+ if (non_match)
+ bitset_not (sbcset);
+
+#ifdef RE_ENABLE_I18N
+ /* Ensure only single byte characters are set. */
+ if (dfa->mb_cur_max > 1)
+ bitset_mask (sbcset, dfa->sb_char);
+#endif
+
+ /* Build a tree for simple bracket. */
+ br_token.type = SIMPLE_BRACKET;
+ br_token.opr.sbcset = sbcset;
+ tree = create_token_tree (dfa, NULL, NULL, &br_token);
+ if (BE (tree == NULL, 0))
+ goto build_word_op_espace;
+
+#ifdef RE_ENABLE_I18N
+ if (dfa->mb_cur_max > 1)
+ {
+ bin_tree_t *mbc_tree;
+ /* Build a tree for complex bracket. */
+ br_token.type = COMPLEX_BRACKET;
+ br_token.opr.mbcset = mbcset;
+ dfa->has_mb_node = 1;
+ mbc_tree = create_token_tree (dfa, NULL, NULL, &br_token);
+ if (BE (mbc_tree == NULL, 0))
+ goto build_word_op_espace;
+ /* Then join them by ALT node. */
+ tree = create_tree (dfa, tree, mbc_tree, OP_ALT);
+ if (BE (mbc_tree != NULL, 1))
+ return tree;
+ }
+ else
+ {
+ free_charset (mbcset);
+ return tree;
+ }
+#else /* not RE_ENABLE_I18N */
+ return tree;
+#endif /* not RE_ENABLE_I18N */
+
+ build_word_op_espace:
+ re_free (sbcset);
+#ifdef RE_ENABLE_I18N
+ free_charset (mbcset);
+#endif /* RE_ENABLE_I18N */
+ *err = REG_ESPACE;
+ return NULL;
+}
+
+/* This is intended for the expressions like "a{1,3}".
+ Fetch a number from `input', and return the number.
+ Return -1, if the number field is empty like "{,1}".
+ Return -2, If an error is occured. */
+
+static int
+fetch_number (re_string_t *input, re_token_t *token, reg_syntax_t syntax)
+{
+ int num = -1;
+ unsigned char c;
+ while (1)
+ {
+ fetch_token (token, input, syntax);
+ c = token->opr.c;
+ if (BE (token->type == END_OF_RE, 0))
+ return -2;
+ if (token->type == OP_CLOSE_DUP_NUM || c == ',')
+ break;
+ num = ((token->type != CHARACTER || c < '0' || '9' < c || num == -2)
+ ? -2 : ((num == -1) ? c - '0' : num * 10 + c - '0'));
+ num = (num > RE_DUP_MAX) ? -2 : num;
+ }
+ return num;
+}
+
+#ifdef RE_ENABLE_I18N
+static void
+free_charset (re_charset_t *cset)
+{
+ re_free (cset->mbchars);
+# ifdef _LIBC
+ re_free (cset->coll_syms);
+ re_free (cset->equiv_classes);
+ re_free (cset->range_starts);
+ re_free (cset->range_ends);
+# endif
+ re_free (cset->char_classes);
+ re_free (cset);
+}
+#endif /* RE_ENABLE_I18N */
+
+/* Functions for binary tree operation. */
+
+/* Create a tree node. */
+
+static bin_tree_t *
+create_tree (re_dfa_t *dfa, bin_tree_t *left, bin_tree_t *right,
+ re_token_type_t type)
+{
+ re_token_t t;
+ t.type = type;
+ return create_token_tree (dfa, left, right, &t);
+}
+
+static bin_tree_t *
+create_token_tree (re_dfa_t *dfa, bin_tree_t *left, bin_tree_t *right,
+ const re_token_t *token)
+{
+ bin_tree_t *tree;
+ if (BE (dfa->str_tree_storage_idx == BIN_TREE_STORAGE_SIZE, 0))
+ {
+ bin_tree_storage_t *storage = re_malloc (bin_tree_storage_t, 1);
+
+ if (storage == NULL)
+ return NULL;
+ storage->next = dfa->str_tree_storage;
+ dfa->str_tree_storage = storage;
+ dfa->str_tree_storage_idx = 0;
+ }
+ tree = &dfa->str_tree_storage->data[dfa->str_tree_storage_idx++];
+
+ tree->parent = NULL;
+ tree->left = left;
+ tree->right = right;
+ tree->token = *token;
+ tree->token.duplicated = 0;
+ tree->token.opt_subexp = 0;
+ tree->first = NULL;
+ tree->next = NULL;
+ tree->node_idx = -1;
+
+ if (left != NULL)
+ left->parent = tree;
+ if (right != NULL)
+ right->parent = tree;
+ return tree;
+}
+
+/* Mark the tree SRC as an optional subexpression.
+ To be called from preorder or postorder. */
+
+static reg_errcode_t
+mark_opt_subexp (void *extra, bin_tree_t *node)
+{
+ int idx = (int) (long) extra;
+ if (node->token.type == SUBEXP && node->token.opr.idx == idx)
+ node->token.opt_subexp = 1;
+
+ return REG_NOERROR;
+}
+
+/* Free the allocated memory inside NODE. */
+
+static void
+free_token (re_token_t *node)
+{
+#ifdef RE_ENABLE_I18N
+ if (node->type == COMPLEX_BRACKET && node->duplicated == 0)
+ free_charset (node->opr.mbcset);
+ else
+#endif /* RE_ENABLE_I18N */
+ if (node->type == SIMPLE_BRACKET && node->duplicated == 0)
+ re_free (node->opr.sbcset);
+}
+
+/* Worker function for tree walking. Free the allocated memory inside NODE
+ and its children. */
+
+static reg_errcode_t
+free_tree (UNUSED void *extra, bin_tree_t *node)
+{
+ free_token (&node->token);
+ return REG_NOERROR;
+}
+
+
+/* Duplicate the node SRC, and return new node. This is a preorder
+ visit similar to the one implemented by the generic visitor, but
+ we need more infrastructure to maintain two parallel trees --- so,
+ it's easier to duplicate. */
+
+static bin_tree_t *
+duplicate_tree (const bin_tree_t *root, re_dfa_t *dfa)
+{
+ const bin_tree_t *node;
+ bin_tree_t *dup_root;
+ bin_tree_t **p_new = &dup_root, *dup_node = root->parent;
+
+ for (node = root; ; )
+ {
+ /* Create a new tree and link it back to the current parent. */
+ *p_new = create_token_tree (dfa, NULL, NULL, &node->token);
+ if (*p_new == NULL)
+ return NULL;
+ (*p_new)->parent = dup_node;
+ (*p_new)->token.duplicated = 1;
+ dup_node = *p_new;
+
+ /* Go to the left node, or up and to the right. */
+ if (node->left)
+ {
+ node = node->left;
+ p_new = &dup_node->left;
+ }
+ else
+ {
+ const bin_tree_t *prev = NULL;
+ while (node->right == prev || node->right == NULL)
+ {
+ prev = node;
+ node = node->parent;
+ dup_node = dup_node->parent;
+ if (!node)
+ return dup_root;
+ }
+ node = node->right;
+ p_new = &dup_node->right;
+ }
+ }
+}
diff --git a/deps/regex/regex.c b/deps/regex/regex.c
new file mode 100644
index 000000000..225a001ee
--- /dev/null
+++ b/deps/regex/regex.c
@@ -0,0 +1,92 @@
+/* Extended regular expression matching and search library.
+ Copyright (C) 2002, 2003, 2005 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Isamu Hasegawa <isamu@yamato.ibm.com>.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA. */
+
+#include "config.h"
+
+/* Make sure noone compiles this code with a C++ compiler. */
+#ifdef __cplusplus
+# error "This is C code, use a C compiler"
+#endif
+
+#ifdef _LIBC
+/* We have to keep the namespace clean. */
+# define regfree(preg) __regfree (preg)
+# define regexec(pr, st, nm, pm, ef) __regexec (pr, st, nm, pm, ef)
+# define regcomp(preg, pattern, cflags) __regcomp (preg, pattern, cflags)
+# define regerror(errcode, preg, errbuf, errbuf_size) \
+ __regerror(errcode, preg, errbuf, errbuf_size)
+# define re_set_registers(bu, re, nu, st, en) \
+ __re_set_registers (bu, re, nu, st, en)
+# define re_match_2(bufp, string1, size1, string2, size2, pos, regs, stop) \
+ __re_match_2 (bufp, string1, size1, string2, size2, pos, regs, stop)
+# define re_match(bufp, string, size, pos, regs) \
+ __re_match (bufp, string, size, pos, regs)
+# define re_search(bufp, string, size, startpos, range, regs) \
+ __re_search (bufp, string, size, startpos, range, regs)
+# define re_compile_pattern(pattern, length, bufp) \
+ __re_compile_pattern (pattern, length, bufp)
+# define re_set_syntax(syntax) __re_set_syntax (syntax)
+# define re_search_2(bufp, st1, s1, st2, s2, startpos, range, regs, stop) \
+ __re_search_2 (bufp, st1, s1, st2, s2, startpos, range, regs, stop)
+# define re_compile_fastmap(bufp) __re_compile_fastmap (bufp)
+
+# include "../locale/localeinfo.h"
+#endif
+
+#if defined (_MSC_VER)
+#include <stdio.h> /* for size_t */
+#endif
+
+/* On some systems, limits.h sets RE_DUP_MAX to a lower value than
+ GNU regex allows. Include it before <regex.h>, which correctly
+ #undefs RE_DUP_MAX and sets it to the right value. */
+#include <limits.h>
+
+#ifdef GAWK
+#undef alloca
+#define alloca alloca_is_bad_you_should_never_use_it
+#endif
+#include <regex.h>
+#include "regex_internal.h"
+
+#include "regex_internal.c"
+
+#ifdef GAWK
+# define bool int
+
+# ifndef true
+# define true (1)
+# endif
+
+# ifndef false
+# define false (0)
+# endif
+#endif
+#include "regcomp.c"
+#include "regexec.c"
+
+/* Binary backward compatibility. */
+#if _LIBC
+# include <shlib-compat.h>
+# if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_3)
+link_warning (re_max_failures, "the 're_max_failures' variable is obsolete and will go away.")
+int re_max_failures = 2000;
+# endif
+#endif
diff --git a/deps/regex/regex.h b/deps/regex/regex.h
new file mode 100644
index 000000000..61c968387
--- /dev/null
+++ b/deps/regex/regex.h
@@ -0,0 +1,582 @@
+#include <stdio.h>
+#include <stddef.h>
+
+/* Definitions for data structures and routines for the regular
+ expression library.
+ Copyright (C) 1985,1989-93,1995-98,2000,2001,2002,2003,2005,2006,2008
+ Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA. */
+
+#ifndef _REGEX_H
+#define _REGEX_H 1
+
+#ifdef HAVE_STDDEF_H
+#include <stddef.h>
+#endif
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#ifndef _LIBC
+#define __USE_GNU 1
+#endif
+
+/* Allow the use in C++ code. */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The following two types have to be signed and unsigned integer type
+ wide enough to hold a value of a pointer. For most ANSI compilers
+ ptrdiff_t and size_t should be likely OK. Still size of these two
+ types is 2 for Microsoft C. Ugh... */
+typedef long int s_reg_t;
+typedef unsigned long int active_reg_t;
+
+/* The following bits are used to determine the regexp syntax we
+ recognize. The set/not-set meanings are chosen so that Emacs syntax
+ remains the value 0. The bits are given in alphabetical order, and
+ the definitions shifted by one from the previous bit; thus, when we
+ add or remove a bit, only one other definition need change. */
+typedef unsigned long int reg_syntax_t;
+
+#ifdef __USE_GNU
+/* If this bit is not set, then \ inside a bracket expression is literal.
+ If set, then such a \ quotes the following character. */
+# define RE_BACKSLASH_ESCAPE_IN_LISTS ((unsigned long int) 1)
+
+/* If this bit is not set, then + and ? are operators, and \+ and \? are
+ literals.
+ If set, then \+ and \? are operators and + and ? are literals. */
+# define RE_BK_PLUS_QM (RE_BACKSLASH_ESCAPE_IN_LISTS << 1)
+
+/* If this bit is set, then character classes are supported. They are:
+ [:alpha:], [:upper:], [:lower:], [:digit:], [:alnum:], [:xdigit:],
+ [:space:], [:print:], [:punct:], [:graph:], and [:cntrl:].
+ If not set, then character classes are not supported. */
+# define RE_CHAR_CLASSES (RE_BK_PLUS_QM << 1)
+
+/* If this bit is set, then ^ and $ are always anchors (outside bracket
+ expressions, of course).
+ If this bit is not set, then it depends:
+ ^ is an anchor if it is at the beginning of a regular
+ expression or after an open-group or an alternation operator;
+ $ is an anchor if it is at the end of a regular expression, or
+ before a close-group or an alternation operator.
+
+ This bit could be (re)combined with RE_CONTEXT_INDEP_OPS, because
+ POSIX draft 11.2 says that * etc. in leading positions is undefined.
+ We already implemented a previous draft which made those constructs
+ invalid, though, so we haven't changed the code back. */
+# define RE_CONTEXT_INDEP_ANCHORS (RE_CHAR_CLASSES << 1)
+
+/* If this bit is set, then special characters are always special
+ regardless of where they are in the pattern.
+ If this bit is not set, then special characters are special only in
+ some contexts; otherwise they are ordinary. Specifically,
+ * + ? and intervals are only special when not after the beginning,
+ open-group, or alternation operator. */
+# define RE_CONTEXT_INDEP_OPS (RE_CONTEXT_INDEP_ANCHORS << 1)
+
+/* If this bit is set, then *, +, ?, and { cannot be first in an re or
+ immediately after an alternation or begin-group operator. */
+# define RE_CONTEXT_INVALID_OPS (RE_CONTEXT_INDEP_OPS << 1)
+
+/* If this bit is set, then . matches newline.
+ If not set, then it doesn't. */
+# define RE_DOT_NEWLINE (RE_CONTEXT_INVALID_OPS << 1)
+
+/* If this bit is set, then . doesn't match NUL.
+ If not set, then it does. */
+# define RE_DOT_NOT_NULL (RE_DOT_NEWLINE << 1)
+
+/* If this bit is set, nonmatching lists [^...] do not match newline.
+ If not set, they do. */
+# define RE_HAT_LISTS_NOT_NEWLINE (RE_DOT_NOT_NULL << 1)
+
+/* If this bit is set, either \{...\} or {...} defines an
+ interval, depending on RE_NO_BK_BRACES.
+ If not set, \{, \}, {, and } are literals. */
+# define RE_INTERVALS (RE_HAT_LISTS_NOT_NEWLINE << 1)
+
+/* If this bit is set, +, ? and | aren't recognized as operators.
+ If not set, they are. */
+# define RE_LIMITED_OPS (RE_INTERVALS << 1)
+
+/* If this bit is set, newline is an alternation operator.
+ If not set, newline is literal. */
+# define RE_NEWLINE_ALT (RE_LIMITED_OPS << 1)
+
+/* If this bit is set, then `{...}' defines an interval, and \{ and \}
+ are literals.
+ If not set, then `\{...\}' defines an interval. */
+# define RE_NO_BK_BRACES (RE_NEWLINE_ALT << 1)
+
+/* If this bit is set, (...) defines a group, and \( and \) are literals.
+ If not set, \(...\) defines a group, and ( and ) are literals. */
+# define RE_NO_BK_PARENS (RE_NO_BK_BRACES << 1)
+
+/* If this bit is set, then \<digit> matches <digit>.
+ If not set, then \<digit> is a back-reference. */
+# define RE_NO_BK_REFS (RE_NO_BK_PARENS << 1)
+
+/* If this bit is set, then | is an alternation operator, and \| is literal.
+ If not set, then \| is an alternation operator, and | is literal. */
+# define RE_NO_BK_VBAR (RE_NO_BK_REFS << 1)
+
+/* If this bit is set, then an ending range point collating higher
+ than the starting range point, as in [z-a], is invalid.
+ If not set, then when ending range point collates higher than the
+ starting range point, the range is ignored. */
+# define RE_NO_EMPTY_RANGES (RE_NO_BK_VBAR << 1)
+
+/* If this bit is set, then an unmatched ) is ordinary.
+ If not set, then an unmatched ) is invalid. */
+# define RE_UNMATCHED_RIGHT_PAREN_ORD (RE_NO_EMPTY_RANGES << 1)
+
+/* If this bit is set, succeed as soon as we match the whole pattern,
+ without further backtracking. */
+# define RE_NO_POSIX_BACKTRACKING (RE_UNMATCHED_RIGHT_PAREN_ORD << 1)
+
+/* If this bit is set, do not process the GNU regex operators.
+ If not set, then the GNU regex operators are recognized. */
+# define RE_NO_GNU_OPS (RE_NO_POSIX_BACKTRACKING << 1)
+
+/* If this bit is set, a syntactically invalid interval is treated as
+ a string of ordinary characters. For example, the ERE 'a{1' is
+ treated as 'a\{1'. */
+# define RE_INVALID_INTERVAL_ORD (RE_NO_GNU_OPS << 1)
+
+/* If this bit is set, then ignore case when matching.
+ If not set, then case is significant. */
+# define RE_ICASE (RE_INVALID_INTERVAL_ORD << 1)
+
+/* This bit is used internally like RE_CONTEXT_INDEP_ANCHORS but only
+ for ^, because it is difficult to scan the regex backwards to find
+ whether ^ should be special. */
+# define RE_CARET_ANCHORS_HERE (RE_ICASE << 1)
+
+/* If this bit is set, then \{ cannot be first in an bre or
+ immediately after an alternation or begin-group operator. */
+# define RE_CONTEXT_INVALID_DUP (RE_CARET_ANCHORS_HERE << 1)
+
+/* If this bit is set, then no_sub will be set to 1 during
+ re_compile_pattern. */
+#define RE_NO_SUB (RE_CONTEXT_INVALID_DUP << 1)
+#endif
+
+/* This global variable defines the particular regexp syntax to use (for
+ some interfaces). When a regexp is compiled, the syntax used is
+ stored in the pattern buffer, so changing this does not affect
+ already-compiled regexps. */
+extern reg_syntax_t re_syntax_options;
+
+#ifdef __USE_GNU
+/* Define combinations of the above bits for the standard possibilities.
+ (The [[[ comments delimit what gets put into the Texinfo file, so
+ don't delete them!) */
+/* [[[begin syntaxes]]] */
+#define RE_SYNTAX_EMACS 0
+
+#define RE_SYNTAX_AWK \
+ (RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DOT_NOT_NULL \
+ | RE_NO_BK_PARENS | RE_NO_BK_REFS \
+ | RE_NO_BK_VBAR | RE_NO_EMPTY_RANGES \
+ | RE_DOT_NEWLINE | RE_CONTEXT_INDEP_ANCHORS \
+ | RE_UNMATCHED_RIGHT_PAREN_ORD | RE_NO_GNU_OPS)
+
+#define RE_SYNTAX_GNU_AWK \
+ ((RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS \
+ | RE_INVALID_INTERVAL_ORD) \
+ & ~(RE_DOT_NOT_NULL | RE_CONTEXT_INDEP_OPS \
+ | RE_CONTEXT_INVALID_OPS ))
+
+#define RE_SYNTAX_POSIX_AWK \
+ (RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS \
+ | RE_INTERVALS | RE_NO_GNU_OPS \
+ | RE_INVALID_INTERVAL_ORD)
+
+#define RE_SYNTAX_GREP \
+ (RE_BK_PLUS_QM | RE_CHAR_CLASSES \
+ | RE_HAT_LISTS_NOT_NEWLINE | RE_INTERVALS \
+ | RE_NEWLINE_ALT)
+
+#define RE_SYNTAX_EGREP \
+ (RE_CHAR_CLASSES | RE_CONTEXT_INDEP_ANCHORS \
+ | RE_CONTEXT_INDEP_OPS | RE_HAT_LISTS_NOT_NEWLINE \
+ | RE_NEWLINE_ALT | RE_NO_BK_PARENS \
+ | RE_NO_BK_VBAR)
+
+#define RE_SYNTAX_POSIX_EGREP \
+ (RE_SYNTAX_EGREP | RE_INTERVALS | RE_NO_BK_BRACES \
+ | RE_INVALID_INTERVAL_ORD)
+
+/* P1003.2/D11.2, section 4.20.7.1, lines 5078ff. */
+#define RE_SYNTAX_ED RE_SYNTAX_POSIX_BASIC
+
+#define RE_SYNTAX_SED RE_SYNTAX_POSIX_BASIC
+
+/* Syntax bits common to both basic and extended POSIX regex syntax. */
+#define _RE_SYNTAX_POSIX_COMMON \
+ (RE_CHAR_CLASSES | RE_DOT_NEWLINE | RE_DOT_NOT_NULL \
+ | RE_INTERVALS | RE_NO_EMPTY_RANGES)
+
+#define RE_SYNTAX_POSIX_BASIC \
+ (_RE_SYNTAX_POSIX_COMMON | RE_BK_PLUS_QM | RE_CONTEXT_INVALID_DUP)
+
+/* Differs from ..._POSIX_BASIC only in that RE_BK_PLUS_QM becomes
+ RE_LIMITED_OPS, i.e., \? \+ \| are not recognized. Actually, this
+ isn't minimal, since other operators, such as \`, aren't disabled. */
+#define RE_SYNTAX_POSIX_MINIMAL_BASIC \
+ (_RE_SYNTAX_POSIX_COMMON | RE_LIMITED_OPS)
+
+#define RE_SYNTAX_POSIX_EXTENDED \
+ (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \
+ | RE_CONTEXT_INDEP_OPS | RE_NO_BK_BRACES \
+ | RE_NO_BK_PARENS | RE_NO_BK_VBAR \
+ | RE_CONTEXT_INVALID_OPS | RE_UNMATCHED_RIGHT_PAREN_ORD)
+
+/* Differs from ..._POSIX_EXTENDED in that RE_CONTEXT_INDEP_OPS is
+ removed and RE_NO_BK_REFS is added. */
+#define RE_SYNTAX_POSIX_MINIMAL_EXTENDED \
+ (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \
+ | RE_CONTEXT_INVALID_OPS | RE_NO_BK_BRACES \
+ | RE_NO_BK_PARENS | RE_NO_BK_REFS \
+ | RE_NO_BK_VBAR | RE_UNMATCHED_RIGHT_PAREN_ORD)
+/* [[[end syntaxes]]] */
+
+/* Maximum number of duplicates an interval can allow. Some systems
+ (erroneously) define this in other header files, but we want our
+ value, so remove any previous define. */
+# ifdef RE_DUP_MAX
+# undef RE_DUP_MAX
+# endif
+/* If sizeof(int) == 2, then ((1 << 15) - 1) overflows. */
+# define RE_DUP_MAX (0x7fff)
+#endif
+
+
+/* POSIX `cflags' bits (i.e., information for `regcomp'). */
+
+/* If this bit is set, then use extended regular expression syntax.
+ If not set, then use basic regular expression syntax. */
+#define REG_EXTENDED 1
+
+/* If this bit is set, then ignore case when matching.
+ If not set, then case is significant. */
+#define REG_ICASE (REG_EXTENDED << 1)
+
+/* If this bit is set, then anchors do not match at newline
+ characters in the string.
+ If not set, then anchors do match at newlines. */
+#define REG_NEWLINE (REG_ICASE << 1)
+
+/* If this bit is set, then report only success or fail in regexec.
+ If not set, then returns differ between not matching and errors. */
+#define REG_NOSUB (REG_NEWLINE << 1)
+
+
+/* POSIX `eflags' bits (i.e., information for regexec). */
+
+/* If this bit is set, then the beginning-of-line operator doesn't match
+ the beginning of the string (presumably because it's not the
+ beginning of a line).
+ If not set, then the beginning-of-line operator does match the
+ beginning of the string. */
+#define REG_NOTBOL 1
+
+/* Like REG_NOTBOL, except for the end-of-line. */
+#define REG_NOTEOL (1 << 1)
+
+/* Use PMATCH[0] to delimit the start and end of the search in the
+ buffer. */
+#define REG_STARTEND (1 << 2)
+
+
+/* If any error codes are removed, changed, or added, update the
+ `re_error_msg' table in regex.c. */
+typedef enum
+{
+#if defined _XOPEN_SOURCE || defined __USE_XOPEN2K
+ REG_ENOSYS = -1, /* This will never happen for this implementation. */
+#endif
+
+ REG_NOERROR = 0, /* Success. */
+ REG_NOMATCH, /* Didn't find a match (for regexec). */
+
+ /* POSIX regcomp return error codes. (In the order listed in the
+ standard.) */
+ REG_BADPAT, /* Invalid pattern. */
+ REG_ECOLLATE, /* Inalid collating element. */
+ REG_ECTYPE, /* Invalid character class name. */
+ REG_EESCAPE, /* Trailing backslash. */
+ REG_ESUBREG, /* Invalid back reference. */
+ REG_EBRACK, /* Unmatched left bracket. */
+ REG_EPAREN, /* Parenthesis imbalance. */
+ REG_EBRACE, /* Unmatched \{. */
+ REG_BADBR, /* Invalid contents of \{\}. */
+ REG_ERANGE, /* Invalid range end. */
+ REG_ESPACE, /* Ran out of memory. */
+ REG_BADRPT, /* No preceding re for repetition op. */
+
+ /* Error codes we've added. */
+ REG_EEND, /* Premature end. */
+ REG_ESIZE, /* Compiled pattern bigger than 2^16 bytes. */
+ REG_ERPAREN /* Unmatched ) or \); not returned from regcomp. */
+} reg_errcode_t;
+
+/* This data structure represents a compiled pattern. Before calling
+ the pattern compiler, the fields `buffer', `allocated', `fastmap',
+ `translate', and `no_sub' can be set. After the pattern has been
+ compiled, the `re_nsub' field is available. All other fields are
+ private to the regex routines. */
+
+#ifndef RE_TRANSLATE_TYPE
+# define __RE_TRANSLATE_TYPE unsigned char *
+# ifdef __USE_GNU
+# define RE_TRANSLATE_TYPE __RE_TRANSLATE_TYPE
+# endif
+#endif
+
+#ifdef __USE_GNU
+# define __REPB_PREFIX(name) name
+#else
+# define __REPB_PREFIX(name) __##name
+#endif
+
+struct re_pattern_buffer
+{
+ /* Space that holds the compiled pattern. It is declared as
+ `unsigned char *' because its elements are sometimes used as
+ array indexes. */
+ unsigned char *__REPB_PREFIX(buffer);
+
+ /* Number of bytes to which `buffer' points. */
+ unsigned long int __REPB_PREFIX(allocated);
+
+ /* Number of bytes actually used in `buffer'. */
+ unsigned long int __REPB_PREFIX(used);
+
+ /* Syntax setting with which the pattern was compiled. */
+ reg_syntax_t __REPB_PREFIX(syntax);
+
+ /* Pointer to a fastmap, if any, otherwise zero. re_search uses the
+ fastmap, if there is one, to skip over impossible starting points
+ for matches. */
+ char *__REPB_PREFIX(fastmap);
+
+ /* Either a translate table to apply to all characters before
+ comparing them, or zero for no translation. The translation is
+ applied to a pattern when it is compiled and to a string when it
+ is matched. */
+ __RE_TRANSLATE_TYPE __REPB_PREFIX(translate);
+
+ /* Number of subexpressions found by the compiler. */
+ size_t re_nsub;
+
+ /* Zero if this pattern cannot match the empty string, one else.
+ Well, in truth it's used only in `re_search_2', to see whether or
+ not we should use the fastmap, so we don't set this absolutely
+ perfectly; see `re_compile_fastmap' (the `duplicate' case). */
+ unsigned __REPB_PREFIX(can_be_null) : 1;
+
+ /* If REGS_UNALLOCATED, allocate space in the `regs' structure
+ for `max (RE_NREGS, re_nsub + 1)' groups.
+ If REGS_REALLOCATE, reallocate space if necessary.
+ If REGS_FIXED, use what's there. */
+#ifdef __USE_GNU
+# define REGS_UNALLOCATED 0
+# define REGS_REALLOCATE 1
+# define REGS_FIXED 2
+#endif
+ unsigned __REPB_PREFIX(regs_allocated) : 2;
+
+ /* Set to zero when `regex_compile' compiles a pattern; set to one
+ by `re_compile_fastmap' if it updates the fastmap. */
+ unsigned __REPB_PREFIX(fastmap_accurate) : 1;
+
+ /* If set, `re_match_2' does not return information about
+ subexpressions. */
+ unsigned __REPB_PREFIX(no_sub) : 1;
+
+ /* If set, a beginning-of-line anchor doesn't match at the beginning
+ of the string. */
+ unsigned __REPB_PREFIX(not_bol) : 1;
+
+ /* Similarly for an end-of-line anchor. */
+ unsigned __REPB_PREFIX(not_eol) : 1;
+
+ /* If true, an anchor at a newline matches. */
+ unsigned __REPB_PREFIX(newline_anchor) : 1;
+};
+
+typedef struct re_pattern_buffer regex_t;
+
+/* Type for byte offsets within the string. POSIX mandates this. */
+typedef int regoff_t;
+
+
+#ifdef __USE_GNU
+/* This is the structure we store register match data in. See
+ regex.texinfo for a full description of what registers match. */
+struct re_registers
+{
+ unsigned num_regs;
+ regoff_t *start;
+ regoff_t *end;
+};
+
+
+/* If `regs_allocated' is REGS_UNALLOCATED in the pattern buffer,
+ `re_match_2' returns information about at least this many registers
+ the first time a `regs' structure is passed. */
+# ifndef RE_NREGS
+# define RE_NREGS 30
+# endif
+#endif
+
+
+/* POSIX specification for registers. Aside from the different names than
+ `re_registers', POSIX uses an array of structures, instead of a
+ structure of arrays. */
+typedef struct
+{
+ regoff_t rm_so; /* Byte offset from string's start to substring's start. */
+ regoff_t rm_eo; /* Byte offset from string's start to substring's end. */
+} regmatch_t;
+
+/* Declarations for routines. */
+
+#ifdef __USE_GNU
+/* Sets the current default syntax to SYNTAX, and return the old syntax.
+ You can also simply assign to the `re_syntax_options' variable. */
+extern reg_syntax_t re_set_syntax (reg_syntax_t __syntax);
+
+/* Compile the regular expression PATTERN, with length LENGTH
+ and syntax given by the global `re_syntax_options', into the buffer
+ BUFFER. Return NULL if successful, and an error string if not. */
+extern const char *re_compile_pattern (const char *__pattern, size_t __length,
+ struct re_pattern_buffer *__buffer);
+
+
+/* Compile a fastmap for the compiled pattern in BUFFER; used to
+ accelerate searches. Return 0 if successful and -2 if was an
+ internal error. */
+extern int re_compile_fastmap (struct re_pattern_buffer *__buffer);
+
+
+/* Search in the string STRING (with length LENGTH) for the pattern
+ compiled into BUFFER. Start searching at position START, for RANGE
+ characters. Return the starting position of the match, -1 for no
+ match, or -2 for an internal error. Also return register
+ information in REGS (if REGS and BUFFER->no_sub are nonzero). */
+extern int re_search (struct re_pattern_buffer *__buffer, const char *__cstring,
+ int __length, int __start, int __range,
+ struct re_registers *__regs);
+
+
+/* Like `re_search', but search in the concatenation of STRING1 and
+ STRING2. Also, stop searching at index START + STOP. */
+extern int re_search_2 (struct re_pattern_buffer *__buffer,
+ const char *__string1, int __length1,
+ const char *__string2, int __length2, int __start,
+ int __range, struct re_registers *__regs, int __stop);
+
+
+/* Like `re_search', but return how many characters in STRING the regexp
+ in BUFFER matched, starting at position START. */
+extern int re_match (struct re_pattern_buffer *__buffer, const char *__cstring,
+ int __length, int __start, struct re_registers *__regs);
+
+
+/* Relates to `re_match' as `re_search_2' relates to `re_search'. */
+extern int re_match_2 (struct re_pattern_buffer *__buffer,
+ const char *__string1, int __length1,
+ const char *__string2, int __length2, int __start,
+ struct re_registers *__regs, int __stop);
+
+
+/* Set REGS to hold NUM_REGS registers, storing them in STARTS and
+ ENDS. Subsequent matches using BUFFER and REGS will use this memory
+ for recording register information. STARTS and ENDS must be
+ allocated with malloc, and must each be at least `NUM_REGS * sizeof
+ (regoff_t)' bytes long.
+
+ If NUM_REGS == 0, then subsequent matches should allocate their own
+ register data.
+
+ Unless this function is called, the first search or match using
+ PATTERN_BUFFER will allocate its own register data, without
+ freeing the old data. */
+extern void re_set_registers (struct re_pattern_buffer *__buffer,
+ struct re_registers *__regs,
+ unsigned int __num_regs,
+ regoff_t *__starts, regoff_t *__ends);
+#endif /* Use GNU */
+
+#if defined _REGEX_RE_COMP || (defined _LIBC && defined __USE_BSD)
+# ifndef _CRAY
+/* 4.2 bsd compatibility. */
+extern char *re_comp (const char *);
+extern int re_exec (const char *);
+# endif
+#endif
+
+/* GCC 2.95 and later have "__restrict"; C99 compilers have
+ "restrict", and "configure" may have defined "restrict". */
+#ifndef __restrict
+# if ! (2 < __GNUC__ || (2 == __GNUC__ && 95 <= __GNUC_MINOR__))
+# if defined restrict || 199901L <= __STDC_VERSION__
+# define __restrict restrict
+# else
+# define __restrict
+# endif
+# endif
+#endif
+/* gcc 3.1 and up support the [restrict] syntax. */
+#ifndef __restrict_arr
+# if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) \
+ && !defined __GNUG__
+# define __restrict_arr __restrict
+# else
+# define __restrict_arr
+# endif
+#endif
+
+/* POSIX compatibility. */
+extern int regcomp (regex_t *__restrict __preg,
+ const char *__restrict __pattern,
+ int __cflags);
+
+extern int regexec (const regex_t *__restrict __preg,
+ const char *__restrict __cstring, size_t __nmatch,
+ regmatch_t __pmatch[__restrict_arr],
+ int __eflags);
+
+extern size_t regerror (int __errcode, const regex_t *__restrict __preg,
+ char *__restrict __errbuf, size_t __errbuf_size);
+
+extern void regfree (regex_t *__preg);
+
+
+#ifdef __cplusplus
+}
+#endif /* C++ */
+
+#endif /* regex.h */
diff --git a/deps/regex/regex_internal.c b/deps/regex/regex_internal.c
new file mode 100644
index 000000000..ad57c20dd
--- /dev/null
+++ b/deps/regex/regex_internal.c
@@ -0,0 +1,1744 @@
+/* Extended regular expression matching and search library.
+ Copyright (C) 2002-2006, 2010 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Isamu Hasegawa <isamu@yamato.ibm.com>.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA. */
+
+static void re_string_construct_common (const char *str, int len,
+ re_string_t *pstr,
+ RE_TRANSLATE_TYPE trans, int icase,
+ const re_dfa_t *dfa) internal_function;
+static re_dfastate_t *create_ci_newstate (const re_dfa_t *dfa,
+ const re_node_set *nodes,
+ unsigned int hash) internal_function;
+static re_dfastate_t *create_cd_newstate (const re_dfa_t *dfa,
+ const re_node_set *nodes,
+ unsigned int context,
+ unsigned int hash) internal_function;
+
+#ifdef GAWK
+#undef MAX /* safety */
+static size_t
+MAX(size_t a, size_t b)
+{
+ return (a > b ? a : b);
+}
+#endif
+
+/* Functions for string operation. */
+
+/* This function allocate the buffers. It is necessary to call
+ re_string_reconstruct before using the object. */
+
+static reg_errcode_t
+internal_function
+re_string_allocate (re_string_t *pstr, const char *str, int len, int init_len,
+ RE_TRANSLATE_TYPE trans, int icase, const re_dfa_t *dfa)
+{
+ reg_errcode_t ret;
+ int init_buf_len;
+
+ /* Ensure at least one character fits into the buffers. */
+ if (init_len < dfa->mb_cur_max)
+ init_len = dfa->mb_cur_max;
+ init_buf_len = (len + 1 < init_len) ? len + 1: init_len;
+ re_string_construct_common (str, len, pstr, trans, icase, dfa);
+
+ ret = re_string_realloc_buffers (pstr, init_buf_len);
+ if (BE (ret != REG_NOERROR, 0))
+ return ret;
+
+ pstr->word_char = dfa->word_char;
+ pstr->word_ops_used = dfa->word_ops_used;
+ pstr->mbs = pstr->mbs_allocated ? pstr->mbs : (unsigned char *) str;
+ pstr->valid_len = (pstr->mbs_allocated || dfa->mb_cur_max > 1) ? 0 : len;
+ pstr->valid_raw_len = pstr->valid_len;
+ return REG_NOERROR;
+}
+
+/* This function allocate the buffers, and initialize them. */
+
+static reg_errcode_t
+internal_function
+re_string_construct (re_string_t *pstr, const char *str, int len,
+ RE_TRANSLATE_TYPE trans, int icase, const re_dfa_t *dfa)
+{
+ reg_errcode_t ret;
+ memset (pstr, '\0', sizeof (re_string_t));
+ re_string_construct_common (str, len, pstr, trans, icase, dfa);
+
+ if (len > 0)
+ {
+ ret = re_string_realloc_buffers (pstr, len + 1);
+ if (BE (ret != REG_NOERROR, 0))
+ return ret;
+ }
+ pstr->mbs = pstr->mbs_allocated ? pstr->mbs : (unsigned char *) str;
+
+ if (icase)
+ {
+#ifdef RE_ENABLE_I18N
+ if (dfa->mb_cur_max > 1)
+ {
+ while (1)
+ {
+ ret = build_wcs_upper_buffer (pstr);
+ if (BE (ret != REG_NOERROR, 0))
+ return ret;
+ if (pstr->valid_raw_len >= len)
+ break;
+ if (pstr->bufs_len > pstr->valid_len + dfa->mb_cur_max)
+ break;
+ ret = re_string_realloc_buffers (pstr, pstr->bufs_len * 2);
+ if (BE (ret != REG_NOERROR, 0))
+ return ret;
+ }
+ }
+ else
+#endif /* RE_ENABLE_I18N */
+ build_upper_buffer (pstr);
+ }
+ else
+ {
+#ifdef RE_ENABLE_I18N
+ if (dfa->mb_cur_max > 1)
+ build_wcs_buffer (pstr);
+ else
+#endif /* RE_ENABLE_I18N */
+ {
+ if (trans != NULL)
+ re_string_translate_buffer (pstr);
+ else
+ {
+ pstr->valid_len = pstr->bufs_len;
+ pstr->valid_raw_len = pstr->bufs_len;
+ }
+ }
+ }
+
+ return REG_NOERROR;
+}
+
+/* Helper functions for re_string_allocate, and re_string_construct. */
+
+static reg_errcode_t
+internal_function
+re_string_realloc_buffers (re_string_t *pstr, int new_buf_len)
+{
+#ifdef RE_ENABLE_I18N
+ if (pstr->mb_cur_max > 1)
+ {
+ wint_t *new_wcs;
+
+ /* Avoid overflow in realloc. */
+ const size_t max_object_size = MAX (sizeof (wint_t), sizeof (int));
+ if (BE (SIZE_MAX / max_object_size < new_buf_len, 0))
+ return REG_ESPACE;
+
+ new_wcs = re_realloc (pstr->wcs, wint_t, new_buf_len);
+ if (BE (new_wcs == NULL, 0))
+ return REG_ESPACE;
+ pstr->wcs = new_wcs;
+ if (pstr->offsets != NULL)
+ {
+ int *new_offsets = re_realloc (pstr->offsets, int, new_buf_len);
+ if (BE (new_offsets == NULL, 0))
+ return REG_ESPACE;
+ pstr->offsets = new_offsets;
+ }
+ }
+#endif /* RE_ENABLE_I18N */
+ if (pstr->mbs_allocated)
+ {
+ unsigned char *new_mbs = re_realloc (pstr->mbs, unsigned char,
+ new_buf_len);
+ if (BE (new_mbs == NULL, 0))
+ return REG_ESPACE;
+ pstr->mbs = new_mbs;
+ }
+ pstr->bufs_len = new_buf_len;
+ return REG_NOERROR;
+}
+
+
+static void
+internal_function
+re_string_construct_common (const char *str, int len, re_string_t *pstr,
+ RE_TRANSLATE_TYPE trans, int icase,
+ const re_dfa_t *dfa)
+{
+ pstr->raw_mbs = (const unsigned char *) str;
+ pstr->len = len;
+ pstr->raw_len = len;
+ pstr->trans = trans;
+ pstr->icase = icase ? 1 : 0;
+ pstr->mbs_allocated = (trans != NULL || icase);
+ pstr->mb_cur_max = dfa->mb_cur_max;
+ pstr->is_utf8 = dfa->is_utf8;
+ pstr->map_notascii = dfa->map_notascii;
+ pstr->stop = pstr->len;
+ pstr->raw_stop = pstr->stop;
+}
+
+#ifdef RE_ENABLE_I18N
+
+/* Build wide character buffer PSTR->WCS.
+ If the byte sequence of the string are:
+ <mb1>(0), <mb1>(1), <mb2>(0), <mb2>(1), <sb3>
+ Then wide character buffer will be:
+ <wc1> , WEOF , <wc2> , WEOF , <wc3>
+ We use WEOF for padding, they indicate that the position isn't
+ a first byte of a multibyte character.
+
+ Note that this function assumes PSTR->VALID_LEN elements are already
+ built and starts from PSTR->VALID_LEN. */
+
+static void
+internal_function
+build_wcs_buffer (re_string_t *pstr)
+{
+#ifdef _LIBC
+ unsigned char buf[MB_LEN_MAX];
+ assert (MB_LEN_MAX >= pstr->mb_cur_max);
+#else
+ unsigned char buf[64];
+#endif
+ mbstate_t prev_st;
+ int byte_idx, end_idx, remain_len;
+ size_t mbclen;
+
+ /* Build the buffers from pstr->valid_len to either pstr->len or
+ pstr->bufs_len. */
+ end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len;
+ for (byte_idx = pstr->valid_len; byte_idx < end_idx;)
+ {
+ wchar_t wc;
+ const char *p;
+
+ remain_len = end_idx - byte_idx;
+ prev_st = pstr->cur_state;
+ /* Apply the translation if we need. */
+ if (BE (pstr->trans != NULL, 0))
+ {
+ int i, ch;
+
+ for (i = 0; i < pstr->mb_cur_max && i < remain_len; ++i)
+ {
+ ch = pstr->raw_mbs [pstr->raw_mbs_idx + byte_idx + i];
+ buf[i] = pstr->mbs[byte_idx + i] = pstr->trans[ch];
+ }
+ p = (const char *) buf;
+ }
+ else
+ p = (const char *) pstr->raw_mbs + pstr->raw_mbs_idx + byte_idx;
+ mbclen = __mbrtowc (&wc, p, remain_len, &pstr->cur_state);
+ if (BE (mbclen == (size_t) -2, 0))
+ {
+ /* The buffer doesn't have enough space, finish to build. */
+ pstr->cur_state = prev_st;
+ break;
+ }
+ else if (BE (mbclen == (size_t) -1 || mbclen == 0, 0))
+ {
+ /* We treat these cases as a singlebyte character. */
+ mbclen = 1;
+ wc = (wchar_t) pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx];
+ if (BE (pstr->trans != NULL, 0))
+ wc = pstr->trans[wc];
+ pstr->cur_state = prev_st;
+ }
+
+ /* Write wide character and padding. */
+ pstr->wcs[byte_idx++] = wc;
+ /* Write paddings. */
+ for (remain_len = byte_idx + mbclen - 1; byte_idx < remain_len ;)
+ pstr->wcs[byte_idx++] = WEOF;
+ }
+ pstr->valid_len = byte_idx;
+ pstr->valid_raw_len = byte_idx;
+}
+
+/* Build wide character buffer PSTR->WCS like build_wcs_buffer,
+ but for REG_ICASE. */
+
+static reg_errcode_t
+internal_function
+build_wcs_upper_buffer (re_string_t *pstr)
+{
+ mbstate_t prev_st;
+ int src_idx, byte_idx, end_idx, remain_len;
+ size_t mbclen;
+#ifdef _LIBC
+ char buf[MB_LEN_MAX];
+ assert (MB_LEN_MAX >= pstr->mb_cur_max);
+#else
+ char buf[64];
+#endif
+
+ byte_idx = pstr->valid_len;
+ end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len;
+
+ /* The following optimization assumes that ASCII characters can be
+ mapped to wide characters with a simple cast. */
+ if (! pstr->map_notascii && pstr->trans == NULL && !pstr->offsets_needed)
+ {
+ while (byte_idx < end_idx)
+ {
+ wchar_t wc;
+
+ if (isascii (pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx])
+ && mbsinit (&pstr->cur_state))
+ {
+ /* In case of a singlebyte character. */
+ pstr->mbs[byte_idx]
+ = toupper (pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx]);
+ /* The next step uses the assumption that wchar_t is encoded
+ ASCII-safe: all ASCII values can be converted like this. */
+ pstr->wcs[byte_idx] = (wchar_t) pstr->mbs[byte_idx];
+ ++byte_idx;
+ continue;
+ }
+
+ remain_len = end_idx - byte_idx;
+ prev_st = pstr->cur_state;
+ mbclen = __mbrtowc (&wc,
+ ((const char *) pstr->raw_mbs + pstr->raw_mbs_idx
+ + byte_idx), remain_len, &pstr->cur_state);
+ if (BE (mbclen + 2 > 2, 1))
+ {
+ wchar_t wcu = wc;
+ if (iswlower (wc))
+ {
+ size_t mbcdlen;
+
+ wcu = towupper (wc);
+ mbcdlen = wcrtomb (buf, wcu, &prev_st);
+ if (BE (mbclen == mbcdlen, 1))
+ memcpy (pstr->mbs + byte_idx, buf, mbclen);
+ else
+ {
+ src_idx = byte_idx;
+ goto offsets_needed;
+ }
+ }
+ else
+ memcpy (pstr->mbs + byte_idx,
+ pstr->raw_mbs + pstr->raw_mbs_idx + byte_idx, mbclen);
+ pstr->wcs[byte_idx++] = wcu;
+ /* Write paddings. */
+ for (remain_len = byte_idx + mbclen - 1; byte_idx < remain_len ;)
+ pstr->wcs[byte_idx++] = WEOF;
+ }
+ else if (mbclen == (size_t) -1 || mbclen == 0)
+ {
+ /* It is an invalid character or '\0'. Just use the byte. */
+ int ch = pstr->raw_mbs[pstr->raw_mbs_idx + byte_idx];
+ pstr->mbs[byte_idx] = ch;
+ /* And also cast it to wide char. */
+ pstr->wcs[byte_idx++] = (wchar_t) ch;
+ if (BE (mbclen == (size_t) -1, 0))
+ pstr->cur_state = prev_st;
+ }
+ else
+ {
+ /* The buffer doesn't have enough space, finish to build. */
+ pstr->cur_state = prev_st;
+ break;
+ }
+ }
+ pstr->valid_len = byte_idx;
+ pstr->valid_raw_len = byte_idx;
+ return REG_NOERROR;
+ }
+ else
+ for (src_idx = pstr->valid_raw_len; byte_idx < end_idx;)
+ {
+ wchar_t wc;
+ const char *p;
+ offsets_needed:
+ remain_len = end_idx - byte_idx;
+ prev_st = pstr->cur_state;
+ if (BE (pstr->trans != NULL, 0))
+ {
+ int i, ch;
+
+ for (i = 0; i < pstr->mb_cur_max && i < remain_len; ++i)
+ {
+ ch = pstr->raw_mbs [pstr->raw_mbs_idx + src_idx + i];
+ buf[i] = pstr->trans[ch];
+ }
+ p = (const char *) buf;
+ }
+ else
+ p = (const char *) pstr->raw_mbs + pstr->raw_mbs_idx + src_idx;
+ mbclen = __mbrtowc (&wc, p, remain_len, &pstr->cur_state);
+ if (BE (mbclen + 2 > 2, 1))
+ {
+ wchar_t wcu = wc;
+ if (iswlower (wc))
+ {
+ size_t mbcdlen;
+
+ wcu = towupper (wc);
+ mbcdlen = wcrtomb ((char *) buf, wcu, &prev_st);
+ if (BE (mbclen == mbcdlen, 1))
+ memcpy (pstr->mbs + byte_idx, buf, mbclen);
+ else if (mbcdlen != (size_t) -1)
+ {
+ size_t i;
+
+ if (byte_idx + mbcdlen > pstr->bufs_len)
+ {
+ pstr->cur_state = prev_st;
+ break;
+ }
+
+ if (pstr->offsets == NULL)
+ {
+ pstr->offsets = re_malloc (int, pstr->bufs_len);
+
+ if (pstr->offsets == NULL)
+ return REG_ESPACE;
+ }
+ if (!pstr->offsets_needed)
+ {
+ for (i = 0; i < (size_t) byte_idx; ++i)
+ pstr->offsets[i] = i;
+ pstr->offsets_needed = 1;
+ }
+
+ memcpy (pstr->mbs + byte_idx, buf, mbcdlen);
+ pstr->wcs[byte_idx] = wcu;
+ pstr->offsets[byte_idx] = src_idx;
+ for (i = 1; i < mbcdlen; ++i)
+ {
+ pstr->offsets[byte_idx + i]
+ = src_idx + (i < mbclen ? i : mbclen - 1);
+ pstr->wcs[byte_idx + i] = WEOF;
+ }
+ pstr->len += mbcdlen - mbclen;
+ if (pstr->raw_stop > src_idx)
+ pstr->stop += mbcdlen - mbclen;
+ end_idx = (pstr->bufs_len > pstr->len)
+ ? pstr->len : pstr->bufs_len;
+ byte_idx += mbcdlen;
+ src_idx += mbclen;
+ continue;
+ }
+ else
+ memcpy (pstr->mbs + byte_idx, p, mbclen);
+ }
+ else
+ memcpy (pstr->mbs + byte_idx, p, mbclen);
+
+ if (BE (pstr->offsets_needed != 0, 0))
+ {
+ size_t i;
+ for (i = 0; i < mbclen; ++i)
+ pstr->offsets[byte_idx + i] = src_idx + i;
+ }
+ src_idx += mbclen;
+
+ pstr->wcs[byte_idx++] = wcu;
+ /* Write paddings. */
+ for (remain_len = byte_idx + mbclen - 1; byte_idx < remain_len ;)
+ pstr->wcs[byte_idx++] = WEOF;
+ }
+ else if (mbclen == (size_t) -1 || mbclen == 0)
+ {
+ /* It is an invalid character or '\0'. Just use the byte. */
+ int ch = pstr->raw_mbs[pstr->raw_mbs_idx + src_idx];
+
+ if (BE (pstr->trans != NULL, 0))
+ ch = pstr->trans [ch];
+ pstr->mbs[byte_idx] = ch;
+
+ if (BE (pstr->offsets_needed != 0, 0))
+ pstr->offsets[byte_idx] = src_idx;
+ ++src_idx;
+
+ /* And also cast it to wide char. */
+ pstr->wcs[byte_idx++] = (wchar_t) ch;
+ if (BE (mbclen == (size_t) -1, 0))
+ pstr->cur_state = prev_st;
+ }
+ else
+ {
+ /* The buffer doesn't have enough space, finish to build. */
+ pstr->cur_state = prev_st;
+ break;
+ }
+ }
+ pstr->valid_len = byte_idx;
+ pstr->valid_raw_len = src_idx;
+ return REG_NOERROR;
+}
+
+/* Skip characters until the index becomes greater than NEW_RAW_IDX.
+ Return the index. */
+
+static int
+internal_function
+re_string_skip_chars (re_string_t *pstr, int new_raw_idx, wint_t *last_wc)
+{
+ mbstate_t prev_st;
+ int rawbuf_idx;
+ size_t mbclen;
+ wint_t wc = WEOF;
+
+ /* Skip the characters which are not necessary to check. */
+ for (rawbuf_idx = pstr->raw_mbs_idx + pstr->valid_raw_len;
+ rawbuf_idx < new_raw_idx;)
+ {
+ wchar_t wc2;
+ int remain_len = pstr->len - rawbuf_idx;
+ prev_st = pstr->cur_state;
+ mbclen = __mbrtowc (&wc2, (const char *) pstr->raw_mbs + rawbuf_idx,
+ remain_len, &pstr->cur_state);
+ if (BE (mbclen == (size_t) -2 || mbclen == (size_t) -1 || mbclen == 0, 0))
+ {
+ /* We treat these cases as a single byte character. */
+ if (mbclen == 0 || remain_len == 0)
+ wc = L'\0';
+ else
+ wc = *(unsigned char *) (pstr->raw_mbs + rawbuf_idx);
+ mbclen = 1;
+ pstr->cur_state = prev_st;
+ }
+ else
+ wc = (wint_t) wc2;
+ /* Then proceed the next character. */
+ rawbuf_idx += mbclen;
+ }
+ *last_wc = (wint_t) wc;
+ return rawbuf_idx;
+}
+#endif /* RE_ENABLE_I18N */
+
+/* Build the buffer PSTR->MBS, and apply the translation if we need.
+ This function is used in case of REG_ICASE. */
+
+static void
+internal_function
+build_upper_buffer (re_string_t *pstr)
+{
+ int char_idx, end_idx;
+ end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len;
+
+ for (char_idx = pstr->valid_len; char_idx < end_idx; ++char_idx)
+ {
+ int ch = pstr->raw_mbs[pstr->raw_mbs_idx + char_idx];
+ if (BE (pstr->trans != NULL, 0))
+ ch = pstr->trans[ch];
+ if (islower (ch))
+ pstr->mbs[char_idx] = toupper (ch);
+ else
+ pstr->mbs[char_idx] = ch;
+ }
+ pstr->valid_len = char_idx;
+ pstr->valid_raw_len = char_idx;
+}
+
+/* Apply TRANS to the buffer in PSTR. */
+
+static void
+internal_function
+re_string_translate_buffer (re_string_t *pstr)
+{
+ int buf_idx, end_idx;
+ end_idx = (pstr->bufs_len > pstr->len) ? pstr->len : pstr->bufs_len;
+
+ for (buf_idx = pstr->valid_len; buf_idx < end_idx; ++buf_idx)
+ {
+ int ch = pstr->raw_mbs[pstr->raw_mbs_idx + buf_idx];
+ pstr->mbs[buf_idx] = pstr->trans[ch];
+ }
+
+ pstr->valid_len = buf_idx;
+ pstr->valid_raw_len = buf_idx;
+}
+
+/* This function re-construct the buffers.
+ Concretely, convert to wide character in case of pstr->mb_cur_max > 1,
+ convert to upper case in case of REG_ICASE, apply translation. */
+
+static reg_errcode_t
+internal_function
+re_string_reconstruct (re_string_t *pstr, int idx, int eflags)
+{
+ int offset = idx - pstr->raw_mbs_idx;
+ if (BE (offset < 0, 0))
+ {
+ /* Reset buffer. */
+#ifdef RE_ENABLE_I18N
+ if (pstr->mb_cur_max > 1)
+ memset (&pstr->cur_state, '\0', sizeof (mbstate_t));
+#endif /* RE_ENABLE_I18N */
+ pstr->len = pstr->raw_len;
+ pstr->stop = pstr->raw_stop;
+ pstr->valid_len = 0;
+ pstr->raw_mbs_idx = 0;
+ pstr->valid_raw_len = 0;
+ pstr->offsets_needed = 0;
+ pstr->tip_context = ((eflags & REG_NOTBOL) ? CONTEXT_BEGBUF
+ : CONTEXT_NEWLINE | CONTEXT_BEGBUF);
+ if (!pstr->mbs_allocated)
+ pstr->mbs = (unsigned char *) pstr->raw_mbs;
+ offset = idx;
+ }
+
+ if (BE (offset != 0, 1))
+ {
+ /* Should the already checked characters be kept? */
+ if (BE (offset < pstr->valid_raw_len, 1))
+ {
+ /* Yes, move them to the front of the buffer. */
+#ifdef RE_ENABLE_I18N
+ if (BE (pstr->offsets_needed, 0))
+ {
+ int low = 0, high = pstr->valid_len, mid;
+ do
+ {
+ mid = (high + low) / 2;
+ if (pstr->offsets[mid] > offset)
+ high = mid;
+ else if (pstr->offsets[mid] < offset)
+ low = mid + 1;
+ else
+ break;
+ }
+ while (low < high);
+ if (pstr->offsets[mid] < offset)
+ ++mid;
+ pstr->tip_context = re_string_context_at (pstr, mid - 1,
+ eflags);
+ /* This can be quite complicated, so handle specially
+ only the common and easy case where the character with
+ different length representation of lower and upper
+ case is present at or after offset. */
+ if (pstr->valid_len > offset
+ && mid == offset && pstr->offsets[mid] == offset)
+ {
+ memmove (pstr->wcs, pstr->wcs + offset,
+ (pstr->valid_len - offset) * sizeof (wint_t));
+ memmove (pstr->mbs, pstr->mbs + offset, pstr->valid_len - offset);
+ pstr->valid_len -= offset;
+ pstr->valid_raw_len -= offset;
+ for (low = 0; low < pstr->valid_len; low++)
+ pstr->offsets[low] = pstr->offsets[low + offset] - offset;
+ }
+ else
+ {
+ /* Otherwise, just find out how long the partial multibyte
+ character at offset is and fill it with WEOF/255. */
+ pstr->len = pstr->raw_len - idx + offset;
+ pstr->stop = pstr->raw_stop - idx + offset;
+ pstr->offsets_needed = 0;
+ while (mid > 0 && pstr->offsets[mid - 1] == offset)
+ --mid;
+ while (mid < pstr->valid_len)
+ if (pstr->wcs[mid] != WEOF)
+ break;
+ else
+ ++mid;
+ if (mid == pstr->valid_len)
+ pstr->valid_len = 0;
+ else
+ {
+ pstr->valid_len = pstr->offsets[mid] - offset;
+ if (pstr->valid_len)
+ {
+ for (low = 0; low < pstr->valid_len; ++low)
+ pstr->wcs[low] = WEOF;
+ memset (pstr->mbs, 255, pstr->valid_len);
+ }
+ }
+ pstr->valid_raw_len = pstr->valid_len;
+ }
+ }
+ else
+#endif
+ {
+ pstr->tip_context = re_string_context_at (pstr, offset - 1,
+ eflags);
+#ifdef RE_ENABLE_I18N
+ if (pstr->mb_cur_max > 1)
+ memmove (pstr->wcs, pstr->wcs + offset,
+ (pstr->valid_len - offset) * sizeof (wint_t));
+#endif /* RE_ENABLE_I18N */
+ if (BE (pstr->mbs_allocated, 0))
+ memmove (pstr->mbs, pstr->mbs + offset,
+ pstr->valid_len - offset);
+ pstr->valid_len -= offset;
+ pstr->valid_raw_len -= offset;
+#if DEBUG
+ assert (pstr->valid_len > 0);
+#endif
+ }
+ }
+ else
+ {
+#ifdef RE_ENABLE_I18N
+ /* No, skip all characters until IDX. */
+ int prev_valid_len = pstr->valid_len;
+
+ if (BE (pstr->offsets_needed, 0))
+ {
+ pstr->len = pstr->raw_len - idx + offset;
+ pstr->stop = pstr->raw_stop - idx + offset;
+ pstr->offsets_needed = 0;
+ }
+#endif
+ pstr->valid_len = 0;
+#ifdef RE_ENABLE_I18N
+ if (pstr->mb_cur_max > 1)
+ {
+ int wcs_idx;
+ wint_t wc = WEOF;
+
+ if (pstr->is_utf8)
+ {
+ const unsigned char *raw, *p, *end;
+
+ /* Special case UTF-8. Multi-byte chars start with any
+ byte other than 0x80 - 0xbf. */
+ raw = pstr->raw_mbs + pstr->raw_mbs_idx;
+ end = raw + (offset - pstr->mb_cur_max);
+ if (end < pstr->raw_mbs)
+ end = pstr->raw_mbs;
+ p = raw + offset - 1;
+#ifdef _LIBC
+ /* We know the wchar_t encoding is UCS4, so for the simple
+ case, ASCII characters, skip the conversion step. */
+ if (isascii (*p) && BE (pstr->trans == NULL, 1))
+ {
+ memset (&pstr->cur_state, '\0', sizeof (mbstate_t));
+ /* pstr->valid_len = 0; */
+ wc = (wchar_t) *p;
+ }
+ else
+#endif
+ for (; p >= end; --p)
+ if ((*p & 0xc0) != 0x80)
+ {
+ mbstate_t cur_state;
+ wchar_t wc2;
+ int mlen = raw + pstr->len - p;
+ unsigned char buf[6];
+ size_t mbclen;
+
+ if (BE (pstr->trans != NULL, 0))
+ {
+ int i = mlen < 6 ? mlen : 6;
+ while (--i >= 0)
+ buf[i] = pstr->trans[p[i]];
+ }
+ /* XXX Don't use mbrtowc, we know which conversion
+ to use (UTF-8 -> UCS4). */
+ memset (&cur_state, 0, sizeof (cur_state));
+ mbclen = __mbrtowc (&wc2, (const char *) p, mlen,
+ &cur_state);
+ if (raw + offset - p <= mbclen
+ && mbclen < (size_t) -2)
+ {
+ memset (&pstr->cur_state, '\0',
+ sizeof (mbstate_t));
+ pstr->valid_len = mbclen - (raw + offset - p);
+ wc = wc2;
+ }
+ break;
+ }
+ }
+
+ if (wc == WEOF)
+ pstr->valid_len = re_string_skip_chars (pstr, idx, &wc) - idx;
+ if (wc == WEOF)
+ pstr->tip_context
+ = re_string_context_at (pstr, prev_valid_len - 1, eflags);
+ else
+ pstr->tip_context = ((BE (pstr->word_ops_used != 0, 0)
+ && IS_WIDE_WORD_CHAR (wc))
+ ? CONTEXT_WORD
+ : ((IS_WIDE_NEWLINE (wc)
+ && pstr->newline_anchor)
+ ? CONTEXT_NEWLINE : 0));
+ if (BE (pstr->valid_len, 0))
+ {
+ for (wcs_idx = 0; wcs_idx < pstr->valid_len; ++wcs_idx)
+ pstr->wcs[wcs_idx] = WEOF;
+ if (pstr->mbs_allocated)
+ memset (pstr->mbs, 255, pstr->valid_len);
+ }
+ pstr->valid_raw_len = pstr->valid_len;
+ }
+ else
+#endif /* RE_ENABLE_I18N */
+ {
+ int c = pstr->raw_mbs[pstr->raw_mbs_idx + offset - 1];
+ pstr->valid_raw_len = 0;
+ if (pstr->trans)
+ c = pstr->trans[c];
+ pstr->tip_context = (bitset_contain (pstr->word_char, c)
+ ? CONTEXT_WORD
+ : ((IS_NEWLINE (c) && pstr->newline_anchor)
+ ? CONTEXT_NEWLINE : 0));
+ }
+ }
+ if (!BE (pstr->mbs_allocated, 0))
+ pstr->mbs += offset;
+ }
+ pstr->raw_mbs_idx = idx;
+ pstr->len -= offset;
+ pstr->stop -= offset;
+
+ /* Then build the buffers. */
+#ifdef RE_ENABLE_I18N
+ if (pstr->mb_cur_max > 1)
+ {
+ if (pstr->icase)
+ {
+ reg_errcode_t ret = build_wcs_upper_buffer (pstr);
+ if (BE (ret != REG_NOERROR, 0))
+ return ret;
+ }
+ else
+ build_wcs_buffer (pstr);
+ }
+ else
+#endif /* RE_ENABLE_I18N */
+ if (BE (pstr->mbs_allocated, 0))
+ {
+ if (pstr->icase)
+ build_upper_buffer (pstr);
+ else if (pstr->trans != NULL)
+ re_string_translate_buffer (pstr);
+ }
+ else
+ pstr->valid_len = pstr->len;
+
+ pstr->cur_idx = 0;
+ return REG_NOERROR;
+}
+
+static unsigned char
+internal_function __attribute ((pure))
+re_string_peek_byte_case (const re_string_t *pstr, int idx)
+{
+ int ch, off;
+
+ /* Handle the common (easiest) cases first. */
+ if (BE (!pstr->mbs_allocated, 1))
+ return re_string_peek_byte (pstr, idx);
+
+#ifdef RE_ENABLE_I18N
+ if (pstr->mb_cur_max > 1
+ && ! re_string_is_single_byte_char (pstr, pstr->cur_idx + idx))
+ return re_string_peek_byte (pstr, idx);
+#endif
+
+ off = pstr->cur_idx + idx;
+#ifdef RE_ENABLE_I18N
+ if (pstr->offsets_needed)
+ off = pstr->offsets[off];
+#endif
+
+ ch = pstr->raw_mbs[pstr->raw_mbs_idx + off];
+
+#ifdef RE_ENABLE_I18N
+ /* Ensure that e.g. for tr_TR.UTF-8 BACKSLASH DOTLESS SMALL LETTER I
+ this function returns CAPITAL LETTER I instead of first byte of
+ DOTLESS SMALL LETTER I. The latter would confuse the parser,
+ since peek_byte_case doesn't advance cur_idx in any way. */
+ if (pstr->offsets_needed && !isascii (ch))
+ return re_string_peek_byte (pstr, idx);
+#endif
+
+ return ch;
+}
+
+static unsigned char
+internal_function __attribute ((pure))
+re_string_fetch_byte_case (re_string_t *pstr)
+{
+ if (BE (!pstr->mbs_allocated, 1))
+ return re_string_fetch_byte (pstr);
+
+#ifdef RE_ENABLE_I18N
+ if (pstr->offsets_needed)
+ {
+ int off, ch;
+
+ /* For tr_TR.UTF-8 [[:islower:]] there is
+ [[: CAPITAL LETTER I WITH DOT lower:]] in mbs. Skip
+ in that case the whole multi-byte character and return
+ the original letter. On the other side, with
+ [[: DOTLESS SMALL LETTER I return [[:I, as doing
+ anything else would complicate things too much. */
+
+ if (!re_string_first_byte (pstr, pstr->cur_idx))
+ return re_string_fetch_byte (pstr);
+
+ off = pstr->offsets[pstr->cur_idx];
+ ch = pstr->raw_mbs[pstr->raw_mbs_idx + off];
+
+ if (! isascii (ch))
+ return re_string_fetch_byte (pstr);
+
+ re_string_skip_bytes (pstr,
+ re_string_char_size_at (pstr, pstr->cur_idx));
+ return ch;
+ }
+#endif
+
+ return pstr->raw_mbs[pstr->raw_mbs_idx + pstr->cur_idx++];
+}
+
+static void
+internal_function
+re_string_destruct (re_string_t *pstr)
+{
+#ifdef RE_ENABLE_I18N
+ re_free (pstr->wcs);
+ re_free (pstr->offsets);
+#endif /* RE_ENABLE_I18N */
+ if (pstr->mbs_allocated)
+ re_free (pstr->mbs);
+}
+
+/* Return the context at IDX in INPUT. */
+
+static unsigned int
+internal_function
+re_string_context_at (const re_string_t *input, int idx, int eflags)
+{
+ int c;
+ if (BE (idx < 0, 0))
+ /* In this case, we use the value stored in input->tip_context,
+ since we can't know the character in input->mbs[-1] here. */
+ return input->tip_context;
+ if (BE (idx == input->len, 0))
+ return ((eflags & REG_NOTEOL) ? CONTEXT_ENDBUF
+ : CONTEXT_NEWLINE | CONTEXT_ENDBUF);
+#ifdef RE_ENABLE_I18N
+ if (input->mb_cur_max > 1)
+ {
+ wint_t wc;
+ int wc_idx = idx;
+ while(input->wcs[wc_idx] == WEOF)
+ {
+#ifdef DEBUG
+ /* It must not happen. */
+ assert (wc_idx >= 0);
+#endif
+ --wc_idx;
+ if (wc_idx < 0)
+ return input->tip_context;
+ }
+ wc = input->wcs[wc_idx];
+ if (BE (input->word_ops_used != 0, 0) && IS_WIDE_WORD_CHAR (wc))
+ return CONTEXT_WORD;
+ return (IS_WIDE_NEWLINE (wc) && input->newline_anchor
+ ? CONTEXT_NEWLINE : 0);
+ }
+ else
+#endif
+ {
+ c = re_string_byte_at (input, idx);
+ if (bitset_contain (input->word_char, c))
+ return CONTEXT_WORD;
+ return IS_NEWLINE (c) && input->newline_anchor ? CONTEXT_NEWLINE : 0;
+ }
+}
+
+/* Functions for set operation. */
+
+static reg_errcode_t
+internal_function
+re_node_set_alloc (re_node_set *set, int size)
+{
+ /*
+ * ADR: valgrind says size can be 0, which then doesn't
+ * free the block of size 0. Harumph. This seems
+ * to work ok, though.
+ */
+ if (size == 0)
+ {
+ memset(set, 0, sizeof(*set));
+ return REG_NOERROR;
+ }
+ set->alloc = size;
+ set->nelem = 0;
+ set->elems = re_malloc (int, size);
+ if (BE (set->elems == NULL, 0))
+ return REG_ESPACE;
+ return REG_NOERROR;
+}
+
+static reg_errcode_t
+internal_function
+re_node_set_init_1 (re_node_set *set, int elem)
+{
+ set->alloc = 1;
+ set->nelem = 1;
+ set->elems = re_malloc (int, 1);
+ if (BE (set->elems == NULL, 0))
+ {
+ set->alloc = set->nelem = 0;
+ return REG_ESPACE;
+ }
+ set->elems[0] = elem;
+ return REG_NOERROR;
+}
+
+static reg_errcode_t
+internal_function
+re_node_set_init_2 (re_node_set *set, int elem1, int elem2)
+{
+ set->alloc = 2;
+ set->elems = re_malloc (int, 2);
+ if (BE (set->elems == NULL, 0))
+ return REG_ESPACE;
+ if (elem1 == elem2)
+ {
+ set->nelem = 1;
+ set->elems[0] = elem1;
+ }
+ else
+ {
+ set->nelem = 2;
+ if (elem1 < elem2)
+ {
+ set->elems[0] = elem1;
+ set->elems[1] = elem2;
+ }
+ else
+ {
+ set->elems[0] = elem2;
+ set->elems[1] = elem1;
+ }
+ }
+ return REG_NOERROR;
+}
+
+static reg_errcode_t
+internal_function
+re_node_set_init_copy (re_node_set *dest, const re_node_set *src)
+{
+ dest->nelem = src->nelem;
+ if (src->nelem > 0)
+ {
+ dest->alloc = dest->nelem;
+ dest->elems = re_malloc (int, dest->alloc);
+ if (BE (dest->elems == NULL, 0))
+ {
+ dest->alloc = dest->nelem = 0;
+ return REG_ESPACE;
+ }
+ memcpy (dest->elems, src->elems, src->nelem * sizeof (int));
+ }
+ else
+ re_node_set_init_empty (dest);
+ return REG_NOERROR;
+}
+
+/* Calculate the intersection of the sets SRC1 and SRC2. And merge it to
+ DEST. Return value indicate the error code or REG_NOERROR if succeeded.
+ Note: We assume dest->elems is NULL, when dest->alloc is 0. */
+
+static reg_errcode_t
+internal_function
+re_node_set_add_intersect (re_node_set *dest, const re_node_set *src1,
+ const re_node_set *src2)
+{
+ int i1, i2, is, id, delta, sbase;
+ if (src1->nelem == 0 || src2->nelem == 0)
+ return REG_NOERROR;
+
+ /* We need dest->nelem + 2 * elems_in_intersection; this is a
+ conservative estimate. */
+ if (src1->nelem + src2->nelem + dest->nelem > dest->alloc)
+ {
+ int new_alloc = src1->nelem + src2->nelem + dest->alloc;
+ int *new_elems = re_realloc (dest->elems, int, new_alloc);
+ if (BE (new_elems == NULL, 0))
+ return REG_ESPACE;
+ dest->elems = new_elems;
+ dest->alloc = new_alloc;
+ }
+
+ /* Find the items in the intersection of SRC1 and SRC2, and copy
+ into the top of DEST those that are not already in DEST itself. */
+ sbase = dest->nelem + src1->nelem + src2->nelem;
+ i1 = src1->nelem - 1;
+ i2 = src2->nelem - 1;
+ id = dest->nelem - 1;
+ for (;;)
+ {
+ if (src1->elems[i1] == src2->elems[i2])
+ {
+ /* Try to find the item in DEST. Maybe we could binary search? */
+ while (id >= 0 && dest->elems[id] > src1->elems[i1])
+ --id;
+
+ if (id < 0 || dest->elems[id] != src1->elems[i1])
+ dest->elems[--sbase] = src1->elems[i1];
+
+ if (--i1 < 0 || --i2 < 0)
+ break;
+ }
+
+ /* Lower the highest of the two items. */
+ else if (src1->elems[i1] < src2->elems[i2])
+ {
+ if (--i2 < 0)
+ break;
+ }
+ else
+ {
+ if (--i1 < 0)
+ break;
+ }
+ }
+
+ id = dest->nelem - 1;
+ is = dest->nelem + src1->nelem + src2->nelem - 1;
+ delta = is - sbase + 1;
+
+ /* Now copy. When DELTA becomes zero, the remaining
+ DEST elements are already in place; this is more or
+ less the same loop that is in re_node_set_merge. */
+ dest->nelem += delta;
+ if (delta > 0 && id >= 0)
+ for (;;)
+ {
+ if (dest->elems[is] > dest->elems[id])
+ {
+ /* Copy from the top. */
+ dest->elems[id + delta--] = dest->elems[is--];
+ if (delta == 0)
+ break;
+ }
+ else
+ {
+ /* Slide from the bottom. */
+ dest->elems[id + delta] = dest->elems[id];
+ if (--id < 0)
+ break;
+ }
+ }
+
+ /* Copy remaining SRC elements. */
+ memcpy (dest->elems, dest->elems + sbase, delta * sizeof (int));
+
+ return REG_NOERROR;
+}
+
+/* Calculate the union set of the sets SRC1 and SRC2. And store it to
+ DEST. Return value indicate the error code or REG_NOERROR if succeeded. */
+
+static reg_errcode_t
+internal_function
+re_node_set_init_union (re_node_set *dest, const re_node_set *src1,
+ const re_node_set *src2)
+{
+ int i1, i2, id;
+ if (src1 != NULL && src1->nelem > 0 && src2 != NULL && src2->nelem > 0)
+ {
+ dest->alloc = src1->nelem + src2->nelem;
+ dest->elems = re_malloc (int, dest->alloc);
+ if (BE (dest->elems == NULL, 0))
+ return REG_ESPACE;
+ }
+ else
+ {
+ if (src1 != NULL && src1->nelem > 0)
+ return re_node_set_init_copy (dest, src1);
+ else if (src2 != NULL && src2->nelem > 0)
+ return re_node_set_init_copy (dest, src2);
+ else
+ re_node_set_init_empty (dest);
+ return REG_NOERROR;
+ }
+ for (i1 = i2 = id = 0 ; i1 < src1->nelem && i2 < src2->nelem ;)
+ {
+ if (src1->elems[i1] > src2->elems[i2])
+ {
+ dest->elems[id++] = src2->elems[i2++];
+ continue;
+ }
+ if (src1->elems[i1] == src2->elems[i2])
+ ++i2;
+ dest->elems[id++] = src1->elems[i1++];
+ }
+ if (i1 < src1->nelem)
+ {
+ memcpy (dest->elems + id, src1->elems + i1,
+ (src1->nelem - i1) * sizeof (int));
+ id += src1->nelem - i1;
+ }
+ else if (i2 < src2->nelem)
+ {
+ memcpy (dest->elems + id, src2->elems + i2,
+ (src2->nelem - i2) * sizeof (int));
+ id += src2->nelem - i2;
+ }
+ dest->nelem = id;
+ return REG_NOERROR;
+}
+
+/* Calculate the union set of the sets DEST and SRC. And store it to
+ DEST. Return value indicate the error code or REG_NOERROR if succeeded. */
+
+static reg_errcode_t
+internal_function
+re_node_set_merge (re_node_set *dest, const re_node_set *src)
+{
+ int is, id, sbase, delta;
+ if (src == NULL || src->nelem == 0)
+ return REG_NOERROR;
+ if (dest->alloc < 2 * src->nelem + dest->nelem)
+ {
+ int new_alloc = 2 * (src->nelem + dest->alloc);
+ int *new_buffer = re_realloc (dest->elems, int, new_alloc);
+ if (BE (new_buffer == NULL, 0))
+ return REG_ESPACE;
+ dest->elems = new_buffer;
+ dest->alloc = new_alloc;
+ }
+
+ if (BE (dest->nelem == 0, 0))
+ {
+ dest->nelem = src->nelem;
+ memcpy (dest->elems, src->elems, src->nelem * sizeof (int));
+ return REG_NOERROR;
+ }
+
+ /* Copy into the top of DEST the items of SRC that are not
+ found in DEST. Maybe we could binary search in DEST? */
+ for (sbase = dest->nelem + 2 * src->nelem,
+ is = src->nelem - 1, id = dest->nelem - 1; is >= 0 && id >= 0; )
+ {
+ if (dest->elems[id] == src->elems[is])
+ is--, id--;
+ else if (dest->elems[id] < src->elems[is])
+ dest->elems[--sbase] = src->elems[is--];
+ else /* if (dest->elems[id] > src->elems[is]) */
+ --id;
+ }
+
+ if (is >= 0)
+ {
+ /* If DEST is exhausted, the remaining items of SRC must be unique. */
+ sbase -= is + 1;
+ memcpy (dest->elems + sbase, src->elems, (is + 1) * sizeof (int));
+ }
+
+ id = dest->nelem - 1;
+ is = dest->nelem + 2 * src->nelem - 1;
+ delta = is - sbase + 1;
+ if (delta == 0)
+ return REG_NOERROR;
+
+ /* Now copy. When DELTA becomes zero, the remaining
+ DEST elements are already in place. */
+ dest->nelem += delta;
+ for (;;)
+ {
+ if (dest->elems[is] > dest->elems[id])
+ {
+ /* Copy from the top. */
+ dest->elems[id + delta--] = dest->elems[is--];
+ if (delta == 0)
+ break;
+ }
+ else
+ {
+ /* Slide from the bottom. */
+ dest->elems[id + delta] = dest->elems[id];
+ if (--id < 0)
+ {
+ /* Copy remaining SRC elements. */
+ memcpy (dest->elems, dest->elems + sbase,
+ delta * sizeof (int));
+ break;
+ }
+ }
+ }
+
+ return REG_NOERROR;
+}
+
+/* Insert the new element ELEM to the re_node_set* SET.
+ SET should not already have ELEM.
+ return -1 if an error is occured, return 1 otherwise. */
+
+static int
+internal_function
+re_node_set_insert (re_node_set *set, int elem)
+{
+ int idx;
+ /* In case the set is empty. */
+ if (set->alloc == 0)
+ {
+ if (BE (re_node_set_init_1 (set, elem) == REG_NOERROR, 1))
+ return 1;
+ else
+ return -1;
+ }
+
+ if (BE (set->nelem, 0) == 0)
+ {
+ /* We already guaranteed above that set->alloc != 0. */
+ set->elems[0] = elem;
+ ++set->nelem;
+ return 1;
+ }
+
+ /* Realloc if we need. */
+ if (set->alloc == set->nelem)
+ {
+ int *new_elems;
+ set->alloc = set->alloc * 2;
+ new_elems = re_realloc (set->elems, int, set->alloc);
+ if (BE (new_elems == NULL, 0))
+ return -1;
+ set->elems = new_elems;
+ }
+
+ /* Move the elements which follows the new element. Test the
+ first element separately to skip a check in the inner loop. */
+ if (elem < set->elems[0])
+ {
+ idx = 0;
+ for (idx = set->nelem; idx > 0; idx--)
+ set->elems[idx] = set->elems[idx - 1];
+ }
+ else
+ {
+ for (idx = set->nelem; set->elems[idx - 1] > elem; idx--)
+ set->elems[idx] = set->elems[idx - 1];
+ }
+
+ /* Insert the new element. */
+ set->elems[idx] = elem;
+ ++set->nelem;
+ return 1;
+}
+
+/* Insert the new element ELEM to the re_node_set* SET.
+ SET should not already have any element greater than or equal to ELEM.
+ Return -1 if an error is occured, return 1 otherwise. */
+
+static int
+internal_function
+re_node_set_insert_last (re_node_set *set, int elem)
+{
+ /* Realloc if we need. */
+ if (set->alloc == set->nelem)
+ {
+ int *new_elems;
+ set->alloc = (set->alloc + 1) * 2;
+ new_elems = re_realloc (set->elems, int, set->alloc);
+ if (BE (new_elems == NULL, 0))
+ return -1;
+ set->elems = new_elems;
+ }
+
+ /* Insert the new element. */
+ set->elems[set->nelem++] = elem;
+ return 1;
+}
+
+/* Compare two node sets SET1 and SET2.
+ return 1 if SET1 and SET2 are equivalent, return 0 otherwise. */
+
+static int
+internal_function __attribute ((pure))
+re_node_set_compare (const re_node_set *set1, const re_node_set *set2)
+{
+ int i;
+ if (set1 == NULL || set2 == NULL || set1->nelem != set2->nelem)
+ return 0;
+ for (i = set1->nelem ; --i >= 0 ; )
+ if (set1->elems[i] != set2->elems[i])
+ return 0;
+ return 1;
+}
+
+/* Return (idx + 1) if SET contains the element ELEM, return 0 otherwise. */
+
+static int
+internal_function __attribute ((pure))
+re_node_set_contains (const re_node_set *set, int elem)
+{
+ unsigned int idx, right, mid;
+ if (set->nelem <= 0)
+ return 0;
+
+ /* Binary search the element. */
+ idx = 0;
+ right = set->nelem - 1;
+ while (idx < right)
+ {
+ mid = (idx + right) / 2;
+ if (set->elems[mid] < elem)
+ idx = mid + 1;
+ else
+ right = mid;
+ }
+ return set->elems[idx] == elem ? idx + 1 : 0;
+}
+
+static void
+internal_function
+re_node_set_remove_at (re_node_set *set, int idx)
+{
+ if (idx < 0 || idx >= set->nelem)
+ return;
+ --set->nelem;
+ for (; idx < set->nelem; idx++)
+ set->elems[idx] = set->elems[idx + 1];
+}
+
+
+/* Add the token TOKEN to dfa->nodes, and return the index of the token.
+ Or return -1, if an error will be occured. */
+
+static int
+internal_function
+re_dfa_add_node (re_dfa_t *dfa, re_token_t token)
+{
+ if (BE (dfa->nodes_len >= dfa->nodes_alloc, 0))
+ {
+ size_t new_nodes_alloc = dfa->nodes_alloc * 2;
+ int *new_nexts, *new_indices;
+ re_node_set *new_edests, *new_eclosures;
+ re_token_t *new_nodes;
+
+ /* Avoid overflows in realloc. */
+ const size_t max_object_size = MAX (sizeof (re_token_t),
+ MAX (sizeof (re_node_set),
+ sizeof (int)));
+ if (BE (SIZE_MAX / max_object_size < new_nodes_alloc, 0))
+ return -1;
+
+ new_nodes = re_realloc (dfa->nodes, re_token_t, new_nodes_alloc);
+ if (BE (new_nodes == NULL, 0))
+ return -1;
+ dfa->nodes = new_nodes;
+ new_nexts = re_realloc (dfa->nexts, int, new_nodes_alloc);
+ new_indices = re_realloc (dfa->org_indices, int, new_nodes_alloc);
+ new_edests = re_realloc (dfa->edests, re_node_set, new_nodes_alloc);
+ new_eclosures = re_realloc (dfa->eclosures, re_node_set, new_nodes_alloc);
+ if (BE (new_nexts == NULL || new_indices == NULL
+ || new_edests == NULL || new_eclosures == NULL, 0))
+ return -1;
+ dfa->nexts = new_nexts;
+ dfa->org_indices = new_indices;
+ dfa->edests = new_edests;
+ dfa->eclosures = new_eclosures;
+ dfa->nodes_alloc = new_nodes_alloc;
+ }
+ dfa->nodes[dfa->nodes_len] = token;
+ dfa->nodes[dfa->nodes_len].constraint = 0;
+#ifdef RE_ENABLE_I18N
+ dfa->nodes[dfa->nodes_len].accept_mb =
+ (token.type == OP_PERIOD && dfa->mb_cur_max > 1) || token.type == COMPLEX_BRACKET;
+#endif
+ dfa->nexts[dfa->nodes_len] = -1;
+ re_node_set_init_empty (dfa->edests + dfa->nodes_len);
+ re_node_set_init_empty (dfa->eclosures + dfa->nodes_len);
+ return dfa->nodes_len++;
+}
+
+static inline unsigned int
+internal_function
+calc_state_hash (const re_node_set *nodes, unsigned int context)
+{
+ unsigned int hash = nodes->nelem + context;
+ int i;
+ for (i = 0 ; i < nodes->nelem ; i++)
+ hash += nodes->elems[i];
+ return hash;
+}
+
+/* Search for the state whose node_set is equivalent to NODES.
+ Return the pointer to the state, if we found it in the DFA.
+ Otherwise create the new one and return it. In case of an error
+ return NULL and set the error code in ERR.
+ Note: - We assume NULL as the invalid state, then it is possible that
+ return value is NULL and ERR is REG_NOERROR.
+ - We never return non-NULL value in case of any errors, it is for
+ optimization. */
+
+static re_dfastate_t *
+internal_function
+re_acquire_state (reg_errcode_t *err, const re_dfa_t *dfa,
+ const re_node_set *nodes)
+{
+ unsigned int hash;
+ re_dfastate_t *new_state;
+ struct re_state_table_entry *spot;
+ int i;
+ if (BE (nodes->nelem == 0, 0))
+ {
+ *err = REG_NOERROR;
+ return NULL;
+ }
+ hash = calc_state_hash (nodes, 0);
+ spot = dfa->state_table + (hash & dfa->state_hash_mask);
+
+ for (i = 0 ; i < spot->num ; i++)
+ {
+ re_dfastate_t *state = spot->array[i];
+ if (hash != state->hash)
+ continue;
+ if (re_node_set_compare (&state->nodes, nodes))
+ return state;
+ }
+
+ /* There are no appropriate state in the dfa, create the new one. */
+ new_state = create_ci_newstate (dfa, nodes, hash);
+ if (BE (new_state == NULL, 0))
+ *err = REG_ESPACE;
+
+ return new_state;
+}
+
+/* Search for the state whose node_set is equivalent to NODES and
+ whose context is equivalent to CONTEXT.
+ Return the pointer to the state, if we found it in the DFA.
+ Otherwise create the new one and return it. In case of an error
+ return NULL and set the error code in ERR.
+ Note: - We assume NULL as the invalid state, then it is possible that
+ return value is NULL and ERR is REG_NOERROR.
+ - We never return non-NULL value in case of any errors, it is for
+ optimization. */
+
+static re_dfastate_t *
+internal_function
+re_acquire_state_context (reg_errcode_t *err, const re_dfa_t *dfa,
+ const re_node_set *nodes, unsigned int context)
+{
+ unsigned int hash;
+ re_dfastate_t *new_state;
+ struct re_state_table_entry *spot;
+ int i;
+ if (nodes->nelem == 0)
+ {
+ *err = REG_NOERROR;
+ return NULL;
+ }
+ hash = calc_state_hash (nodes, context);
+ spot = dfa->state_table + (hash & dfa->state_hash_mask);
+
+ for (i = 0 ; i < spot->num ; i++)
+ {
+ re_dfastate_t *state = spot->array[i];
+ if (state->hash == hash
+ && state->context == context
+ && re_node_set_compare (state->entrance_nodes, nodes))
+ return state;
+ }
+ /* There are no appropriate state in `dfa', create the new one. */
+ new_state = create_cd_newstate (dfa, nodes, context, hash);
+ if (BE (new_state == NULL, 0))
+ *err = REG_ESPACE;
+
+ return new_state;
+}
+
+/* Finish initialization of the new state NEWSTATE, and using its hash value
+ HASH put in the appropriate bucket of DFA's state table. Return value
+ indicates the error code if failed. */
+
+static reg_errcode_t
+register_state (const re_dfa_t *dfa, re_dfastate_t *newstate,
+ unsigned int hash)
+{
+ struct re_state_table_entry *spot;
+ reg_errcode_t err;
+ int i;
+
+ newstate->hash = hash;
+ err = re_node_set_alloc (&newstate->non_eps_nodes, newstate->nodes.nelem);
+ if (BE (err != REG_NOERROR, 0))
+ return REG_ESPACE;
+ for (i = 0; i < newstate->nodes.nelem; i++)
+ {
+ int elem = newstate->nodes.elems[i];
+ if (!IS_EPSILON_NODE (dfa->nodes[elem].type))
+ if (re_node_set_insert_last (&newstate->non_eps_nodes, elem) < 0)
+ return REG_ESPACE;
+ }
+
+ spot = dfa->state_table + (hash & dfa->state_hash_mask);
+ if (BE (spot->alloc <= spot->num, 0))
+ {
+ int new_alloc = 2 * spot->num + 2;
+ re_dfastate_t **new_array = re_realloc (spot->array, re_dfastate_t *,
+ new_alloc);
+ if (BE (new_array == NULL, 0))
+ return REG_ESPACE;
+ spot->array = new_array;
+ spot->alloc = new_alloc;
+ }
+ spot->array[spot->num++] = newstate;
+ return REG_NOERROR;
+}
+
+static void
+free_state (re_dfastate_t *state)
+{
+ re_node_set_free (&state->non_eps_nodes);
+ re_node_set_free (&state->inveclosure);
+ if (state->entrance_nodes != &state->nodes)
+ {
+ re_node_set_free (state->entrance_nodes);
+ re_free (state->entrance_nodes);
+ }
+ re_node_set_free (&state->nodes);
+ re_free (state->word_trtable);
+ re_free (state->trtable);
+ re_free (state);
+}
+
+/* Create the new state which is independ of contexts.
+ Return the new state if succeeded, otherwise return NULL. */
+
+static re_dfastate_t *
+internal_function
+create_ci_newstate (const re_dfa_t *dfa, const re_node_set *nodes,
+ unsigned int hash)
+{
+ int i;
+ reg_errcode_t err;
+ re_dfastate_t *newstate;
+
+ newstate = (re_dfastate_t *) calloc (sizeof (re_dfastate_t), 1);
+ if (BE (newstate == NULL, 0))
+ return NULL;
+ err = re_node_set_init_copy (&newstate->nodes, nodes);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_free (newstate);
+ return NULL;
+ }
+
+ newstate->entrance_nodes = &newstate->nodes;
+ for (i = 0 ; i < nodes->nelem ; i++)
+ {
+ re_token_t *node = dfa->nodes + nodes->elems[i];
+ re_token_type_t type = node->type;
+ if (type == CHARACTER && !node->constraint)
+ continue;
+#ifdef RE_ENABLE_I18N
+ newstate->accept_mb |= node->accept_mb;
+#endif /* RE_ENABLE_I18N */
+
+ /* If the state has the halt node, the state is a halt state. */
+ if (type == END_OF_RE)
+ newstate->halt = 1;
+ else if (type == OP_BACK_REF)
+ newstate->has_backref = 1;
+ else if (type == ANCHOR || node->constraint)
+ newstate->has_constraint = 1;
+ }
+ err = register_state (dfa, newstate, hash);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ free_state (newstate);
+ newstate = NULL;
+ }
+ return newstate;
+}
+
+/* Create the new state which is depend on the context CONTEXT.
+ Return the new state if succeeded, otherwise return NULL. */
+
+static re_dfastate_t *
+internal_function
+create_cd_newstate (const re_dfa_t *dfa, const re_node_set *nodes,
+ unsigned int context, unsigned int hash)
+{
+ int i, nctx_nodes = 0;
+ reg_errcode_t err;
+ re_dfastate_t *newstate;
+
+ newstate = (re_dfastate_t *) calloc (sizeof (re_dfastate_t), 1);
+ if (BE (newstate == NULL, 0))
+ return NULL;
+ err = re_node_set_init_copy (&newstate->nodes, nodes);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_free (newstate);
+ return NULL;
+ }
+
+ newstate->context = context;
+ newstate->entrance_nodes = &newstate->nodes;
+
+ for (i = 0 ; i < nodes->nelem ; i++)
+ {
+ re_token_t *node = dfa->nodes + nodes->elems[i];
+ re_token_type_t type = node->type;
+ unsigned int constraint = node->constraint;
+
+ if (type == CHARACTER && !constraint)
+ continue;
+#ifdef RE_ENABLE_I18N
+ newstate->accept_mb |= node->accept_mb;
+#endif /* RE_ENABLE_I18N */
+
+ /* If the state has the halt node, the state is a halt state. */
+ if (type == END_OF_RE)
+ newstate->halt = 1;
+ else if (type == OP_BACK_REF)
+ newstate->has_backref = 1;
+
+ if (constraint)
+ {
+ if (newstate->entrance_nodes == &newstate->nodes)
+ {
+ newstate->entrance_nodes = re_malloc (re_node_set, 1);
+ if (BE (newstate->entrance_nodes == NULL, 0))
+ {
+ free_state (newstate);
+ return NULL;
+ }
+ if (re_node_set_init_copy (newstate->entrance_nodes, nodes)
+ != REG_NOERROR)
+ return NULL;
+ nctx_nodes = 0;
+ newstate->has_constraint = 1;
+ }
+
+ if (NOT_SATISFY_PREV_CONSTRAINT (constraint,context))
+ {
+ re_node_set_remove_at (&newstate->nodes, i - nctx_nodes);
+ ++nctx_nodes;
+ }
+ }
+ }
+ err = register_state (dfa, newstate, hash);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ free_state (newstate);
+ newstate = NULL;
+ }
+ return newstate;
+}
diff --git a/deps/regex/regex_internal.h b/deps/regex/regex_internal.h
new file mode 100644
index 000000000..53ccebecd
--- /dev/null
+++ b/deps/regex/regex_internal.h
@@ -0,0 +1,819 @@
+/* Extended regular expression matching and search library.
+ Copyright (C) 2002-2005, 2007, 2008, 2010 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Isamu Hasegawa <isamu@yamato.ibm.com>.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, write to the Free
+ Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ 02111-1307 USA. */
+
+#ifndef _REGEX_INTERNAL_H
+#define _REGEX_INTERNAL_H 1
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef UNUSED
+# ifdef __GNUC__
+# define UNUSED __attribute__((unused))
+# else
+# define UNUSED
+# endif
+#endif
+
+#if defined HAVE_LANGINFO_H || defined HAVE_LANGINFO_CODESET || defined _LIBC
+# include <langinfo.h>
+#endif
+#if defined HAVE_LOCALE_H || defined _LIBC
+# include <locale.h>
+#endif
+#if defined HAVE_WCHAR_H || defined _LIBC
+# include <wchar.h>
+#endif /* HAVE_WCHAR_H || _LIBC */
+#if defined HAVE_WCTYPE_H || defined _LIBC
+# include <wctype.h>
+#endif /* HAVE_WCTYPE_H || _LIBC */
+#if defined HAVE_STDBOOL_H || defined _LIBC
+# include <stdbool.h>
+#endif /* HAVE_STDBOOL_H || _LIBC */
+#if !defined(ZOS_USS)
+#if defined HAVE_STDINT_H || defined _LIBC
+# include <stdint.h>
+#endif /* HAVE_STDINT_H || _LIBC */
+#endif /* !ZOS_USS */
+#if defined _LIBC
+# include <bits/libc-lock.h>
+#else
+# define __libc_lock_define(CLASS,NAME)
+# define __libc_lock_init(NAME) do { } while (0)
+# define __libc_lock_lock(NAME) do { } while (0)
+# define __libc_lock_unlock(NAME) do { } while (0)
+#endif
+
+#ifndef GAWK
+/* In case that the system doesn't have isblank(). */
+#if !defined _LIBC && !defined HAVE_ISBLANK && !defined isblank
+# define isblank(ch) ((ch) == ' ' || (ch) == '\t')
+#endif
+#else /* GAWK */
+/*
+ * This is a mess. On glibc systems you have to define
+ * a magic constant to get isblank() out of <ctype.h>, since it's
+ * a C99 function. To heck with all that and borrow a page from
+ * dfa.c's book.
+ */
+
+static int
+is_blank (int c)
+{
+ return (c == ' ' || c == '\t');
+}
+#endif /* GAWK */
+
+#ifdef _LIBC
+# ifndef _RE_DEFINE_LOCALE_FUNCTIONS
+# define _RE_DEFINE_LOCALE_FUNCTIONS 1
+# include <locale/localeinfo.h>
+# include <locale/elem-hash.h>
+# include <locale/coll-lookup.h>
+# endif
+#endif
+
+/* This is for other GNU distributions with internationalized messages. */
+#if (HAVE_LIBINTL_H && ENABLE_NLS) || defined _LIBC
+# include <libintl.h>
+# ifdef _LIBC
+# undef gettext
+# define gettext(msgid) \
+ INTUSE(__dcgettext) (_libc_intl_domainname, msgid, LC_MESSAGES)
+# endif
+#else
+# define gettext(msgid) (msgid)
+#endif
+
+#ifndef gettext_noop
+/* This define is so xgettext can find the internationalizable
+ strings. */
+# define gettext_noop(String) String
+#endif
+
+/* For loser systems without the definition. */
+#ifndef SIZE_MAX
+# define SIZE_MAX ((size_t) -1)
+#endif
+
+#ifndef NO_MBSUPPORT
+#include "mbsupport.h" /* gawk */
+#endif
+#ifndef MB_CUR_MAX
+#define MB_CUR_MAX 1
+#endif
+
+#if (defined MBS_SUPPORT) || _LIBC
+# define RE_ENABLE_I18N
+#endif
+
+#if __GNUC__ >= 3
+# define BE(expr, val) __builtin_expect (expr, val)
+#else
+# define BE(expr, val) (expr)
+# ifdef inline
+# undef inline
+# endif
+# define inline
+#endif
+
+/* Number of single byte character. */
+#define SBC_MAX 256
+
+#define COLL_ELEM_LEN_MAX 8
+
+/* The character which represents newline. */
+#define NEWLINE_CHAR '\n'
+#define WIDE_NEWLINE_CHAR L'\n'
+
+/* Rename to standard API for using out of glibc. */
+#ifndef _LIBC
+# ifdef __wctype
+# undef __wctype
+# endif
+# define __wctype wctype
+# ifdef __iswctype
+# undef __iswctype
+# endif
+# define __iswctype iswctype
+# define __btowc btowc
+# define __mbrtowc mbrtowc
+#undef __mempcpy /* GAWK */
+# define __mempcpy mempcpy
+# define __wcrtomb wcrtomb
+# define __regfree regfree
+# define attribute_hidden
+#endif /* not _LIBC */
+
+#ifdef __GNUC__
+# define __attribute(arg) __attribute__ (arg)
+#else
+# define __attribute(arg)
+#endif
+
+extern const char __re_error_msgid[] attribute_hidden;
+extern const size_t __re_error_msgid_idx[] attribute_hidden;
+
+/* An integer used to represent a set of bits. It must be unsigned,
+ and must be at least as wide as unsigned int. */
+typedef unsigned long int bitset_word_t;
+/* All bits set in a bitset_word_t. */
+#define BITSET_WORD_MAX ULONG_MAX
+/* Number of bits in a bitset_word_t. Cast to int as most code use it
+ * like that for counting */
+#define BITSET_WORD_BITS ((int)(sizeof (bitset_word_t) * CHAR_BIT))
+/* Number of bitset_word_t in a bit_set. */
+#define BITSET_WORDS (SBC_MAX / BITSET_WORD_BITS)
+typedef bitset_word_t bitset_t[BITSET_WORDS];
+typedef bitset_word_t *re_bitset_ptr_t;
+typedef const bitset_word_t *re_const_bitset_ptr_t;
+
+#define bitset_set(set,i) \
+ (set[i / BITSET_WORD_BITS] |= (bitset_word_t) 1 << i % BITSET_WORD_BITS)
+#define bitset_clear(set,i) \
+ (set[i / BITSET_WORD_BITS] &= ~((bitset_word_t) 1 << i % BITSET_WORD_BITS))
+#define bitset_contain(set,i) \
+ (set[i / BITSET_WORD_BITS] & ((bitset_word_t) 1 << i % BITSET_WORD_BITS))
+#define bitset_empty(set) memset (set, '\0', sizeof (bitset_t))
+#define bitset_set_all(set) memset (set, '\xff', sizeof (bitset_t))
+#define bitset_copy(dest,src) memcpy (dest, src, sizeof (bitset_t))
+
+#define PREV_WORD_CONSTRAINT 0x0001
+#define PREV_NOTWORD_CONSTRAINT 0x0002
+#define NEXT_WORD_CONSTRAINT 0x0004
+#define NEXT_NOTWORD_CONSTRAINT 0x0008
+#define PREV_NEWLINE_CONSTRAINT 0x0010
+#define NEXT_NEWLINE_CONSTRAINT 0x0020
+#define PREV_BEGBUF_CONSTRAINT 0x0040
+#define NEXT_ENDBUF_CONSTRAINT 0x0080
+#define WORD_DELIM_CONSTRAINT 0x0100
+#define NOT_WORD_DELIM_CONSTRAINT 0x0200
+
+typedef enum
+{
+ INSIDE_WORD = PREV_WORD_CONSTRAINT | NEXT_WORD_CONSTRAINT,
+ WORD_FIRST = PREV_NOTWORD_CONSTRAINT | NEXT_WORD_CONSTRAINT,
+ WORD_LAST = PREV_WORD_CONSTRAINT | NEXT_NOTWORD_CONSTRAINT,
+ INSIDE_NOTWORD = PREV_NOTWORD_CONSTRAINT | NEXT_NOTWORD_CONSTRAINT,
+ LINE_FIRST = PREV_NEWLINE_CONSTRAINT,
+ LINE_LAST = NEXT_NEWLINE_CONSTRAINT,
+ BUF_FIRST = PREV_BEGBUF_CONSTRAINT,
+ BUF_LAST = NEXT_ENDBUF_CONSTRAINT,
+ WORD_DELIM = WORD_DELIM_CONSTRAINT,
+ NOT_WORD_DELIM = NOT_WORD_DELIM_CONSTRAINT
+} re_context_type;
+
+typedef struct
+{
+ int alloc;
+ int nelem;
+ int *elems;
+} re_node_set;
+
+typedef enum
+{
+ NON_TYPE = 0,
+
+ /* Node type, These are used by token, node, tree. */
+ CHARACTER = 1,
+ END_OF_RE = 2,
+ SIMPLE_BRACKET = 3,
+ OP_BACK_REF = 4,
+ OP_PERIOD = 5,
+#ifdef RE_ENABLE_I18N
+ COMPLEX_BRACKET = 6,
+ OP_UTF8_PERIOD = 7,
+#endif /* RE_ENABLE_I18N */
+
+ /* We define EPSILON_BIT as a macro so that OP_OPEN_SUBEXP is used
+ when the debugger shows values of this enum type. */
+#define EPSILON_BIT 8
+ OP_OPEN_SUBEXP = EPSILON_BIT | 0,
+ OP_CLOSE_SUBEXP = EPSILON_BIT | 1,
+ OP_ALT = EPSILON_BIT | 2,
+ OP_DUP_ASTERISK = EPSILON_BIT | 3,
+ ANCHOR = EPSILON_BIT | 4,
+
+ /* Tree type, these are used only by tree. */
+ CONCAT = 16,
+ SUBEXP = 17,
+
+ /* Token type, these are used only by token. */
+ OP_DUP_PLUS = 18,
+ OP_DUP_QUESTION,
+ OP_OPEN_BRACKET,
+ OP_CLOSE_BRACKET,
+ OP_CHARSET_RANGE,
+ OP_OPEN_DUP_NUM,
+ OP_CLOSE_DUP_NUM,
+ OP_NON_MATCH_LIST,
+ OP_OPEN_COLL_ELEM,
+ OP_CLOSE_COLL_ELEM,
+ OP_OPEN_EQUIV_CLASS,
+ OP_CLOSE_EQUIV_CLASS,
+ OP_OPEN_CHAR_CLASS,
+ OP_CLOSE_CHAR_CLASS,
+ OP_WORD,
+ OP_NOTWORD,
+ OP_SPACE,
+ OP_NOTSPACE,
+ BACK_SLASH
+
+} re_token_type_t;
+
+#ifdef RE_ENABLE_I18N
+typedef struct
+{
+ /* Multibyte characters. */
+ wchar_t *mbchars;
+
+ /* Collating symbols. */
+# ifdef _LIBC
+ int32_t *coll_syms;
+# endif
+
+ /* Equivalence classes. */
+# ifdef _LIBC
+ int32_t *equiv_classes;
+# endif
+
+ /* Range expressions. */
+# ifdef _LIBC
+ uint32_t *range_starts;
+ uint32_t *range_ends;
+# else /* not _LIBC */
+ wchar_t *range_starts;
+ wchar_t *range_ends;
+# endif /* not _LIBC */
+
+ /* Character classes. */
+ wctype_t *char_classes;
+
+ /* If this character set is the non-matching list. */
+ unsigned int non_match : 1;
+
+ /* # of multibyte characters. */
+ int nmbchars;
+
+ /* # of collating symbols. */
+ int ncoll_syms;
+
+ /* # of equivalence classes. */
+ int nequiv_classes;
+
+ /* # of range expressions. */
+ int nranges;
+
+ /* # of character classes. */
+ int nchar_classes;
+} re_charset_t;
+#endif /* RE_ENABLE_I18N */
+
+typedef struct
+{
+ union
+ {
+ unsigned char c; /* for CHARACTER */
+ re_bitset_ptr_t sbcset; /* for SIMPLE_BRACKET */
+#ifdef RE_ENABLE_I18N
+ re_charset_t *mbcset; /* for COMPLEX_BRACKET */
+#endif /* RE_ENABLE_I18N */
+ int idx; /* for BACK_REF */
+ re_context_type ctx_type; /* for ANCHOR */
+ } opr;
+#if __GNUC__ >= 2
+ re_token_type_t type : 8;
+#else
+ re_token_type_t type;
+#endif
+ unsigned int constraint : 10; /* context constraint */
+ unsigned int duplicated : 1;
+ unsigned int opt_subexp : 1;
+#ifdef RE_ENABLE_I18N
+ unsigned int accept_mb : 1;
+ /* These 2 bits can be moved into the union if needed (e.g. if running out
+ of bits; move opr.c to opr.c.c and move the flags to opr.c.flags). */
+ unsigned int mb_partial : 1;
+#endif
+ unsigned int word_char : 1;
+} re_token_t;
+
+#define IS_EPSILON_NODE(type) ((type) & EPSILON_BIT)
+
+struct re_string_t
+{
+ /* Indicate the raw buffer which is the original string passed as an
+ argument of regexec(), re_search(), etc.. */
+ const unsigned char *raw_mbs;
+ /* Store the multibyte string. In case of "case insensitive mode" like
+ REG_ICASE, upper cases of the string are stored, otherwise MBS points
+ the same address that RAW_MBS points. */
+ unsigned char *mbs;
+#ifdef RE_ENABLE_I18N
+ /* Store the wide character string which is corresponding to MBS. */
+ wint_t *wcs;
+ int *offsets;
+ mbstate_t cur_state;
+#endif
+ /* Index in RAW_MBS. Each character mbs[i] corresponds to
+ raw_mbs[raw_mbs_idx + i]. */
+ int raw_mbs_idx;
+ /* The length of the valid characters in the buffers. */
+ int valid_len;
+ /* The corresponding number of bytes in raw_mbs array. */
+ int valid_raw_len;
+ /* The length of the buffers MBS and WCS. */
+ int bufs_len;
+ /* The index in MBS, which is updated by re_string_fetch_byte. */
+ int cur_idx;
+ /* length of RAW_MBS array. */
+ int raw_len;
+ /* This is RAW_LEN - RAW_MBS_IDX + VALID_LEN - VALID_RAW_LEN. */
+ int len;
+ /* End of the buffer may be shorter than its length in the cases such
+ as re_match_2, re_search_2. Then, we use STOP for end of the buffer
+ instead of LEN. */
+ int raw_stop;
+ /* This is RAW_STOP - RAW_MBS_IDX adjusted through OFFSETS. */
+ int stop;
+
+ /* The context of mbs[0]. We store the context independently, since
+ the context of mbs[0] may be different from raw_mbs[0], which is
+ the beginning of the input string. */
+ unsigned int tip_context;
+ /* The translation passed as a part of an argument of re_compile_pattern. */
+ RE_TRANSLATE_TYPE trans;
+ /* Copy of re_dfa_t's word_char. */
+ re_const_bitset_ptr_t word_char;
+ /* 1 if REG_ICASE. */
+ unsigned char icase;
+ unsigned char is_utf8;
+ unsigned char map_notascii;
+ unsigned char mbs_allocated;
+ unsigned char offsets_needed;
+ unsigned char newline_anchor;
+ unsigned char word_ops_used;
+ int mb_cur_max;
+};
+typedef struct re_string_t re_string_t;
+
+
+struct re_dfa_t;
+typedef struct re_dfa_t re_dfa_t;
+
+#ifndef _LIBC
+# ifdef __i386__
+# define internal_function __attribute ((regparm (3), stdcall))
+# else
+# define internal_function
+# endif
+#endif
+
+#ifndef NOT_IN_libc
+static reg_errcode_t re_string_realloc_buffers (re_string_t *pstr,
+ int new_buf_len)
+ internal_function;
+# ifdef RE_ENABLE_I18N
+static void build_wcs_buffer (re_string_t *pstr) internal_function;
+static reg_errcode_t build_wcs_upper_buffer (re_string_t *pstr)
+ internal_function;
+# endif /* RE_ENABLE_I18N */
+static void build_upper_buffer (re_string_t *pstr) internal_function;
+static void re_string_translate_buffer (re_string_t *pstr) internal_function;
+static unsigned int re_string_context_at (const re_string_t *input, int idx,
+ int eflags)
+ internal_function __attribute ((pure));
+#endif
+#define re_string_peek_byte(pstr, offset) \
+ ((pstr)->mbs[(pstr)->cur_idx + offset])
+#define re_string_fetch_byte(pstr) \
+ ((pstr)->mbs[(pstr)->cur_idx++])
+#define re_string_first_byte(pstr, idx) \
+ ((idx) == (pstr)->valid_len || (pstr)->wcs[idx] != WEOF)
+#define re_string_is_single_byte_char(pstr, idx) \
+ ((pstr)->wcs[idx] != WEOF && ((pstr)->valid_len == (idx) + 1 \
+ || (pstr)->wcs[(idx) + 1] != WEOF))
+#define re_string_eoi(pstr) ((pstr)->stop <= (pstr)->cur_idx)
+#define re_string_cur_idx(pstr) ((pstr)->cur_idx)
+#define re_string_get_buffer(pstr) ((pstr)->mbs)
+#define re_string_length(pstr) ((pstr)->len)
+#define re_string_byte_at(pstr,idx) ((pstr)->mbs[idx])
+#define re_string_skip_bytes(pstr,idx) ((pstr)->cur_idx += (idx))
+#define re_string_set_index(pstr,idx) ((pstr)->cur_idx = (idx))
+
+#ifndef _LIBC
+# if HAVE_ALLOCA
+# if (_MSC_VER)
+# include <malloc.h>
+# define __libc_use_alloca(n) 0
+# else
+# include <alloca.h>
+/* The OS usually guarantees only one guard page at the bottom of the stack,
+ and a page size can be as small as 4096 bytes. So we cannot safely
+ allocate anything larger than 4096 bytes. Also care for the possibility
+ of a few compiler-allocated temporary stack slots. */
+# define __libc_use_alloca(n) ((n) < 4032)
+# endif
+# else
+/* alloca is implemented with malloc, so just use malloc. */
+# define __libc_use_alloca(n) 0
+# endif
+#endif
+
+#define re_malloc(t,n) ((t *) malloc ((n) * sizeof (t)))
+/* SunOS 4.1.x realloc doesn't accept null pointers: pre-Standard C. Sigh. */
+#define re_realloc(p,t,n) ((p != NULL) ? (t *) realloc (p,(n)*sizeof(t)) : (t *) calloc(n,sizeof(t)))
+#define re_free(p) free (p)
+
+struct bin_tree_t
+{
+ struct bin_tree_t *parent;
+ struct bin_tree_t *left;
+ struct bin_tree_t *right;
+ struct bin_tree_t *first;
+ struct bin_tree_t *next;
+
+ re_token_t token;
+
+ /* `node_idx' is the index in dfa->nodes, if `type' == 0.
+ Otherwise `type' indicate the type of this node. */
+ int node_idx;
+};
+typedef struct bin_tree_t bin_tree_t;
+
+#define BIN_TREE_STORAGE_SIZE \
+ ((1024 - sizeof (void *)) / sizeof (bin_tree_t))
+
+struct bin_tree_storage_t
+{
+ struct bin_tree_storage_t *next;
+ bin_tree_t data[BIN_TREE_STORAGE_SIZE];
+};
+typedef struct bin_tree_storage_t bin_tree_storage_t;
+
+#define CONTEXT_WORD 1
+#define CONTEXT_NEWLINE (CONTEXT_WORD << 1)
+#define CONTEXT_BEGBUF (CONTEXT_NEWLINE << 1)
+#define CONTEXT_ENDBUF (CONTEXT_BEGBUF << 1)
+
+#define IS_WORD_CONTEXT(c) ((c) & CONTEXT_WORD)
+#define IS_NEWLINE_CONTEXT(c) ((c) & CONTEXT_NEWLINE)
+#define IS_BEGBUF_CONTEXT(c) ((c) & CONTEXT_BEGBUF)
+#define IS_ENDBUF_CONTEXT(c) ((c) & CONTEXT_ENDBUF)
+#define IS_ORDINARY_CONTEXT(c) ((c) == 0)
+
+#define IS_WORD_CHAR(ch) (isalnum (ch) || (ch) == '_')
+#define IS_NEWLINE(ch) ((ch) == NEWLINE_CHAR)
+#define IS_WIDE_WORD_CHAR(ch) (iswalnum (ch) || (ch) == L'_')
+#define IS_WIDE_NEWLINE(ch) ((ch) == WIDE_NEWLINE_CHAR)
+
+#define NOT_SATISFY_PREV_CONSTRAINT(constraint,context) \
+ ((((constraint) & PREV_WORD_CONSTRAINT) && !IS_WORD_CONTEXT (context)) \
+ || ((constraint & PREV_NOTWORD_CONSTRAINT) && IS_WORD_CONTEXT (context)) \
+ || ((constraint & PREV_NEWLINE_CONSTRAINT) && !IS_NEWLINE_CONTEXT (context))\
+ || ((constraint & PREV_BEGBUF_CONSTRAINT) && !IS_BEGBUF_CONTEXT (context)))
+
+#define NOT_SATISFY_NEXT_CONSTRAINT(constraint,context) \
+ ((((constraint) & NEXT_WORD_CONSTRAINT) && !IS_WORD_CONTEXT (context)) \
+ || (((constraint) & NEXT_NOTWORD_CONSTRAINT) && IS_WORD_CONTEXT (context)) \
+ || (((constraint) & NEXT_NEWLINE_CONSTRAINT) && !IS_NEWLINE_CONTEXT (context)) \
+ || (((constraint) & NEXT_ENDBUF_CONSTRAINT) && !IS_ENDBUF_CONTEXT (context)))
+
+struct re_dfastate_t
+{
+ unsigned int hash;
+ re_node_set nodes;
+ re_node_set non_eps_nodes;
+ re_node_set inveclosure;
+ re_node_set *entrance_nodes;
+ struct re_dfastate_t **trtable, **word_trtable;
+ unsigned int context : 4;
+ unsigned int halt : 1;
+ /* If this state can accept `multi byte'.
+ Note that we refer to multibyte characters, and multi character
+ collating elements as `multi byte'. */
+ unsigned int accept_mb : 1;
+ /* If this state has backreference node(s). */
+ unsigned int has_backref : 1;
+ unsigned int has_constraint : 1;
+};
+typedef struct re_dfastate_t re_dfastate_t;
+
+struct re_state_table_entry
+{
+ int num;
+ int alloc;
+ re_dfastate_t **array;
+};
+
+/* Array type used in re_sub_match_last_t and re_sub_match_top_t. */
+
+typedef struct
+{
+ int next_idx;
+ int alloc;
+ re_dfastate_t **array;
+} state_array_t;
+
+/* Store information about the node NODE whose type is OP_CLOSE_SUBEXP. */
+
+typedef struct
+{
+ int node;
+ int str_idx; /* The position NODE match at. */
+ state_array_t path;
+} re_sub_match_last_t;
+
+/* Store information about the node NODE whose type is OP_OPEN_SUBEXP.
+ And information about the node, whose type is OP_CLOSE_SUBEXP,
+ corresponding to NODE is stored in LASTS. */
+
+typedef struct
+{
+ int str_idx;
+ int node;
+ state_array_t *path;
+ int alasts; /* Allocation size of LASTS. */
+ int nlasts; /* The number of LASTS. */
+ re_sub_match_last_t **lasts;
+} re_sub_match_top_t;
+
+struct re_backref_cache_entry
+{
+ int node;
+ int str_idx;
+ int subexp_from;
+ int subexp_to;
+ char more;
+ char unused;
+ unsigned short int eps_reachable_subexps_map;
+};
+
+typedef struct
+{
+ /* The string object corresponding to the input string. */
+ re_string_t input;
+#if defined _LIBC || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L)
+ const re_dfa_t *const dfa;
+#else
+ const re_dfa_t *dfa;
+#endif
+ /* EFLAGS of the argument of regexec. */
+ int eflags;
+ /* Where the matching ends. */
+ int match_last;
+ int last_node;
+ /* The state log used by the matcher. */
+ re_dfastate_t **state_log;
+ int state_log_top;
+ /* Back reference cache. */
+ int nbkref_ents;
+ int abkref_ents;
+ struct re_backref_cache_entry *bkref_ents;
+ int max_mb_elem_len;
+ int nsub_tops;
+ int asub_tops;
+ re_sub_match_top_t **sub_tops;
+} re_match_context_t;
+
+typedef struct
+{
+ re_dfastate_t **sifted_states;
+ re_dfastate_t **limited_states;
+ int last_node;
+ int last_str_idx;
+ re_node_set limits;
+} re_sift_context_t;
+
+struct re_fail_stack_ent_t
+{
+ int idx;
+ int node;
+ regmatch_t *regs;
+ re_node_set eps_via_nodes;
+};
+
+struct re_fail_stack_t
+{
+ int num;
+ int alloc;
+ struct re_fail_stack_ent_t *stack;
+};
+
+struct re_dfa_t
+{
+ re_token_t *nodes;
+ size_t nodes_alloc;
+ size_t nodes_len;
+ int *nexts;
+ int *org_indices;
+ re_node_set *edests;
+ re_node_set *eclosures;
+ re_node_set *inveclosures;
+ struct re_state_table_entry *state_table;
+ re_dfastate_t *init_state;
+ re_dfastate_t *init_state_word;
+ re_dfastate_t *init_state_nl;
+ re_dfastate_t *init_state_begbuf;
+ bin_tree_t *str_tree;
+ bin_tree_storage_t *str_tree_storage;
+ re_bitset_ptr_t sb_char;
+ int str_tree_storage_idx;
+
+ /* number of subexpressions `re_nsub' is in regex_t. */
+ unsigned int state_hash_mask;
+ int init_node;
+ int nbackref; /* The number of backreference in this dfa. */
+
+ /* Bitmap expressing which backreference is used. */
+ bitset_word_t used_bkref_map;
+ bitset_word_t completed_bkref_map;
+
+ unsigned int has_plural_match : 1;
+ /* If this dfa has "multibyte node", which is a backreference or
+ a node which can accept multibyte character or multi character
+ collating element. */
+ unsigned int has_mb_node : 1;
+ unsigned int is_utf8 : 1;
+ unsigned int map_notascii : 1;
+ unsigned int word_ops_used : 1;
+ int mb_cur_max;
+ bitset_t word_char;
+ reg_syntax_t syntax;
+ int *subexp_map;
+#ifdef DEBUG
+ char* re_str;
+#endif
+#if defined _LIBC
+ __libc_lock_define (, lock)
+#endif
+};
+
+#define re_node_set_init_empty(set) memset (set, '\0', sizeof (re_node_set))
+#define re_node_set_remove(set,id) \
+ (re_node_set_remove_at (set, re_node_set_contains (set, id) - 1))
+#define re_node_set_empty(p) ((p)->nelem = 0)
+#define re_node_set_free(set) re_free ((set)->elems)
+
+
+typedef enum
+{
+ SB_CHAR,
+ MB_CHAR,
+ EQUIV_CLASS,
+ COLL_SYM,
+ CHAR_CLASS
+} bracket_elem_type;
+
+typedef struct
+{
+ bracket_elem_type type;
+ union
+ {
+ unsigned char ch;
+ unsigned char *name;
+ wchar_t wch;
+ } opr;
+} bracket_elem_t;
+
+
+/* Inline functions for bitset operation. */
+static inline void
+bitset_not (bitset_t set)
+{
+ int bitset_i;
+ for (bitset_i = 0; bitset_i < BITSET_WORDS; ++bitset_i)
+ set[bitset_i] = ~set[bitset_i];
+}
+
+static inline void
+bitset_merge (bitset_t dest, const bitset_t src)
+{
+ int bitset_i;
+ for (bitset_i = 0; bitset_i < BITSET_WORDS; ++bitset_i)
+ dest[bitset_i] |= src[bitset_i];
+}
+
+static inline void
+bitset_mask (bitset_t dest, const bitset_t src)
+{
+ int bitset_i;
+ for (bitset_i = 0; bitset_i < BITSET_WORDS; ++bitset_i)
+ dest[bitset_i] &= src[bitset_i];
+}
+
+#ifdef RE_ENABLE_I18N
+/* Inline functions for re_string. */
+static inline int
+internal_function __attribute ((pure))
+re_string_char_size_at (const re_string_t *pstr, int idx)
+{
+ int byte_idx;
+ if (pstr->mb_cur_max == 1)
+ return 1;
+ for (byte_idx = 1; idx + byte_idx < pstr->valid_len; ++byte_idx)
+ if (pstr->wcs[idx + byte_idx] != WEOF)
+ break;
+ return byte_idx;
+}
+
+static inline wint_t
+internal_function __attribute ((pure))
+re_string_wchar_at (const re_string_t *pstr, int idx)
+{
+ if (pstr->mb_cur_max == 1)
+ return (wint_t) pstr->mbs[idx];
+ return (wint_t) pstr->wcs[idx];
+}
+
+# ifndef NOT_IN_libc
+static int
+internal_function __attribute ((pure))
+re_string_elem_size_at (const re_string_t *pstr, int idx)
+{
+# ifdef _LIBC
+ const unsigned char *p, *extra;
+ const int32_t *table, *indirect;
+ int32_t tmp;
+# include <locale/weight.h>
+ uint_fast32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES);
+
+ if (nrules != 0)
+ {
+ table = (const int32_t *) _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB);
+ extra = (const unsigned char *)
+ _NL_CURRENT (LC_COLLATE, _NL_COLLATE_EXTRAMB);
+ indirect = (const int32_t *) _NL_CURRENT (LC_COLLATE,
+ _NL_COLLATE_INDIRECTMB);
+ p = pstr->mbs + idx;
+ tmp = findidx (&p);
+ return p - pstr->mbs - idx;
+ }
+ else
+# endif /* _LIBC */
+ return 1;
+}
+# endif
+#endif /* RE_ENABLE_I18N */
+
+#endif /* _REGEX_INTERNAL_H */
diff --git a/deps/regex/regexec.c b/deps/regex/regexec.c
new file mode 100644
index 000000000..0a1602e5a
--- /dev/null
+++ b/deps/regex/regexec.c
@@ -0,0 +1,4369 @@
+/* Extended regular expression matching and search library.
+ Copyright (C) 2002-2005, 2007, 2009, 2010 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+ Contributed by Isamu Hasegawa <isamu@yamato.ibm.com>.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA. */
+
+static reg_errcode_t match_ctx_init (re_match_context_t *cache, int eflags,
+ int n) internal_function;
+static void match_ctx_clean (re_match_context_t *mctx) internal_function;
+static void match_ctx_free (re_match_context_t *cache) internal_function;
+static reg_errcode_t match_ctx_add_entry (re_match_context_t *cache, int node,
+ int str_idx, int from, int to)
+ internal_function;
+static int search_cur_bkref_entry (const re_match_context_t *mctx, int str_idx)
+ internal_function;
+static reg_errcode_t match_ctx_add_subtop (re_match_context_t *mctx, int node,
+ int str_idx) internal_function;
+static re_sub_match_last_t * match_ctx_add_sublast (re_sub_match_top_t *subtop,
+ int node, int str_idx)
+ internal_function;
+static void sift_ctx_init (re_sift_context_t *sctx, re_dfastate_t **sifted_sts,
+ re_dfastate_t **limited_sts, int last_node,
+ int last_str_idx)
+ internal_function;
+static reg_errcode_t re_search_internal (const regex_t *preg,
+ const char *string, int length,
+ int start, int range, int stop,
+ size_t nmatch, regmatch_t pmatch[],
+ int eflags);
+static int re_search_2_stub (struct re_pattern_buffer *bufp,
+ const char *string1, int length1,
+ const char *string2, int length2,
+ int start, int range, struct re_registers *regs,
+ int stop, int ret_len);
+static int re_search_stub (struct re_pattern_buffer *bufp,
+ const char *string, int length, int start,
+ int range, int stop, struct re_registers *regs,
+ int ret_len);
+static unsigned re_copy_regs (struct re_registers *regs, regmatch_t *pmatch,
+ unsigned int nregs, int regs_allocated);
+static reg_errcode_t prune_impossible_nodes (re_match_context_t *mctx);
+static int check_matching (re_match_context_t *mctx, int fl_longest_match,
+ int *p_match_first) internal_function;
+static int check_halt_state_context (const re_match_context_t *mctx,
+ const re_dfastate_t *state, int idx)
+ internal_function;
+static void update_regs (const re_dfa_t *dfa, regmatch_t *pmatch,
+ regmatch_t *prev_idx_match, int cur_node,
+ int cur_idx, int nmatch) internal_function;
+static reg_errcode_t push_fail_stack (struct re_fail_stack_t *fs,
+ int str_idx, int dest_node, int nregs,
+ regmatch_t *regs,
+ re_node_set *eps_via_nodes)
+ internal_function;
+static reg_errcode_t set_regs (const regex_t *preg,
+ const re_match_context_t *mctx,
+ size_t nmatch, regmatch_t *pmatch,
+ int fl_backtrack) internal_function;
+static reg_errcode_t free_fail_stack_return (struct re_fail_stack_t *fs)
+ internal_function;
+
+#ifdef RE_ENABLE_I18N
+static int sift_states_iter_mb (const re_match_context_t *mctx,
+ re_sift_context_t *sctx,
+ int node_idx, int str_idx, int max_str_idx)
+ internal_function;
+#endif /* RE_ENABLE_I18N */
+static reg_errcode_t sift_states_backward (const re_match_context_t *mctx,
+ re_sift_context_t *sctx)
+ internal_function;
+static reg_errcode_t build_sifted_states (const re_match_context_t *mctx,
+ re_sift_context_t *sctx, int str_idx,
+ re_node_set *cur_dest)
+ internal_function;
+static reg_errcode_t update_cur_sifted_state (const re_match_context_t *mctx,
+ re_sift_context_t *sctx,
+ int str_idx,
+ re_node_set *dest_nodes)
+ internal_function;
+static reg_errcode_t add_epsilon_src_nodes (const re_dfa_t *dfa,
+ re_node_set *dest_nodes,
+ const re_node_set *candidates)
+ internal_function;
+static int check_dst_limits (const re_match_context_t *mctx,
+ re_node_set *limits,
+ int dst_node, int dst_idx, int src_node,
+ int src_idx) internal_function;
+static int check_dst_limits_calc_pos_1 (const re_match_context_t *mctx,
+ int boundaries, int subexp_idx,
+ int from_node, int bkref_idx)
+ internal_function;
+static int check_dst_limits_calc_pos (const re_match_context_t *mctx,
+ int limit, int subexp_idx,
+ int node, int str_idx,
+ int bkref_idx) internal_function;
+static reg_errcode_t check_subexp_limits (const re_dfa_t *dfa,
+ re_node_set *dest_nodes,
+ const re_node_set *candidates,
+ re_node_set *limits,
+ struct re_backref_cache_entry *bkref_ents,
+ int str_idx) internal_function;
+static reg_errcode_t sift_states_bkref (const re_match_context_t *mctx,
+ re_sift_context_t *sctx,
+ int str_idx, const re_node_set *candidates)
+ internal_function;
+static reg_errcode_t merge_state_array (const re_dfa_t *dfa,
+ re_dfastate_t **dst,
+ re_dfastate_t **src, int num)
+ internal_function;
+static re_dfastate_t *find_recover_state (reg_errcode_t *err,
+ re_match_context_t *mctx) internal_function;
+static re_dfastate_t *transit_state (reg_errcode_t *err,
+ re_match_context_t *mctx,
+ re_dfastate_t *state) internal_function;
+static re_dfastate_t *merge_state_with_log (reg_errcode_t *err,
+ re_match_context_t *mctx,
+ re_dfastate_t *next_state)
+ internal_function;
+static reg_errcode_t check_subexp_matching_top (re_match_context_t *mctx,
+ re_node_set *cur_nodes,
+ int str_idx) internal_function;
+#if 0
+static re_dfastate_t *transit_state_sb (reg_errcode_t *err,
+ re_match_context_t *mctx,
+ re_dfastate_t *pstate)
+ internal_function;
+#endif
+#ifdef RE_ENABLE_I18N
+static reg_errcode_t transit_state_mb (re_match_context_t *mctx,
+ re_dfastate_t *pstate)
+ internal_function;
+#endif /* RE_ENABLE_I18N */
+static reg_errcode_t transit_state_bkref (re_match_context_t *mctx,
+ const re_node_set *nodes)
+ internal_function;
+static reg_errcode_t get_subexp (re_match_context_t *mctx,
+ int bkref_node, int bkref_str_idx)
+ internal_function;
+static reg_errcode_t get_subexp_sub (re_match_context_t *mctx,
+ const re_sub_match_top_t *sub_top,
+ re_sub_match_last_t *sub_last,
+ int bkref_node, int bkref_str)
+ internal_function;
+static int find_subexp_node (const re_dfa_t *dfa, const re_node_set *nodes,
+ int subexp_idx, int type) internal_function;
+static reg_errcode_t check_arrival (re_match_context_t *mctx,
+ state_array_t *path, int top_node,
+ int top_str, int last_node, int last_str,
+ int type) internal_function;
+static reg_errcode_t check_arrival_add_next_nodes (re_match_context_t *mctx,
+ int str_idx,
+ re_node_set *cur_nodes,
+ re_node_set *next_nodes)
+ internal_function;
+static reg_errcode_t check_arrival_expand_ecl (const re_dfa_t *dfa,
+ re_node_set *cur_nodes,
+ int ex_subexp, int type)
+ internal_function;
+static reg_errcode_t check_arrival_expand_ecl_sub (const re_dfa_t *dfa,
+ re_node_set *dst_nodes,
+ int target, int ex_subexp,
+ int type) internal_function;
+static reg_errcode_t expand_bkref_cache (re_match_context_t *mctx,
+ re_node_set *cur_nodes, int cur_str,
+ int subexp_num, int type)
+ internal_function;
+static int build_trtable (const re_dfa_t *dfa,
+ re_dfastate_t *state) internal_function;
+#ifdef RE_ENABLE_I18N
+static int check_node_accept_bytes (const re_dfa_t *dfa, int node_idx,
+ const re_string_t *input, int idx)
+ internal_function;
+# ifdef _LIBC
+static unsigned int find_collation_sequence_value (const unsigned char *mbs,
+ size_t name_len)
+ internal_function;
+# endif /* _LIBC */
+#endif /* RE_ENABLE_I18N */
+static int group_nodes_into_DFAstates (const re_dfa_t *dfa,
+ const re_dfastate_t *state,
+ re_node_set *states_node,
+ bitset_t *states_ch) internal_function;
+static int check_node_accept (const re_match_context_t *mctx,
+ const re_token_t *node, int idx)
+ internal_function;
+static reg_errcode_t extend_buffers (re_match_context_t *mctx)
+ internal_function;
+
+/* Entry point for POSIX code. */
+
+/* regexec searches for a given pattern, specified by PREG, in the
+ string STRING.
+
+ If NMATCH is zero or REG_NOSUB was set in the cflags argument to
+ `regcomp', we ignore PMATCH. Otherwise, we assume PMATCH has at
+ least NMATCH elements, and we set them to the offsets of the
+ corresponding matched substrings.
+
+ EFLAGS specifies `execution flags' which affect matching: if
+ REG_NOTBOL is set, then ^ does not match at the beginning of the
+ string; if REG_NOTEOL is set, then $ does not match at the end.
+
+ We return 0 if we find a match and REG_NOMATCH if not. */
+
+int
+regexec (
+ const regex_t *__restrict preg,
+ const char *__restrict string,
+ size_t nmatch,
+ regmatch_t pmatch[],
+ int eflags)
+{
+ reg_errcode_t err;
+ int start, length;
+
+ if (eflags & ~(REG_NOTBOL | REG_NOTEOL | REG_STARTEND))
+ return REG_BADPAT;
+
+ if (eflags & REG_STARTEND)
+ {
+ start = pmatch[0].rm_so;
+ length = pmatch[0].rm_eo;
+ }
+ else
+ {
+ start = 0;
+ length = strlen (string);
+ }
+
+ __libc_lock_lock (dfa->lock);
+ if (preg->no_sub)
+ err = re_search_internal (preg, string, length, start, length - start,
+ length, 0, NULL, eflags);
+ else
+ err = re_search_internal (preg, string, length, start, length - start,
+ length, nmatch, pmatch, eflags);
+ __libc_lock_unlock (dfa->lock);
+ return err != REG_NOERROR;
+}
+
+#ifdef _LIBC
+# include <shlib-compat.h>
+versioned_symbol (libc, __regexec, regexec, GLIBC_2_3_4);
+
+# if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_3_4)
+__typeof__ (__regexec) __compat_regexec;
+
+int
+attribute_compat_text_section
+__compat_regexec (const regex_t *__restrict preg,
+ const char *__restrict string, size_t nmatch,
+ regmatch_t pmatch[], int eflags)
+{
+ return regexec (preg, string, nmatch, pmatch,
+ eflags & (REG_NOTBOL | REG_NOTEOL));
+}
+compat_symbol (libc, __compat_regexec, regexec, GLIBC_2_0);
+# endif
+#endif
+
+/* Entry points for GNU code. */
+
+/* re_match, re_search, re_match_2, re_search_2
+
+ The former two functions operate on STRING with length LENGTH,
+ while the later two operate on concatenation of STRING1 and STRING2
+ with lengths LENGTH1 and LENGTH2, respectively.
+
+ re_match() matches the compiled pattern in BUFP against the string,
+ starting at index START.
+
+ re_search() first tries matching at index START, then it tries to match
+ starting from index START + 1, and so on. The last start position tried
+ is START + RANGE. (Thus RANGE = 0 forces re_search to operate the same
+ way as re_match().)
+
+ The parameter STOP of re_{match,search}_2 specifies that no match exceeding
+ the first STOP characters of the concatenation of the strings should be
+ concerned.
+
+ If REGS is not NULL, and BUFP->no_sub is not set, the offsets of the match
+ and all groups is stroed in REGS. (For the "_2" variants, the offsets are
+ computed relative to the concatenation, not relative to the individual
+ strings.)
+
+ On success, re_match* functions return the length of the match, re_search*
+ return the position of the start of the match. Return value -1 means no
+ match was found and -2 indicates an internal error. */
+
+int
+re_match (struct re_pattern_buffer *bufp,
+ const char *string,
+ int length,
+ int start,
+ struct re_registers *regs)
+{
+ return re_search_stub (bufp, string, length, start, 0, length, regs, 1);
+}
+#ifdef _LIBC
+weak_alias (__re_match, re_match)
+#endif
+
+int
+re_search (struct re_pattern_buffer *bufp,
+ const char *string,
+ int length, int start, int range,
+ struct re_registers *regs)
+{
+ return re_search_stub (bufp, string, length, start, range, length, regs, 0);
+}
+#ifdef _LIBC
+weak_alias (__re_search, re_search)
+#endif
+
+int
+re_match_2 (struct re_pattern_buffer *bufp,
+ const char *string1, int length1,
+ const char *string2, int length2, int start,
+ struct re_registers *regs, int stop)
+{
+ return re_search_2_stub (bufp, string1, length1, string2, length2,
+ start, 0, regs, stop, 1);
+}
+#ifdef _LIBC
+weak_alias (__re_match_2, re_match_2)
+#endif
+
+int
+re_search_2 (struct re_pattern_buffer *bufp,
+ const char *string1, int length1,
+ const char *string2, int length2, int start,
+ int range, struct re_registers *regs, int stop)
+{
+ return re_search_2_stub (bufp, string1, length1, string2, length2,
+ start, range, regs, stop, 0);
+}
+#ifdef _LIBC
+weak_alias (__re_search_2, re_search_2)
+#endif
+
+static int
+re_search_2_stub (struct re_pattern_buffer *bufp,
+ const char *string1, int length1,
+ const char *string2, int length2, int start,
+ int range, struct re_registers *regs,
+ int stop, int ret_len)
+{
+ const char *str;
+ int rval;
+ int len = length1 + length2;
+ int free_str = 0;
+
+ if (BE (length1 < 0 || length2 < 0 || stop < 0, 0))
+ return -2;
+
+ /* Concatenate the strings. */
+ if (length2 > 0)
+ if (length1 > 0)
+ {
+ char *s = re_malloc (char, len);
+
+ if (BE (s == NULL, 0))
+ return -2;
+ memcpy (s, string1, length1);
+ memcpy (s + length1, string2, length2);
+ str = s;
+ free_str = 1;
+ }
+ else
+ str = string2;
+ else
+ str = string1;
+
+ rval = re_search_stub (bufp, str, len, start, range, stop, regs, ret_len);
+ if (free_str)
+ re_free ((char *) str);
+ return rval;
+}
+
+/* The parameters have the same meaning as those of re_search.
+ Additional parameters:
+ If RET_LEN is nonzero the length of the match is returned (re_match style);
+ otherwise the position of the match is returned. */
+
+static int
+re_search_stub (struct re_pattern_buffer *bufp,
+ const char *string, int length, int start,
+ int range, int stop,
+ struct re_registers *regs, int ret_len)
+{
+ reg_errcode_t result;
+ regmatch_t *pmatch;
+ int nregs, rval;
+ int eflags = 0;
+
+ /* Check for out-of-range. */
+ if (BE (start < 0 || start > length, 0))
+ return -1;
+ if (BE (start + range > length, 0))
+ range = length - start;
+ else if (BE (start + range < 0, 0))
+ range = -start;
+
+ __libc_lock_lock (dfa->lock);
+
+ eflags |= (bufp->not_bol) ? REG_NOTBOL : 0;
+ eflags |= (bufp->not_eol) ? REG_NOTEOL : 0;
+
+ /* Compile fastmap if we haven't yet. */
+ if (range > 0 && bufp->fastmap != NULL && !bufp->fastmap_accurate)
+ re_compile_fastmap (bufp);
+
+ if (BE (bufp->no_sub, 0))
+ regs = NULL;
+
+ /* We need at least 1 register. */
+ if (regs == NULL)
+ nregs = 1;
+ else if (BE (bufp->regs_allocated == REGS_FIXED &&
+ regs->num_regs < bufp->re_nsub + 1, 0))
+ {
+ nregs = regs->num_regs;
+ if (BE (nregs < 1, 0))
+ {
+ /* Nothing can be copied to regs. */
+ regs = NULL;
+ nregs = 1;
+ }
+ }
+ else
+ nregs = bufp->re_nsub + 1;
+ pmatch = re_malloc (regmatch_t, nregs);
+ if (BE (pmatch == NULL, 0))
+ {
+ rval = -2;
+ goto out;
+ }
+
+ result = re_search_internal (bufp, string, length, start, range, stop,
+ nregs, pmatch, eflags);
+
+ rval = 0;
+
+ /* I hope we needn't fill ther regs with -1's when no match was found. */
+ if (result != REG_NOERROR)
+ rval = -1;
+ else if (regs != NULL)
+ {
+ /* If caller wants register contents data back, copy them. */
+ bufp->regs_allocated = re_copy_regs (regs, pmatch, nregs,
+ bufp->regs_allocated);
+ if (BE (bufp->regs_allocated == REGS_UNALLOCATED, 0))
+ rval = -2;
+ }
+
+ if (BE (rval == 0, 1))
+ {
+ if (ret_len)
+ {
+ assert (pmatch[0].rm_so == start);
+ rval = pmatch[0].rm_eo - start;
+ }
+ else
+ rval = pmatch[0].rm_so;
+ }
+ re_free (pmatch);
+ out:
+ __libc_lock_unlock (dfa->lock);
+ return rval;
+}
+
+static unsigned
+re_copy_regs (struct re_registers *regs,
+ regmatch_t *pmatch,
+ unsigned int nregs, int regs_allocated)
+{
+ int rval = REGS_REALLOCATE;
+ unsigned int i;
+ unsigned int need_regs = nregs + 1;
+ /* We need one extra element beyond `num_regs' for the `-1' marker GNU code
+ uses. */
+
+ /* Have the register data arrays been allocated? */
+ if (regs_allocated == REGS_UNALLOCATED)
+ { /* No. So allocate them with malloc. */
+ regs->start = re_malloc (regoff_t, need_regs);
+ if (BE (regs->start == NULL, 0))
+ return REGS_UNALLOCATED;
+ regs->end = re_malloc (regoff_t, need_regs);
+ if (BE (regs->end == NULL, 0))
+ {
+ re_free (regs->start);
+ return REGS_UNALLOCATED;
+ }
+ regs->num_regs = need_regs;
+ }
+ else if (regs_allocated == REGS_REALLOCATE)
+ { /* Yes. If we need more elements than were already
+ allocated, reallocate them. If we need fewer, just
+ leave it alone. */
+ if (BE (need_regs > regs->num_regs, 0))
+ {
+ regoff_t *new_start = re_realloc (regs->start, regoff_t, need_regs);
+ regoff_t *new_end;
+ if (BE (new_start == NULL, 0))
+ return REGS_UNALLOCATED;
+ new_end = re_realloc (regs->end, regoff_t, need_regs);
+ if (BE (new_end == NULL, 0))
+ {
+ re_free (new_start);
+ return REGS_UNALLOCATED;
+ }
+ regs->start = new_start;
+ regs->end = new_end;
+ regs->num_regs = need_regs;
+ }
+ }
+ else
+ {
+ assert (regs_allocated == REGS_FIXED);
+ /* This function may not be called with REGS_FIXED and nregs too big. */
+ assert (regs->num_regs >= nregs);
+ rval = REGS_FIXED;
+ }
+
+ /* Copy the regs. */
+ for (i = 0; i < nregs; ++i)
+ {
+ regs->start[i] = pmatch[i].rm_so;
+ regs->end[i] = pmatch[i].rm_eo;
+ }
+ for ( ; i < regs->num_regs; ++i)
+ regs->start[i] = regs->end[i] = -1;
+
+ return rval;
+}
+
+/* Set REGS to hold NUM_REGS registers, storing them in STARTS and
+ ENDS. Subsequent matches using PATTERN_BUFFER and REGS will use
+ this memory for recording register information. STARTS and ENDS
+ must be allocated using the malloc library routine, and must each
+ be at least NUM_REGS * sizeof (regoff_t) bytes long.
+
+ If NUM_REGS == 0, then subsequent matches should allocate their own
+ register data.
+
+ Unless this function is called, the first search or match using
+ PATTERN_BUFFER will allocate its own register data, without
+ freeing the old data. */
+
+void
+re_set_registers (struct re_pattern_buffer *bufp,
+ struct re_registers *regs,
+ unsigned num_regs,
+ regoff_t *starts,
+ regoff_t *ends)
+{
+ if (num_regs)
+ {
+ bufp->regs_allocated = REGS_REALLOCATE;
+ regs->num_regs = num_regs;
+ regs->start = starts;
+ regs->end = ends;
+ }
+ else
+ {
+ bufp->regs_allocated = REGS_UNALLOCATED;
+ regs->num_regs = 0;
+ regs->start = regs->end = (regoff_t *) 0;
+ }
+}
+#ifdef _LIBC
+weak_alias (__re_set_registers, re_set_registers)
+#endif
+
+/* Entry points compatible with 4.2 BSD regex library. We don't define
+ them unless specifically requested. */
+
+#if defined _REGEX_RE_COMP || defined _LIBC
+int
+# ifdef _LIBC
+weak_function
+# endif
+re_exec (s)
+ const char *s;
+{
+ return 0 == regexec (&re_comp_buf, s, 0, NULL, 0);
+}
+#endif /* _REGEX_RE_COMP */
+
+/* Internal entry point. */
+
+/* Searches for a compiled pattern PREG in the string STRING, whose
+ length is LENGTH. NMATCH, PMATCH, and EFLAGS have the same
+ mingings with regexec. START, and RANGE have the same meanings
+ with re_search.
+ Return REG_NOERROR if we find a match, and REG_NOMATCH if not,
+ otherwise return the error code.
+ Note: We assume front end functions already check ranges.
+ (START + RANGE >= 0 && START + RANGE <= LENGTH) */
+
+static reg_errcode_t
+re_search_internal (const regex_t *preg,
+ const char *string,
+ int length, int start, int range, int stop,
+ size_t nmatch, regmatch_t pmatch[],
+ int eflags)
+{
+ reg_errcode_t err;
+ const re_dfa_t *dfa = (const re_dfa_t *) preg->buffer;
+ int left_lim, right_lim, incr;
+ int fl_longest_match, match_first, match_kind, match_last = -1;
+ unsigned int extra_nmatch;
+ int sb, ch;
+#if defined _LIBC || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L)
+ re_match_context_t mctx = { .dfa = dfa };
+#else
+ re_match_context_t mctx;
+#endif
+ char *fastmap = (preg->fastmap != NULL && preg->fastmap_accurate
+ && range && !preg->can_be_null) ? preg->fastmap : NULL;
+ RE_TRANSLATE_TYPE t = preg->translate;
+
+#if !(defined _LIBC || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L))
+ memset (&mctx, '\0', sizeof (re_match_context_t));
+ mctx.dfa = dfa;
+#endif
+
+ extra_nmatch = (nmatch > preg->re_nsub) ? nmatch - (preg->re_nsub + 1) : 0;
+ nmatch -= extra_nmatch;
+
+ /* Check if the DFA haven't been compiled. */
+ if (BE (preg->used == 0 || dfa->init_state == NULL
+ || dfa->init_state_word == NULL || dfa->init_state_nl == NULL
+ || dfa->init_state_begbuf == NULL, 0))
+ return REG_NOMATCH;
+
+#ifdef DEBUG
+ /* We assume front-end functions already check them. */
+ assert (start + range >= 0 && start + range <= length);
+#endif
+
+ /* If initial states with non-begbuf contexts have no elements,
+ the regex must be anchored. If preg->newline_anchor is set,
+ we'll never use init_state_nl, so do not check it. */
+ if (dfa->init_state->nodes.nelem == 0
+ && dfa->init_state_word->nodes.nelem == 0
+ && (dfa->init_state_nl->nodes.nelem == 0
+ || !preg->newline_anchor))
+ {
+ if (start != 0 && start + range != 0)
+ return REG_NOMATCH;
+ start = range = 0;
+ }
+
+ /* We must check the longest matching, if nmatch > 0. */
+ fl_longest_match = (nmatch != 0 || dfa->nbackref);
+
+ err = re_string_allocate (&mctx.input, string, length, dfa->nodes_len + 1,
+ preg->translate, preg->syntax & RE_ICASE, dfa);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+ mctx.input.stop = stop;
+ mctx.input.raw_stop = stop;
+ mctx.input.newline_anchor = preg->newline_anchor;
+
+ err = match_ctx_init (&mctx, eflags, dfa->nbackref * 2);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+
+ /* We will log all the DFA states through which the dfa pass,
+ if nmatch > 1, or this dfa has "multibyte node", which is a
+ back-reference or a node which can accept multibyte character or
+ multi character collating element. */
+ if (nmatch > 1 || dfa->has_mb_node)
+ {
+ /* Avoid overflow. */
+ if (BE (SIZE_MAX / sizeof (re_dfastate_t *) <= (size_t)mctx.input.bufs_len, 0))
+ {
+ err = REG_ESPACE;
+ goto free_return;
+ }
+
+ mctx.state_log = re_malloc (re_dfastate_t *, mctx.input.bufs_len + 1);
+ if (BE (mctx.state_log == NULL, 0))
+ {
+ err = REG_ESPACE;
+ goto free_return;
+ }
+ }
+ else
+ mctx.state_log = NULL;
+
+ match_first = start;
+ mctx.input.tip_context = (eflags & REG_NOTBOL) ? CONTEXT_BEGBUF
+ : CONTEXT_NEWLINE | CONTEXT_BEGBUF;
+
+ /* Check incrementally whether of not the input string match. */
+ incr = (range < 0) ? -1 : 1;
+ left_lim = (range < 0) ? start + range : start;
+ right_lim = (range < 0) ? start : start + range;
+ sb = dfa->mb_cur_max == 1;
+ match_kind =
+ (fastmap
+ ? ((sb || !(preg->syntax & RE_ICASE || t) ? 4 : 0)
+ | (range >= 0 ? 2 : 0)
+ | (t != NULL ? 1 : 0))
+ : 8);
+
+ for (;; match_first += incr)
+ {
+ err = REG_NOMATCH;
+ if (match_first < left_lim || right_lim < match_first)
+ goto free_return;
+
+ /* Advance as rapidly as possible through the string, until we
+ find a plausible place to start matching. This may be done
+ with varying efficiency, so there are various possibilities:
+ only the most common of them are specialized, in order to
+ save on code size. We use a switch statement for speed. */
+ switch (match_kind)
+ {
+ case 8:
+ /* No fastmap. */
+ break;
+
+ case 7:
+ /* Fastmap with single-byte translation, match forward. */
+ while (BE (match_first < right_lim, 1)
+ && !fastmap[t[(unsigned char) string[match_first]]])
+ ++match_first;
+ goto forward_match_found_start_or_reached_end;
+
+ case 6:
+ /* Fastmap without translation, match forward. */
+ while (BE (match_first < right_lim, 1)
+ && !fastmap[(unsigned char) string[match_first]])
+ ++match_first;
+
+ forward_match_found_start_or_reached_end:
+ if (BE (match_first == right_lim, 0))
+ {
+ ch = match_first >= length
+ ? 0 : (unsigned char) string[match_first];
+ if (!fastmap[t ? t[ch] : ch])
+ goto free_return;
+ }
+ break;
+
+ case 4:
+ case 5:
+ /* Fastmap without multi-byte translation, match backwards. */
+ while (match_first >= left_lim)
+ {
+ ch = match_first >= length
+ ? 0 : (unsigned char) string[match_first];
+ if (fastmap[t ? t[ch] : ch])
+ break;
+ --match_first;
+ }
+ if (match_first < left_lim)
+ goto free_return;
+ break;
+
+ default:
+ /* In this case, we can't determine easily the current byte,
+ since it might be a component byte of a multibyte
+ character. Then we use the constructed buffer instead. */
+ for (;;)
+ {
+ /* If MATCH_FIRST is out of the valid range, reconstruct the
+ buffers. */
+ unsigned int offset = match_first - mctx.input.raw_mbs_idx;
+ if (BE (offset >= (unsigned int) mctx.input.valid_raw_len, 0))
+ {
+ err = re_string_reconstruct (&mctx.input, match_first,
+ eflags);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+
+ offset = match_first - mctx.input.raw_mbs_idx;
+ }
+ /* If MATCH_FIRST is out of the buffer, leave it as '\0'.
+ Note that MATCH_FIRST must not be smaller than 0. */
+ ch = (match_first >= length
+ ? 0 : re_string_byte_at (&mctx.input, offset));
+ if (fastmap[ch])
+ break;
+ match_first += incr;
+ if (match_first < left_lim || match_first > right_lim)
+ {
+ err = REG_NOMATCH;
+ goto free_return;
+ }
+ }
+ break;
+ }
+
+ /* Reconstruct the buffers so that the matcher can assume that
+ the matching starts from the beginning of the buffer. */
+ err = re_string_reconstruct (&mctx.input, match_first, eflags);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+
+#ifdef RE_ENABLE_I18N
+ /* Don't consider this char as a possible match start if it part,
+ yet isn't the head, of a multibyte character. */
+ if (!sb && !re_string_first_byte (&mctx.input, 0))
+ continue;
+#endif
+
+ /* It seems to be appropriate one, then use the matcher. */
+ /* We assume that the matching starts from 0. */
+ mctx.state_log_top = mctx.nbkref_ents = mctx.max_mb_elem_len = 0;
+ match_last = check_matching (&mctx, fl_longest_match,
+ range >= 0 ? &match_first : NULL);
+ if (match_last != -1)
+ {
+ if (BE (match_last == -2, 0))
+ {
+ err = REG_ESPACE;
+ goto free_return;
+ }
+ else
+ {
+ mctx.match_last = match_last;
+ if ((!preg->no_sub && nmatch > 1) || dfa->nbackref)
+ {
+ re_dfastate_t *pstate = mctx.state_log[match_last];
+ mctx.last_node = check_halt_state_context (&mctx, pstate,
+ match_last);
+ }
+ if ((!preg->no_sub && nmatch > 1 && dfa->has_plural_match)
+ || dfa->nbackref)
+ {
+ err = prune_impossible_nodes (&mctx);
+ if (err == REG_NOERROR)
+ break;
+ if (BE (err != REG_NOMATCH, 0))
+ goto free_return;
+ match_last = -1;
+ }
+ else
+ break; /* We found a match. */
+ }
+ }
+
+ match_ctx_clean (&mctx);
+ }
+
+#ifdef DEBUG
+ assert (match_last != -1);
+ assert (err == REG_NOERROR);
+#endif
+
+ /* Set pmatch[] if we need. */
+ if (nmatch > 0)
+ {
+ unsigned int reg_idx;
+
+ /* Initialize registers. */
+ for (reg_idx = 1; reg_idx < nmatch; ++reg_idx)
+ pmatch[reg_idx].rm_so = pmatch[reg_idx].rm_eo = -1;
+
+ /* Set the points where matching start/end. */
+ pmatch[0].rm_so = 0;
+ pmatch[0].rm_eo = mctx.match_last;
+
+ if (!preg->no_sub && nmatch > 1)
+ {
+ err = set_regs (preg, &mctx, nmatch, pmatch,
+ dfa->has_plural_match && dfa->nbackref > 0);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+ }
+
+ /* At last, add the offset to the each registers, since we slided
+ the buffers so that we could assume that the matching starts
+ from 0. */
+ for (reg_idx = 0; reg_idx < nmatch; ++reg_idx)
+ if (pmatch[reg_idx].rm_so != -1)
+ {
+#ifdef RE_ENABLE_I18N
+ if (BE (mctx.input.offsets_needed != 0, 0))
+ {
+ pmatch[reg_idx].rm_so =
+ (pmatch[reg_idx].rm_so == mctx.input.valid_len
+ ? mctx.input.valid_raw_len
+ : mctx.input.offsets[pmatch[reg_idx].rm_so]);
+ pmatch[reg_idx].rm_eo =
+ (pmatch[reg_idx].rm_eo == mctx.input.valid_len
+ ? mctx.input.valid_raw_len
+ : mctx.input.offsets[pmatch[reg_idx].rm_eo]);
+ }
+#else
+ assert (mctx.input.offsets_needed == 0);
+#endif
+ pmatch[reg_idx].rm_so += match_first;
+ pmatch[reg_idx].rm_eo += match_first;
+ }
+ for (reg_idx = 0; reg_idx < extra_nmatch; ++reg_idx)
+ {
+ pmatch[nmatch + reg_idx].rm_so = -1;
+ pmatch[nmatch + reg_idx].rm_eo = -1;
+ }
+
+ if (dfa->subexp_map)
+ for (reg_idx = 0; reg_idx + 1 < nmatch; reg_idx++)
+ if (dfa->subexp_map[reg_idx] != (int)reg_idx)
+ {
+ pmatch[reg_idx + 1].rm_so
+ = pmatch[dfa->subexp_map[reg_idx] + 1].rm_so;
+ pmatch[reg_idx + 1].rm_eo
+ = pmatch[dfa->subexp_map[reg_idx] + 1].rm_eo;
+ }
+ }
+
+ free_return:
+ re_free (mctx.state_log);
+ if (dfa->nbackref)
+ match_ctx_free (&mctx);
+ re_string_destruct (&mctx.input);
+ return err;
+}
+
+static reg_errcode_t
+prune_impossible_nodes (re_match_context_t *mctx)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ int halt_node, match_last;
+ reg_errcode_t ret;
+ re_dfastate_t **sifted_states;
+ re_dfastate_t **lim_states = NULL;
+ re_sift_context_t sctx;
+#ifdef DEBUG
+ assert (mctx->state_log != NULL);
+#endif
+ match_last = mctx->match_last;
+ halt_node = mctx->last_node;
+
+ /* Avoid overflow. */
+ if (BE (SIZE_MAX / sizeof (re_dfastate_t *) <= (size_t)match_last, 0))
+ return REG_ESPACE;
+
+ sifted_states = re_malloc (re_dfastate_t *, match_last + 1);
+ if (BE (sifted_states == NULL, 0))
+ {
+ ret = REG_ESPACE;
+ goto free_return;
+ }
+ if (dfa->nbackref)
+ {
+ lim_states = re_malloc (re_dfastate_t *, match_last + 1);
+ if (BE (lim_states == NULL, 0))
+ {
+ ret = REG_ESPACE;
+ goto free_return;
+ }
+ while (1)
+ {
+ memset (lim_states, '\0',
+ sizeof (re_dfastate_t *) * (match_last + 1));
+ sift_ctx_init (&sctx, sifted_states, lim_states, halt_node,
+ match_last);
+ ret = sift_states_backward (mctx, &sctx);
+ re_node_set_free (&sctx.limits);
+ if (BE (ret != REG_NOERROR, 0))
+ goto free_return;
+ if (sifted_states[0] != NULL || lim_states[0] != NULL)
+ break;
+ do
+ {
+ --match_last;
+ if (match_last < 0)
+ {
+ ret = REG_NOMATCH;
+ goto free_return;
+ }
+ } while (mctx->state_log[match_last] == NULL
+ || !mctx->state_log[match_last]->halt);
+ halt_node = check_halt_state_context (mctx,
+ mctx->state_log[match_last],
+ match_last);
+ }
+ ret = merge_state_array (dfa, sifted_states, lim_states,
+ match_last + 1);
+ re_free (lim_states);
+ lim_states = NULL;
+ if (BE (ret != REG_NOERROR, 0))
+ goto free_return;
+ }
+ else
+ {
+ sift_ctx_init (&sctx, sifted_states, lim_states, halt_node, match_last);
+ ret = sift_states_backward (mctx, &sctx);
+ re_node_set_free (&sctx.limits);
+ if (BE (ret != REG_NOERROR, 0))
+ goto free_return;
+ if (sifted_states[0] == NULL)
+ {
+ ret = REG_NOMATCH;
+ goto free_return;
+ }
+ }
+ re_free (mctx->state_log);
+ mctx->state_log = sifted_states;
+ sifted_states = NULL;
+ mctx->last_node = halt_node;
+ mctx->match_last = match_last;
+ ret = REG_NOERROR;
+ free_return:
+ re_free (sifted_states);
+ re_free (lim_states);
+ return ret;
+}
+
+/* Acquire an initial state and return it.
+ We must select appropriate initial state depending on the context,
+ since initial states may have constraints like "\<", "^", etc.. */
+
+static inline re_dfastate_t *
+__attribute ((always_inline)) internal_function
+acquire_init_state_context (reg_errcode_t *err, const re_match_context_t *mctx,
+ int idx)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ if (dfa->init_state->has_constraint)
+ {
+ unsigned int context;
+ context = re_string_context_at (&mctx->input, idx - 1, mctx->eflags);
+ if (IS_WORD_CONTEXT (context))
+ return dfa->init_state_word;
+ else if (IS_ORDINARY_CONTEXT (context))
+ return dfa->init_state;
+ else if (IS_BEGBUF_CONTEXT (context) && IS_NEWLINE_CONTEXT (context))
+ return dfa->init_state_begbuf;
+ else if (IS_NEWLINE_CONTEXT (context))
+ return dfa->init_state_nl;
+ else if (IS_BEGBUF_CONTEXT (context))
+ {
+ /* It is relatively rare case, then calculate on demand. */
+ return re_acquire_state_context (err, dfa,
+ dfa->init_state->entrance_nodes,
+ context);
+ }
+ else
+ /* Must not happen? */
+ return dfa->init_state;
+ }
+ else
+ return dfa->init_state;
+}
+
+/* Check whether the regular expression match input string INPUT or not,
+ and return the index where the matching end, return -1 if not match,
+ or return -2 in case of an error.
+ FL_LONGEST_MATCH means we want the POSIX longest matching.
+ If P_MATCH_FIRST is not NULL, and the match fails, it is set to the
+ next place where we may want to try matching.
+ Note that the matcher assume that the maching starts from the current
+ index of the buffer. */
+
+static int
+internal_function
+check_matching (re_match_context_t *mctx, int fl_longest_match,
+ int *p_match_first)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ reg_errcode_t err;
+ int match = 0;
+ int match_last = -1;
+ int cur_str_idx = re_string_cur_idx (&mctx->input);
+ re_dfastate_t *cur_state;
+ int at_init_state = p_match_first != NULL;
+ int next_start_idx = cur_str_idx;
+
+ err = REG_NOERROR;
+ cur_state = acquire_init_state_context (&err, mctx, cur_str_idx);
+ /* An initial state must not be NULL (invalid). */
+ if (BE (cur_state == NULL, 0))
+ {
+ assert (err == REG_ESPACE);
+ return -2;
+ }
+
+ if (mctx->state_log != NULL)
+ {
+ mctx->state_log[cur_str_idx] = cur_state;
+
+ /* Check OP_OPEN_SUBEXP in the initial state in case that we use them
+ later. E.g. Processing back references. */
+ if (BE (dfa->nbackref, 0))
+ {
+ at_init_state = 0;
+ err = check_subexp_matching_top (mctx, &cur_state->nodes, 0);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+
+ if (cur_state->has_backref)
+ {
+ err = transit_state_bkref (mctx, &cur_state->nodes);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ }
+ }
+
+ /* If the RE accepts NULL string. */
+ if (BE (cur_state->halt, 0))
+ {
+ if (!cur_state->has_constraint
+ || check_halt_state_context (mctx, cur_state, cur_str_idx))
+ {
+ if (!fl_longest_match)
+ return cur_str_idx;
+ else
+ {
+ match_last = cur_str_idx;
+ match = 1;
+ }
+ }
+ }
+
+ while (!re_string_eoi (&mctx->input))
+ {
+ re_dfastate_t *old_state = cur_state;
+ int next_char_idx = re_string_cur_idx (&mctx->input) + 1;
+
+ if (BE (next_char_idx >= mctx->input.bufs_len, 0)
+ || (BE (next_char_idx >= mctx->input.valid_len, 0)
+ && mctx->input.valid_len < mctx->input.len))
+ {
+ err = extend_buffers (mctx);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ assert (err == REG_ESPACE);
+ return -2;
+ }
+ }
+
+ cur_state = transit_state (&err, mctx, cur_state);
+ if (mctx->state_log != NULL)
+ cur_state = merge_state_with_log (&err, mctx, cur_state);
+
+ if (cur_state == NULL)
+ {
+ /* Reached the invalid state or an error. Try to recover a valid
+ state using the state log, if available and if we have not
+ already found a valid (even if not the longest) match. */
+ if (BE (err != REG_NOERROR, 0))
+ return -2;
+
+ if (mctx->state_log == NULL
+ || (match && !fl_longest_match)
+ || (cur_state = find_recover_state (&err, mctx)) == NULL)
+ break;
+ }
+
+ if (BE (at_init_state, 0))
+ {
+ if (old_state == cur_state)
+ next_start_idx = next_char_idx;
+ else
+ at_init_state = 0;
+ }
+
+ if (cur_state->halt)
+ {
+ /* Reached a halt state.
+ Check the halt state can satisfy the current context. */
+ if (!cur_state->has_constraint
+ || check_halt_state_context (mctx, cur_state,
+ re_string_cur_idx (&mctx->input)))
+ {
+ /* We found an appropriate halt state. */
+ match_last = re_string_cur_idx (&mctx->input);
+ match = 1;
+
+ /* We found a match, do not modify match_first below. */
+ p_match_first = NULL;
+ if (!fl_longest_match)
+ break;
+ }
+ }
+ }
+
+ if (p_match_first)
+ *p_match_first += next_start_idx;
+
+ return match_last;
+}
+
+/* Check NODE match the current context. */
+
+static int
+internal_function
+check_halt_node_context (const re_dfa_t *dfa, int node, unsigned int context)
+{
+ re_token_type_t type = dfa->nodes[node].type;
+ unsigned int constraint = dfa->nodes[node].constraint;
+ if (type != END_OF_RE)
+ return 0;
+ if (!constraint)
+ return 1;
+ if (NOT_SATISFY_NEXT_CONSTRAINT (constraint, context))
+ return 0;
+ return 1;
+}
+
+/* Check the halt state STATE match the current context.
+ Return 0 if not match, if the node, STATE has, is a halt node and
+ match the context, return the node. */
+
+static int
+internal_function
+check_halt_state_context (const re_match_context_t *mctx,
+ const re_dfastate_t *state, int idx)
+{
+ int i;
+ unsigned int context;
+#ifdef DEBUG
+ assert (state->halt);
+#endif
+ context = re_string_context_at (&mctx->input, idx, mctx->eflags);
+ for (i = 0; i < state->nodes.nelem; ++i)
+ if (check_halt_node_context (mctx->dfa, state->nodes.elems[i], context))
+ return state->nodes.elems[i];
+ return 0;
+}
+
+/* Compute the next node to which "NFA" transit from NODE("NFA" is a NFA
+ corresponding to the DFA).
+ Return the destination node, and update EPS_VIA_NODES, return -1 in case
+ of errors. */
+
+static int
+internal_function
+proceed_next_node (const re_match_context_t *mctx, int nregs, regmatch_t *regs,
+ int *pidx, int node, re_node_set *eps_via_nodes,
+ struct re_fail_stack_t *fs)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ int i, err;
+ if (IS_EPSILON_NODE (dfa->nodes[node].type))
+ {
+ re_node_set *cur_nodes = &mctx->state_log[*pidx]->nodes;
+ re_node_set *edests = &dfa->edests[node];
+ int dest_node;
+ err = re_node_set_insert (eps_via_nodes, node);
+ if (BE (err < 0, 0))
+ return -2;
+ /* Pick up a valid destination, or return -1 if none is found. */
+ for (dest_node = -1, i = 0; i < edests->nelem; ++i)
+ {
+ int candidate = edests->elems[i];
+ if (!re_node_set_contains (cur_nodes, candidate))
+ continue;
+ if (dest_node == -1)
+ dest_node = candidate;
+
+ else
+ {
+ /* In order to avoid infinite loop like "(a*)*", return the second
+ epsilon-transition if the first was already considered. */
+ if (re_node_set_contains (eps_via_nodes, dest_node))
+ return candidate;
+
+ /* Otherwise, push the second epsilon-transition on the fail stack. */
+ else if (fs != NULL
+ && push_fail_stack (fs, *pidx, candidate, nregs, regs,
+ eps_via_nodes))
+ return -2;
+
+ /* We know we are going to exit. */
+ break;
+ }
+ }
+ return dest_node;
+ }
+ else
+ {
+ int naccepted = 0;
+ re_token_type_t type = dfa->nodes[node].type;
+
+#ifdef RE_ENABLE_I18N
+ if (dfa->nodes[node].accept_mb)
+ naccepted = check_node_accept_bytes (dfa, node, &mctx->input, *pidx);
+ else
+#endif /* RE_ENABLE_I18N */
+ if (type == OP_BACK_REF)
+ {
+ int subexp_idx = dfa->nodes[node].opr.idx + 1;
+ naccepted = regs[subexp_idx].rm_eo - regs[subexp_idx].rm_so;
+ if (fs != NULL)
+ {
+ if (regs[subexp_idx].rm_so == -1 || regs[subexp_idx].rm_eo == -1)
+ return -1;
+ else if (naccepted)
+ {
+ char *buf = (char *) re_string_get_buffer (&mctx->input);
+ if (memcmp (buf + regs[subexp_idx].rm_so, buf + *pidx,
+ naccepted) != 0)
+ return -1;
+ }
+ }
+
+ if (naccepted == 0)
+ {
+ int dest_node;
+ err = re_node_set_insert (eps_via_nodes, node);
+ if (BE (err < 0, 0))
+ return -2;
+ dest_node = dfa->edests[node].elems[0];
+ if (re_node_set_contains (&mctx->state_log[*pidx]->nodes,
+ dest_node))
+ return dest_node;
+ }
+ }
+
+ if (naccepted != 0
+ || check_node_accept (mctx, dfa->nodes + node, *pidx))
+ {
+ int dest_node = dfa->nexts[node];
+ *pidx = (naccepted == 0) ? *pidx + 1 : *pidx + naccepted;
+ if (fs && (*pidx > mctx->match_last || mctx->state_log[*pidx] == NULL
+ || !re_node_set_contains (&mctx->state_log[*pidx]->nodes,
+ dest_node)))
+ return -1;
+ re_node_set_empty (eps_via_nodes);
+ return dest_node;
+ }
+ }
+ return -1;
+}
+
+static reg_errcode_t
+internal_function
+push_fail_stack (struct re_fail_stack_t *fs, int str_idx, int dest_node,
+ int nregs, regmatch_t *regs, re_node_set *eps_via_nodes)
+{
+ reg_errcode_t err;
+ int num = fs->num++;
+ if (fs->num == fs->alloc)
+ {
+ struct re_fail_stack_ent_t *new_array;
+ new_array = realloc (fs->stack, (sizeof (struct re_fail_stack_ent_t)
+ * fs->alloc * 2));
+ if (new_array == NULL)
+ return REG_ESPACE;
+ fs->alloc *= 2;
+ fs->stack = new_array;
+ }
+ fs->stack[num].idx = str_idx;
+ fs->stack[num].node = dest_node;
+ fs->stack[num].regs = re_malloc (regmatch_t, nregs);
+ if (fs->stack[num].regs == NULL)
+ return REG_ESPACE;
+ memcpy (fs->stack[num].regs, regs, sizeof (regmatch_t) * nregs);
+ err = re_node_set_init_copy (&fs->stack[num].eps_via_nodes, eps_via_nodes);
+ return err;
+}
+
+static int
+internal_function
+pop_fail_stack (struct re_fail_stack_t *fs, int *pidx, int nregs,
+ regmatch_t *regs, re_node_set *eps_via_nodes)
+{
+ int num = --fs->num;
+ assert (num >= 0);
+ *pidx = fs->stack[num].idx;
+ memcpy (regs, fs->stack[num].regs, sizeof (regmatch_t) * nregs);
+ re_node_set_free (eps_via_nodes);
+ re_free (fs->stack[num].regs);
+ *eps_via_nodes = fs->stack[num].eps_via_nodes;
+ return fs->stack[num].node;
+}
+
+/* Set the positions where the subexpressions are starts/ends to registers
+ PMATCH.
+ Note: We assume that pmatch[0] is already set, and
+ pmatch[i].rm_so == pmatch[i].rm_eo == -1 for 0 < i < nmatch. */
+
+static reg_errcode_t
+internal_function
+set_regs (const regex_t *preg, const re_match_context_t *mctx, size_t nmatch,
+ regmatch_t *pmatch, int fl_backtrack)
+{
+ const re_dfa_t *dfa = (const re_dfa_t *) preg->buffer;
+ int idx, cur_node;
+ re_node_set eps_via_nodes;
+ struct re_fail_stack_t *fs;
+ struct re_fail_stack_t fs_body = { 0, 2, NULL };
+ regmatch_t *prev_idx_match;
+ int prev_idx_match_malloced = 0;
+
+#ifdef DEBUG
+ assert (nmatch > 1);
+ assert (mctx->state_log != NULL);
+#endif
+ if (fl_backtrack)
+ {
+ fs = &fs_body;
+ fs->stack = re_malloc (struct re_fail_stack_ent_t, fs->alloc);
+ if (fs->stack == NULL)
+ return REG_ESPACE;
+ }
+ else
+ fs = NULL;
+
+ cur_node = dfa->init_node;
+ re_node_set_init_empty (&eps_via_nodes);
+
+#ifdef HAVE_ALLOCA
+ if (__libc_use_alloca (nmatch * sizeof (regmatch_t)))
+ prev_idx_match = (regmatch_t *) alloca (nmatch * sizeof (regmatch_t));
+ else
+#endif
+ {
+ prev_idx_match = re_malloc (regmatch_t, nmatch);
+ if (prev_idx_match == NULL)
+ {
+ free_fail_stack_return (fs);
+ return REG_ESPACE;
+ }
+ prev_idx_match_malloced = 1;
+ }
+ memcpy (prev_idx_match, pmatch, sizeof (regmatch_t) * nmatch);
+
+ for (idx = pmatch[0].rm_so; idx <= pmatch[0].rm_eo ;)
+ {
+ update_regs (dfa, pmatch, prev_idx_match, cur_node, idx, nmatch);
+
+ if (idx == pmatch[0].rm_eo && cur_node == mctx->last_node)
+ {
+ unsigned int reg_idx;
+ if (fs)
+ {
+ for (reg_idx = 0; reg_idx < nmatch; ++reg_idx)
+ if (pmatch[reg_idx].rm_so > -1 && pmatch[reg_idx].rm_eo == -1)
+ break;
+ if (reg_idx == nmatch)
+ {
+ re_node_set_free (&eps_via_nodes);
+ if (prev_idx_match_malloced)
+ re_free (prev_idx_match);
+ return free_fail_stack_return (fs);
+ }
+ cur_node = pop_fail_stack (fs, &idx, nmatch, pmatch,
+ &eps_via_nodes);
+ }
+ else
+ {
+ re_node_set_free (&eps_via_nodes);
+ if (prev_idx_match_malloced)
+ re_free (prev_idx_match);
+ return REG_NOERROR;
+ }
+ }
+
+ /* Proceed to next node. */
+ cur_node = proceed_next_node (mctx, nmatch, pmatch, &idx, cur_node,
+ &eps_via_nodes, fs);
+
+ if (BE (cur_node < 0, 0))
+ {
+ if (BE (cur_node == -2, 0))
+ {
+ re_node_set_free (&eps_via_nodes);
+ if (prev_idx_match_malloced)
+ re_free (prev_idx_match);
+ free_fail_stack_return (fs);
+ return REG_ESPACE;
+ }
+ if (fs)
+ cur_node = pop_fail_stack (fs, &idx, nmatch, pmatch,
+ &eps_via_nodes);
+ else
+ {
+ re_node_set_free (&eps_via_nodes);
+ if (prev_idx_match_malloced)
+ re_free (prev_idx_match);
+ return REG_NOMATCH;
+ }
+ }
+ }
+ re_node_set_free (&eps_via_nodes);
+ if (prev_idx_match_malloced)
+ re_free (prev_idx_match);
+ return free_fail_stack_return (fs);
+}
+
+static reg_errcode_t
+internal_function
+free_fail_stack_return (struct re_fail_stack_t *fs)
+{
+ if (fs)
+ {
+ int fs_idx;
+ for (fs_idx = 0; fs_idx < fs->num; ++fs_idx)
+ {
+ re_node_set_free (&fs->stack[fs_idx].eps_via_nodes);
+ re_free (fs->stack[fs_idx].regs);
+ }
+ re_free (fs->stack);
+ }
+ return REG_NOERROR;
+}
+
+static void
+internal_function
+update_regs (const re_dfa_t *dfa, regmatch_t *pmatch,
+ regmatch_t *prev_idx_match, int cur_node, int cur_idx, int nmatch)
+{
+ int type = dfa->nodes[cur_node].type;
+ if (type == OP_OPEN_SUBEXP)
+ {
+ int reg_num = dfa->nodes[cur_node].opr.idx + 1;
+
+ /* We are at the first node of this sub expression. */
+ if (reg_num < nmatch)
+ {
+ pmatch[reg_num].rm_so = cur_idx;
+ pmatch[reg_num].rm_eo = -1;
+ }
+ }
+ else if (type == OP_CLOSE_SUBEXP)
+ {
+ int reg_num = dfa->nodes[cur_node].opr.idx + 1;
+ if (reg_num < nmatch)
+ {
+ /* We are at the last node of this sub expression. */
+ if (pmatch[reg_num].rm_so < cur_idx)
+ {
+ pmatch[reg_num].rm_eo = cur_idx;
+ /* This is a non-empty match or we are not inside an optional
+ subexpression. Accept this right away. */
+ memcpy (prev_idx_match, pmatch, sizeof (regmatch_t) * nmatch);
+ }
+ else
+ {
+ if (dfa->nodes[cur_node].opt_subexp
+ && prev_idx_match[reg_num].rm_so != -1)
+ /* We transited through an empty match for an optional
+ subexpression, like (a?)*, and this is not the subexp's
+ first match. Copy back the old content of the registers
+ so that matches of an inner subexpression are undone as
+ well, like in ((a?))*. */
+ memcpy (pmatch, prev_idx_match, sizeof (regmatch_t) * nmatch);
+ else
+ /* We completed a subexpression, but it may be part of
+ an optional one, so do not update PREV_IDX_MATCH. */
+ pmatch[reg_num].rm_eo = cur_idx;
+ }
+ }
+ }
+}
+
+/* This function checks the STATE_LOG from the SCTX->last_str_idx to 0
+ and sift the nodes in each states according to the following rules.
+ Updated state_log will be wrote to STATE_LOG.
+
+ Rules: We throw away the Node `a' in the STATE_LOG[STR_IDX] if...
+ 1. When STR_IDX == MATCH_LAST(the last index in the state_log):
+ If `a' isn't the LAST_NODE and `a' can't epsilon transit to
+ the LAST_NODE, we throw away the node `a'.
+ 2. When 0 <= STR_IDX < MATCH_LAST and `a' accepts
+ string `s' and transit to `b':
+ i. If 'b' isn't in the STATE_LOG[STR_IDX+strlen('s')], we throw
+ away the node `a'.
+ ii. If 'b' is in the STATE_LOG[STR_IDX+strlen('s')] but 'b' is
+ thrown away, we throw away the node `a'.
+ 3. When 0 <= STR_IDX < MATCH_LAST and 'a' epsilon transit to 'b':
+ i. If 'b' isn't in the STATE_LOG[STR_IDX], we throw away the
+ node `a'.
+ ii. If 'b' is in the STATE_LOG[STR_IDX] but 'b' is thrown away,
+ we throw away the node `a'. */
+
+#define STATE_NODE_CONTAINS(state,node) \
+ ((state) != NULL && re_node_set_contains (&(state)->nodes, node))
+
+static reg_errcode_t
+internal_function
+sift_states_backward (const re_match_context_t *mctx, re_sift_context_t *sctx)
+{
+ reg_errcode_t err;
+ int null_cnt = 0;
+ int str_idx = sctx->last_str_idx;
+ re_node_set cur_dest;
+
+#ifdef DEBUG
+ assert (mctx->state_log != NULL && mctx->state_log[str_idx] != NULL);
+#endif
+
+ /* Build sifted state_log[str_idx]. It has the nodes which can epsilon
+ transit to the last_node and the last_node itself. */
+ err = re_node_set_init_1 (&cur_dest, sctx->last_node);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ err = update_cur_sifted_state (mctx, sctx, str_idx, &cur_dest);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+
+ /* Then check each states in the state_log. */
+ while (str_idx > 0)
+ {
+ /* Update counters. */
+ null_cnt = (sctx->sifted_states[str_idx] == NULL) ? null_cnt + 1 : 0;
+ if (null_cnt > mctx->max_mb_elem_len)
+ {
+ memset (sctx->sifted_states, '\0',
+ sizeof (re_dfastate_t *) * str_idx);
+ re_node_set_free (&cur_dest);
+ return REG_NOERROR;
+ }
+ re_node_set_empty (&cur_dest);
+ --str_idx;
+
+ if (mctx->state_log[str_idx])
+ {
+ err = build_sifted_states (mctx, sctx, str_idx, &cur_dest);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+ }
+
+ /* Add all the nodes which satisfy the following conditions:
+ - It can epsilon transit to a node in CUR_DEST.
+ - It is in CUR_SRC.
+ And update state_log. */
+ err = update_cur_sifted_state (mctx, sctx, str_idx, &cur_dest);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+ }
+ err = REG_NOERROR;
+ free_return:
+ re_node_set_free (&cur_dest);
+ return err;
+}
+
+static reg_errcode_t
+internal_function
+build_sifted_states (const re_match_context_t *mctx, re_sift_context_t *sctx,
+ int str_idx, re_node_set *cur_dest)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ const re_node_set *cur_src = &mctx->state_log[str_idx]->non_eps_nodes;
+ int i;
+
+ /* Then build the next sifted state.
+ We build the next sifted state on `cur_dest', and update
+ `sifted_states[str_idx]' with `cur_dest'.
+ Note:
+ `cur_dest' is the sifted state from `state_log[str_idx + 1]'.
+ `cur_src' points the node_set of the old `state_log[str_idx]'
+ (with the epsilon nodes pre-filtered out). */
+ for (i = 0; i < cur_src->nelem; i++)
+ {
+ int prev_node = cur_src->elems[i];
+ int naccepted = 0;
+ int ret;
+
+#ifdef DEBUG
+ re_token_type_t type = dfa->nodes[prev_node].type;
+ assert (!IS_EPSILON_NODE (type));
+#endif
+#ifdef RE_ENABLE_I18N
+ /* If the node may accept `multi byte'. */
+ if (dfa->nodes[prev_node].accept_mb)
+ naccepted = sift_states_iter_mb (mctx, sctx, prev_node,
+ str_idx, sctx->last_str_idx);
+#endif /* RE_ENABLE_I18N */
+
+ /* We don't check backreferences here.
+ See update_cur_sifted_state(). */
+ if (!naccepted
+ && check_node_accept (mctx, dfa->nodes + prev_node, str_idx)
+ && STATE_NODE_CONTAINS (sctx->sifted_states[str_idx + 1],
+ dfa->nexts[prev_node]))
+ naccepted = 1;
+
+ if (naccepted == 0)
+ continue;
+
+ if (sctx->limits.nelem)
+ {
+ int to_idx = str_idx + naccepted;
+ if (check_dst_limits (mctx, &sctx->limits,
+ dfa->nexts[prev_node], to_idx,
+ prev_node, str_idx))
+ continue;
+ }
+ ret = re_node_set_insert (cur_dest, prev_node);
+ if (BE (ret == -1, 0))
+ return REG_ESPACE;
+ }
+
+ return REG_NOERROR;
+}
+
+/* Helper functions. */
+
+static reg_errcode_t
+internal_function
+clean_state_log_if_needed (re_match_context_t *mctx, int next_state_log_idx)
+{
+ int top = mctx->state_log_top;
+
+ if (next_state_log_idx >= mctx->input.bufs_len
+ || (next_state_log_idx >= mctx->input.valid_len
+ && mctx->input.valid_len < mctx->input.len))
+ {
+ reg_errcode_t err;
+ err = extend_buffers (mctx);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+
+ if (top < next_state_log_idx)
+ {
+ memset (mctx->state_log + top + 1, '\0',
+ sizeof (re_dfastate_t *) * (next_state_log_idx - top));
+ mctx->state_log_top = next_state_log_idx;
+ }
+ return REG_NOERROR;
+}
+
+static reg_errcode_t
+internal_function
+merge_state_array (const re_dfa_t *dfa, re_dfastate_t **dst,
+ re_dfastate_t **src, int num)
+{
+ int st_idx;
+ reg_errcode_t err;
+ for (st_idx = 0; st_idx < num; ++st_idx)
+ {
+ if (dst[st_idx] == NULL)
+ dst[st_idx] = src[st_idx];
+ else if (src[st_idx] != NULL)
+ {
+ re_node_set merged_set;
+ err = re_node_set_init_union (&merged_set, &dst[st_idx]->nodes,
+ &src[st_idx]->nodes);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ dst[st_idx] = re_acquire_state (&err, dfa, &merged_set);
+ re_node_set_free (&merged_set);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ }
+ return REG_NOERROR;
+}
+
+static reg_errcode_t
+internal_function
+update_cur_sifted_state (const re_match_context_t *mctx,
+ re_sift_context_t *sctx, int str_idx,
+ re_node_set *dest_nodes)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ reg_errcode_t err = REG_NOERROR;
+ const re_node_set *candidates;
+ candidates = ((mctx->state_log[str_idx] == NULL) ? NULL
+ : &mctx->state_log[str_idx]->nodes);
+
+ if (dest_nodes->nelem == 0)
+ sctx->sifted_states[str_idx] = NULL;
+ else
+ {
+ if (candidates)
+ {
+ /* At first, add the nodes which can epsilon transit to a node in
+ DEST_NODE. */
+ err = add_epsilon_src_nodes (dfa, dest_nodes, candidates);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+
+ /* Then, check the limitations in the current sift_context. */
+ if (sctx->limits.nelem)
+ {
+ err = check_subexp_limits (dfa, dest_nodes, candidates, &sctx->limits,
+ mctx->bkref_ents, str_idx);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ }
+
+ sctx->sifted_states[str_idx] = re_acquire_state (&err, dfa, dest_nodes);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+
+ if (candidates && mctx->state_log[str_idx]->has_backref)
+ {
+ err = sift_states_bkref (mctx, sctx, str_idx, candidates);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ return REG_NOERROR;
+}
+
+static reg_errcode_t
+internal_function
+add_epsilon_src_nodes (const re_dfa_t *dfa, re_node_set *dest_nodes,
+ const re_node_set *candidates)
+{
+ reg_errcode_t err = REG_NOERROR;
+ int i;
+
+ re_dfastate_t *state = re_acquire_state (&err, dfa, dest_nodes);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+
+ if (!state->inveclosure.alloc)
+ {
+ err = re_node_set_alloc (&state->inveclosure, dest_nodes->nelem);
+ if (BE (err != REG_NOERROR, 0))
+ return REG_ESPACE;
+ for (i = 0; i < dest_nodes->nelem; i++)
+ {
+ err = re_node_set_merge (&state->inveclosure,
+ dfa->inveclosures + dest_nodes->elems[i]);
+ if (BE (err != REG_NOERROR, 0))
+ return REG_ESPACE;
+ }
+ }
+ return re_node_set_add_intersect (dest_nodes, candidates,
+ &state->inveclosure);
+}
+
+static reg_errcode_t
+internal_function
+sub_epsilon_src_nodes (const re_dfa_t *dfa, int node, re_node_set *dest_nodes,
+ const re_node_set *candidates)
+{
+ int ecl_idx;
+ reg_errcode_t err;
+ re_node_set *inv_eclosure = dfa->inveclosures + node;
+ re_node_set except_nodes;
+ re_node_set_init_empty (&except_nodes);
+ for (ecl_idx = 0; ecl_idx < inv_eclosure->nelem; ++ecl_idx)
+ {
+ int cur_node = inv_eclosure->elems[ecl_idx];
+ if (cur_node == node)
+ continue;
+ if (IS_EPSILON_NODE (dfa->nodes[cur_node].type))
+ {
+ int edst1 = dfa->edests[cur_node].elems[0];
+ int edst2 = ((dfa->edests[cur_node].nelem > 1)
+ ? dfa->edests[cur_node].elems[1] : -1);
+ if ((!re_node_set_contains (inv_eclosure, edst1)
+ && re_node_set_contains (dest_nodes, edst1))
+ || (edst2 > 0
+ && !re_node_set_contains (inv_eclosure, edst2)
+ && re_node_set_contains (dest_nodes, edst2)))
+ {
+ err = re_node_set_add_intersect (&except_nodes, candidates,
+ dfa->inveclosures + cur_node);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&except_nodes);
+ return err;
+ }
+ }
+ }
+ }
+ for (ecl_idx = 0; ecl_idx < inv_eclosure->nelem; ++ecl_idx)
+ {
+ int cur_node = inv_eclosure->elems[ecl_idx];
+ if (!re_node_set_contains (&except_nodes, cur_node))
+ {
+ int idx = re_node_set_contains (dest_nodes, cur_node) - 1;
+ re_node_set_remove_at (dest_nodes, idx);
+ }
+ }
+ re_node_set_free (&except_nodes);
+ return REG_NOERROR;
+}
+
+static int
+internal_function
+check_dst_limits (const re_match_context_t *mctx, re_node_set *limits,
+ int dst_node, int dst_idx, int src_node, int src_idx)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ int lim_idx, src_pos, dst_pos;
+
+ int dst_bkref_idx = search_cur_bkref_entry (mctx, dst_idx);
+ int src_bkref_idx = search_cur_bkref_entry (mctx, src_idx);
+ for (lim_idx = 0; lim_idx < limits->nelem; ++lim_idx)
+ {
+ int subexp_idx;
+ struct re_backref_cache_entry *ent;
+ ent = mctx->bkref_ents + limits->elems[lim_idx];
+ subexp_idx = dfa->nodes[ent->node].opr.idx;
+
+ dst_pos = check_dst_limits_calc_pos (mctx, limits->elems[lim_idx],
+ subexp_idx, dst_node, dst_idx,
+ dst_bkref_idx);
+ src_pos = check_dst_limits_calc_pos (mctx, limits->elems[lim_idx],
+ subexp_idx, src_node, src_idx,
+ src_bkref_idx);
+
+ /* In case of:
+ <src> <dst> ( <subexp> )
+ ( <subexp> ) <src> <dst>
+ ( <subexp1> <src> <subexp2> <dst> <subexp3> ) */
+ if (src_pos == dst_pos)
+ continue; /* This is unrelated limitation. */
+ else
+ return 1;
+ }
+ return 0;
+}
+
+static int
+internal_function
+check_dst_limits_calc_pos_1 (const re_match_context_t *mctx, int boundaries,
+ int subexp_idx, int from_node, int bkref_idx)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ const re_node_set *eclosures = dfa->eclosures + from_node;
+ int node_idx;
+
+ /* Else, we are on the boundary: examine the nodes on the epsilon
+ closure. */
+ for (node_idx = 0; node_idx < eclosures->nelem; ++node_idx)
+ {
+ int node = eclosures->elems[node_idx];
+ switch (dfa->nodes[node].type)
+ {
+ case OP_BACK_REF:
+ if (bkref_idx != -1)
+ {
+ struct re_backref_cache_entry *ent = mctx->bkref_ents + bkref_idx;
+ do
+ {
+ int dst, cpos;
+
+ if (ent->node != node)
+ continue;
+
+ if (subexp_idx < BITSET_WORD_BITS
+ && !(ent->eps_reachable_subexps_map
+ & ((bitset_word_t) 1 << subexp_idx)))
+ continue;
+
+ /* Recurse trying to reach the OP_OPEN_SUBEXP and
+ OP_CLOSE_SUBEXP cases below. But, if the
+ destination node is the same node as the source
+ node, don't recurse because it would cause an
+ infinite loop: a regex that exhibits this behavior
+ is ()\1*\1* */
+ dst = dfa->edests[node].elems[0];
+ if (dst == from_node)
+ {
+ if (boundaries & 1)
+ return -1;
+ else /* if (boundaries & 2) */
+ return 0;
+ }
+
+ cpos =
+ check_dst_limits_calc_pos_1 (mctx, boundaries, subexp_idx,
+ dst, bkref_idx);
+ if (cpos == -1 /* && (boundaries & 1) */)
+ return -1;
+ if (cpos == 0 && (boundaries & 2))
+ return 0;
+
+ if (subexp_idx < BITSET_WORD_BITS)
+ ent->eps_reachable_subexps_map
+ &= ~((bitset_word_t) 1 << subexp_idx);
+ }
+ while (ent++->more);
+ }
+ break;
+
+ case OP_OPEN_SUBEXP:
+ if ((boundaries & 1) && subexp_idx == dfa->nodes[node].opr.idx)
+ return -1;
+ break;
+
+ case OP_CLOSE_SUBEXP:
+ if ((boundaries & 2) && subexp_idx == dfa->nodes[node].opr.idx)
+ return 0;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return (boundaries & 2) ? 1 : 0;
+}
+
+static int
+internal_function
+check_dst_limits_calc_pos (const re_match_context_t *mctx, int limit,
+ int subexp_idx, int from_node, int str_idx,
+ int bkref_idx)
+{
+ struct re_backref_cache_entry *lim = mctx->bkref_ents + limit;
+ int boundaries;
+
+ /* If we are outside the range of the subexpression, return -1 or 1. */
+ if (str_idx < lim->subexp_from)
+ return -1;
+
+ if (lim->subexp_to < str_idx)
+ return 1;
+
+ /* If we are within the subexpression, return 0. */
+ boundaries = (str_idx == lim->subexp_from);
+ boundaries |= (str_idx == lim->subexp_to) << 1;
+ if (boundaries == 0)
+ return 0;
+
+ /* Else, examine epsilon closure. */
+ return check_dst_limits_calc_pos_1 (mctx, boundaries, subexp_idx,
+ from_node, bkref_idx);
+}
+
+/* Check the limitations of sub expressions LIMITS, and remove the nodes
+ which are against limitations from DEST_NODES. */
+
+static reg_errcode_t
+internal_function
+check_subexp_limits (const re_dfa_t *dfa, re_node_set *dest_nodes,
+ const re_node_set *candidates, re_node_set *limits,
+ struct re_backref_cache_entry *bkref_ents, int str_idx)
+{
+ reg_errcode_t err;
+ int node_idx, lim_idx;
+
+ for (lim_idx = 0; lim_idx < limits->nelem; ++lim_idx)
+ {
+ int subexp_idx;
+ struct re_backref_cache_entry *ent;
+ ent = bkref_ents + limits->elems[lim_idx];
+
+ if (str_idx <= ent->subexp_from || ent->str_idx < str_idx)
+ continue; /* This is unrelated limitation. */
+
+ subexp_idx = dfa->nodes[ent->node].opr.idx;
+ if (ent->subexp_to == str_idx)
+ {
+ int ops_node = -1;
+ int cls_node = -1;
+ for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx)
+ {
+ int node = dest_nodes->elems[node_idx];
+ re_token_type_t type = dfa->nodes[node].type;
+ if (type == OP_OPEN_SUBEXP
+ && subexp_idx == dfa->nodes[node].opr.idx)
+ ops_node = node;
+ else if (type == OP_CLOSE_SUBEXP
+ && subexp_idx == dfa->nodes[node].opr.idx)
+ cls_node = node;
+ }
+
+ /* Check the limitation of the open subexpression. */
+ /* Note that (ent->subexp_to = str_idx != ent->subexp_from). */
+ if (ops_node >= 0)
+ {
+ err = sub_epsilon_src_nodes (dfa, ops_node, dest_nodes,
+ candidates);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+
+ /* Check the limitation of the close subexpression. */
+ if (cls_node >= 0)
+ for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx)
+ {
+ int node = dest_nodes->elems[node_idx];
+ if (!re_node_set_contains (dfa->inveclosures + node,
+ cls_node)
+ && !re_node_set_contains (dfa->eclosures + node,
+ cls_node))
+ {
+ /* It is against this limitation.
+ Remove it form the current sifted state. */
+ err = sub_epsilon_src_nodes (dfa, node, dest_nodes,
+ candidates);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ --node_idx;
+ }
+ }
+ }
+ else /* (ent->subexp_to != str_idx) */
+ {
+ for (node_idx = 0; node_idx < dest_nodes->nelem; ++node_idx)
+ {
+ int node = dest_nodes->elems[node_idx];
+ re_token_type_t type = dfa->nodes[node].type;
+ if (type == OP_CLOSE_SUBEXP || type == OP_OPEN_SUBEXP)
+ {
+ if (subexp_idx != dfa->nodes[node].opr.idx)
+ continue;
+ /* It is against this limitation.
+ Remove it form the current sifted state. */
+ err = sub_epsilon_src_nodes (dfa, node, dest_nodes,
+ candidates);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ }
+ }
+ }
+ return REG_NOERROR;
+}
+
+static reg_errcode_t
+internal_function
+sift_states_bkref (const re_match_context_t *mctx, re_sift_context_t *sctx,
+ int str_idx, const re_node_set *candidates)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ reg_errcode_t err;
+ int node_idx, node;
+ re_sift_context_t local_sctx;
+ int first_idx = search_cur_bkref_entry (mctx, str_idx);
+
+ if (first_idx == -1)
+ return REG_NOERROR;
+
+ local_sctx.sifted_states = NULL; /* Mark that it hasn't been initialized. */
+
+ for (node_idx = 0; node_idx < candidates->nelem; ++node_idx)
+ {
+ int enabled_idx;
+ re_token_type_t type;
+ struct re_backref_cache_entry *entry;
+ node = candidates->elems[node_idx];
+ type = dfa->nodes[node].type;
+ /* Avoid infinite loop for the REs like "()\1+". */
+ if (node == sctx->last_node && str_idx == sctx->last_str_idx)
+ continue;
+ if (type != OP_BACK_REF)
+ continue;
+
+ entry = mctx->bkref_ents + first_idx;
+ enabled_idx = first_idx;
+ do
+ {
+ int subexp_len;
+ int to_idx;
+ int dst_node;
+ int ret;
+ re_dfastate_t *cur_state;
+
+ if (entry->node != node)
+ continue;
+ subexp_len = entry->subexp_to - entry->subexp_from;
+ to_idx = str_idx + subexp_len;
+ dst_node = (subexp_len ? dfa->nexts[node]
+ : dfa->edests[node].elems[0]);
+
+ if (to_idx > sctx->last_str_idx
+ || sctx->sifted_states[to_idx] == NULL
+ || !STATE_NODE_CONTAINS (sctx->sifted_states[to_idx], dst_node)
+ || check_dst_limits (mctx, &sctx->limits, node,
+ str_idx, dst_node, to_idx))
+ continue;
+
+ if (local_sctx.sifted_states == NULL)
+ {
+ local_sctx = *sctx;
+ err = re_node_set_init_copy (&local_sctx.limits, &sctx->limits);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+ }
+ local_sctx.last_node = node;
+ local_sctx.last_str_idx = str_idx;
+ ret = re_node_set_insert (&local_sctx.limits, enabled_idx);
+ if (BE (ret < 0, 0))
+ {
+ err = REG_ESPACE;
+ goto free_return;
+ }
+ cur_state = local_sctx.sifted_states[str_idx];
+ err = sift_states_backward (mctx, &local_sctx);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+ if (sctx->limited_states != NULL)
+ {
+ err = merge_state_array (dfa, sctx->limited_states,
+ local_sctx.sifted_states,
+ str_idx + 1);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+ }
+ local_sctx.sifted_states[str_idx] = cur_state;
+ re_node_set_remove (&local_sctx.limits, enabled_idx);
+
+ /* mctx->bkref_ents may have changed, reload the pointer. */
+ entry = mctx->bkref_ents + enabled_idx;
+ }
+ while (enabled_idx++, entry++->more);
+ }
+ err = REG_NOERROR;
+ free_return:
+ if (local_sctx.sifted_states != NULL)
+ {
+ re_node_set_free (&local_sctx.limits);
+ }
+
+ return err;
+}
+
+
+#ifdef RE_ENABLE_I18N
+static int
+internal_function
+sift_states_iter_mb (const re_match_context_t *mctx, re_sift_context_t *sctx,
+ int node_idx, int str_idx, int max_str_idx)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ int naccepted;
+ /* Check the node can accept `multi byte'. */
+ naccepted = check_node_accept_bytes (dfa, node_idx, &mctx->input, str_idx);
+ if (naccepted > 0 && str_idx + naccepted <= max_str_idx &&
+ !STATE_NODE_CONTAINS (sctx->sifted_states[str_idx + naccepted],
+ dfa->nexts[node_idx]))
+ /* The node can't accept the `multi byte', or the
+ destination was already thrown away, then the node
+ could't accept the current input `multi byte'. */
+ naccepted = 0;
+ /* Otherwise, it is sure that the node could accept
+ `naccepted' bytes input. */
+ return naccepted;
+}
+#endif /* RE_ENABLE_I18N */
+
+
+/* Functions for state transition. */
+
+/* Return the next state to which the current state STATE will transit by
+ accepting the current input byte, and update STATE_LOG if necessary.
+ If STATE can accept a multibyte char/collating element/back reference
+ update the destination of STATE_LOG. */
+
+static re_dfastate_t *
+internal_function
+transit_state (reg_errcode_t *err, re_match_context_t *mctx,
+ re_dfastate_t *state)
+{
+ re_dfastate_t **trtable;
+ unsigned char ch;
+
+#ifdef RE_ENABLE_I18N
+ /* If the current state can accept multibyte. */
+ if (BE (state->accept_mb, 0))
+ {
+ *err = transit_state_mb (mctx, state);
+ if (BE (*err != REG_NOERROR, 0))
+ return NULL;
+ }
+#endif /* RE_ENABLE_I18N */
+
+ /* Then decide the next state with the single byte. */
+#if 0
+ if (0)
+ /* don't use transition table */
+ return transit_state_sb (err, mctx, state);
+#endif
+
+ /* Use transition table */
+ ch = re_string_fetch_byte (&mctx->input);
+ for (;;)
+ {
+ trtable = state->trtable;
+ if (BE (trtable != NULL, 1))
+ return trtable[ch];
+
+ trtable = state->word_trtable;
+ if (BE (trtable != NULL, 1))
+ {
+ unsigned int context;
+ context
+ = re_string_context_at (&mctx->input,
+ re_string_cur_idx (&mctx->input) - 1,
+ mctx->eflags);
+ if (IS_WORD_CONTEXT (context))
+ return trtable[ch + SBC_MAX];
+ else
+ return trtable[ch];
+ }
+
+ if (!build_trtable (mctx->dfa, state))
+ {
+ *err = REG_ESPACE;
+ return NULL;
+ }
+
+ /* Retry, we now have a transition table. */
+ }
+}
+
+/* Update the state_log if we need */
+re_dfastate_t *
+internal_function
+merge_state_with_log (reg_errcode_t *err, re_match_context_t *mctx,
+ re_dfastate_t *next_state)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ int cur_idx = re_string_cur_idx (&mctx->input);
+
+ if (cur_idx > mctx->state_log_top)
+ {
+ mctx->state_log[cur_idx] = next_state;
+ mctx->state_log_top = cur_idx;
+ }
+ else if (mctx->state_log[cur_idx] == 0)
+ {
+ mctx->state_log[cur_idx] = next_state;
+ }
+ else
+ {
+ re_dfastate_t *pstate;
+ unsigned int context;
+ re_node_set next_nodes, *log_nodes, *table_nodes = NULL;
+ /* If (state_log[cur_idx] != 0), it implies that cur_idx is
+ the destination of a multibyte char/collating element/
+ back reference. Then the next state is the union set of
+ these destinations and the results of the transition table. */
+ pstate = mctx->state_log[cur_idx];
+ log_nodes = pstate->entrance_nodes;
+ if (next_state != NULL)
+ {
+ table_nodes = next_state->entrance_nodes;
+ *err = re_node_set_init_union (&next_nodes, table_nodes,
+ log_nodes);
+ if (BE (*err != REG_NOERROR, 0))
+ return NULL;
+ }
+ else
+ next_nodes = *log_nodes;
+ /* Note: We already add the nodes of the initial state,
+ then we don't need to add them here. */
+
+ context = re_string_context_at (&mctx->input,
+ re_string_cur_idx (&mctx->input) - 1,
+ mctx->eflags);
+ next_state = mctx->state_log[cur_idx]
+ = re_acquire_state_context (err, dfa, &next_nodes, context);
+ /* We don't need to check errors here, since the return value of
+ this function is next_state and ERR is already set. */
+
+ if (table_nodes != NULL)
+ re_node_set_free (&next_nodes);
+ }
+
+ if (BE (dfa->nbackref, 0) && next_state != NULL)
+ {
+ /* Check OP_OPEN_SUBEXP in the current state in case that we use them
+ later. We must check them here, since the back references in the
+ next state might use them. */
+ *err = check_subexp_matching_top (mctx, &next_state->nodes,
+ cur_idx);
+ if (BE (*err != REG_NOERROR, 0))
+ return NULL;
+
+ /* If the next state has back references. */
+ if (next_state->has_backref)
+ {
+ *err = transit_state_bkref (mctx, &next_state->nodes);
+ if (BE (*err != REG_NOERROR, 0))
+ return NULL;
+ next_state = mctx->state_log[cur_idx];
+ }
+ }
+
+ return next_state;
+}
+
+/* Skip bytes in the input that correspond to part of a
+ multi-byte match, then look in the log for a state
+ from which to restart matching. */
+re_dfastate_t *
+internal_function
+find_recover_state (reg_errcode_t *err, re_match_context_t *mctx)
+{
+ re_dfastate_t *cur_state;
+ do
+ {
+ int max = mctx->state_log_top;
+ int cur_str_idx = re_string_cur_idx (&mctx->input);
+
+ do
+ {
+ if (++cur_str_idx > max)
+ return NULL;
+ re_string_skip_bytes (&mctx->input, 1);
+ }
+ while (mctx->state_log[cur_str_idx] == NULL);
+
+ cur_state = merge_state_with_log (err, mctx, NULL);
+ }
+ while (*err == REG_NOERROR && cur_state == NULL);
+ return cur_state;
+}
+
+/* Helper functions for transit_state. */
+
+/* From the node set CUR_NODES, pick up the nodes whose types are
+ OP_OPEN_SUBEXP and which have corresponding back references in the regular
+ expression. And register them to use them later for evaluating the
+ correspoding back references. */
+
+static reg_errcode_t
+internal_function
+check_subexp_matching_top (re_match_context_t *mctx, re_node_set *cur_nodes,
+ int str_idx)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ int node_idx;
+ reg_errcode_t err;
+
+ /* TODO: This isn't efficient.
+ Because there might be more than one nodes whose types are
+ OP_OPEN_SUBEXP and whose index is SUBEXP_IDX, we must check all
+ nodes.
+ E.g. RE: (a){2} */
+ for (node_idx = 0; node_idx < cur_nodes->nelem; ++node_idx)
+ {
+ int node = cur_nodes->elems[node_idx];
+ if (dfa->nodes[node].type == OP_OPEN_SUBEXP
+ && dfa->nodes[node].opr.idx < BITSET_WORD_BITS
+ && (dfa->used_bkref_map
+ & ((bitset_word_t) 1 << dfa->nodes[node].opr.idx)))
+ {
+ err = match_ctx_add_subtop (mctx, node, str_idx);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ }
+ return REG_NOERROR;
+}
+
+#if 0
+/* Return the next state to which the current state STATE will transit by
+ accepting the current input byte. */
+
+static re_dfastate_t *
+transit_state_sb (reg_errcode_t *err, re_match_context_t *mctx,
+ re_dfastate_t *state)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ re_node_set next_nodes;
+ re_dfastate_t *next_state;
+ int node_cnt, cur_str_idx = re_string_cur_idx (&mctx->input);
+ unsigned int context;
+
+ *err = re_node_set_alloc (&next_nodes, state->nodes.nelem + 1);
+ if (BE (*err != REG_NOERROR, 0))
+ return NULL;
+ for (node_cnt = 0; node_cnt < state->nodes.nelem; ++node_cnt)
+ {
+ int cur_node = state->nodes.elems[node_cnt];
+ if (check_node_accept (mctx, dfa->nodes + cur_node, cur_str_idx))
+ {
+ *err = re_node_set_merge (&next_nodes,
+ dfa->eclosures + dfa->nexts[cur_node]);
+ if (BE (*err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&next_nodes);
+ return NULL;
+ }
+ }
+ }
+ context = re_string_context_at (&mctx->input, cur_str_idx, mctx->eflags);
+ next_state = re_acquire_state_context (err, dfa, &next_nodes, context);
+ /* We don't need to check errors here, since the return value of
+ this function is next_state and ERR is already set. */
+
+ re_node_set_free (&next_nodes);
+ re_string_skip_bytes (&mctx->input, 1);
+ return next_state;
+}
+#endif
+
+#ifdef RE_ENABLE_I18N
+static reg_errcode_t
+internal_function
+transit_state_mb (re_match_context_t *mctx, re_dfastate_t *pstate)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ reg_errcode_t err;
+ int i;
+
+ for (i = 0; i < pstate->nodes.nelem; ++i)
+ {
+ re_node_set dest_nodes, *new_nodes;
+ int cur_node_idx = pstate->nodes.elems[i];
+ int naccepted, dest_idx;
+ unsigned int context;
+ re_dfastate_t *dest_state;
+
+ if (!dfa->nodes[cur_node_idx].accept_mb)
+ continue;
+
+ if (dfa->nodes[cur_node_idx].constraint)
+ {
+ context = re_string_context_at (&mctx->input,
+ re_string_cur_idx (&mctx->input),
+ mctx->eflags);
+ if (NOT_SATISFY_NEXT_CONSTRAINT (dfa->nodes[cur_node_idx].constraint,
+ context))
+ continue;
+ }
+
+ /* How many bytes the node can accept? */
+ naccepted = check_node_accept_bytes (dfa, cur_node_idx, &mctx->input,
+ re_string_cur_idx (&mctx->input));
+ if (naccepted == 0)
+ continue;
+
+ /* The node can accepts `naccepted' bytes. */
+ dest_idx = re_string_cur_idx (&mctx->input) + naccepted;
+ mctx->max_mb_elem_len = ((mctx->max_mb_elem_len < naccepted) ? naccepted
+ : mctx->max_mb_elem_len);
+ err = clean_state_log_if_needed (mctx, dest_idx);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+#ifdef DEBUG
+ assert (dfa->nexts[cur_node_idx] != -1);
+#endif
+ new_nodes = dfa->eclosures + dfa->nexts[cur_node_idx];
+
+ dest_state = mctx->state_log[dest_idx];
+ if (dest_state == NULL)
+ dest_nodes = *new_nodes;
+ else
+ {
+ err = re_node_set_init_union (&dest_nodes,
+ dest_state->entrance_nodes, new_nodes);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ context = re_string_context_at (&mctx->input, dest_idx - 1,
+ mctx->eflags);
+ mctx->state_log[dest_idx]
+ = re_acquire_state_context (&err, dfa, &dest_nodes, context);
+ if (dest_state != NULL)
+ re_node_set_free (&dest_nodes);
+ if (BE (mctx->state_log[dest_idx] == NULL && err != REG_NOERROR, 0))
+ return err;
+ }
+ return REG_NOERROR;
+}
+#endif /* RE_ENABLE_I18N */
+
+static reg_errcode_t
+internal_function
+transit_state_bkref (re_match_context_t *mctx, const re_node_set *nodes)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ reg_errcode_t err;
+ int i;
+ int cur_str_idx = re_string_cur_idx (&mctx->input);
+
+ for (i = 0; i < nodes->nelem; ++i)
+ {
+ int dest_str_idx, prev_nelem, bkc_idx;
+ int node_idx = nodes->elems[i];
+ unsigned int context;
+ const re_token_t *node = dfa->nodes + node_idx;
+ re_node_set *new_dest_nodes;
+
+ /* Check whether `node' is a backreference or not. */
+ if (node->type != OP_BACK_REF)
+ continue;
+
+ if (node->constraint)
+ {
+ context = re_string_context_at (&mctx->input, cur_str_idx,
+ mctx->eflags);
+ if (NOT_SATISFY_NEXT_CONSTRAINT (node->constraint, context))
+ continue;
+ }
+
+ /* `node' is a backreference.
+ Check the substring which the substring matched. */
+ bkc_idx = mctx->nbkref_ents;
+ err = get_subexp (mctx, node_idx, cur_str_idx);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+
+ /* And add the epsilon closures (which is `new_dest_nodes') of
+ the backreference to appropriate state_log. */
+#ifdef DEBUG
+ assert (dfa->nexts[node_idx] != -1);
+#endif
+ for (; bkc_idx < mctx->nbkref_ents; ++bkc_idx)
+ {
+ int subexp_len;
+ re_dfastate_t *dest_state;
+ struct re_backref_cache_entry *bkref_ent;
+ bkref_ent = mctx->bkref_ents + bkc_idx;
+ if (bkref_ent->node != node_idx || bkref_ent->str_idx != cur_str_idx)
+ continue;
+ subexp_len = bkref_ent->subexp_to - bkref_ent->subexp_from;
+ new_dest_nodes = (subexp_len == 0
+ ? dfa->eclosures + dfa->edests[node_idx].elems[0]
+ : dfa->eclosures + dfa->nexts[node_idx]);
+ dest_str_idx = (cur_str_idx + bkref_ent->subexp_to
+ - bkref_ent->subexp_from);
+ context = re_string_context_at (&mctx->input, dest_str_idx - 1,
+ mctx->eflags);
+ dest_state = mctx->state_log[dest_str_idx];
+ prev_nelem = ((mctx->state_log[cur_str_idx] == NULL) ? 0
+ : mctx->state_log[cur_str_idx]->nodes.nelem);
+ /* Add `new_dest_node' to state_log. */
+ if (dest_state == NULL)
+ {
+ mctx->state_log[dest_str_idx]
+ = re_acquire_state_context (&err, dfa, new_dest_nodes,
+ context);
+ if (BE (mctx->state_log[dest_str_idx] == NULL
+ && err != REG_NOERROR, 0))
+ goto free_return;
+ }
+ else
+ {
+ re_node_set dest_nodes;
+ err = re_node_set_init_union (&dest_nodes,
+ dest_state->entrance_nodes,
+ new_dest_nodes);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&dest_nodes);
+ goto free_return;
+ }
+ mctx->state_log[dest_str_idx]
+ = re_acquire_state_context (&err, dfa, &dest_nodes, context);
+ re_node_set_free (&dest_nodes);
+ if (BE (mctx->state_log[dest_str_idx] == NULL
+ && err != REG_NOERROR, 0))
+ goto free_return;
+ }
+ /* We need to check recursively if the backreference can epsilon
+ transit. */
+ if (subexp_len == 0
+ && mctx->state_log[cur_str_idx]->nodes.nelem > prev_nelem)
+ {
+ err = check_subexp_matching_top (mctx, new_dest_nodes,
+ cur_str_idx);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+ err = transit_state_bkref (mctx, new_dest_nodes);
+ if (BE (err != REG_NOERROR, 0))
+ goto free_return;
+ }
+ }
+ }
+ err = REG_NOERROR;
+ free_return:
+ return err;
+}
+
+/* Enumerate all the candidates which the backreference BKREF_NODE can match
+ at BKREF_STR_IDX, and register them by match_ctx_add_entry().
+ Note that we might collect inappropriate candidates here.
+ However, the cost of checking them strictly here is too high, then we
+ delay these checking for prune_impossible_nodes(). */
+
+static reg_errcode_t
+internal_function
+get_subexp (re_match_context_t *mctx, int bkref_node, int bkref_str_idx)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ int subexp_num, sub_top_idx;
+ const char *buf = (const char *) re_string_get_buffer (&mctx->input);
+ /* Return if we have already checked BKREF_NODE at BKREF_STR_IDX. */
+ int cache_idx = search_cur_bkref_entry (mctx, bkref_str_idx);
+ if (cache_idx != -1)
+ {
+ const struct re_backref_cache_entry *entry
+ = mctx->bkref_ents + cache_idx;
+ do
+ if (entry->node == bkref_node)
+ return REG_NOERROR; /* We already checked it. */
+ while (entry++->more);
+ }
+
+ subexp_num = dfa->nodes[bkref_node].opr.idx;
+
+ /* For each sub expression */
+ for (sub_top_idx = 0; sub_top_idx < mctx->nsub_tops; ++sub_top_idx)
+ {
+ reg_errcode_t err;
+ re_sub_match_top_t *sub_top = mctx->sub_tops[sub_top_idx];
+ re_sub_match_last_t *sub_last;
+ int sub_last_idx, sl_str, bkref_str_off;
+
+ if (dfa->nodes[sub_top->node].opr.idx != subexp_num)
+ continue; /* It isn't related. */
+
+ sl_str = sub_top->str_idx;
+ bkref_str_off = bkref_str_idx;
+ /* At first, check the last node of sub expressions we already
+ evaluated. */
+ for (sub_last_idx = 0; sub_last_idx < sub_top->nlasts; ++sub_last_idx)
+ {
+ int sl_str_diff;
+ sub_last = sub_top->lasts[sub_last_idx];
+ sl_str_diff = sub_last->str_idx - sl_str;
+ /* The matched string by the sub expression match with the substring
+ at the back reference? */
+ if (sl_str_diff > 0)
+ {
+ if (BE (bkref_str_off + sl_str_diff > mctx->input.valid_len, 0))
+ {
+ /* Not enough chars for a successful match. */
+ if (bkref_str_off + sl_str_diff > mctx->input.len)
+ break;
+
+ err = clean_state_log_if_needed (mctx,
+ bkref_str_off
+ + sl_str_diff);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ buf = (const char *) re_string_get_buffer (&mctx->input);
+ }
+ if (memcmp (buf + bkref_str_off, buf + sl_str, sl_str_diff) != 0)
+ /* We don't need to search this sub expression any more. */
+ break;
+ }
+ bkref_str_off += sl_str_diff;
+ sl_str += sl_str_diff;
+ err = get_subexp_sub (mctx, sub_top, sub_last, bkref_node,
+ bkref_str_idx);
+
+ /* Reload buf, since the preceding call might have reallocated
+ the buffer. */
+ buf = (const char *) re_string_get_buffer (&mctx->input);
+
+ if (err == REG_NOMATCH)
+ continue;
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+
+ if (sub_last_idx < sub_top->nlasts)
+ continue;
+ if (sub_last_idx > 0)
+ ++sl_str;
+ /* Then, search for the other last nodes of the sub expression. */
+ for (; sl_str <= bkref_str_idx; ++sl_str)
+ {
+ int cls_node, sl_str_off;
+ const re_node_set *nodes;
+ sl_str_off = sl_str - sub_top->str_idx;
+ /* The matched string by the sub expression match with the substring
+ at the back reference? */
+ if (sl_str_off > 0)
+ {
+ if (BE (bkref_str_off >= mctx->input.valid_len, 0))
+ {
+ /* If we are at the end of the input, we cannot match. */
+ if (bkref_str_off >= mctx->input.len)
+ break;
+
+ err = extend_buffers (mctx);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+
+ buf = (const char *) re_string_get_buffer (&mctx->input);
+ }
+ if (buf [bkref_str_off++] != buf[sl_str - 1])
+ break; /* We don't need to search this sub expression
+ any more. */
+ }
+ if (mctx->state_log[sl_str] == NULL)
+ continue;
+ /* Does this state have a ')' of the sub expression? */
+ nodes = &mctx->state_log[sl_str]->nodes;
+ cls_node = find_subexp_node (dfa, nodes, subexp_num,
+ OP_CLOSE_SUBEXP);
+ if (cls_node == -1)
+ continue; /* No. */
+ if (sub_top->path == NULL)
+ {
+ sub_top->path = calloc (sizeof (state_array_t),
+ sl_str - sub_top->str_idx + 1);
+ if (sub_top->path == NULL)
+ return REG_ESPACE;
+ }
+ /* Can the OP_OPEN_SUBEXP node arrive the OP_CLOSE_SUBEXP node
+ in the current context? */
+ err = check_arrival (mctx, sub_top->path, sub_top->node,
+ sub_top->str_idx, cls_node, sl_str,
+ OP_CLOSE_SUBEXP);
+ if (err == REG_NOMATCH)
+ continue;
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ sub_last = match_ctx_add_sublast (sub_top, cls_node, sl_str);
+ if (BE (sub_last == NULL, 0))
+ return REG_ESPACE;
+ err = get_subexp_sub (mctx, sub_top, sub_last, bkref_node,
+ bkref_str_idx);
+ if (err == REG_NOMATCH)
+ continue;
+ }
+ }
+ return REG_NOERROR;
+}
+
+/* Helper functions for get_subexp(). */
+
+/* Check SUB_LAST can arrive to the back reference BKREF_NODE at BKREF_STR.
+ If it can arrive, register the sub expression expressed with SUB_TOP
+ and SUB_LAST. */
+
+static reg_errcode_t
+internal_function
+get_subexp_sub (re_match_context_t *mctx, const re_sub_match_top_t *sub_top,
+ re_sub_match_last_t *sub_last, int bkref_node, int bkref_str)
+{
+ reg_errcode_t err;
+ int to_idx;
+ /* Can the subexpression arrive the back reference? */
+ err = check_arrival (mctx, &sub_last->path, sub_last->node,
+ sub_last->str_idx, bkref_node, bkref_str,
+ OP_OPEN_SUBEXP);
+ if (err != REG_NOERROR)
+ return err;
+ err = match_ctx_add_entry (mctx, bkref_node, bkref_str, sub_top->str_idx,
+ sub_last->str_idx);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ to_idx = bkref_str + sub_last->str_idx - sub_top->str_idx;
+ return clean_state_log_if_needed (mctx, to_idx);
+}
+
+/* Find the first node which is '(' or ')' and whose index is SUBEXP_IDX.
+ Search '(' if FL_OPEN, or search ')' otherwise.
+ TODO: This function isn't efficient...
+ Because there might be more than one nodes whose types are
+ OP_OPEN_SUBEXP and whose index is SUBEXP_IDX, we must check all
+ nodes.
+ E.g. RE: (a){2} */
+
+static int
+internal_function
+find_subexp_node (const re_dfa_t *dfa, const re_node_set *nodes,
+ int subexp_idx, int type)
+{
+ int cls_idx;
+ for (cls_idx = 0; cls_idx < nodes->nelem; ++cls_idx)
+ {
+ int cls_node = nodes->elems[cls_idx];
+ const re_token_t *node = dfa->nodes + cls_node;
+ if (node->type == type
+ && node->opr.idx == subexp_idx)
+ return cls_node;
+ }
+ return -1;
+}
+
+/* Check whether the node TOP_NODE at TOP_STR can arrive to the node
+ LAST_NODE at LAST_STR. We record the path onto PATH since it will be
+ heavily reused.
+ Return REG_NOERROR if it can arrive, or REG_NOMATCH otherwise. */
+
+static reg_errcode_t
+internal_function
+check_arrival (re_match_context_t *mctx, state_array_t *path, int top_node,
+ int top_str, int last_node, int last_str, int type)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ reg_errcode_t err = REG_NOERROR;
+ int subexp_num, backup_cur_idx, str_idx, null_cnt;
+ re_dfastate_t *cur_state = NULL;
+ re_node_set *cur_nodes, next_nodes;
+ re_dfastate_t **backup_state_log;
+ unsigned int context;
+
+ subexp_num = dfa->nodes[top_node].opr.idx;
+ /* Extend the buffer if we need. */
+ if (BE (path->alloc < last_str + mctx->max_mb_elem_len + 1, 0))
+ {
+ re_dfastate_t **new_array;
+ int old_alloc = path->alloc;
+ path->alloc += last_str + mctx->max_mb_elem_len + 1;
+ new_array = re_realloc (path->array, re_dfastate_t *, path->alloc);
+ if (BE (new_array == NULL, 0))
+ {
+ path->alloc = old_alloc;
+ return REG_ESPACE;
+ }
+ path->array = new_array;
+ memset (new_array + old_alloc, '\0',
+ sizeof (re_dfastate_t *) * (path->alloc - old_alloc));
+ }
+
+ str_idx = path->next_idx ? path->next_idx : top_str;
+
+ /* Temporary modify MCTX. */
+ backup_state_log = mctx->state_log;
+ backup_cur_idx = mctx->input.cur_idx;
+ mctx->state_log = path->array;
+ mctx->input.cur_idx = str_idx;
+
+ /* Setup initial node set. */
+ context = re_string_context_at (&mctx->input, str_idx - 1, mctx->eflags);
+ if (str_idx == top_str)
+ {
+ err = re_node_set_init_1 (&next_nodes, top_node);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ err = check_arrival_expand_ecl (dfa, &next_nodes, subexp_num, type);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&next_nodes);
+ return err;
+ }
+ }
+ else
+ {
+ cur_state = mctx->state_log[str_idx];
+ if (cur_state && cur_state->has_backref)
+ {
+ err = re_node_set_init_copy (&next_nodes, &cur_state->nodes);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ else
+ re_node_set_init_empty (&next_nodes);
+ }
+ if (str_idx == top_str || (cur_state && cur_state->has_backref))
+ {
+ if (next_nodes.nelem)
+ {
+ err = expand_bkref_cache (mctx, &next_nodes, str_idx,
+ subexp_num, type);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&next_nodes);
+ return err;
+ }
+ }
+ cur_state = re_acquire_state_context (&err, dfa, &next_nodes, context);
+ if (BE (cur_state == NULL && err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&next_nodes);
+ return err;
+ }
+ mctx->state_log[str_idx] = cur_state;
+ }
+
+ for (null_cnt = 0; str_idx < last_str && null_cnt <= mctx->max_mb_elem_len;)
+ {
+ re_node_set_empty (&next_nodes);
+ if (mctx->state_log[str_idx + 1])
+ {
+ err = re_node_set_merge (&next_nodes,
+ &mctx->state_log[str_idx + 1]->nodes);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&next_nodes);
+ return err;
+ }
+ }
+ if (cur_state)
+ {
+ err = check_arrival_add_next_nodes (mctx, str_idx,
+ &cur_state->non_eps_nodes,
+ &next_nodes);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&next_nodes);
+ return err;
+ }
+ }
+ ++str_idx;
+ if (next_nodes.nelem)
+ {
+ err = check_arrival_expand_ecl (dfa, &next_nodes, subexp_num, type);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&next_nodes);
+ return err;
+ }
+ err = expand_bkref_cache (mctx, &next_nodes, str_idx,
+ subexp_num, type);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&next_nodes);
+ return err;
+ }
+ }
+ context = re_string_context_at (&mctx->input, str_idx - 1, mctx->eflags);
+ cur_state = re_acquire_state_context (&err, dfa, &next_nodes, context);
+ if (BE (cur_state == NULL && err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&next_nodes);
+ return err;
+ }
+ mctx->state_log[str_idx] = cur_state;
+ null_cnt = cur_state == NULL ? null_cnt + 1 : 0;
+ }
+ re_node_set_free (&next_nodes);
+ cur_nodes = (mctx->state_log[last_str] == NULL ? NULL
+ : &mctx->state_log[last_str]->nodes);
+ path->next_idx = str_idx;
+
+ /* Fix MCTX. */
+ mctx->state_log = backup_state_log;
+ mctx->input.cur_idx = backup_cur_idx;
+
+ /* Then check the current node set has the node LAST_NODE. */
+ if (cur_nodes != NULL && re_node_set_contains (cur_nodes, last_node))
+ return REG_NOERROR;
+
+ return REG_NOMATCH;
+}
+
+/* Helper functions for check_arrival. */
+
+/* Calculate the destination nodes of CUR_NODES at STR_IDX, and append them
+ to NEXT_NODES.
+ TODO: This function is similar to the functions transit_state*(),
+ however this function has many additional works.
+ Can't we unify them? */
+
+static reg_errcode_t
+internal_function
+check_arrival_add_next_nodes (re_match_context_t *mctx, int str_idx,
+ re_node_set *cur_nodes, re_node_set *next_nodes)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ int result;
+ int cur_idx;
+#ifdef RE_ENABLE_I18N
+ reg_errcode_t err = REG_NOERROR;
+#endif
+ re_node_set union_set;
+ re_node_set_init_empty (&union_set);
+ for (cur_idx = 0; cur_idx < cur_nodes->nelem; ++cur_idx)
+ {
+ int naccepted = 0;
+ int cur_node = cur_nodes->elems[cur_idx];
+#ifdef DEBUG
+ re_token_type_t type = dfa->nodes[cur_node].type;
+ assert (!IS_EPSILON_NODE (type));
+#endif
+#ifdef RE_ENABLE_I18N
+ /* If the node may accept `multi byte'. */
+ if (dfa->nodes[cur_node].accept_mb)
+ {
+ naccepted = check_node_accept_bytes (dfa, cur_node, &mctx->input,
+ str_idx);
+ if (naccepted > 1)
+ {
+ re_dfastate_t *dest_state;
+ int next_node = dfa->nexts[cur_node];
+ int next_idx = str_idx + naccepted;
+ dest_state = mctx->state_log[next_idx];
+ re_node_set_empty (&union_set);
+ if (dest_state)
+ {
+ err = re_node_set_merge (&union_set, &dest_state->nodes);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&union_set);
+ return err;
+ }
+ }
+ result = re_node_set_insert (&union_set, next_node);
+ if (BE (result < 0, 0))
+ {
+ re_node_set_free (&union_set);
+ return REG_ESPACE;
+ }
+ mctx->state_log[next_idx] = re_acquire_state (&err, dfa,
+ &union_set);
+ if (BE (mctx->state_log[next_idx] == NULL
+ && err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&union_set);
+ return err;
+ }
+ }
+ }
+#endif /* RE_ENABLE_I18N */
+ if (naccepted
+ || check_node_accept (mctx, dfa->nodes + cur_node, str_idx))
+ {
+ result = re_node_set_insert (next_nodes, dfa->nexts[cur_node]);
+ if (BE (result < 0, 0))
+ {
+ re_node_set_free (&union_set);
+ return REG_ESPACE;
+ }
+ }
+ }
+ re_node_set_free (&union_set);
+ return REG_NOERROR;
+}
+
+/* For all the nodes in CUR_NODES, add the epsilon closures of them to
+ CUR_NODES, however exclude the nodes which are:
+ - inside the sub expression whose number is EX_SUBEXP, if FL_OPEN.
+ - out of the sub expression whose number is EX_SUBEXP, if !FL_OPEN.
+*/
+
+static reg_errcode_t
+internal_function
+check_arrival_expand_ecl (const re_dfa_t *dfa, re_node_set *cur_nodes,
+ int ex_subexp, int type)
+{
+ reg_errcode_t err;
+ int idx, outside_node;
+ re_node_set new_nodes;
+#ifdef DEBUG
+ assert (cur_nodes->nelem);
+#endif
+ err = re_node_set_alloc (&new_nodes, cur_nodes->nelem);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ /* Create a new node set NEW_NODES with the nodes which are epsilon
+ closures of the node in CUR_NODES. */
+
+ for (idx = 0; idx < cur_nodes->nelem; ++idx)
+ {
+ int cur_node = cur_nodes->elems[idx];
+ const re_node_set *eclosure = dfa->eclosures + cur_node;
+ outside_node = find_subexp_node (dfa, eclosure, ex_subexp, type);
+ if (outside_node == -1)
+ {
+ /* There are no problematic nodes, just merge them. */
+ err = re_node_set_merge (&new_nodes, eclosure);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&new_nodes);
+ return err;
+ }
+ }
+ else
+ {
+ /* There are problematic nodes, re-calculate incrementally. */
+ err = check_arrival_expand_ecl_sub (dfa, &new_nodes, cur_node,
+ ex_subexp, type);
+ if (BE (err != REG_NOERROR, 0))
+ {
+ re_node_set_free (&new_nodes);
+ return err;
+ }
+ }
+ }
+ re_node_set_free (cur_nodes);
+ *cur_nodes = new_nodes;
+ return REG_NOERROR;
+}
+
+/* Helper function for check_arrival_expand_ecl.
+ Check incrementally the epsilon closure of TARGET, and if it isn't
+ problematic append it to DST_NODES. */
+
+static reg_errcode_t
+internal_function
+check_arrival_expand_ecl_sub (const re_dfa_t *dfa, re_node_set *dst_nodes,
+ int target, int ex_subexp, int type)
+{
+ int cur_node;
+ for (cur_node = target; !re_node_set_contains (dst_nodes, cur_node);)
+ {
+ int err;
+
+ if (dfa->nodes[cur_node].type == type
+ && dfa->nodes[cur_node].opr.idx == ex_subexp)
+ {
+ if (type == OP_CLOSE_SUBEXP)
+ {
+ err = re_node_set_insert (dst_nodes, cur_node);
+ if (BE (err == -1, 0))
+ return REG_ESPACE;
+ }
+ break;
+ }
+ err = re_node_set_insert (dst_nodes, cur_node);
+ if (BE (err == -1, 0))
+ return REG_ESPACE;
+ if (dfa->edests[cur_node].nelem == 0)
+ break;
+ if (dfa->edests[cur_node].nelem == 2)
+ {
+ err = check_arrival_expand_ecl_sub (dfa, dst_nodes,
+ dfa->edests[cur_node].elems[1],
+ ex_subexp, type);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ cur_node = dfa->edests[cur_node].elems[0];
+ }
+ return REG_NOERROR;
+}
+
+
+/* For all the back references in the current state, calculate the
+ destination of the back references by the appropriate entry
+ in MCTX->BKREF_ENTS. */
+
+static reg_errcode_t
+internal_function
+expand_bkref_cache (re_match_context_t *mctx, re_node_set *cur_nodes,
+ int cur_str, int subexp_num, int type)
+{
+ const re_dfa_t *const dfa = mctx->dfa;
+ reg_errcode_t err;
+ int cache_idx_start = search_cur_bkref_entry (mctx, cur_str);
+ struct re_backref_cache_entry *ent;
+
+ if (cache_idx_start == -1)
+ return REG_NOERROR;
+
+ restart:
+ ent = mctx->bkref_ents + cache_idx_start;
+ do
+ {
+ int to_idx, next_node;
+
+ /* Is this entry ENT is appropriate? */
+ if (!re_node_set_contains (cur_nodes, ent->node))
+ continue; /* No. */
+
+ to_idx = cur_str + ent->subexp_to - ent->subexp_from;
+ /* Calculate the destination of the back reference, and append it
+ to MCTX->STATE_LOG. */
+ if (to_idx == cur_str)
+ {
+ /* The backreference did epsilon transit, we must re-check all the
+ node in the current state. */
+ re_node_set new_dests;
+ reg_errcode_t err2, err3;
+ next_node = dfa->edests[ent->node].elems[0];
+ if (re_node_set_contains (cur_nodes, next_node))
+ continue;
+ err = re_node_set_init_1 (&new_dests, next_node);
+ err2 = check_arrival_expand_ecl (dfa, &new_dests, subexp_num, type);
+ err3 = re_node_set_merge (cur_nodes, &new_dests);
+ re_node_set_free (&new_dests);
+ if (BE (err != REG_NOERROR || err2 != REG_NOERROR
+ || err3 != REG_NOERROR, 0))
+ {
+ err = (err != REG_NOERROR ? err
+ : (err2 != REG_NOERROR ? err2 : err3));
+ return err;
+ }
+ /* TODO: It is still inefficient... */
+ goto restart;
+ }
+ else
+ {
+ re_node_set union_set;
+ next_node = dfa->nexts[ent->node];
+ if (mctx->state_log[to_idx])
+ {
+ int ret;
+ if (re_node_set_contains (&mctx->state_log[to_idx]->nodes,
+ next_node))
+ continue;
+ err = re_node_set_init_copy (&union_set,
+ &mctx->state_log[to_idx]->nodes);
+ ret = re_node_set_insert (&union_set, next_node);
+ if (BE (err != REG_NOERROR || ret < 0, 0))
+ {
+ re_node_set_free (&union_set);
+ err = err != REG_NOERROR ? err : REG_ESPACE;
+ return err;
+ }
+ }
+ else
+ {
+ err = re_node_set_init_1 (&union_set, next_node);
+ if (BE (err != REG_NOERROR, 0))
+ return err;
+ }
+ mctx->state_log[to_idx] = re_acquire_state (&err, dfa, &union_set);
+ re_node_set_free (&union_set);
+ if (BE (mctx->state_log[to_idx] == NULL
+ && err != REG_NOERROR, 0))
+ return err;
+ }
+ }
+ while (ent++->more);
+ return REG_NOERROR;
+}
+
+/* Build transition table for the state.
+ Return 1 if succeeded, otherwise return NULL. */
+
+static int
+internal_function
+build_trtable (const re_dfa_t *dfa, re_dfastate_t *state)
+{
+ reg_errcode_t err;
+ int i, j, ch, need_word_trtable = 0;
+ bitset_word_t elem, mask;
+ bool dests_node_malloced = false;
+ bool dest_states_malloced = false;
+ int ndests; /* Number of the destination states from `state'. */
+ re_dfastate_t **trtable;
+ re_dfastate_t **dest_states = NULL, **dest_states_word, **dest_states_nl;
+ re_node_set follows, *dests_node;
+ bitset_t *dests_ch;
+ bitset_t acceptable;
+
+ struct dests_alloc
+ {
+ re_node_set dests_node[SBC_MAX];
+ bitset_t dests_ch[SBC_MAX];
+ } *dests_alloc;
+
+ /* We build DFA states which corresponds to the destination nodes
+ from `state'. `dests_node[i]' represents the nodes which i-th
+ destination state contains, and `dests_ch[i]' represents the
+ characters which i-th destination state accepts. */
+#ifdef HAVE_ALLOCA
+ if (__libc_use_alloca (sizeof (struct dests_alloc)))
+ dests_alloc = (struct dests_alloc *) alloca (sizeof (struct dests_alloc));
+ else
+#endif
+ {
+ dests_alloc = re_malloc (struct dests_alloc, 1);
+ if (BE (dests_alloc == NULL, 0))
+ return 0;
+ dests_node_malloced = true;
+ }
+ dests_node = dests_alloc->dests_node;
+ dests_ch = dests_alloc->dests_ch;
+
+ /* Initialize transiton table. */
+ state->word_trtable = state->trtable = NULL;
+
+ /* At first, group all nodes belonging to `state' into several
+ destinations. */
+ ndests = group_nodes_into_DFAstates (dfa, state, dests_node, dests_ch);
+ if (BE (ndests <= 0, 0))
+ {
+ if (dests_node_malloced)
+ free (dests_alloc);
+ /* Return 0 in case of an error, 1 otherwise. */
+ if (ndests == 0)
+ {
+ state->trtable = (re_dfastate_t **)
+ calloc (sizeof (re_dfastate_t *), SBC_MAX);
+ return 1;
+ }
+ return 0;
+ }
+
+ err = re_node_set_alloc (&follows, ndests + 1);
+ if (BE (err != REG_NOERROR, 0))
+ goto out_free;
+
+ /* Avoid arithmetic overflow in size calculation. */
+ if (BE ((((SIZE_MAX - (sizeof (re_node_set) + sizeof (bitset_t)) * SBC_MAX)
+ / (3 * sizeof (re_dfastate_t *)))
+ < (size_t)ndests),
+ 0))
+ goto out_free;
+
+#ifdef HAVE_ALLOCA
+ if (__libc_use_alloca ((sizeof (re_node_set) + sizeof (bitset_t)) * SBC_MAX
+ + ndests * 3 * sizeof (re_dfastate_t *)))
+ dest_states = (re_dfastate_t **)
+ alloca (ndests * 3 * sizeof (re_dfastate_t *));
+ else
+#endif
+ {
+ dest_states = (re_dfastate_t **)
+ malloc (ndests * 3 * sizeof (re_dfastate_t *));
+ if (BE (dest_states == NULL, 0))
+ {
+out_free:
+ if (dest_states_malloced)
+ free (dest_states);
+ re_node_set_free (&follows);
+ for (i = 0; i < ndests; ++i)
+ re_node_set_free (dests_node + i);
+ if (dests_node_malloced)
+ free (dests_alloc);
+ return 0;
+ }
+ dest_states_malloced = true;
+ }
+ dest_states_word = dest_states + ndests;
+ dest_states_nl = dest_states_word + ndests;
+ bitset_empty (acceptable);
+
+ /* Then build the states for all destinations. */
+ for (i = 0; i < ndests; ++i)
+ {
+ int next_node;
+ re_node_set_empty (&follows);
+ /* Merge the follows of this destination states. */
+ for (j = 0; j < dests_node[i].nelem; ++j)
+ {
+ next_node = dfa->nexts[dests_node[i].elems[j]];
+ if (next_node != -1)
+ {
+ err = re_node_set_merge (&follows, dfa->eclosures + next_node);
+ if (BE (err != REG_NOERROR, 0))
+ goto out_free;
+ }
+ }
+ dest_states[i] = re_acquire_state_context (&err, dfa, &follows, 0);
+ if (BE (dest_states[i] == NULL && err != REG_NOERROR, 0))
+ goto out_free;
+ /* If the new state has context constraint,
+ build appropriate states for these contexts. */
+ if (dest_states[i]->has_constraint)
+ {
+ dest_states_word[i] = re_acquire_state_context (&err, dfa, &follows,
+ CONTEXT_WORD);
+ if (BE (dest_states_word[i] == NULL && err != REG_NOERROR, 0))
+ goto out_free;
+
+ if (dest_states[i] != dest_states_word[i] && dfa->mb_cur_max > 1)
+ need_word_trtable = 1;
+
+ dest_states_nl[i] = re_acquire_state_context (&err, dfa, &follows,
+ CONTEXT_NEWLINE);
+ if (BE (dest_states_nl[i] == NULL && err != REG_NOERROR, 0))
+ goto out_free;
+ }
+ else
+ {
+ dest_states_word[i] = dest_states[i];
+ dest_states_nl[i] = dest_states[i];
+ }
+ bitset_merge (acceptable, dests_ch[i]);
+ }
+
+ if (!BE (need_word_trtable, 0))
+ {
+ /* We don't care about whether the following character is a word
+ character, or we are in a single-byte character set so we can
+ discern by looking at the character code: allocate a
+ 256-entry transition table. */
+ trtable = state->trtable =
+ (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), SBC_MAX);
+ if (BE (trtable == NULL, 0))
+ goto out_free;
+
+ /* For all characters ch...: */
+ for (i = 0; i < BITSET_WORDS; ++i)
+ for (ch = i * BITSET_WORD_BITS, elem = acceptable[i], mask = 1;
+ elem;
+ mask <<= 1, elem >>= 1, ++ch)
+ if (BE (elem & 1, 0))
+ {
+ /* There must be exactly one destination which accepts
+ character ch. See group_nodes_into_DFAstates. */
+ for (j = 0; (dests_ch[j][i] & mask) == 0; ++j)
+ ;
+
+ /* j-th destination accepts the word character ch. */
+ if (dfa->word_char[i] & mask)
+ trtable[ch] = dest_states_word[j];
+ else
+ trtable[ch] = dest_states[j];
+ }
+ }
+ else
+ {
+ /* We care about whether the following character is a word
+ character, and we are in a multi-byte character set: discern
+ by looking at the character code: build two 256-entry
+ transition tables, one starting at trtable[0] and one
+ starting at trtable[SBC_MAX]. */
+ trtable = state->word_trtable =
+ (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), 2 * SBC_MAX);
+ if (BE (trtable == NULL, 0))
+ goto out_free;
+
+ /* For all characters ch...: */
+ for (i = 0; i < BITSET_WORDS; ++i)
+ for (ch = i * BITSET_WORD_BITS, elem = acceptable[i], mask = 1;
+ elem;
+ mask <<= 1, elem >>= 1, ++ch)
+ if (BE (elem & 1, 0))
+ {
+ /* There must be exactly one destination which accepts
+ character ch. See group_nodes_into_DFAstates. */
+ for (j = 0; (dests_ch[j][i] & mask) == 0; ++j)
+ ;
+
+ /* j-th destination accepts the word character ch. */
+ trtable[ch] = dest_states[j];
+ trtable[ch + SBC_MAX] = dest_states_word[j];
+ }
+ }
+
+ /* new line */
+ if (bitset_contain (acceptable, NEWLINE_CHAR))
+ {
+ /* The current state accepts newline character. */
+ for (j = 0; j < ndests; ++j)
+ if (bitset_contain (dests_ch[j], NEWLINE_CHAR))
+ {
+ /* k-th destination accepts newline character. */
+ trtable[NEWLINE_CHAR] = dest_states_nl[j];
+ if (need_word_trtable)
+ trtable[NEWLINE_CHAR + SBC_MAX] = dest_states_nl[j];
+ /* There must be only one destination which accepts
+ newline. See group_nodes_into_DFAstates. */
+ break;
+ }
+ }
+
+ if (dest_states_malloced)
+ free (dest_states);
+
+ re_node_set_free (&follows);
+ for (i = 0; i < ndests; ++i)
+ re_node_set_free (dests_node + i);
+
+ if (dests_node_malloced)
+ free (dests_alloc);
+
+ return 1;
+}
+
+/* Group all nodes belonging to STATE into several destinations.
+ Then for all destinations, set the nodes belonging to the destination
+ to DESTS_NODE[i] and set the characters accepted by the destination
+ to DEST_CH[i]. This function return the number of destinations. */
+
+static int
+internal_function
+group_nodes_into_DFAstates (const re_dfa_t *dfa, const re_dfastate_t *state,
+ re_node_set *dests_node, bitset_t *dests_ch)
+{
+ reg_errcode_t err;
+ int result;
+ int i, j, k;
+ int ndests; /* Number of the destinations from `state'. */
+ bitset_t accepts; /* Characters a node can accept. */
+ const re_node_set *cur_nodes = &state->nodes;
+ bitset_empty (accepts);
+ ndests = 0;
+
+ /* For all the nodes belonging to `state', */
+ for (i = 0; i < cur_nodes->nelem; ++i)
+ {
+ re_token_t *node = &dfa->nodes[cur_nodes->elems[i]];
+ re_token_type_t type = node->type;
+ unsigned int constraint = node->constraint;
+
+ /* Enumerate all single byte character this node can accept. */
+ if (type == CHARACTER)
+ bitset_set (accepts, node->opr.c);
+ else if (type == SIMPLE_BRACKET)
+ {
+ bitset_merge (accepts, node->opr.sbcset);
+ }
+ else if (type == OP_PERIOD)
+ {
+#ifdef RE_ENABLE_I18N
+ if (dfa->mb_cur_max > 1)
+ bitset_merge (accepts, dfa->sb_char);
+ else
+#endif
+ bitset_set_all (accepts);
+ if (!(dfa->syntax & RE_DOT_NEWLINE))
+ bitset_clear (accepts, '\n');
+ if (dfa->syntax & RE_DOT_NOT_NULL)
+ bitset_clear (accepts, '\0');
+ }
+#ifdef RE_ENABLE_I18N
+ else if (type == OP_UTF8_PERIOD)
+ {
+ memset (accepts, '\xff', sizeof (bitset_t) / 2);
+ if (!(dfa->syntax & RE_DOT_NEWLINE))
+ bitset_clear (accepts, '\n');
+ if (dfa->syntax & RE_DOT_NOT_NULL)
+ bitset_clear (accepts, '\0');
+ }
+#endif
+ else
+ continue;
+
+ /* Check the `accepts' and sift the characters which are not
+ match it the context. */
+ if (constraint)
+ {
+ if (constraint & NEXT_NEWLINE_CONSTRAINT)
+ {
+ bool accepts_newline = bitset_contain (accepts, NEWLINE_CHAR);
+ bitset_empty (accepts);
+ if (accepts_newline)
+ bitset_set (accepts, NEWLINE_CHAR);
+ else
+ continue;
+ }
+ if (constraint & NEXT_ENDBUF_CONSTRAINT)
+ {
+ bitset_empty (accepts);
+ continue;
+ }
+
+ if (constraint & NEXT_WORD_CONSTRAINT)
+ {
+ bitset_word_t any_set = 0;
+ if (type == CHARACTER && !node->word_char)
+ {
+ bitset_empty (accepts);
+ continue;
+ }
+#ifdef RE_ENABLE_I18N
+ if (dfa->mb_cur_max > 1)
+ for (j = 0; j < BITSET_WORDS; ++j)
+ any_set |= (accepts[j] &= (dfa->word_char[j] | ~dfa->sb_char[j]));
+ else
+#endif
+ for (j = 0; j < BITSET_WORDS; ++j)
+ any_set |= (accepts[j] &= dfa->word_char[j]);
+ if (!any_set)
+ continue;
+ }
+ if (constraint & NEXT_NOTWORD_CONSTRAINT)
+ {
+ bitset_word_t any_set = 0;
+ if (type == CHARACTER && node->word_char)
+ {
+ bitset_empty (accepts);
+ continue;
+ }
+#ifdef RE_ENABLE_I18N
+ if (dfa->mb_cur_max > 1)
+ for (j = 0; j < BITSET_WORDS; ++j)
+ any_set |= (accepts[j] &= ~(dfa->word_char[j] & dfa->sb_char[j]));
+ else
+#endif
+ for (j = 0; j < BITSET_WORDS; ++j)
+ any_set |= (accepts[j] &= ~dfa->word_char[j]);
+ if (!any_set)
+ continue;
+ }
+ }
+
+ /* Then divide `accepts' into DFA states, or create a new
+ state. Above, we make sure that accepts is not empty. */
+ for (j = 0; j < ndests; ++j)
+ {
+ bitset_t intersec; /* Intersection sets, see below. */
+ bitset_t remains;
+ /* Flags, see below. */
+ bitset_word_t has_intersec, not_subset, not_consumed;
+
+ /* Optimization, skip if this state doesn't accept the character. */
+ if (type == CHARACTER && !bitset_contain (dests_ch[j], node->opr.c))
+ continue;
+
+ /* Enumerate the intersection set of this state and `accepts'. */
+ has_intersec = 0;
+ for (k = 0; k < BITSET_WORDS; ++k)
+ has_intersec |= intersec[k] = accepts[k] & dests_ch[j][k];
+ /* And skip if the intersection set is empty. */
+ if (!has_intersec)
+ continue;
+
+ /* Then check if this state is a subset of `accepts'. */
+ not_subset = not_consumed = 0;
+ for (k = 0; k < BITSET_WORDS; ++k)
+ {
+ not_subset |= remains[k] = ~accepts[k] & dests_ch[j][k];
+ not_consumed |= accepts[k] = accepts[k] & ~dests_ch[j][k];
+ }
+
+ /* If this state isn't a subset of `accepts', create a
+ new group state, which has the `remains'. */
+ if (not_subset)
+ {
+ bitset_copy (dests_ch[ndests], remains);
+ bitset_copy (dests_ch[j], intersec);
+ err = re_node_set_init_copy (dests_node + ndests, &dests_node[j]);
+ if (BE (err != REG_NOERROR, 0))
+ goto error_return;
+ ++ndests;
+ }
+
+ /* Put the position in the current group. */
+ result = re_node_set_insert (&dests_node[j], cur_nodes->elems[i]);
+ if (BE (result < 0, 0))
+ goto error_return;
+
+ /* If all characters are consumed, go to next node. */
+ if (!not_consumed)
+ break;
+ }
+ /* Some characters remain, create a new group. */
+ if (j == ndests)
+ {
+ bitset_copy (dests_ch[ndests], accepts);
+ err = re_node_set_init_1 (dests_node + ndests, cur_nodes->elems[i]);
+ if (BE (err != REG_NOERROR, 0))
+ goto error_return;
+ ++ndests;
+ bitset_empty (accepts);
+ }
+ }
+ return ndests;
+ error_return:
+ for (j = 0; j < ndests; ++j)
+ re_node_set_free (dests_node + j);
+ return -1;
+}
+
+#ifdef RE_ENABLE_I18N
+/* Check how many bytes the node `dfa->nodes[node_idx]' accepts.
+ Return the number of the bytes the node accepts.
+ STR_IDX is the current index of the input string.
+
+ This function handles the nodes which can accept one character, or
+ one collating element like '.', '[a-z]', opposite to the other nodes
+ can only accept one byte. */
+
+static int
+internal_function
+check_node_accept_bytes (const re_dfa_t *dfa, int node_idx,
+ const re_string_t *input, int str_idx)
+{
+ const re_token_t *node = dfa->nodes + node_idx;
+ int char_len, elem_len;
+ int i;
+ wint_t wc;
+
+ if (BE (node->type == OP_UTF8_PERIOD, 0))
+ {
+ unsigned char c = re_string_byte_at (input, str_idx), d;
+ if (BE (c < 0xc2, 1))
+ return 0;
+
+ if (str_idx + 2 > input->len)
+ return 0;
+
+ d = re_string_byte_at (input, str_idx + 1);
+ if (c < 0xe0)
+ return (d < 0x80 || d > 0xbf) ? 0 : 2;
+ else if (c < 0xf0)
+ {
+ char_len = 3;
+ if (c == 0xe0 && d < 0xa0)
+ return 0;
+ }
+ else if (c < 0xf8)
+ {
+ char_len = 4;
+ if (c == 0xf0 && d < 0x90)
+ return 0;
+ }
+ else if (c < 0xfc)
+ {
+ char_len = 5;
+ if (c == 0xf8 && d < 0x88)
+ return 0;
+ }
+ else if (c < 0xfe)
+ {
+ char_len = 6;
+ if (c == 0xfc && d < 0x84)
+ return 0;
+ }
+ else
+ return 0;
+
+ if (str_idx + char_len > input->len)
+ return 0;
+
+ for (i = 1; i < char_len; ++i)
+ {
+ d = re_string_byte_at (input, str_idx + i);
+ if (d < 0x80 || d > 0xbf)
+ return 0;
+ }
+ return char_len;
+ }
+
+ char_len = re_string_char_size_at (input, str_idx);
+ if (node->type == OP_PERIOD)
+ {
+ if (char_len <= 1)
+ return 0;
+ /* FIXME: I don't think this if is needed, as both '\n'
+ and '\0' are char_len == 1. */
+ /* '.' accepts any one character except the following two cases. */
+ if ((!(dfa->syntax & RE_DOT_NEWLINE) &&
+ re_string_byte_at (input, str_idx) == '\n') ||
+ ((dfa->syntax & RE_DOT_NOT_NULL) &&
+ re_string_byte_at (input, str_idx) == '\0'))
+ return 0;
+ return char_len;
+ }
+
+ elem_len = re_string_elem_size_at (input, str_idx);
+ wc = __btowc(*(input->mbs+str_idx));
+ if (((elem_len <= 1 && char_len <= 1) || char_len == 0) && (wc != WEOF && wc < SBC_MAX))
+ return 0;
+
+ if (node->type == COMPLEX_BRACKET)
+ {
+ const re_charset_t *cset = node->opr.mbcset;
+# ifdef _LIBC
+ const unsigned char *pin
+ = ((const unsigned char *) re_string_get_buffer (input) + str_idx);
+ int j;
+ uint32_t nrules;
+# endif /* _LIBC */
+ int match_len = 0;
+ wchar_t wc = ((cset->nranges || cset->nchar_classes || cset->nmbchars)
+ ? re_string_wchar_at (input, str_idx) : 0);
+
+ /* match with multibyte character? */
+ for (i = 0; i < cset->nmbchars; ++i)
+ if (wc == cset->mbchars[i])
+ {
+ match_len = char_len;
+ goto check_node_accept_bytes_match;
+ }
+ /* match with character_class? */
+ for (i = 0; i < cset->nchar_classes; ++i)
+ {
+ wctype_t wt = cset->char_classes[i];
+ if (__iswctype (wc, wt))
+ {
+ match_len = char_len;
+ goto check_node_accept_bytes_match;
+ }
+ }
+
+# ifdef _LIBC
+ nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES);
+ if (nrules != 0)
+ {
+ unsigned int in_collseq = 0;
+ const int32_t *table, *indirect;
+ const unsigned char *weights, *extra;
+ const char *collseqwc;
+ /* This #include defines a local function! */
+# include <locale/weight.h>
+
+ /* match with collating_symbol? */
+ if (cset->ncoll_syms)
+ extra = (const unsigned char *)
+ _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB);
+ for (i = 0; i < cset->ncoll_syms; ++i)
+ {
+ const unsigned char *coll_sym = extra + cset->coll_syms[i];
+ /* Compare the length of input collating element and
+ the length of current collating element. */
+ if (*coll_sym != elem_len)
+ continue;
+ /* Compare each bytes. */
+ for (j = 0; j < *coll_sym; j++)
+ if (pin[j] != coll_sym[1 + j])
+ break;
+ if (j == *coll_sym)
+ {
+ /* Match if every bytes is equal. */
+ match_len = j;
+ goto check_node_accept_bytes_match;
+ }
+ }
+
+ if (cset->nranges)
+ {
+ if (elem_len <= char_len)
+ {
+ collseqwc = _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQWC);
+ in_collseq = __collseq_table_lookup (collseqwc, wc);
+ }
+ else
+ in_collseq = find_collation_sequence_value (pin, elem_len);
+ }
+ /* match with range expression? */
+ for (i = 0; i < cset->nranges; ++i)
+ if (cset->range_starts[i] <= in_collseq
+ && in_collseq <= cset->range_ends[i])
+ {
+ match_len = elem_len;
+ goto check_node_accept_bytes_match;
+ }
+
+ /* match with equivalence_class? */
+ if (cset->nequiv_classes)
+ {
+ const unsigned char *cp = pin;
+ table = (const int32_t *)
+ _NL_CURRENT (LC_COLLATE, _NL_COLLATE_TABLEMB);
+ weights = (const unsigned char *)
+ _NL_CURRENT (LC_COLLATE, _NL_COLLATE_WEIGHTMB);
+ extra = (const unsigned char *)
+ _NL_CURRENT (LC_COLLATE, _NL_COLLATE_EXTRAMB);
+ indirect = (const int32_t *)
+ _NL_CURRENT (LC_COLLATE, _NL_COLLATE_INDIRECTMB);
+ int32_t idx = findidx (&cp);
+ if (idx > 0)
+ for (i = 0; i < cset->nequiv_classes; ++i)
+ {
+ int32_t equiv_class_idx = cset->equiv_classes[i];
+ size_t weight_len = weights[idx & 0xffffff];
+ if (weight_len == weights[equiv_class_idx & 0xffffff]
+ && (idx >> 24) == (equiv_class_idx >> 24))
+ {
+ int cnt = 0;
+
+ idx &= 0xffffff;
+ equiv_class_idx &= 0xffffff;
+
+ while (cnt <= weight_len
+ && (weights[equiv_class_idx + 1 + cnt]
+ == weights[idx + 1 + cnt]))
+ ++cnt;
+ if (cnt > weight_len)
+ {
+ match_len = elem_len;
+ goto check_node_accept_bytes_match;
+ }
+ }
+ }
+ }
+ }
+ else
+# endif /* _LIBC */
+ {
+ /* match with range expression? */
+#if __GNUC__ >= 2
+ wchar_t cmp_buf[] = {L'\0', L'\0', wc, L'\0', L'\0', L'\0'};
+#else
+ wchar_t cmp_buf[] = {L'\0', L'\0', L'\0', L'\0', L'\0', L'\0'};
+ cmp_buf[2] = wc;
+#endif
+ for (i = 0; i < cset->nranges; ++i)
+ {
+ cmp_buf[0] = cset->range_starts[i];
+ cmp_buf[4] = cset->range_ends[i];
+ if (wcscoll (cmp_buf, cmp_buf + 2) <= 0
+ && wcscoll (cmp_buf + 2, cmp_buf + 4) <= 0)
+ {
+ match_len = char_len;
+ goto check_node_accept_bytes_match;
+ }
+ }
+ }
+ check_node_accept_bytes_match:
+ if (!cset->non_match)
+ return match_len;
+ else
+ {
+ if (match_len > 0)
+ return 0;
+ else
+ return (elem_len > char_len) ? elem_len : char_len;
+ }
+ }
+ return 0;
+}
+
+# ifdef _LIBC
+static unsigned int
+internal_function
+find_collation_sequence_value (const unsigned char *mbs, size_t mbs_len)
+{
+ uint32_t nrules = _NL_CURRENT_WORD (LC_COLLATE, _NL_COLLATE_NRULES);
+ if (nrules == 0)
+ {
+ if (mbs_len == 1)
+ {
+ /* No valid character. Match it as a single byte character. */
+ const unsigned char *collseq = (const unsigned char *)
+ _NL_CURRENT (LC_COLLATE, _NL_COLLATE_COLLSEQMB);
+ return collseq[mbs[0]];
+ }
+ return UINT_MAX;
+ }
+ else
+ {
+ int32_t idx;
+ const unsigned char *extra = (const unsigned char *)
+ _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB);
+ int32_t extrasize = (const unsigned char *)
+ _NL_CURRENT (LC_COLLATE, _NL_COLLATE_SYMB_EXTRAMB + 1) - extra;
+
+ for (idx = 0; idx < extrasize;)
+ {
+ int mbs_cnt, found = 0;
+ int32_t elem_mbs_len;
+ /* Skip the name of collating element name. */
+ idx = idx + extra[idx] + 1;
+ elem_mbs_len = extra[idx++];
+ if (mbs_len == elem_mbs_len)
+ {
+ for (mbs_cnt = 0; mbs_cnt < elem_mbs_len; ++mbs_cnt)
+ if (extra[idx + mbs_cnt] != mbs[mbs_cnt])
+ break;
+ if (mbs_cnt == elem_mbs_len)
+ /* Found the entry. */
+ found = 1;
+ }
+ /* Skip the byte sequence of the collating element. */
+ idx += elem_mbs_len;
+ /* Adjust for the alignment. */
+ idx = (idx + 3) & ~3;
+ /* Skip the collation sequence value. */
+ idx += sizeof (uint32_t);
+ /* Skip the wide char sequence of the collating element. */
+ idx = idx + sizeof (uint32_t) * (extra[idx] + 1);
+ /* If we found the entry, return the sequence value. */
+ if (found)
+ return *(uint32_t *) (extra + idx);
+ /* Skip the collation sequence value. */
+ idx += sizeof (uint32_t);
+ }
+ return UINT_MAX;
+ }
+}
+# endif /* _LIBC */
+#endif /* RE_ENABLE_I18N */
+
+/* Check whether the node accepts the byte which is IDX-th
+ byte of the INPUT. */
+
+static int
+internal_function
+check_node_accept (const re_match_context_t *mctx, const re_token_t *node,
+ int idx)
+{
+ unsigned char ch;
+ ch = re_string_byte_at (&mctx->input, idx);
+ switch (node->type)
+ {
+ case CHARACTER:
+ if (node->opr.c != ch)
+ return 0;
+ break;
+
+ case SIMPLE_BRACKET:
+ if (!bitset_contain (node->opr.sbcset, ch))
+ return 0;
+ break;
+
+#ifdef RE_ENABLE_I18N
+ case OP_UTF8_PERIOD:
+ if (ch >= 0x80)
+ return 0;
+ /* FALLTHROUGH */
+#endif
+ case OP_PERIOD:
+ if ((ch == '\n' && !(mctx->dfa->syntax & RE_DOT_NEWLINE))
+ || (ch == '\0' && (mctx->dfa->syntax & RE_DOT_NOT_NULL)))
+ return 0;
+ break;
+
+ default:
+ return 0;
+ }
+
+ if (node->constraint)
+ {
+ /* The node has constraints. Check whether the current context
+ satisfies the constraints. */
+ unsigned int context = re_string_context_at (&mctx->input, idx,
+ mctx->eflags);
+ if (NOT_SATISFY_NEXT_CONSTRAINT (node->constraint, context))
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Extend the buffers, if the buffers have run out. */
+
+static reg_errcode_t
+internal_function
+extend_buffers (re_match_context_t *mctx)
+{
+ reg_errcode_t ret;
+ re_string_t *pstr = &mctx->input;
+
+ /* Avoid overflow. */
+ if (BE (INT_MAX / 2 / sizeof (re_dfastate_t *) <= (size_t)pstr->bufs_len, 0))
+ return REG_ESPACE;
+
+ /* Double the lengthes of the buffers. */
+ ret = re_string_realloc_buffers (pstr, pstr->bufs_len * 2);
+ if (BE (ret != REG_NOERROR, 0))
+ return ret;
+
+ if (mctx->state_log != NULL)
+ {
+ /* And double the length of state_log. */
+ /* XXX We have no indication of the size of this buffer. If this
+ allocation fail we have no indication that the state_log array
+ does not have the right size. */
+ re_dfastate_t **new_array = re_realloc (mctx->state_log, re_dfastate_t *,
+ pstr->bufs_len + 1);
+ if (BE (new_array == NULL, 0))
+ return REG_ESPACE;
+ mctx->state_log = new_array;
+ }
+
+ /* Then reconstruct the buffers. */
+ if (pstr->icase)
+ {
+#ifdef RE_ENABLE_I18N
+ if (pstr->mb_cur_max > 1)
+ {
+ ret = build_wcs_upper_buffer (pstr);
+ if (BE (ret != REG_NOERROR, 0))
+ return ret;
+ }
+ else
+#endif /* RE_ENABLE_I18N */
+ build_upper_buffer (pstr);
+ }
+ else
+ {
+#ifdef RE_ENABLE_I18N
+ if (pstr->mb_cur_max > 1)
+ build_wcs_buffer (pstr);
+ else
+#endif /* RE_ENABLE_I18N */
+ {
+ if (pstr->trans != NULL)
+ re_string_translate_buffer (pstr);
+ }
+ }
+ return REG_NOERROR;
+}
+
+
+/* Functions for matching context. */
+
+/* Initialize MCTX. */
+
+static reg_errcode_t
+internal_function
+match_ctx_init (re_match_context_t *mctx, int eflags, int n)
+{
+ mctx->eflags = eflags;
+ mctx->match_last = -1;
+ if (n > 0)
+ {
+ mctx->bkref_ents = re_malloc (struct re_backref_cache_entry, n);
+ mctx->sub_tops = re_malloc (re_sub_match_top_t *, n);
+ if (BE (mctx->bkref_ents == NULL || mctx->sub_tops == NULL, 0))
+ return REG_ESPACE;
+ }
+ /* Already zero-ed by the caller.
+ else
+ mctx->bkref_ents = NULL;
+ mctx->nbkref_ents = 0;
+ mctx->nsub_tops = 0; */
+ mctx->abkref_ents = n;
+ mctx->max_mb_elem_len = 1;
+ mctx->asub_tops = n;
+ return REG_NOERROR;
+}
+
+/* Clean the entries which depend on the current input in MCTX.
+ This function must be invoked when the matcher changes the start index
+ of the input, or changes the input string. */
+
+static void
+internal_function
+match_ctx_clean (re_match_context_t *mctx)
+{
+ int st_idx;
+ for (st_idx = 0; st_idx < mctx->nsub_tops; ++st_idx)
+ {
+ int sl_idx;
+ re_sub_match_top_t *top = mctx->sub_tops[st_idx];
+ for (sl_idx = 0; sl_idx < top->nlasts; ++sl_idx)
+ {
+ re_sub_match_last_t *last = top->lasts[sl_idx];
+ re_free (last->path.array);
+ re_free (last);
+ }
+ re_free (top->lasts);
+ if (top->path)
+ {
+ re_free (top->path->array);
+ re_free (top->path);
+ }
+ free (top);
+ }
+
+ mctx->nsub_tops = 0;
+ mctx->nbkref_ents = 0;
+}
+
+/* Free all the memory associated with MCTX. */
+
+static void
+internal_function
+match_ctx_free (re_match_context_t *mctx)
+{
+ /* First, free all the memory associated with MCTX->SUB_TOPS. */
+ match_ctx_clean (mctx);
+ re_free (mctx->sub_tops);
+ re_free (mctx->bkref_ents);
+}
+
+/* Add a new backreference entry to MCTX.
+ Note that we assume that caller never call this function with duplicate
+ entry, and call with STR_IDX which isn't smaller than any existing entry.
+*/
+
+static reg_errcode_t
+internal_function
+match_ctx_add_entry (re_match_context_t *mctx, int node, int str_idx, int from,
+ int to)
+{
+ if (mctx->nbkref_ents >= mctx->abkref_ents)
+ {
+ struct re_backref_cache_entry* new_entry;
+ new_entry = re_realloc (mctx->bkref_ents, struct re_backref_cache_entry,
+ mctx->abkref_ents * 2);
+ if (BE (new_entry == NULL, 0))
+ {
+ re_free (mctx->bkref_ents);
+ return REG_ESPACE;
+ }
+ mctx->bkref_ents = new_entry;
+ memset (mctx->bkref_ents + mctx->nbkref_ents, '\0',
+ sizeof (struct re_backref_cache_entry) * mctx->abkref_ents);
+ mctx->abkref_ents *= 2;
+ }
+ if (mctx->nbkref_ents > 0
+ && mctx->bkref_ents[mctx->nbkref_ents - 1].str_idx == str_idx)
+ mctx->bkref_ents[mctx->nbkref_ents - 1].more = 1;
+
+ mctx->bkref_ents[mctx->nbkref_ents].node = node;
+ mctx->bkref_ents[mctx->nbkref_ents].str_idx = str_idx;
+ mctx->bkref_ents[mctx->nbkref_ents].subexp_from = from;
+ mctx->bkref_ents[mctx->nbkref_ents].subexp_to = to;
+
+ /* This is a cache that saves negative results of check_dst_limits_calc_pos.
+ If bit N is clear, means that this entry won't epsilon-transition to
+ an OP_OPEN_SUBEXP or OP_CLOSE_SUBEXP for the N+1-th subexpression. If
+ it is set, check_dst_limits_calc_pos_1 will recurse and try to find one
+ such node.
+
+ A backreference does not epsilon-transition unless it is empty, so set
+ to all zeros if FROM != TO. */
+ mctx->bkref_ents[mctx->nbkref_ents].eps_reachable_subexps_map
+ = (from == to ? ~0 : 0);
+
+ mctx->bkref_ents[mctx->nbkref_ents++].more = 0;
+ if (mctx->max_mb_elem_len < to - from)
+ mctx->max_mb_elem_len = to - from;
+ return REG_NOERROR;
+}
+
+/* Search for the first entry which has the same str_idx, or -1 if none is
+ found. Note that MCTX->BKREF_ENTS is already sorted by MCTX->STR_IDX. */
+
+static int
+internal_function
+search_cur_bkref_entry (const re_match_context_t *mctx, int str_idx)
+{
+ int left, right, mid, last;
+ last = right = mctx->nbkref_ents;
+ for (left = 0; left < right;)
+ {
+ mid = (left + right) / 2;
+ if (mctx->bkref_ents[mid].str_idx < str_idx)
+ left = mid + 1;
+ else
+ right = mid;
+ }
+ if (left < last && mctx->bkref_ents[left].str_idx == str_idx)
+ return left;
+ else
+ return -1;
+}
+
+/* Register the node NODE, whose type is OP_OPEN_SUBEXP, and which matches
+ at STR_IDX. */
+
+static reg_errcode_t
+internal_function
+match_ctx_add_subtop (re_match_context_t *mctx, int node, int str_idx)
+{
+#ifdef DEBUG
+ assert (mctx->sub_tops != NULL);
+ assert (mctx->asub_tops > 0);
+#endif
+ if (BE (mctx->nsub_tops == mctx->asub_tops, 0))
+ {
+ int new_asub_tops = mctx->asub_tops * 2;
+ re_sub_match_top_t **new_array = re_realloc (mctx->sub_tops,
+ re_sub_match_top_t *,
+ new_asub_tops);
+ if (BE (new_array == NULL, 0))
+ return REG_ESPACE;
+ mctx->sub_tops = new_array;
+ mctx->asub_tops = new_asub_tops;
+ }
+ mctx->sub_tops[mctx->nsub_tops] = calloc (1, sizeof (re_sub_match_top_t));
+ if (BE (mctx->sub_tops[mctx->nsub_tops] == NULL, 0))
+ return REG_ESPACE;
+ mctx->sub_tops[mctx->nsub_tops]->node = node;
+ mctx->sub_tops[mctx->nsub_tops++]->str_idx = str_idx;
+ return REG_NOERROR;
+}
+
+/* Register the node NODE, whose type is OP_CLOSE_SUBEXP, and which matches
+ at STR_IDX, whose corresponding OP_OPEN_SUBEXP is SUB_TOP. */
+
+static re_sub_match_last_t *
+internal_function
+match_ctx_add_sublast (re_sub_match_top_t *subtop, int node, int str_idx)
+{
+ re_sub_match_last_t *new_entry;
+ if (BE (subtop->nlasts == subtop->alasts, 0))
+ {
+ int new_alasts = 2 * subtop->alasts + 1;
+ re_sub_match_last_t **new_array = re_realloc (subtop->lasts,
+ re_sub_match_last_t *,
+ new_alasts);
+ if (BE (new_array == NULL, 0))
+ return NULL;
+ subtop->lasts = new_array;
+ subtop->alasts = new_alasts;
+ }
+ new_entry = calloc (1, sizeof (re_sub_match_last_t));
+ if (BE (new_entry != NULL, 1))
+ {
+ subtop->lasts[subtop->nlasts] = new_entry;
+ new_entry->node = node;
+ new_entry->str_idx = str_idx;
+ ++subtop->nlasts;
+ }
+ return new_entry;
+}
+
+static void
+internal_function
+sift_ctx_init (re_sift_context_t *sctx, re_dfastate_t **sifted_sts,
+ re_dfastate_t **limited_sts, int last_node, int last_str_idx)
+{
+ sctx->sifted_states = sifted_sts;
+ sctx->limited_states = limited_sts;
+ sctx->last_node = last_node;
+ sctx->last_str_idx = last_str_idx;
+ re_node_set_init_empty (&sctx->limits);
+}
diff --git a/docs/checkout-internals.md b/docs/checkout-internals.md
new file mode 100644
index 000000000..6147ffdd8
--- /dev/null
+++ b/docs/checkout-internals.md
@@ -0,0 +1,204 @@
+Checkout Internals
+==================
+
+Checkout has to handle a lot of different cases. It examines the
+differences between the target tree, the baseline tree and the working
+directory, plus the contents of the index, and groups files into five
+categories:
+
+1. UNMODIFIED - Files that match in all places.
+2. SAFE - Files where the working directory and the baseline content
+ match that can be safely updated to the target.
+3. DIRTY/MISSING - Files where the working directory differs from the
+ baseline but there is no conflicting change with the target. One
+ example is a file that doesn't exist in the working directory - no
+ data would be lost as a result of writing this file. Which action
+ will be taken with these files depends on the options you use.
+4. CONFLICTS - Files where changes in the working directory conflict
+ with changes to be applied by the target. If conflicts are found,
+ they prevent any other modifications from being made (although there
+ are options to override that and force the update, of course).
+5. UNTRACKED/IGNORED - Files in the working directory that are untracked
+ or ignored (i.e. only in the working directory, not the other places).
+
+Right now, this classification is done via 3 iterators (for the three
+trees), with a final lookup in the index. At some point, this may move to
+a 4 iterator version to incorporate the index better.
+
+The actual checkout is done in five phases (at least right now).
+
+1. The diff between the baseline and the target tree is used as a base
+ list of possible updates to be applied.
+2. Iterate through the diff and the working directory, building a list of
+ actions to be taken (and sending notifications about conflicts and
+ dirty files).
+3. Remove any files / directories as needed (because alphabetical
+ iteration means that an untracked directory will end up sorted *after*
+ a blob that should be checked out with the same name).
+4. Update all blobs.
+5. Update all submodules (after 4 in case a new .gitmodules blob was
+ checked out)
+
+Checkout could be driven either off a target-to-workdir diff or a
+baseline-to-target diff. There are pros and cons of each.
+
+Target-to-workdir means the diff includes every file that could be
+modified, which simplifies bookkeeping, but the code to constantly refer
+back to the baseline gets complicated.
+
+Baseline-to-target has simpler code because the diff defines the action to
+take, but needs special handling for untracked and ignored files, if they
+need to be removed.
+
+The current checkout implementation is based on a baseline-to-target diff.
+
+
+Picking Actions
+===============
+
+The most interesting aspect of this is phase 2, picking the actions that
+should be taken. There are a lot of corner cases, so it may be easier to
+start by looking at the rules for a simple 2-iterator diff:
+
+Key
+---
+- B1,B2,B3 - blobs with different SHAs,
+- Bi - ignored blob (WD only)
+- T1,T2,T3 - trees with different SHAs,
+- Ti - ignored tree (WD only)
+- x - nothing
+
+Diff with 2 non-workdir iterators
+---------------------------------
+
+| | Old | New | |
+|----|-----|-----|------------------------------------------------------------|
+| 0 | x | x | nothing |
+| 1 | x | B1 | added blob |
+| 2 | x | T1 | added tree |
+| 3 | B1 | x | removed blob |
+| 4 | B1 | B1 | unmodified blob |
+| 5 | B1 | B2 | modified blob |
+| 6 | B1 | T1 | typechange blob -> tree |
+| 7 | T1 | x | removed tree |
+| 8 | T1 | B1 | typechange tree -> blob |
+| 9 | T1 | T1 | unmodified tree |
+| 10 | T1 | T2 | modified tree (implies modified/added/removed blob inside) |
+
+
+Now, let's make the "New" iterator into a working directory iterator, so
+we replace "added" items with either untracked or ignored, like this:
+
+Diff with non-work & workdir iterators
+--------------------------------------
+
+| | Old | New | |
+|----|-----|-----|------------------------------------------------------------|
+| 0 | x | x | nothing |
+| 1 | x | B1 | untracked blob |
+| 2 | x | Bi | ignored file |
+| 3 | x | T1 | untracked tree |
+| 4 | x | Ti | ignored tree |
+| 5 | B1 | x | removed blob |
+| 6 | B1 | B1 | unmodified blob |
+| 7 | B1 | B2 | modified blob |
+| 8 | B1 | T1 | typechange blob -> tree |
+| 9 | B1 | Ti | removed blob AND ignored tree as separate items |
+| 10 | T1 | x | removed tree |
+| 11 | T1 | B1 | typechange tree -> blob |
+| 12 | T1 | Bi | removed tree AND ignored blob as separate items |
+| 13 | T1 | T1 | unmodified tree |
+| 14 | T1 | T2 | modified tree (implies modified/added/removed blob inside) |
+
+Note: if there is a corresponding entry in the old tree, then a working
+directory item won't be ignored (i.e. no Bi or Ti for tracked items).
+
+
+Now, expand this to three iterators: a baseline tree, a target tree, and
+an actual working directory tree:
+
+Checkout From 3 Iterators (2 not workdir, 1 workdir)
+----------------------------------------------------
+
+(base == old HEAD; target == what to checkout; actual == working dir)
+
+| |base | target | actual/workdir | |
+|-----|-----|------- |----------------|--------------------------------------------------------------------|
+| 0 | x | x | x | nothing |
+| 1 | x | x | B1/Bi/T1/Ti | untracked/ignored blob/tree (SAFE) |
+| 2+ | x | B1 | x | add blob (SAFE) |
+| 3 | x | B1 | B1 | independently added blob (FORCEABLE-2) |
+| 4* | x | B1 | B2/Bi/T1/Ti | add blob with content conflict (FORCEABLE-2) |
+| 5+ | x | T1 | x | add tree (SAFE) |
+| 6* | x | T1 | B1/Bi | add tree with blob conflict (FORCEABLE-2) |
+| 7 | x | T1 | T1/i | independently added tree (SAFE+MISSING) |
+| 8 | B1 | x | x | independently deleted blob (SAFE+MISSING) |
+| 9- | B1 | x | B1 | delete blob (SAFE) |
+| 10- | B1 | x | B2 | delete of modified blob (FORCEABLE-1) |
+| 11 | B1 | x | T1/Ti | independently deleted blob AND untrack/ign tree (SAFE+MISSING !!!) |
+| 12 | B1 | B1 | x | locally deleted blob (DIRTY || SAFE+CREATE) |
+| 13+ | B1 | B2 | x | update to deleted blob (SAFE+MISSING) |
+| 14 | B1 | B1 | B1 | unmodified file (SAFE) |
+| 15 | B1 | B1 | B2 | locally modified file (DIRTY) |
+| 16+ | B1 | B2 | B1 | update unmodified blob (SAFE) |
+| 17 | B1 | B2 | B2 | independently updated blob (FORCEABLE-1) |
+| 18+ | B1 | B2 | B3 | update to modified blob (FORCEABLE-1) |
+| 19 | B1 | B1 | T1/Ti | locally deleted blob AND untrack/ign tree (DIRTY) |
+| 20* | B1 | B2 | T1/Ti | update to deleted blob AND untrack/ign tree (F-1) |
+| 21+ | B1 | T1 | x | add tree with locally deleted blob (SAFE+MISSING) |
+| 22* | B1 | T1 | B1 | add tree AND deleted blob (SAFE) |
+| 23* | B1 | T1 | B2 | add tree with delete of modified blob (F-1) |
+| 24 | B1 | T1 | T1 | add tree with deleted blob (F-1) |
+| 25 | T1 | x | x | independently deleted tree (SAFE+MISSING) |
+| 26 | T1 | x | B1/Bi | independently deleted tree AND untrack/ign blob (F-1) |
+| 27- | T1 | x | T1 | deleted tree (MAYBE SAFE) |
+| 28+ | T1 | B1 | x | deleted tree AND added blob (SAFE+MISSING) |
+| 29 | T1 | B1 | B1 | independently typechanged tree -> blob (F-1) |
+| 30+ | T1 | B1 | B2 | typechange tree->blob with conflicting blob (F-1) |
+| 31* | T1 | B1 | T1/T2 | typechange tree->blob (MAYBE SAFE) |
+| 32+ | T1 | T1 | x | restore locally deleted tree (SAFE+MISSING) |
+| 33 | T1 | T1 | B1/Bi | locally typechange tree->untrack/ign blob (DIRTY) |
+| 34 | T1 | T1 | T1/T2 | unmodified tree (MAYBE SAFE) |
+| 35+ | T1 | T2 | x | update locally deleted tree (SAFE+MISSING) |
+| 36* | T1 | T2 | B1/Bi | update to tree with typechanged tree->blob conflict (F-1) |
+| 37 | T1 | T2 | T1/T2/T3 | update to existing tree (MAYBE SAFE) |
+
+
+The number is followed by ' ' if no change is needed or '+' if the case
+needs to write to disk or '-' if something must be deleted and '*' if
+there should be a delete followed by an write.
+
+There are four tiers of safe cases:
+
+* SAFE == completely safe to update
+* SAFE+MISSING == safe except the workdir is missing the expect content
+* MAYBE SAFE == safe if workdir tree matches (or is missing) baseline
+ content, which is unknown at this point
+* FORCEABLE == conflict unless FORCE is given
+* DIRTY == no conflict but change is not applied unless FORCE
+
+Some slightly unusual circumstances:
+
+* 8 - parent dir is only deleted when file is, so parent will be left if
+ empty even though it would be deleted if the file were present
+* 11 - core git does not consider this a conflict but attempts to delete T1
+ and gives "unable to unlink file" error yet does not skip the rest
+ of the operation
+* 12 - without FORCE file is left deleted (i.e. not restored) so new wd is
+ dirty (and warning message "D file" is printed), with FORCE, file is
+ restored.
+* 24 - This should be considered MAYBE SAFE since effectively it is 7 and 8
+ combined, but core git considers this a conflict unless forced.
+* 26 - This combines two cases (1 & 25) (and also implied 8 for tree content)
+ which are ok on their own, but core git treat this as a conflict.
+ If not forced, this is a conflict. If forced, this actually doesn't
+ have to write anything and leaves the new blob as an untracked file.
+* 32 - This is the only case where the baseline and target values match
+ and yet we will still write to the working directory. In all other
+ cases, if baseline == target, we don't touch the workdir (it is
+ either already right or is "dirty"). However, since this case also
+ implies that a ?/B1/x case will exist as well, it can be skipped.
+
+Cases 3, 17, 24, 26, and 29 are all considered conflicts even though
+none of them will require making any updates to the working directory.
+
diff --git a/docs/diff-internals.md b/docs/diff-internals.md
new file mode 100644
index 000000000..da4c5a17c
--- /dev/null
+++ b/docs/diff-internals.md
@@ -0,0 +1,92 @@
+Diff is broken into four phases:
+
+1. Building a list of things that have changed. These changes are called
+ deltas (git_diff_delta objects) and are grouped into a git_diff_list.
+2. Applying file similarity measurement for rename and copy detection (and
+ to potentially split files that have changed radically). This step is
+ optional.
+3. Computing the textual diff for each delta. Not all deltas have a
+ meaningful textual diff. For those that do, the textual diff can
+ either be generated on the fly and passed to output callbacks or can be
+ turned into a git_diff_patch object.
+4. Formatting the diff and/or patch into standard text formats (such as
+ patches, raw lists, etc).
+
+In the source code, step 1 is implemented in `src/diff.c`, step 2 in
+`src/diff_tform.c`, step 3 in `src/diff_patch.c`, and step 4 in
+`src/diff_print.c`. Additionally, when it comes to accessing file
+content, everything goes through diff drivers that are implemented in
+`src/diff_driver.c`.
+
+External Objects
+----------------
+
+* `git_diff_options` represents user choices about how a diff should be
+ performed and is passed to most diff generating functions.
+* `git_diff_file` represents an item on one side of a possible delta
+* `git_diff_delta` represents a pair of items that have changed in some
+ way - it contains two `git_diff_file` plus a status and other stuff.
+* `git_diff_list` is a list of deltas along with information about how
+ those particular deltas were found.
+* `git_diff_patch` represents the actual diff between a pair of items. In
+ some cases, a delta may not have a corresponding patch, if the objects
+ are binary, for example. The content of a patch will be a set of hunks
+ and lines.
+* A `hunk` is range of lines described by a `git_diff_range` (i.e. "lines
+ 10-20 in the old file became lines 12-23 in the new"). It will have a
+ header that compactly represents that information, and it will have a
+ number of lines of context surrounding added and deleted lines.
+* A `line` is simple a line of data along with a `git_diff_line_t` value
+ that tells how the data should be interpreted (e.g. context or added).
+
+Internal Objects
+----------------
+
+* `git_diff_file_content` is an internal structure that represents the
+ data on one side of an item to be diffed; it is an augmented
+ `git_diff_file` with more flags and the actual file data.
+
+ * it is created from a repository plus a) a git_diff_file, b) a git_blob,
+ or c) raw data and size
+ * there are three main operations on git_diff_file_content:
+
+ * _initialization_ sets up the data structure and does what it can up to,
+ but not including loading and looking at the actual data
+ * _loading_ loads the data, preprocesses it (i.e. applies filters) and
+ potentially analyzes it (to decide if binary)
+ * _free_ releases loaded data and frees any allocated memory
+
+* The internal structure of a `git_diff_patch` stores the actual diff
+ between a pair of `git_diff_file_content` items
+
+ * it may be "unset" if the items are not diffable
+ * "empty" if the items are the same
+ * otherwise it will consist of a set of hunks each of which covers some
+ number of lines of context, additions and deletions
+ * a patch is created from two git_diff_file_content items
+ * a patch is fully instantiated in three phases:
+
+ * initial creation and initialization
+ * loading of data and preliminary data examination
+ * diffing of data and optional storage of diffs
+ * (TBD) if a patch is asked to store the diffs and the size of the diff
+ is significantly smaller than the raw data of the two sides, then the
+ patch may be flattened using a pool of string data
+
+* `git_diff_output` is an internal structure that represents an output
+ target for a `git_diff_patch`
+ * It consists of file, hunk, and line callbacks, plus a payload
+ * There is a standard flattened output that can be used for plain text output
+ * Typically we use a `git_xdiff_output` which drives the callbacks via the
+ xdiff code taken from core Git.
+
+* `git_diff_driver` is an internal structure that encapsulates the logic
+ for a given type of file
+ * a driver is looked up based on the name and mode of a file.
+ * the driver can then be used to:
+ * determine if a file is binary (by attributes, by git_diff_options
+ settings, or by examining the content)
+ * give you a function pointer that is used to evaluate function context
+ for hunk headers
+ * At some point, the logic for getting a filtered version of file content
+ or calculating the OID of a file may be moved into the driver.
diff --git a/docs/error-handling.md b/docs/error-handling.md
new file mode 100644
index 000000000..2dbe64a71
--- /dev/null
+++ b/docs/error-handling.md
@@ -0,0 +1,270 @@
+Error reporting in libgit2
+==========================
+
+Libgit2 tries to follow the POSIX style: functions return an `int` value
+with 0 (zero) indicating success and negative values indicating an error.
+There are specific negative error codes for each "expected failure"
+(e.g. `GIT_ENOTFOUND` for files that take a path which might be missing)
+and a generic error code (-1) for all critical or non-specific failures
+(e.g. running out of memory or system corruption).
+
+When a negative value is returned, an error message is also set. The
+message can be accessed via the `giterr_last` function which will return a
+pointer to a `git_error` structure containing the error message text and
+the class of error (i.e. what part of the library generated the error).
+
+For instance: An object lookup by SHA prefix (`git_object_lookup_prefix`)
+has two expected failure cases: the SHA is not found at all which returns
+`GIT_ENOTFOUND` or the SHA prefix is ambiguous (i.e. two or more objects
+share the prefix) which returns `GIT_EAMBIGUOUS`. There are any number of
+critical failures (such as a packfile being corrupted, a loose object
+having the wrong access permissions, etc.) all of which will return -1.
+When the object lookup is successful, it will return 0.
+
+If libgit2 was compiled with threads enabled (`-DTHREADSAFE=ON` when using
+CMake), then the error message will be kept in thread-local storage, so it
+will not be modified by other threads. If threads are not enabled, then
+the error message is in global data.
+
+All of the error return codes, the `git_error` type, the error access
+functions, and the error classes are defined in `include/git2/errors.h`.
+See the documentation there for details on the APIs for accessing,
+clearing, and even setting error codes.
+
+When writing libgit2 code, please be smart and conservative when returning
+error codes. Functions usually have a maximum of two or three "expected
+errors" and in most cases only one. If you feel there are more possible
+expected error scenarios, then the API you are writing may be at too high
+a level for core libgit2.
+
+Example usage
+-------------
+
+When using libgit2, you will typically capture the return value from
+functions using an `int` variable and check to see if it is negative.
+When that happens, you can, if you wish, look at the specific value or
+look at the error message that was generated.
+
+~~~c
+{
+ git_repository *repo;
+ int error = git_repository_open(&repo, "path/to/repo");
+
+ if (error < 0) {
+ fprintf(stderr, "Could not open repository: %s\n", giterr_last()->message);
+ exit(1);
+ }
+
+ ... use `repo` here ...
+
+ git_repository_free(repo); /* void function - no error return code */
+}
+~~~
+
+Some of the error return values do have meaning. Optionally, you can look
+at the specific error values to decide what to do.
+
+~~~c
+{
+ git_repository *repo;
+ const char *path = "path/to/repo";
+ int error = git_repository_open(&repo, path);
+
+ if (error < 0) {
+ if (error == GIT_ENOTFOUND)
+ fprintf(stderr, "Could not find repository at path '%s'\n", path);
+ else
+ fprintf(stderr, "Unable to open repository: %s\n",
+ giterr_last()->message);
+ exit(1);
+ }
+
+ ... happy ...
+}
+~~~
+
+Some of the higher-level language bindings may use a range of information
+from libgit2 to convert error return codes into exceptions, including the
+specific error return codes and even the class of error and the error
+message returned by `giterr_last`, but the full range of that logic is
+beyond the scope of this document.
+
+Example internal implementation
+-------------------------------
+
+Internally, libgit2 detects error scenarios, records error messages, and
+returns error values. Errors from low-level functions are generally
+passed upwards (unless the higher level can either handle the error or
+wants to translate the error into something more meaningful).
+
+~~~c
+int git_repository_open(git_repository **repository, const char *path)
+{
+ /* perform some logic to open the repository */
+ if (p_exists(path) < 0) {
+ giterr_set(GITERR_REPOSITORY, "The path '%s' doesn't exist", path);
+ return GIT_ENOTFOUND;
+ }
+
+ ...
+}
+~~~
+
+The public error API
+--------------------
+
+- `const git_error *giterr_last(void)`: The main function used to look up
+ the last error. This may return NULL if no error has occurred.
+ Otherwise this should return a `git_error` object indicating the class
+ of error and the error message that was generated by the library.
+
+ The last error is stored in thread-local storage when libgit2 is
+ compiled with thread support, so you do not have to worry about another
+ thread overwriting the value. When thread support is off, the last
+ error is a global value.
+
+ _Note_ There are some known bugs in the library where this may return
+ NULL even when an error code was generated. Please report these as
+ bugs, but in the meantime, please code defensively and check for NULL
+ when calling this function.
+
+- `void geterr_clear(void)`: This function clears the last error. The
+ library will call this when an error is generated by low level function
+ and the higher level function handles the error.
+
+ _Note_ There are some known bugs in the library where a low level
+ function's error message is not cleared by higher level code that
+ handles the error and returns zero. Please report these as bugs, but in
+ the meantime, a zero return value from a libgit2 API does not guarantee
+ that `giterr_last()` will return NULL.
+
+- `void giterr_set_str(int error_class, const char *message)`: This
+ function can be used when writing a custom backend module to set the
+ libgit2 error message. See the documentation on this function for its
+ use. Normal usage of libgit2 will probably never need to call this API.
+
+- `void giterr_set_oom(void)`: This is a standard function for reporting
+ an out-of-memory error. It is written in a manner that it doesn't have
+ to allocate any extra memory in order to record the error, so this is
+ the best way to report that scenario.
+
+Deviations from the standard
+----------------------------
+
+There are some public functions that do not return `int` values. There
+are two primary cases:
+
+* `void` return values: If a function has a `void` return, then it will
+ never fail. This primary will be used for object destructors.
+
+* `git_xyz *` return values: These are simple accessor functions where the
+ only meaningful error would typically be looking something up by index
+ and having the index be out of bounds. In those cases, the function
+ will typically return NULL.
+
+* Boolean return values: There are some cases where a function cannot fail
+ and wants to return a boolean value. In those cases, we try to return 1
+ for true and 0 for false. These cases are rare and the return value for
+ the function should probably be an `unsigned int` to denote these cases.
+ If you find an exception, please open an issue and let's fix it.
+
+There are a few other exceptions to these rules here and there in the
+library, but those are extremely rare and should probably be converted
+over to other to more standard patterns for usage. Feel free to open
+issues pointing these out.
+
+There are some known bugs in the library where some functions may return a
+negative value but not set an error message and some other functions may
+return zero (no error) and yet leave an error message set. Please report
+these cases as issues and they will be fixed. In the meanwhile, please
+code defensively, checking that the return value of `giterr_last` is not
+NULL before using it, and not relying on `giterr_last` to return NULL when
+a function returns 0 for success.
+
+The internal error API
+----------------------
+
+- `void giterr_set(int error_class, const char *fmt, ...)`: This is the
+ main internal function for setting an error. It works like `printf` to
+ format the error message. See the notes of `giterr_set_str` for a
+ general description of how error messages are stored (and also about
+ special handling for `error_class` of `GITERR_OS`).
+
+Writing error messages
+----------------------
+
+Here are some guidelines when writing error messages:
+
+- Use proper English, and an impersonal or past tenses: *The given path
+ does not exist*, *Failed to lookup object in ODB*
+
+- Use short, direct and objective messages. **One line, max**. libgit2 is
+ a low level library: think that all the messages reported will be thrown
+ as Ruby or Python exceptions. Think how long are common exception
+ messages in those languages.
+
+- **Do not add redundant information to the error message**, specially
+ information that can be inferred from the context.
+
+ E.g. in `git_repository_open`, do not report a message like "Failed to
+ open repository: path not found". Somebody is calling that
+ function. If it fails, they already know that the repository failed to
+ open!
+
+General guidelines for error reporting
+--------------------------------------
+
+- Libgit2 does not handle programming errors with these
+ functions. Programming errors are `assert`ed, and when their source is
+ internal, fixed as soon as possible. This is C, people.
+
+ Example of programming errors that would **not** be handled: passing
+ NULL to a function that expects a valid pointer; passing a `git_tree`
+ to a function that expects a `git_commit`. All these cases need to be
+ identified with `assert` and fixed asap.
+
+ Example of a runtime error: failing to parse a `git_tree` because it
+ contains invalid data. Failing to open a file because it doesn't exist
+ on disk. These errors are handled, a meaningful error message is set,
+ and an error code is returned.
+
+- In general, *do not* try to overwrite errors internally and *do*
+ propagate error codes from lower level functions to the higher level.
+ There are some cases where propagating an error code will be more
+ confusing rather than less, so there are some exceptions to this rule,
+ but the default behavior should be to simply clean up and pass the error
+ on up to the caller.
+
+ **WRONG**
+
+ ~~~c
+ int git_commit_parent(...)
+ {
+ ...
+
+ if (git_commit_lookup(parent, repo, parent_id) < 0) {
+ giterr_set(GITERR_COMMIT, "Overwrite lookup error message");
+ return -1; /* mask error code */
+ }
+
+ ...
+ }
+ ~~~
+
+ **RIGHT**
+
+ ~~~c
+ int git_commit_parent(...)
+ {
+ ...
+
+ error = git_commit_lookup(parent, repo, parent_id);
+ if (error < 0) {
+ /* cleanup intermediate objects if necessary */
+ /* leave error message and propagate error code */
+ return error;
+ }
+
+ ...
+ }
+ ~~~
diff --git a/docs/merge-df_conflicts.txt b/docs/merge-df_conflicts.txt
new file mode 100644
index 000000000..09780ee2d
--- /dev/null
+++ b/docs/merge-df_conflicts.txt
@@ -0,0 +1,41 @@
+Anc / Our / Thr represent the ancestor / ours / theirs side of a merge
+from branch "branch" into HEAD. Workdir represents the expected files in
+the working directory. Index represents the expected files in the index,
+with stage markers.
+
+ Anc Our Thr Workdir Index
+1 D D
+ D/F D/F D/F [0]
+
+2 D D+ D~HEAD (mod/del) D/F [0]
+ D/F D/F D [1]
+ D [2]
+
+3 D D D/F D/F [0]
+ D/F
+
+4 D D+ D~branch (mod/del) D/F [0]
+ D/F D/F D [1]
+ D [3]
+
+5 D D/F (add/add) D/F [2]
+ D/F D/F [3]
+ D/F
+
+6 D/F D/F D D [0]
+ D
+
+7 D/F D/F+ D/F (mod/del) D/F [1]
+ D D~branch (fil/dir) D/F [2]
+ D [3]
+
+8 D/F D/F D D [0]
+ D
+
+9 D/F D/F+ D/F (mod/del) D/F [1]
+ D D~HEAD (fil/dir) D [2]
+ D/F [3]
+
+10 D/F D/F (fil/dir) D/F [0]
+ D D~HEAD D [2]
+ D
diff --git a/examples/.gitignore b/examples/.gitignore
index 4c34e4ab5..083c8835e 100644
--- a/examples/.gitignore
+++ b/examples/.gitignore
@@ -1,2 +1,13 @@
general
showindex
+diff
+rev-list
+blame
+cat-file
+init
+log
+rev-parse
+status
+tag
+for-each-ref
+*.dSYM
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
new file mode 100644
index 000000000..596be45ed
--- /dev/null
+++ b/examples/CMakeLists.txt
@@ -0,0 +1,16 @@
+FILE(GLOB_RECURSE SRC_EXAMPLE_GIT2 network/*.c network/*.h)
+ADD_EXECUTABLE(cgit2 ${SRC_EXAMPLE_GIT2})
+IF(WIN32 OR ANDROID)
+ TARGET_LINK_LIBRARIES(cgit2 git2)
+ELSE()
+ TARGET_LINK_LIBRARIES(cgit2 git2 pthread)
+ENDIF()
+
+FILE(GLOB SRC_EXAMPLE_APPS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c)
+FOREACH(src_app ${SRC_EXAMPLE_APPS})
+ STRING(REPLACE ".c" "" app_name ${src_app})
+ IF(NOT ${app_name} STREQUAL "common")
+ ADD_EXECUTABLE(${app_name} ${src_app} "common.c")
+ TARGET_LINK_LIBRARIES(${app_name} git2)
+ ENDIF()
+ENDFOREACH()
diff --git a/examples/COPYING b/examples/COPYING
new file mode 100644
index 000000000..0e259d42c
--- /dev/null
+++ b/examples/COPYING
@@ -0,0 +1,121 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+ HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display,
+ communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+ likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+ v. rights protecting the extraction, dissemination, use and reuse of data
+ in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation
+ thereof, including any amended or successor version of such
+ directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+ world based on applicable law or treaty, and any national
+ implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+ warranties of any kind concerning the Work, express, implied,
+ statutory or otherwise, including without limitation warranties of
+ title, merchantability, fitness for a particular purpose, non
+ infringement, or the absence of latent or other defects, accuracy, or
+ the present or absence of errors, whether or not discoverable, all to
+ the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person's Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the
+ Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to
+ this CC0 or use of the Work.
diff --git a/examples/Makefile b/examples/Makefile
index f7bf469a5..11b019984 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -1,10 +1,16 @@
-all: general showindex
+.PHONY: all
-general : general.c
- gcc -lgit2 -o general general.c
+CC = gcc
+CFLAGS = -g -I../include -I../src -Wall -Wextra -Wmissing-prototypes -Wno-missing-field-initializers
+LFLAGS = -L../build -lgit2 -lz
+APPS = general showindex diff rev-list cat-file status log rev-parse init blame tag
+APPS += for-each-ref
-showindex : showindex.c
- gcc -lgit2 -o showindex showindex.c
+all: $(APPS)
+
+% : %.c
+ $(CC) -o $@ common.c $(CFLAGS) $< $(LFLAGS)
clean:
- rm general showindex
+ $(RM) $(APPS)
+ $(RM) -r *.dSYM
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 000000000..769c4b267
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,22 @@
+libgit2 examples
+================
+
+These examples are a mixture of basic emulation of core Git command line
+functions and simple snippets demonstrating libgit2 API usage (for use
+with Docurium). As a whole, they are not vetted carefully for bugs, error
+handling, and cross-platform compatibility in the same manner as the rest
+of the code in libgit2, so copy with caution.
+
+That being said, you are welcome to copy code from these examples as
+desired when using libgit2. They have been [released to the public domain][cc0],
+so there are no restrictions on their use.
+
+[cc0]: COPYING
+
+For annotated HTML versions, see the "Examples" section of:
+
+ http://libgit2.github.com/libgit2
+
+such as:
+
+ http://libgit2.github.com/libgit2/ex/HEAD/general.html
diff --git a/examples/add.c b/examples/add.c
new file mode 100644
index 000000000..0c6076e81
--- /dev/null
+++ b/examples/add.c
@@ -0,0 +1,159 @@
+/*
+ * libgit2 "add" example - shows how to modify the index
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#include "common.h"
+#include <assert.h>
+
+enum print_options {
+ SKIP = 1,
+ VERBOSE = 2,
+ UPDATE = 4,
+};
+
+struct print_payload {
+ enum print_options options;
+ git_repository *repo;
+};
+
+/* Forward declarations for helpers */
+static void parse_opts(int *options, int *count, int argc, char *argv[]);
+void init_array(git_strarray *array, int argc, char **argv);
+int print_matched_cb(const char *path, const char *matched_pathspec, void *payload);
+
+int main (int argc, char** argv)
+{
+ git_index_matched_path_cb matched_cb = NULL;
+ git_repository *repo = NULL;
+ git_index *index;
+ git_strarray array = {0};
+ int options = 0, count = 0;
+ struct print_payload payload = {0};
+
+ git_threads_init();
+
+ parse_opts(&options, &count, argc, argv);
+
+ init_array(&array, argc-count, argv+count);
+
+ check_lg2(git_repository_open(&repo, "."), "No git repository", NULL);
+ check_lg2(git_repository_index(&index, repo), "Could not open repository index", NULL);
+
+ if (options&VERBOSE || options&SKIP) {
+ matched_cb = &print_matched_cb;
+ }
+
+ payload.options = options;
+ payload.repo = repo;
+
+ if (options&UPDATE) {
+ git_index_update_all(index, &array, matched_cb, &payload);
+ } else {
+ git_index_add_all(index, &array, 0, matched_cb, &payload);
+ }
+
+ git_index_write(index);
+ git_index_free(index);
+ git_repository_free(repo);
+
+ git_threads_shutdown();
+
+ return 0;
+}
+
+int print_matched_cb(const char *path, const char *matched_pathspec, void *payload)
+{
+ struct print_payload p = *(struct print_payload*)(payload);
+ int ret;
+ git_status_t status;
+ (void)matched_pathspec;
+
+ if (git_status_file((unsigned int*)(&status), p.repo, path)) {
+ return -1; //abort
+ }
+
+ if (status & GIT_STATUS_WT_MODIFIED ||
+ status & GIT_STATUS_WT_NEW) {
+ printf("add '%s'\n", path);
+ ret = 0;
+ } else {
+ ret = 1;
+ }
+
+ if(p.options & SKIP) {
+ ret = 1;
+ }
+
+ return ret;
+}
+
+void init_array(git_strarray *array, int argc, char **argv)
+{
+ unsigned int i;
+
+ array->count = argc;
+ array->strings = malloc(sizeof(char*) * array->count);
+ assert(array->strings!=NULL);
+
+ for(i=0; i<array->count; i++) {
+ array->strings[i]=argv[i];
+ }
+
+ return;
+}
+
+void print_usage(void)
+{
+ fprintf(stderr, "usage: add [options] [--] file-spec [file-spec] [...]\n\n");
+ fprintf(stderr, "\t-n, --dry-run dry run\n");
+ fprintf(stderr, "\t-v, --verbose be verbose\n");
+ fprintf(stderr, "\t-u, --update update tracked files\n");
+ exit(1);
+}
+
+static void parse_opts(int *options, int *count, int argc, char *argv[])
+{
+ int i;
+
+ for (i = 1; i < argc; ++i) {
+ if (argv[i][0] != '-') {
+ break;
+ }
+ else if(!strcmp(argv[i], "--verbose") || !strcmp(argv[i], "-v")) {
+ *options |= VERBOSE;
+ }
+ else if(!strcmp(argv[i], "--dry-run") || !strcmp(argv[i], "-n")) {
+ *options |= SKIP;
+ }
+ else if(!strcmp(argv[i], "--update") || !strcmp(argv[i], "-u")) {
+ *options |= UPDATE;
+ }
+ else if(!strcmp(argv[i], "-h")) {
+ print_usage();
+ break;
+ }
+ else if(!strcmp(argv[i], "--")) {
+ i++;
+ break;
+ }
+ else {
+ fprintf(stderr, "Unsupported option %s.\n", argv[i]);
+ print_usage();
+ }
+ }
+
+ if (argc<=i)
+ print_usage();
+
+ *count = i;
+}
diff --git a/examples/blame.c b/examples/blame.c
new file mode 100644
index 000000000..fda605bce
--- /dev/null
+++ b/examples/blame.c
@@ -0,0 +1,210 @@
+/*
+ * libgit2 "blame" example - shows how to use the blame API
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#include "common.h"
+
+#ifdef _MSC_VER
+#define snprintf sprintf_s
+#define strcasecmp strcmpi
+#endif
+
+/**
+ * This example demonstrates how to invoke the libgit2 blame API to roughly
+ * simulate the output of `git blame` and a few of its command line arguments.
+ */
+
+struct opts {
+ char *path;
+ char *commitspec;
+ int C;
+ int M;
+ int start_line;
+ int end_line;
+ int F;
+};
+static void parse_opts(struct opts *o, int argc, char *argv[]);
+
+int main(int argc, char *argv[])
+{
+ int i, line, break_on_null_hunk;
+ char spec[1024] = {0};
+ struct opts o = {0};
+ const char *rawdata;
+ git_repository *repo = NULL;
+ git_revspec revspec = {0};
+ git_blame_options blameopts = GIT_BLAME_OPTIONS_INIT;
+ git_blame *blame = NULL;
+ git_blob *blob;
+ git_object *obj;
+
+ git_threads_init();
+
+ parse_opts(&o, argc, argv);
+ if (o.M) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES;
+ if (o.C) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES;
+ if (o.F) blameopts.flags |= GIT_BLAME_FIRST_PARENT;
+
+ /** Open the repository. */
+ check_lg2(git_repository_open_ext(&repo, ".", 0, NULL), "Couldn't open repository", NULL);
+
+ /**
+ * The commit range comes in "commitish" form. Use the rev-parse API to
+ * nail down the end points.
+ */
+ if (o.commitspec) {
+ check_lg2(git_revparse(&revspec, repo, o.commitspec), "Couldn't parse commit spec", NULL);
+ if (revspec.flags & GIT_REVPARSE_SINGLE) {
+ git_oid_cpy(&blameopts.newest_commit, git_object_id(revspec.from));
+ git_object_free(revspec.from);
+ } else {
+ git_oid_cpy(&blameopts.oldest_commit, git_object_id(revspec.from));
+ git_oid_cpy(&blameopts.newest_commit, git_object_id(revspec.to));
+ git_object_free(revspec.from);
+ git_object_free(revspec.to);
+ }
+ }
+
+ /** Run the blame. */
+ check_lg2(git_blame_file(&blame, repo, o.path, &blameopts), "Blame error", NULL);
+
+ /**
+ * Get the raw data inside the blob for output. We use the
+ * `commitish:path/to/file.txt` format to find it.
+ */
+ if (git_oid_iszero(&blameopts.newest_commit))
+ strcpy(spec, "HEAD");
+ else
+ git_oid_tostr(spec, sizeof(spec), &blameopts.newest_commit);
+ strcat(spec, ":");
+ strcat(spec, o.path);
+
+ check_lg2(git_revparse_single(&obj, repo, spec), "Object lookup error", NULL);
+ check_lg2(git_blob_lookup(&blob, repo, git_object_id(obj)), "Blob lookup error", NULL);
+ git_object_free(obj);
+
+ rawdata = git_blob_rawcontent(blob);
+
+ /** Produce the output. */
+ line = 1;
+ i = 0;
+ break_on_null_hunk = 0;
+ while (i < git_blob_rawsize(blob)) {
+ const char *eol = strchr(rawdata+i, '\n');
+ char oid[10] = {0};
+ const git_blame_hunk *hunk = git_blame_get_hunk_byline(blame, line);
+
+ if (break_on_null_hunk && !hunk) break;
+
+ if (hunk) {
+ char sig[128] = {0};
+ break_on_null_hunk = 1;
+
+
+ git_oid_tostr(oid, 10, &hunk->final_commit_id);
+ snprintf(sig, 30, "%s <%s>", hunk->final_signature->name, hunk->final_signature->email);
+
+ printf("%s ( %-30s %3d) %.*s\n",
+ oid,
+ sig,
+ line,
+ (int)(eol-rawdata-i),
+ rawdata+i);
+ }
+
+ i = (int)(eol - rawdata + 1);
+ line++;
+ }
+
+ /** Cleanup. */
+ git_blob_free(blob);
+ git_blame_free(blame);
+ git_repository_free(repo);
+
+ git_threads_shutdown();
+
+ return 0;
+}
+
+/** Tell the user how to make this thing work. */
+static void usage(const char *msg, const char *arg)
+{
+ if (msg && arg)
+ fprintf(stderr, "%s: %s\n", msg, arg);
+ else if (msg)
+ fprintf(stderr, "%s\n", msg);
+ fprintf(stderr, "usage: blame [options] [<commit range>] <path>\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, " <commit range> example: `HEAD~10..HEAD`, or `1234abcd`\n");
+ fprintf(stderr, " -L <n,m> process only line range n-m, counting from 1\n");
+ fprintf(stderr, " -M find line moves within and across files\n");
+ fprintf(stderr, " -C find line copies within and across files\n");
+ fprintf(stderr, " -F follow only the first parent commits\n");
+ fprintf(stderr, "\n");
+ exit(1);
+}
+
+/** Parse the arguments. */
+static void parse_opts(struct opts *o, int argc, char *argv[])
+{
+ int i;
+ char *bare_args[3] = {0};
+
+ if (argc < 2) usage(NULL, NULL);
+
+ for (i=1; i<argc; i++) {
+ char *a = argv[i];
+
+ if (a[0] != '-') {
+ int i=0;
+ while (bare_args[i] && i < 3) ++i;
+ if (i >= 3)
+ usage("Invalid argument set", NULL);
+ bare_args[i] = a;
+ }
+ else if (!strcmp(a, "--"))
+ continue;
+ else if (!strcasecmp(a, "-M"))
+ o->M = 1;
+ else if (!strcasecmp(a, "-C"))
+ o->C = 1;
+ else if (!strcasecmp(a, "-F"))
+ o->F = 1;
+ else if (!strcasecmp(a, "-L")) {
+ i++; a = argv[i];
+ if (i >= argc) fatal("Not enough arguments to -L", NULL);
+ check_lg2(sscanf(a, "%d,%d", &o->start_line, &o->end_line)-2, "-L format error", NULL);
+ }
+ else {
+ /* commit range */
+ if (o->commitspec) fatal("Only one commit spec allowed", NULL);
+ o->commitspec = a;
+ }
+ }
+
+ /* Handle the bare arguments */
+ if (!bare_args[0]) usage("Please specify a path", NULL);
+ o->path = bare_args[0];
+ if (bare_args[1]) {
+ /* <commitspec> <path> */
+ o->path = bare_args[1];
+ o->commitspec = bare_args[0];
+ }
+ if (bare_args[2]) {
+ /* <oldcommit> <newcommit> <path> */
+ char spec[128] = {0};
+ o->path = bare_args[2];
+ sprintf(spec, "%s..%s", bare_args[0], bare_args[1]);
+ o->commitspec = spec;
+ }
+}
diff --git a/examples/cat-file.c b/examples/cat-file.c
new file mode 100644
index 000000000..52399fa8a
--- /dev/null
+++ b/examples/cat-file.c
@@ -0,0 +1,246 @@
+/*
+ * libgit2 "cat-file" example - shows how to print data from the ODB
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#include "common.h"
+
+static void print_signature(const char *header, const git_signature *sig)
+{
+ char sign;
+ int offset, hours, minutes;
+
+ if (!sig)
+ return;
+
+ offset = sig->when.offset;
+ if (offset < 0) {
+ sign = '-';
+ offset = -offset;
+ } else {
+ sign = '+';
+ }
+
+ hours = offset / 60;
+ minutes = offset % 60;
+
+ printf("%s %s <%s> %ld %c%02d%02d\n",
+ header, sig->name, sig->email, (long)sig->when.time,
+ sign, hours, minutes);
+}
+
+/** Printing out a blob is simple, get the contents and print */
+static void show_blob(const git_blob *blob)
+{
+ /* ? Does this need crlf filtering? */
+ fwrite(git_blob_rawcontent(blob), (size_t)git_blob_rawsize(blob), 1, stdout);
+}
+
+/** Show each entry with its type, id and attributes */
+static void show_tree(const git_tree *tree)
+{
+ size_t i, max_i = (int)git_tree_entrycount(tree);
+ char oidstr[GIT_OID_HEXSZ + 1];
+ const git_tree_entry *te;
+
+ for (i = 0; i < max_i; ++i) {
+ te = git_tree_entry_byindex(tree, i);
+
+ git_oid_tostr(oidstr, sizeof(oidstr), git_tree_entry_id(te));
+
+ printf("%06o %s %s\t%s\n",
+ git_tree_entry_filemode(te),
+ git_object_type2string(git_tree_entry_type(te)),
+ oidstr, git_tree_entry_name(te));
+ }
+}
+
+/**
+ * Commits and tags have a few interesting fields in their header.
+ */
+static void show_commit(const git_commit *commit)
+{
+ unsigned int i, max_i;
+ char oidstr[GIT_OID_HEXSZ + 1];
+
+ git_oid_tostr(oidstr, sizeof(oidstr), git_commit_tree_id(commit));
+ printf("tree %s\n", oidstr);
+
+ max_i = (unsigned int)git_commit_parentcount(commit);
+ for (i = 0; i < max_i; ++i) {
+ git_oid_tostr(oidstr, sizeof(oidstr), git_commit_parent_id(commit, i));
+ printf("parent %s\n", oidstr);
+ }
+
+ print_signature("author", git_commit_author(commit));
+ print_signature("committer", git_commit_committer(commit));
+
+ if (git_commit_message(commit))
+ printf("\n%s\n", git_commit_message(commit));
+}
+
+static void show_tag(const git_tag *tag)
+{
+ char oidstr[GIT_OID_HEXSZ + 1];
+
+ git_oid_tostr(oidstr, sizeof(oidstr), git_tag_target_id(tag));;
+ printf("object %s\n", oidstr);
+ printf("type %s\n", git_object_type2string(git_tag_target_type(tag)));
+ printf("tag %s\n", git_tag_name(tag));
+ print_signature("tagger", git_tag_tagger(tag));
+
+ if (git_tag_message(tag))
+ printf("\n%s\n", git_tag_message(tag));
+}
+
+enum {
+ SHOW_TYPE = 1,
+ SHOW_SIZE = 2,
+ SHOW_NONE = 3,
+ SHOW_PRETTY = 4
+};
+
+/* Forward declarations for option-parsing helper */
+struct opts {
+ const char *dir;
+ const char *rev;
+ int action;
+ int verbose;
+};
+static void parse_opts(struct opts *o, int argc, char *argv[]);
+
+
+/** Entry point for this command */
+int main(int argc, char *argv[])
+{
+ git_repository *repo;
+ struct opts o = { ".", NULL, 0, 0 };
+ git_object *obj = NULL;
+ char oidstr[GIT_OID_HEXSZ + 1];
+
+ git_threads_init();
+
+ parse_opts(&o, argc, argv);
+
+ check_lg2(git_repository_open_ext(&repo, o.dir, 0, NULL),
+ "Could not open repository", NULL);
+ check_lg2(git_revparse_single(&obj, repo, o.rev),
+ "Could not resolve", o.rev);
+
+ if (o.verbose) {
+ char oidstr[GIT_OID_HEXSZ + 1];
+ git_oid_tostr(oidstr, sizeof(oidstr), git_object_id(obj));
+
+ printf("%s %s\n--\n",
+ git_object_type2string(git_object_type(obj)), oidstr);
+ }
+
+ switch (o.action) {
+ case SHOW_TYPE:
+ printf("%s\n", git_object_type2string(git_object_type(obj)));
+ break;
+ case SHOW_SIZE: {
+ git_odb *odb;
+ git_odb_object *odbobj;
+
+ check_lg2(git_repository_odb(&odb, repo), "Could not open ODB", NULL);
+ check_lg2(git_odb_read(&odbobj, odb, git_object_id(obj)),
+ "Could not find obj", NULL);
+
+ printf("%ld\n", (long)git_odb_object_size(odbobj));
+
+ git_odb_object_free(odbobj);
+ git_odb_free(odb);
+ }
+ break;
+ case SHOW_NONE:
+ /* just want return result */
+ break;
+ case SHOW_PRETTY:
+
+ switch (git_object_type(obj)) {
+ case GIT_OBJ_BLOB:
+ show_blob((const git_blob *)obj);
+ break;
+ case GIT_OBJ_COMMIT:
+ show_commit((const git_commit *)obj);
+ break;
+ case GIT_OBJ_TREE:
+ show_tree((const git_tree *)obj);
+ break;
+ case GIT_OBJ_TAG:
+ show_tag((const git_tag *)obj);
+ break;
+ default:
+ printf("unknown %s\n", oidstr);
+ break;
+ }
+ break;
+ }
+
+ git_object_free(obj);
+ git_repository_free(repo);
+
+ git_threads_shutdown();
+
+ return 0;
+}
+
+/** Print out usage information */
+static void usage(const char *message, const char *arg)
+{
+ if (message && arg)
+ fprintf(stderr, "%s: %s\n", message, arg);
+ else if (message)
+ fprintf(stderr, "%s\n", message);
+ fprintf(stderr,
+ "usage: cat-file (-t | -s | -e | -p) [-v] [-q] "
+ "[-h|--help] [--git-dir=<dir>] <object>\n");
+ exit(1);
+}
+
+/** Parse the command-line options taken from git */
+static void parse_opts(struct opts *o, int argc, char *argv[])
+{
+ struct args_info args = ARGS_INFO_INIT;
+
+ for (args.pos = 1; args.pos < argc; ++args.pos) {
+ char *a = argv[args.pos];
+
+ if (a[0] != '-') {
+ if (o->rev != NULL)
+ usage("Only one rev should be provided", NULL);
+ else
+ o->rev = a;
+ }
+ else if (!strcmp(a, "-t"))
+ o->action = SHOW_TYPE;
+ else if (!strcmp(a, "-s"))
+ o->action = SHOW_SIZE;
+ else if (!strcmp(a, "-e"))
+ o->action = SHOW_NONE;
+ else if (!strcmp(a, "-p"))
+ o->action = SHOW_PRETTY;
+ else if (!strcmp(a, "-q"))
+ o->verbose = 0;
+ else if (!strcmp(a, "-v"))
+ o->verbose = 1;
+ else if (!strcmp(a, "--help") || !strcmp(a, "-h"))
+ usage(NULL, NULL);
+ else if (!match_str_arg(&o->dir, &args, "--git-dir"))
+ usage("Unknown option", a);
+ }
+
+ if (!o->action || !o->rev)
+ usage(NULL, NULL);
+
+}
diff --git a/examples/common.c b/examples/common.c
new file mode 100644
index 000000000..a066c153c
--- /dev/null
+++ b/examples/common.c
@@ -0,0 +1,191 @@
+/*
+ * Utilities library for libgit2 examples
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#include "common.h"
+
+void check_lg2(int error, const char *message, const char *extra)
+{
+ const git_error *lg2err;
+ const char *lg2msg = "", *lg2spacer = "";
+
+ if (!error)
+ return;
+
+ if ((lg2err = giterr_last()) != NULL && lg2err->message != NULL) {
+ lg2msg = lg2err->message;
+ lg2spacer = " - ";
+ }
+
+ if (extra)
+ fprintf(stderr, "%s '%s' [%d]%s%s\n",
+ message, extra, error, lg2spacer, lg2msg);
+ else
+ fprintf(stderr, "%s [%d]%s%s\n",
+ message, error, lg2spacer, lg2msg);
+
+ exit(1);
+}
+
+void fatal(const char *message, const char *extra)
+{
+ if (extra)
+ fprintf(stderr, "%s %s\n", message, extra);
+ else
+ fprintf(stderr, "%s\n", message);
+
+ exit(1);
+}
+
+size_t is_prefixed(const char *str, const char *pfx)
+{
+ size_t len = strlen(pfx);
+ return strncmp(str, pfx, len) ? 0 : len;
+}
+
+int match_str_arg(
+ const char **out, struct args_info *args, const char *opt)
+{
+ const char *found = args->argv[args->pos];
+ size_t len = is_prefixed(found, opt);
+
+ if (!len)
+ return 0;
+
+ if (!found[len]) {
+ if (args->pos + 1 == args->argc)
+ fatal("expected value following argument", opt);
+ args->pos += 1;
+ *out = args->argv[args->pos];
+ return 1;
+ }
+
+ if (found[len] == '=') {
+ *out = found + len + 1;
+ return 1;
+ }
+
+ return 0;
+}
+
+static const char *match_numeric_arg(struct args_info *args, const char *opt)
+{
+ const char *found = args->argv[args->pos];
+ size_t len = is_prefixed(found, opt);
+
+ if (!len)
+ return NULL;
+
+ if (!found[len]) {
+ if (args->pos + 1 == args->argc)
+ fatal("expected numeric value following argument", opt);
+ args->pos += 1;
+ found = args->argv[args->pos];
+ } else {
+ found = found + len;
+ if (*found == '=')
+ found++;
+ }
+
+ return found;
+}
+
+int match_uint16_arg(
+ uint16_t *out, struct args_info *args, const char *opt)
+{
+ const char *found = match_numeric_arg(args, opt);
+ uint16_t val;
+ char *endptr = NULL;
+
+ if (!found)
+ return 0;
+
+ val = (uint16_t)strtoul(found, &endptr, 0);
+ if (!endptr || *endptr != '\0')
+ fatal("expected number after argument", opt);
+
+ if (out)
+ *out = val;
+ return 1;
+}
+
+static int match_int_internal(
+ int *out, const char *str, int allow_negative, const char *opt)
+{
+ char *endptr = NULL;
+ int val = (int)strtol(str, &endptr, 10);
+
+ if (!endptr || *endptr != '\0')
+ fatal("expected number", opt);
+ else if (val < 0 && !allow_negative)
+ fatal("negative values are not allowed", opt);
+
+ if (out)
+ *out = val;
+
+ return 1;
+}
+
+int is_integer(int *out, const char *str, int allow_negative)
+{
+ return match_int_internal(out, str, allow_negative, NULL);
+}
+
+int match_int_arg(
+ int *out, struct args_info *args, const char *opt, int allow_negative)
+{
+ const char *found = match_numeric_arg(args, opt);
+ if (!found)
+ return 0;
+ return match_int_internal(out, found, allow_negative, opt);
+}
+
+int diff_output(
+ const git_diff_delta *d,
+ const git_diff_hunk *h,
+ const git_diff_line *l,
+ void *p)
+{
+ FILE *fp = (FILE*)p;
+
+ (void)d; (void)h;
+
+ if (!fp)
+ fp = stdout;
+
+ if (l->origin == GIT_DIFF_LINE_CONTEXT ||
+ l->origin == GIT_DIFF_LINE_ADDITION ||
+ l->origin == GIT_DIFF_LINE_DELETION)
+ fputc(l->origin, fp);
+
+ fwrite(l->content, 1, l->content_len, fp);
+
+ return 0;
+}
+
+void treeish_to_tree(
+ git_tree **out, git_repository *repo, const char *treeish)
+{
+ git_object *obj = NULL;
+
+ check_lg2(
+ git_revparse_single(&obj, repo, treeish),
+ "looking up object", treeish);
+
+ check_lg2(
+ git_object_peel((git_object **)out, obj, GIT_OBJ_TREE),
+ "resolving object to tree", treeish);
+
+ git_object_free(obj);
+}
+
diff --git a/examples/common.h b/examples/common.h
new file mode 100644
index 000000000..2fd7d579f
--- /dev/null
+++ b/examples/common.h
@@ -0,0 +1,87 @@
+/*
+ * Utilities library for libgit2 examples
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <git2.h>
+
+/**
+ * Check libgit2 error code, printing error to stderr on failure and
+ * exiting the program.
+ */
+extern void check_lg2(int error, const char *message, const char *extra);
+
+/**
+ * Exit the program, printing error to stderr
+ */
+extern void fatal(const char *message, const char *extra);
+
+/**
+ * Check if a string has the given prefix. Returns 0 if not prefixed
+ * or the length of the prefix if it is.
+ */
+extern size_t is_prefixed(const char *str, const char *pfx);
+
+/**
+ * Match an integer string, returning 1 if matched, 0 if not.
+ */
+extern int is_integer(int *out, const char *str, int allow_negative);
+
+struct args_info {
+ int argc;
+ char **argv;
+ int pos;
+};
+#define ARGS_INFO_INIT { argc, argv, 0 }
+
+/**
+ * Check current `args` entry against `opt` string. If it matches
+ * exactly, take the next arg as a string; if it matches as a prefix with
+ * an equal sign, take the remainder as a string; otherwise return 0.
+ */
+extern int match_str_arg(
+ const char **out, struct args_info *args, const char *opt);
+
+/**
+ * Check current `args` entry against `opt` string parsing as uint16. If
+ * `opt` matches exactly, take the next arg as a uint16_t value; if `opt`
+ * is a prefix (equal sign optional), take the remainder of the arg as a
+ * uint16_t value; otherwise return 0.
+ */
+extern int match_uint16_arg(
+ uint16_t *out, struct args_info *args, const char *opt);
+
+/**
+ * Check current `args` entry against `opt` string parsing as int. If
+ * `opt` matches exactly, take the next arg as an int value; if it matches
+ * as a prefix (equal sign optional), take the remainder of the arg as a
+ * int value; otherwise return 0.
+ */
+extern int match_int_arg(
+ int *out, struct args_info *args, const char *opt, int allow_negative);
+
+/**
+ * Basic output function for plain text diff output
+ * Pass `FILE*` such as `stdout` or `stderr` as payload (or NULL == `stdout`)
+ */
+extern int diff_output(
+ const git_diff_delta*, const git_diff_hunk*, const git_diff_line*, void*);
+
+/**
+ * Convert a treeish argument to an actual tree; this will call check_lg2
+ * and exit the program if `treeish` cannot be resolved to a tree
+ */
+extern void treeish_to_tree(
+ git_tree **out, git_repository *repo, const char *treeish);
diff --git a/examples/diff.c b/examples/diff.c
new file mode 100644
index 000000000..76ac2f311
--- /dev/null
+++ b/examples/diff.c
@@ -0,0 +1,337 @@
+/*
+ * libgit2 "diff" example - shows how to use the diff API
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#include "common.h"
+
+/**
+ * This example demonstrates the use of the libgit2 diff APIs to
+ * create `git_diff` objects and display them, emulating a number of
+ * core Git `diff` command line options.
+ *
+ * This covers on a portion of the core Git diff options and doesn't
+ * have particularly good error handling, but it should show most of
+ * the core libgit2 diff APIs, including various types of diffs and
+ * how to do renaming detection and patch formatting.
+ */
+
+static const char *colors[] = {
+ "\033[m", /* reset */
+ "\033[1m", /* bold */
+ "\033[31m", /* red */
+ "\033[32m", /* green */
+ "\033[36m" /* cyan */
+};
+
+enum {
+ OUTPUT_DIFF = (1 << 0),
+ OUTPUT_STAT = (1 << 1),
+ OUTPUT_SHORTSTAT = (1 << 2),
+ OUTPUT_NUMSTAT = (1 << 3),
+ OUTPUT_SUMMARY = (1 << 4)
+};
+
+enum {
+ CACHE_NORMAL = 0,
+ CACHE_ONLY = 1,
+ CACHE_NONE = 2
+};
+
+/** The 'opts' struct captures all the various parsed command line options. */
+struct opts {
+ git_diff_options diffopts;
+ git_diff_find_options findopts;
+ int color;
+ int cache;
+ int output;
+ git_diff_format_t format;
+ const char *treeish1;
+ const char *treeish2;
+ const char *dir;
+};
+
+/** These functions are implemented at the end */
+static void usage(const char *message, const char *arg);
+static void parse_opts(struct opts *o, int argc, char *argv[]);
+static int color_printer(
+ const git_diff_delta*, const git_diff_hunk*, const git_diff_line*, void*);
+static void diff_print_stats(git_diff *diff, struct opts *o);
+
+int main(int argc, char *argv[])
+{
+ git_repository *repo = NULL;
+ git_tree *t1 = NULL, *t2 = NULL;
+ git_diff *diff;
+ struct opts o = {
+ GIT_DIFF_OPTIONS_INIT, GIT_DIFF_FIND_OPTIONS_INIT,
+ -1, 0, 0, GIT_DIFF_FORMAT_PATCH, NULL, NULL, "."
+ };
+
+ git_threads_init();
+
+ parse_opts(&o, argc, argv);
+
+ check_lg2(git_repository_open_ext(&repo, o.dir, 0, NULL),
+ "Could not open repository", o.dir);
+
+ /**
+ * Possible argument patterns:
+ *
+ * * &lt;sha1&gt; &lt;sha2&gt;
+ * * &lt;sha1&gt; --cached
+ * * &lt;sha1&gt;
+ * * --cached
+ * * --nocache (don't use index data in diff at all)
+ * * nothing
+ *
+ * Currently ranged arguments like &lt;sha1&gt;..&lt;sha2&gt; and &lt;sha1&gt;...&lt;sha2&gt;
+ * are not supported in this example
+ */
+
+ if (o.treeish1)
+ treeish_to_tree(&t1, repo, o.treeish1);
+ if (o.treeish2)
+ treeish_to_tree(&t2, repo, o.treeish2);
+
+ if (t1 && t2)
+ check_lg2(
+ git_diff_tree_to_tree(&diff, repo, t1, t2, &o.diffopts),
+ "diff trees", NULL);
+ else if (o.cache != CACHE_NORMAL) {
+ if (!t1)
+ treeish_to_tree(&t1, repo, "HEAD");
+
+ if (o.cache == CACHE_NONE)
+ check_lg2(
+ git_diff_tree_to_workdir(&diff, repo, t1, &o.diffopts),
+ "diff tree to working directory", NULL);
+ else
+ check_lg2(
+ git_diff_tree_to_index(&diff, repo, t1, NULL, &o.diffopts),
+ "diff tree to index", NULL);
+ }
+ else if (t1)
+ check_lg2(
+ git_diff_tree_to_workdir_with_index(&diff, repo, t1, &o.diffopts),
+ "diff tree to working directory", NULL);
+ else
+ check_lg2(
+ git_diff_index_to_workdir(&diff, repo, NULL, &o.diffopts),
+ "diff index to working directory", NULL);
+
+ /** Apply rename and copy detection if requested. */
+
+ if ((o.findopts.flags & GIT_DIFF_FIND_ALL) != 0)
+ check_lg2(
+ git_diff_find_similar(diff, &o.findopts),
+ "finding renames and copies", NULL);
+
+ /** Generate simple output using libgit2 display helper. */
+
+ if (!o.output)
+ o.output = OUTPUT_DIFF;
+
+ if (o.output != OUTPUT_DIFF)
+ diff_print_stats(diff, &o);
+
+ if ((o.output & OUTPUT_DIFF) != 0) {
+ if (o.color >= 0)
+ fputs(colors[0], stdout);
+
+ check_lg2(
+ git_diff_print(diff, o.format, color_printer, &o.color),
+ "displaying diff", NULL);
+
+ if (o.color >= 0)
+ fputs(colors[0], stdout);
+ }
+
+ /** Cleanup before exiting. */
+
+ git_diff_free(diff);
+ git_tree_free(t1);
+ git_tree_free(t2);
+ git_repository_free(repo);
+
+ git_threads_shutdown();
+
+ return 0;
+}
+
+static void usage(const char *message, const char *arg)
+{
+ if (message && arg)
+ fprintf(stderr, "%s: %s\n", message, arg);
+ else if (message)
+ fprintf(stderr, "%s\n", message);
+ fprintf(stderr, "usage: diff [<tree-oid> [<tree-oid>]]\n");
+ exit(1);
+}
+
+/** This implements very rudimentary colorized output. */
+static int color_printer(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk,
+ const git_diff_line *line,
+ void *data)
+{
+ int *last_color = data, color = 0;
+
+ (void)delta; (void)hunk;
+
+ if (*last_color >= 0) {
+ switch (line->origin) {
+ case GIT_DIFF_LINE_ADDITION: color = 3; break;
+ case GIT_DIFF_LINE_DELETION: color = 2; break;
+ case GIT_DIFF_LINE_ADD_EOFNL: color = 3; break;
+ case GIT_DIFF_LINE_DEL_EOFNL: color = 2; break;
+ case GIT_DIFF_LINE_FILE_HDR: color = 1; break;
+ case GIT_DIFF_LINE_HUNK_HDR: color = 4; break;
+ default: break;
+ }
+
+ if (color != *last_color) {
+ if (*last_color == 1 || color == 1)
+ fputs(colors[0], stdout);
+ fputs(colors[color], stdout);
+ *last_color = color;
+ }
+ }
+
+ return diff_output(delta, hunk, line, stdout);
+}
+
+/** Parse arguments as copied from git-diff. */
+static void parse_opts(struct opts *o, int argc, char *argv[])
+{
+ struct args_info args = ARGS_INFO_INIT;
+
+
+ for (args.pos = 1; args.pos < argc; ++args.pos) {
+ const char *a = argv[args.pos];
+
+ if (a[0] != '-') {
+ if (o->treeish1 == NULL)
+ o->treeish1 = a;
+ else if (o->treeish2 == NULL)
+ o->treeish2 = a;
+ else
+ usage("Only one or two tree identifiers can be provided", NULL);
+ }
+ else if (!strcmp(a, "-p") || !strcmp(a, "-u") ||
+ !strcmp(a, "--patch")) {
+ o->output |= OUTPUT_DIFF;
+ o->format = GIT_DIFF_FORMAT_PATCH;
+ }
+ else if (!strcmp(a, "--cached"))
+ o->cache = CACHE_ONLY;
+ else if (!strcmp(a, "--nocache"))
+ o->cache = CACHE_NONE;
+ else if (!strcmp(a, "--name-only") || !strcmp(a, "--format=name"))
+ o->format = GIT_DIFF_FORMAT_NAME_ONLY;
+ else if (!strcmp(a, "--name-status") ||
+ !strcmp(a, "--format=name-status"))
+ o->format = GIT_DIFF_FORMAT_NAME_STATUS;
+ else if (!strcmp(a, "--raw") || !strcmp(a, "--format=raw"))
+ o->format = GIT_DIFF_FORMAT_RAW;
+ else if (!strcmp(a, "--format=diff-index")) {
+ o->format = GIT_DIFF_FORMAT_RAW;
+ o->diffopts.id_abbrev = 40;
+ }
+ else if (!strcmp(a, "--color"))
+ o->color = 0;
+ else if (!strcmp(a, "--no-color"))
+ o->color = -1;
+ else if (!strcmp(a, "-R"))
+ o->diffopts.flags |= GIT_DIFF_REVERSE;
+ else if (!strcmp(a, "-a") || !strcmp(a, "--text"))
+ o->diffopts.flags |= GIT_DIFF_FORCE_TEXT;
+ else if (!strcmp(a, "--ignore-space-at-eol"))
+ o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL;
+ else if (!strcmp(a, "-b") || !strcmp(a, "--ignore-space-change"))
+ o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE;
+ else if (!strcmp(a, "-w") || !strcmp(a, "--ignore-all-space"))
+ o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE;
+ else if (!strcmp(a, "--ignored"))
+ o->diffopts.flags |= GIT_DIFF_INCLUDE_IGNORED;
+ else if (!strcmp(a, "--untracked"))
+ o->diffopts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
+ else if (!strcmp(a, "--patience"))
+ o->diffopts.flags |= GIT_DIFF_PATIENCE;
+ else if (!strcmp(a, "--minimal"))
+ o->diffopts.flags |= GIT_DIFF_MINIMAL;
+ else if (!strcmp(a, "--stat"))
+ o->output |= OUTPUT_STAT;
+ else if (!strcmp(a, "--numstat"))
+ o->output |= OUTPUT_NUMSTAT;
+ else if (!strcmp(a, "--shortstat"))
+ o->output |= OUTPUT_SHORTSTAT;
+ else if (!strcmp(a, "--summary"))
+ o->output |= OUTPUT_SUMMARY;
+ else if (match_uint16_arg(
+ &o->findopts.rename_threshold, &args, "-M") ||
+ match_uint16_arg(
+ &o->findopts.rename_threshold, &args, "--find-renames"))
+ o->findopts.flags |= GIT_DIFF_FIND_RENAMES;
+ else if (match_uint16_arg(
+ &o->findopts.copy_threshold, &args, "-C") ||
+ match_uint16_arg(
+ &o->findopts.copy_threshold, &args, "--find-copies"))
+ o->findopts.flags |= GIT_DIFF_FIND_COPIES;
+ else if (!strcmp(a, "--find-copies-harder"))
+ o->findopts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED;
+ else if (is_prefixed(a, "-B") || is_prefixed(a, "--break-rewrites"))
+ /* TODO: parse thresholds */
+ o->findopts.flags |= GIT_DIFF_FIND_REWRITES;
+ else if (!match_uint16_arg(
+ &o->diffopts.context_lines, &args, "-U") &&
+ !match_uint16_arg(
+ &o->diffopts.context_lines, &args, "--unified") &&
+ !match_uint16_arg(
+ &o->diffopts.interhunk_lines, &args, "--inter-hunk-context") &&
+ !match_uint16_arg(
+ &o->diffopts.id_abbrev, &args, "--abbrev") &&
+ !match_str_arg(&o->diffopts.old_prefix, &args, "--src-prefix") &&
+ !match_str_arg(&o->diffopts.new_prefix, &args, "--dst-prefix") &&
+ !match_str_arg(&o->dir, &args, "--git-dir"))
+ usage("Unknown command line argument", a);
+ }
+}
+
+/** Display diff output with "--stat", "--numstat", or "--shortstat" */
+static void diff_print_stats(git_diff *diff, struct opts *o)
+{
+ git_diff_stats *stats;
+ git_buf b = GIT_BUF_INIT_CONST(NULL, 0);
+ git_diff_stats_format_t format = 0;
+
+ check_lg2(
+ git_diff_get_stats(&stats, diff), "generating stats for diff", NULL);
+
+ if (o->output & OUTPUT_STAT)
+ format |= GIT_DIFF_STATS_FULL;
+ if (o->output & OUTPUT_SHORTSTAT)
+ format |= GIT_DIFF_STATS_SHORT;
+ if (o->output & OUTPUT_NUMSTAT)
+ format |= GIT_DIFF_STATS_NUMBER;
+ if (o->output & OUTPUT_SUMMARY)
+ format |= GIT_DIFF_STATS_INCLUDE_SUMMARY;
+
+ check_lg2(
+ git_diff_stats_to_buf(&b, stats, format, 80), "formatting stats", NULL);
+
+ fputs(b.ptr, stdout);
+
+ git_buf_free(&b);
+ git_diff_stats_free(stats);
+}
diff --git a/examples/for-each-ref.c b/examples/for-each-ref.c
new file mode 100644
index 000000000..d6846bb0d
--- /dev/null
+++ b/examples/for-each-ref.c
@@ -0,0 +1,46 @@
+#include <git2.h>
+#include <stdio.h>
+#include "common.h"
+
+static int show_ref(git_reference *ref, void *data)
+{
+ git_repository *repo = data;
+ git_reference *resolved = NULL;
+ char hex[GIT_OID_HEXSZ+1];
+ const git_oid *oid;
+ git_object *obj;
+
+ if (git_reference_type(ref) == GIT_REF_SYMBOLIC)
+ check_lg2(git_reference_resolve(&resolved, ref),
+ "Unable to resolve symbolic reference",
+ git_reference_name(ref));
+
+ oid = git_reference_target(resolved ? resolved : ref);
+ git_oid_fmt(hex, oid);
+ hex[GIT_OID_HEXSZ] = 0;
+ check_lg2(git_object_lookup(&obj, repo, oid, GIT_OBJ_ANY),
+ "Unable to lookup object", hex);
+
+ printf("%s %-6s\t%s\n",
+ hex,
+ git_object_type2string(git_object_type(obj)),
+ git_reference_name(ref));
+
+ if (resolved)
+ git_reference_free(resolved);
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ git_repository *repo;
+
+ if (argc != 1 || argv[1] /* silence -Wunused-parameter */)
+ fatal("Sorry, no for-each-ref options supported yet", NULL);
+
+ check_lg2(git_repository_open(&repo, "."),
+ "Could not open repository", NULL);
+ check_lg2(git_reference_foreach(repo, show_ref, repo),
+ "Could not iterate over references", NULL);
+ return 0;
+}
diff --git a/examples/general.c b/examples/general.c
index 91b6ee859..ae8756338 100644
--- a/examples/general.c
+++ b/examples/general.c
@@ -1,60 +1,98 @@
-// [**libgit2**][lg] is a portable, pure C implementation of the Git core methods
-// provided as a re-entrant linkable library with a solid API, allowing you
-// to write native speed custom Git applications in any language which
-// supports C bindings.
+/*
+ * libgit2 "general" example - shows basic libgit2 concepts
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+// [**libgit2**][lg] is a portable, pure C implementation of the Git core
+// methods provided as a re-entrant linkable library with a solid API,
+// allowing you to write native speed custom Git applications in any
+// language which supports C bindings.
//
// This file is an example of using that API in a real, compilable C file.
-// As the API is updated, this file will be updated to demonstrate the
-// new functionality.
+// As the API is updated, this file will be updated to demonstrate the new
+// functionality.
//
-// If you're trying to write something in C using [libgit2][lg], you will also want
-// to check out the generated [API documentation][ap] and the [Usage Guide][ug]. We've
-// tried to link to the relevant sections of the API docs in each section in this file.
+// If you're trying to write something in C using [libgit2][lg], you should
+// also check out the generated [API documentation][ap]. We try to link to
+// the relevant sections of the API docs in each section in this file.
//
-// **libgit2** only implements the core plumbing functions, not really the higher
-// level porcelain stuff. For a primer on Git Internals that you will need to know
-// to work with Git at this level, check out [Chapter 9][pg] of the Pro Git book.
+// **libgit2** (for the most part) only implements the core plumbing
+// functions, not really the higher level porcelain stuff. For a primer on
+// Git Internals that you will need to know to work with Git at this level,
+// check out [Chapter 9][pg] of the Pro Git book.
//
// [lg]: http://libgit2.github.com
// [ap]: http://libgit2.github.com/libgit2
-// [ug]: http://libgit2.github.com/api.html
// [pg]: http://progit.org/book/ch9-0.html
// ### Includes
-// Including the `git2.h` header will include all the other libgit2 headers that you need.
-// It should be the only thing you need to include in order to compile properly and get
-// all the libgit2 API.
+// Including the `git2.h` header will include all the other libgit2 headers
+// that you need. It should be the only thing you need to include in order
+// to compile properly and get all the libgit2 API.
#include <git2.h>
#include <stdio.h>
+// Almost all libgit2 functions return 0 on success or negative on error.
+// This is not production quality error checking, but should be sufficient
+// as an example.
+static void check_error(int error_code, const char *action)
+{
+ const git_error *error = giterr_last();
+ if (!error_code)
+ return;
+
+ printf("Error %d %s - %s\n", error_code, action,
+ (error && error->message) ? error->message : "???");
+
+ exit(1);
+}
+
int main (int argc, char** argv)
{
// ### Opening the Repository
- // There are a couple of methods for opening a repository, this being the simplest.
- // There are also [methods][me] for specifying the index file and work tree locations, here
- // we are assuming they are in the normal places.
+ // There are a couple of methods for opening a repository, this being the
+ // simplest. There are also [methods][me] for specifying the index file
+ // and work tree locations, here we assume they are in the normal places.
+ //
+ // (Try running this program against tests/resources/testrepo.git.)
//
// [me]: http://libgit2.github.com/libgit2/#HEAD/group/repository
+ int error;
+ const char *repo_path = (argc > 1) ? argv[1] : "/opt/libgit2-test/.git";
git_repository *repo;
- git_repository_open(&repo, "/opt/libgit2-test/.git");
+
+ error = git_repository_open(&repo, repo_path);
+ check_error(error, "opening repository");
// ### SHA-1 Value Conversions
- // For our first example, we will convert a 40 character hex value to the 20 byte raw SHA1 value.
+ // For our first example, we will convert a 40 character hex value to the
+ // 20 byte raw SHA1 value.
printf("*Hex to Raw*\n");
- char hex[] = "fd6e612585290339ea8bf39c692a7ff6a29cb7c3";
+ char hex[] = "4a202b346bb0fb0db7eff3cffeb3c70babbd2045";
- // The `git_oid` is the structure that keeps the SHA value. We will use this throughout the example
- // for storing the value of the current SHA key we're working with.
+ // The `git_oid` is the structure that keeps the SHA value. We will use
+ // this throughout the example for storing the value of the current SHA
+ // key we're working with.
git_oid oid;
git_oid_fromstr(&oid, hex);
- // Once we've converted the string into the oid value, we can get the raw value of the SHA.
- printf("Raw 20 bytes: [%s]\n", (&oid)->id);
+ // Once we've converted the string into the oid value, we can get the raw
+ // value of the SHA by accessing `oid.id`
- // Next we will convert the 20 byte raw SHA1 value to a human readable 40 char hex value.
+ // Next we will convert the 20 byte raw SHA1 value to a human readable 40
+ // char hex value.
printf("\n*Raw to Hex*\n");
char out[41];
out[40] = '\0';
@@ -64,13 +102,15 @@ int main (int argc, char** argv)
printf("SHA hex string: %s\n", out);
// ### Working with the Object Database
- // **libgit2** provides [direct access][odb] to the object database.
- // The object database is where the actual objects are stored in Git. For
+
+ // **libgit2** provides [direct access][odb] to the object database. The
+ // object database is where the actual objects are stored in Git. For
// working with raw objects, we'll need to get this structure from the
// repository.
+ //
// [odb]: http://libgit2.github.com/libgit2/#HEAD/group/odb
git_odb *odb;
- odb = git_repository_database(repo);
+ git_repository_odb(&odb, repo);
// #### Raw Object Reading
@@ -79,103 +119,113 @@ int main (int argc, char** argv)
git_otype otype;
const unsigned char *data;
const char *str_type;
- int error;
- // We can read raw objects directly from the object database if we have the oid (SHA)
- // of the object. This allows us to access objects without knowing thier type and inspect
- // the raw bytes unparsed.
+ // We can read raw objects directly from the object database if we have
+ // the oid (SHA) of the object. This allows us to access objects without
+ // knowing thier type and inspect the raw bytes unparsed.
error = git_odb_read(&obj, odb, &oid);
-
- // A raw object only has three properties - the type (commit, blob, tree or tag), the size
- // of the raw data and the raw, unparsed data itself. For a commit or tag, that raw data
- // is human readable plain ASCII text. For a blob it is just file contents, so it could be
- // text or binary data. For a tree it is a special binary format, so it's unlikely to be
- // hugely helpful as a raw object.
+ check_error(error, "finding object in repository");
+
+ // A raw object only has three properties - the type (commit, blob, tree
+ // or tag), the size of the raw data and the raw, unparsed data itself.
+ // For a commit or tag, that raw data is human readable plain ASCII
+ // text. For a blob it is just file contents, so it could be text or
+ // binary data. For a tree it is a special binary format, so it's unlikely
+ // to be hugely helpful as a raw object.
data = (const unsigned char *)git_odb_object_data(obj);
otype = git_odb_object_type(obj);
- // We provide methods to convert from the object type which is an enum, to a string
- // representation of that value (and vice-versa).
+ // We provide methods to convert from the object type which is an enum, to
+ // a string representation of that value (and vice-versa).
str_type = git_object_type2string(otype);
printf("object length and type: %d, %s\n",
(int)git_odb_object_size(obj),
str_type);
- // For proper memory management, close the object when you are done with it or it will leak
- // memory.
- git_odb_object_close(obj);
+ // For proper memory management, close the object when you are done with
+ // it or it will leak memory.
+ git_odb_object_free(obj);
// #### Raw Object Writing
printf("\n*Raw Object Write*\n");
- // You can also write raw object data to Git. This is pretty cool because it gives you
- // direct access to the key/value properties of Git. Here we'll write a new blob object
- // that just contains a simple string. Notice that we have to specify the object type as
- // the `git_otype` enum.
+ // You can also write raw object data to Git. This is pretty cool because
+ // it gives you direct access to the key/value properties of Git. Here
+ // we'll write a new blob object that just contains a simple string.
+ // Notice that we have to specify the object type as the `git_otype` enum.
git_odb_write(&oid, odb, "test data", sizeof("test data") - 1, GIT_OBJ_BLOB);
- // Now that we've written the object, we can check out what SHA1 was generated when the
- // object was written to our database.
+ // Now that we've written the object, we can check out what SHA1 was
+ // generated when the object was written to our database.
git_oid_fmt(out, &oid);
printf("Written Object: %s\n", out);
// ### Object Parsing
- // libgit2 has methods to parse every object type in Git so you don't have to work directly
- // with the raw data. This is much faster and simpler than trying to deal with the raw data
- // yourself.
+
+ // libgit2 has methods to parse every object type in Git so you don't have
+ // to work directly with the raw data. This is much faster and simpler
+ // than trying to deal with the raw data yourself.
// #### Commit Parsing
- // [Parsing commit objects][pco] is simple and gives you access to all the data in the commit
- // - the // author (name, email, datetime), committer (same), tree, message, encoding and parent(s).
+
+ // [Parsing commit objects][pco] is simple and gives you access to all the
+ // data in the commit - the author (name, email, datetime), committer
+ // (same), tree, message, encoding and parent(s).
+ //
// [pco]: http://libgit2.github.com/libgit2/#HEAD/group/commit
printf("\n*Commit Parsing*\n");
git_commit *commit;
- git_oid_fromstr(&oid, "f0877d0b841d75172ec404fc9370173dfffc20d1");
+ git_oid_fromstr(&oid, "8496071c1b46c854b31185ea97743be6a8774479");
error = git_commit_lookup(&commit, repo, &oid);
+ check_error(error, "looking up commit");
const git_signature *author, *cmtter;
- const char *message, *message_short;
+ const char *message;
time_t ctime;
unsigned int parents, p;
- // Each of the properties of the commit object are accessible via methods, including commonly
- // needed variations, such as `git_commit_time` which returns the author time and `_message_short`
- // which gives you just the first line of the commit message.
+ // Each of the properties of the commit object are accessible via methods,
+ // including commonly needed variations, such as `git_commit_time` which
+ // returns the author time and `git_commit_message` which gives you the
+ // commit message (as a NUL-terminated string).
message = git_commit_message(commit);
- message_short = git_commit_message_short(commit);
author = git_commit_author(commit);
cmtter = git_commit_committer(commit);
ctime = git_commit_time(commit);
- // The author and committer methods return [git_signature] structures, which give you name, email
- // and `when`, which is a `git_time` structure, giving you a timestamp and timezone offset.
+ // The author and committer methods return [git_signature] structures,
+ // which give you name, email and `when`, which is a `git_time` structure,
+ // giving you a timestamp and timezone offset.
printf("Author: %s (%s)\n", author->name, author->email);
- // Commits can have zero or more parents. The first (root) commit will have no parents, most commits
- // will have one, which is the commit it was based on, and merge commits will have two or more.
- // Commits can technically have any number, though it's pretty rare to have more than two.
+ // Commits can have zero or more parents. The first (root) commit will
+ // have no parents, most commits will have one (i.e. the commit it was
+ // based on) and merge commits will have two or more. Commits can
+ // technically have any number, though it's rare to have more than two.
parents = git_commit_parentcount(commit);
for (p = 0;p < parents;p++) {
git_commit *parent;
git_commit_parent(&parent, commit, p);
git_oid_fmt(out, git_commit_id(parent));
printf("Parent: %s\n", out);
- git_commit_close(parent);
+ git_commit_free(parent);
}
- // Don't forget to close the object to prevent memory leaks. You will have to do this for
- // all the objects you open and parse.
- git_commit_close(commit);
+ // Don't forget to close the object to prevent memory leaks. You will have
+ // to do this for all the objects you open and parse.
+ git_commit_free(commit);
// #### Writing Commits
+
+ // libgit2 provides a couple of methods to create commit objects easily as
+ // well. There are four different create signatures, we'll just show one
+ // of them here. You can read about the other ones in the [commit API
+ // docs][cd].
//
- // libgit2 provides a couple of methods to create commit objects easily as well. There are four
- // different create signatures, we'll just show one of them here. You can read about the other
- // ones in the [commit API docs][cd].
// [cd]: http://libgit2.github.com/libgit2/#HEAD/group/commit
printf("\n*Commit Writing*\n");
@@ -183,30 +233,34 @@ int main (int argc, char** argv)
git_tree *tree;
git_commit *parent;
- // Creating signatures for an authoring identity and time is pretty simple - you will need to have
- // this to create a commit in order to specify who created it and when. Default values for the name
- // and email should be found in the `user.name` and `user.email` configuration options. See the `config`
- // section of this example file to see how to access config values.
- author = git_signature_new("Scott Chacon", "schacon@gmail.com",
- 123456789, 60);
- cmtter = git_signature_new("Scott A Chacon", "scott@github.com",
- 987654321, 90);
-
- // Commit objects need a tree to point to and optionally one or more parents. Here we're creating oid
- // objects to create the commit with, but you can also use
- git_oid_fromstr(&tree_id, "28873d96b4e8f4e33ea30f4c682fd325f7ba56ac");
+ // Creating signatures for an authoring identity and time is simple. You
+ // will need to do this to specify who created a commit and when. Default
+ // values for the name and email should be found in the `user.name` and
+ // `user.email` configuration options. See the `config` section of this
+ // example file to see how to access config values.
+ git_signature_new((git_signature **)&author,
+ "Scott Chacon", "schacon@gmail.com", 123456789, 60);
+ git_signature_new((git_signature **)&cmtter,
+ "Scott A Chacon", "scott@github.com", 987654321, 90);
+
+ // Commit objects need a tree to point to and optionally one or more
+ // parents. Here we're creating oid objects to create the commit with,
+ // but you can also use
+ git_oid_fromstr(&tree_id, "f60079018b664e4e79329a7ef9559c8d9e0378d1");
git_tree_lookup(&tree, repo, &tree_id);
- git_oid_fromstr(&parent_id, "f0877d0b841d75172ec404fc9370173dfffc20d1");
+ git_oid_fromstr(&parent_id, "5b5b025afb0b4c913b4c338a42934a3863bf3644");
git_commit_lookup(&parent, repo, &parent_id);
- // Here we actually create the commit object with a single call with all the values we need to create
- // the commit. The SHA key is written to the `commit_id` variable here.
+ // Here we actually create the commit object with a single call with all
+ // the values we need to create the commit. The SHA key is written to the
+ // `commit_id` variable here.
git_commit_create_v(
&commit_id, /* out id */
repo,
NULL, /* do not update the HEAD */
author,
cmtter,
+ NULL, /* use default message encoding */
"example commit",
tree,
1, parent);
@@ -216,35 +270,42 @@ int main (int argc, char** argv)
printf("New Commit: %s\n", out);
// #### Tag Parsing
- // You can parse and create tags with the [tag management API][tm], which functions very similarly
- // to the commit lookup, parsing and creation methods, since the objects themselves are very similar.
+
+ // You can parse and create tags with the [tag management API][tm], which
+ // functions very similarly to the commit lookup, parsing and creation
+ // methods, since the objects themselves are very similar.
+ //
// [tm]: http://libgit2.github.com/libgit2/#HEAD/group/tag
printf("\n*Tag Parsing*\n");
git_tag *tag;
const char *tmessage, *tname;
git_otype ttype;
- // We create an oid for the tag object if we know the SHA and look it up in the repository the same
- // way that we would a commit (or any other) object.
- git_oid_fromstr(&oid, "bc422d45275aca289c51d79830b45cecebff7c3a");
+ // We create an oid for the tag object if we know the SHA and look it up
+ // the same way that we would a commit (or any other object).
+ git_oid_fromstr(&oid, "b25fa35b38051e4ae45d4222e795f9df2e43f1d1");
error = git_tag_lookup(&tag, repo, &oid);
+ check_error(error, "looking up tag");
- // Now that we have the tag object, we can extract the information it generally contains: the target
- // (usually a commit object), the type of the target object (usually 'commit'), the name ('v1.0'),
- // the tagger (a git_signature - name, email, timestamp), and the tag message.
+ // Now that we have the tag object, we can extract the information it
+ // generally contains: the target (usually a commit object), the type of
+ // the target object (usually 'commit'), the name ('v1.0'), 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)
- tmessage = git_tag_message(tag); // "tag message\n"
+ tname = git_tag_name(tag); // "test"
+ 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);
- git_commit_close(commit);
+ git_commit_free(commit);
// #### Tree Parsing
- // [Tree parsing][tp] is a bit different than the other objects, in that we have a subtype which is the
- // tree entry. This is not an actual object type in Git, but a useful structure for parsing and
- // traversing tree entries.
+
+ // [Tree parsing][tp] is a bit different than the other objects, in that
+ // we have a subtype which is the tree entry. This is not an actual
+ // object type in Git, but a useful structure for parsing and traversing
+ // tree entries.
//
// [tp]: http://libgit2.github.com/libgit2/#HEAD/group/tree
printf("\n*Tree Parsing*\n");
@@ -256,54 +317,61 @@ int main (int argc, char** argv)
git_oid_fromstr(&oid, "2a741c18ac5ff082a7caaec6e74db3075a1906b5");
git_tree_lookup(&tree, repo, &oid);
- // Getting the count of entries in the tree so you can iterate over them if you want to.
- int cnt = git_tree_entrycount(tree); // 3
- printf("tree entries: %d\n", cnt);
+ // Getting the count of entries in the tree so you can iterate over them
+ // if you want to.
+ size_t cnt = git_tree_entrycount(tree); // 3
+ printf("tree entries: %d\n", (int)cnt);
entry = git_tree_entry_byindex(tree, 0);
printf("Entry name: %s\n", git_tree_entry_name(entry)); // "hello.c"
- // You can also access tree entries by name if you know the name of the entry you're looking for.
- entry = git_tree_entry_byname(tree, "hello.c");
+ // You can also access tree entries by name if you know the name of the
+ // entry you're looking for.
+ entry = git_tree_entry_byname(tree, "README");
git_tree_entry_name(entry); // "hello.c"
- // Once you have the entry object, you can access the content or subtree (or commit, in the case
- // of submodules) that it points to. You can also get the mode if you want.
- git_tree_entry_2object(&objt, repo, entry); // blob
+ // Once you have the entry object, you can access the content or subtree
+ // (or commit, in the case of submodules) that it points to. You can also
+ // get the mode if you want.
+ git_tree_entry_to_object(&objt, repo, entry); // blob
// Remember to close the looked-up object once you are done using it
- git_object_close(objt);
+ git_object_free(objt);
// #### Blob Parsing
- //
- // The last object type is the simplest and requires the least parsing help. Blobs are just file
- // contents and can contain anything, there is no structure to it. The main advantage to using the
- // [simple blob api][ba] is that when you're creating blobs you don't have to calculate the size
- // of the content. There is also a helper for reading a file from disk and writing it to the db and
- // getting the oid back so you don't have to do all those steps yourself.
+
+ // The last object type is the simplest and requires the least parsing
+ // help. Blobs are just file contents and can contain anything, there is
+ // no structure to it. The main advantage to using the [simple blob
+ // api][ba] is that when you're creating blobs you don't have to calculate
+ // the size of the content. There is also a helper for reading a file
+ // from disk and writing it to the db and getting the oid back so you
+ // don't have to do all those steps yourself.
//
// [ba]: http://libgit2.github.com/libgit2/#HEAD/group/blob
printf("\n*Blob Parsing*\n");
git_blob *blob;
- git_oid_fromstr(&oid, "af7574ea73f7b166f869ef1a39be126d9a186ae0");
+ git_oid_fromstr(&oid, "1385f264afb75a56a5bec74243be9b367ba4ca08");
git_blob_lookup(&blob, repo, &oid);
// You can access a buffer with the raw contents of the blob directly.
- // Note that this buffer may not be contain ASCII data for certain blobs (e.g. binary files):
- // do not consider the buffer a NULL-terminated string, and use the `git_blob_rawsize` attribute to
- // find out its exact size in bytes
- printf("Blob Size: %d\n", git_blob_rawsize(blob)); // 8
+ // Note that this buffer may not be contain ASCII data for certain blobs
+ // (e.g. binary files): do not consider the buffer a NULL-terminated
+ // string, and use the `git_blob_rawsize` attribute to find out its exact
+ // size in bytes
+ printf("Blob Size: %ld\n", (long)git_blob_rawsize(blob)); // 8
git_blob_rawcontent(blob); // "content"
// ### Revwalking
- //
- // The libgit2 [revision walking api][rw] provides methods to traverse the directed graph created
- // by the parent pointers of the commit objects. Since all commits point back to the commit that
- // came directly before them, you can walk this parentage as a graph and find all the commits that
- // were ancestors of (reachable from) a given starting point. This can allow you to create `git log`
- // type functionality.
+
+ // The libgit2 [revision walking api][rw] provides methods to traverse the
+ // directed graph created by the parent pointers of the commit objects.
+ // Since all commits point back to the commit that came directly before
+ // them, you can walk this parentage as a graph and find all the commits
+ // that were ancestors of (reachable from) a given starting point. This
+ // can allow you to create `git log` type functionality.
//
// [rw]: http://libgit2.github.com/libgit2/#HEAD/group/revwalk
@@ -311,13 +379,15 @@ int main (int argc, char** argv)
git_revwalk *walk;
git_commit *wcommit;
- git_oid_fromstr(&oid, "f0877d0b841d75172ec404fc9370173dfffc20d1");
+ git_oid_fromstr(&oid, "5b5b025afb0b4c913b4c338a42934a3863bf3644");
- // To use the revwalker, create a new walker, tell it how you want to sort the output and then push
- // one or more starting points onto the walker. If you want to emulate the output of `git log` you
- // would push the SHA of the commit that HEAD points to into the walker and then start traversing them.
- // You can also 'hide' commits that you want to stop at or not see any of their ancestors. So if you
- // want to emulate `git log branch1..branch2`, you would push the oid of `branch2` and hide the oid
+ // To use the revwalker, create a new walker, tell it how you want to sort
+ // the output and then push one or more starting points onto the walker.
+ // If you want to emulate the output of `git log` you would push the SHA
+ // of the commit that HEAD points to into the walker and then start
+ // traversing them. You can also 'hide' commits that you want to stop at
+ // or not see any of their ancestors. So if you want to emulate `git log
+ // branch1..branch2`, you would push the oid of `branch2` and hide the oid
// of `branch1`.
git_revwalk_new(&walk, repo);
git_revwalk_sorting(walk, GIT_SORT_TOPOLOGICAL | GIT_SORT_REVERSE);
@@ -326,28 +396,32 @@ int main (int argc, char** argv)
const git_signature *cauth;
const char *cmsg;
- // Now that we have the starting point pushed onto the walker, we can start asking for ancestors. It
- // will return them in the sorting order we asked for as commit oids.
- // We can then lookup and parse the commited pointed at by the returned OID;
- // note that this operation is specially fast since the raw contents of the commit object will
- // be cached in memory
- while ((git_revwalk_next(&oid, walk)) == GIT_SUCCESS) {
+ // Now that we have the starting point pushed onto the walker, we start
+ // asking for ancestors. It will return them in the sorting order we asked
+ // for as commit oids. We can then lookup and parse the commited pointed
+ // at by the returned OID; note that this operation is specially fast
+ // since the raw contents of the commit object will be cached in memory
+ while ((git_revwalk_next(&oid, walk)) == 0) {
error = git_commit_lookup(&wcommit, repo, &oid);
- cmsg = git_commit_message_short(wcommit);
+ check_error(error, "looking up commit during revwalk");
+
+ cmsg = git_commit_message(wcommit);
cauth = git_commit_author(wcommit);
printf("%s (%s)\n", cmsg, cauth->email);
- git_commit_close(wcommit);
+
+ git_commit_free(wcommit);
}
- // Like the other objects, be sure to free the revwalker when you're done to prevent memory leaks.
- // Also, make sure that the repository being walked it not deallocated while the walk is in
- // progress, or it will result in undefined behavior
+ // Like the other objects, be sure to free the revwalker when you're done
+ // to prevent memory leaks. Also, make sure that the repository being
+ // walked it not deallocated while the walk is in progress, or it will
+ // result in undefined behavior
git_revwalk_free(walk);
// ### Index File Manipulation
- //
- // The [index file API][gi] allows you to read, traverse, update and write the Git index file
- // (sometimes thought of as the staging area).
+
+ // The [index file API][gi] allows you to read, traverse, update and write
+ // the Git index file (sometimes thought of as the staging area).
//
// [gi]: http://libgit2.github.com/libgit2/#HEAD/group/index
@@ -356,18 +430,21 @@ int main (int argc, char** argv)
git_index *index;
unsigned int i, ecount;
- // You can either open the index from the standard location in an open repository, as we're doing
- // here, or you can open and manipulate any index file with `git_index_open_bare()`. The index
- // for the repository will be located and loaded from disk.
+ // You can either open the index from the standard location in an open
+ // repository, as we're doing here, or you can open and manipulate any
+ // index file with `git_index_open_bare()`. The index for the repository
+ // will be located and loaded from disk.
git_repository_index(&index, repo);
- // For each entry in the index, you can get a bunch of information including the SHA (oid), path
- // and mode which map to the tree objects that are written out. It also has filesystem properties
- // to help determine what to inspect for changes (ctime, mtime, dev, ino, uid, gid, file_size and flags)
- // All these properties are exported publicly in the `git_index_entry` struct
+ // For each entry in the index, you can get a bunch of information
+ // including the SHA (oid), path and mode which map to the tree objects
+ // that are written out. It also has filesystem properties to help
+ // determine what to inspect for changes (ctime, mtime, dev, ino, uid,
+ // gid, file_size and flags) 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);
+ const git_index_entry *e = git_index_get_byindex(index, i);
printf("path: %s\n", e->path);
printf("mtime: %d\n", (int)e->mtime.seconds);
@@ -377,36 +454,37 @@ int main (int argc, char** argv)
git_index_free(index);
// ### References
- //
- // The [reference API][ref] allows you to list, resolve, create and update references such as
- // branches, tags and remote references (everything in the .git/refs directory).
+
+ // The [reference API][ref] allows you to list, resolve, create and update
+ // references such as branches, tags and remote references (everything in
+ // the .git/refs directory).
//
// [ref]: http://libgit2.github.com/libgit2/#HEAD/group/reference
printf("\n*Reference Listing*\n");
- // Here we will implement something like `git for-each-ref` simply listing out all available
- // references and the object SHA they resolve to.
+ // Here we will implement something like `git for-each-ref` simply listing
+ // out all available references and the object SHA they resolve to.
git_strarray ref_list;
- git_reference_listall(&ref_list, repo, GIT_REF_LISTALL);
+ git_reference_list(&ref_list, repo);
const char *refname;
git_reference *ref;
- // Now that we have the list of reference names, we can lookup each ref one at a time and
- // resolve them to the SHA, then print both values out.
+ // Now that we have the list of reference names, we can lookup each ref
+ // one at a time and resolve them to the SHA, then print both values out.
for (i = 0; i < ref_list.count; ++i) {
refname = ref_list.strings[i];
git_reference_lookup(&ref, repo, refname);
switch (git_reference_type(ref)) {
case GIT_REF_OID:
- git_oid_fmt(out, git_reference_oid(ref));
+ git_oid_fmt(out, git_reference_target(ref));
printf("%s [%s]\n", refname, out);
break;
case GIT_REF_SYMBOLIC:
- printf("%s => %s\n", refname, git_reference_target(ref));
+ printf("%s => %s\n", refname, git_reference_symbolic_target(ref));
break;
default:
fprintf(stderr, "Unexpected reference type\n");
@@ -417,26 +495,28 @@ int main (int argc, char** argv)
git_strarray_free(&ref_list);
// ### Config Files
- //
- // The [config API][config] allows you to list and updatee config values in
- // any of the accessible config file locations (system, global, local).
+
+ // The [config API][config] allows you to list and updatee config values
+ // in any of the accessible config file locations (system, global, local).
//
// [config]: http://libgit2.github.com/libgit2/#HEAD/group/config
printf("\n*Config Listing*\n");
const char *email;
- int j;
+ int32_t j;
git_config *cfg;
// Open a config object so we can read global values from it.
- git_config_open_ondisk(&cfg, "~/.gitconfig");
+ char config_path[256];
+ sprintf(config_path, "%s/config", repo_path);
+ check_error(git_config_open_ondisk(&cfg, config_path), "opening config");
- git_config_get_int(cfg, "help.autocorrect", &j);
+ git_config_get_int32(&j, cfg, "help.autocorrect");
printf("Autocorrect: %d\n", j);
- git_config_get_string(cfg, "user.email", &email);
+ git_config_get_string(&email, cfg, "user.email");
printf("Email: %s\n", email);
// Finally, when you're done with the repository, you can free it as well.
diff --git a/examples/init.c b/examples/init.c
new file mode 100644
index 000000000..0e823ab81
--- /dev/null
+++ b/examples/init.c
@@ -0,0 +1,253 @@
+/*
+ * libgit2 "init" example - shows how to initialize a new repo
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#include "common.h"
+
+/**
+ * This is a sample program that is similar to "git init". See the
+ * documentation for that (try "git help init") to understand what this
+ * program is emulating.
+ *
+ * This demonstrates using the libgit2 APIs to initialize a new repository.
+ *
+ * This also contains a special additional option that regular "git init"
+ * does not support which is "--initial-commit" to make a first empty commit.
+ * That is demonstrated in the "create_initial_commit" helper function.
+ */
+
+/** Forward declarations of helpers */
+struct opts {
+ int no_options;
+ int quiet;
+ int bare;
+ int initial_commit;
+ uint32_t shared;
+ const char *template;
+ const char *gitdir;
+ const char *dir;
+};
+static void create_initial_commit(git_repository *repo);
+static void parse_opts(struct opts *o, int argc, char *argv[]);
+
+
+int main(int argc, char *argv[])
+{
+ git_repository *repo = NULL;
+ struct opts o = { 1, 0, 0, 0, GIT_REPOSITORY_INIT_SHARED_UMASK, 0, 0, 0 };
+
+ git_threads_init();
+
+ parse_opts(&o, argc, argv);
+
+ /* Initialize repository. */
+
+ if (o.no_options) {
+ /**
+ * No options were specified, so let's demonstrate the default
+ * simple case of git_repository_init() API usage...
+ */
+ check_lg2(git_repository_init(&repo, o.dir, 0),
+ "Could not initialize repository", NULL);
+ }
+ else {
+ /**
+ * Some command line options were specified, so we'll use the
+ * extended init API to handle them
+ */
+ git_repository_init_options initopts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
+ initopts.flags = GIT_REPOSITORY_INIT_MKPATH;
+
+ if (o.bare)
+ initopts.flags |= GIT_REPOSITORY_INIT_BARE;
+
+ if (o.template) {
+ initopts.flags |= GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE;
+ initopts.template_path = o.template;
+ }
+
+ if (o.gitdir) {
+ /**
+ * If you specified a separate git directory, then initialize
+ * the repository at that path and use the second path as the
+ * working directory of the repository (with a git-link file)
+ */
+ initopts.workdir_path = o.dir;
+ o.dir = o.gitdir;
+ }
+
+ if (o.shared != 0)
+ initopts.mode = o.shared;
+
+ check_lg2(git_repository_init_ext(&repo, o.dir, &initopts),
+ "Could not initialize repository", NULL);
+ }
+
+ /** Print a message to stdout like "git init" does. */
+
+ if (!o.quiet) {
+ if (o.bare || o.gitdir)
+ o.dir = git_repository_path(repo);
+ else
+ o.dir = git_repository_workdir(repo);
+
+ printf("Initialized empty Git repository in %s\n", o.dir);
+ }
+
+ /**
+ * As an extension to the basic "git init" command, this example
+ * gives the option to create an empty initial commit. This is
+ * mostly to demonstrate what it takes to do that, but also some
+ * people like to have that empty base commit in their repo.
+ */
+ if (o.initial_commit) {
+ create_initial_commit(repo);
+ printf("Created empty initial commit\n");
+ }
+
+ git_repository_free(repo);
+ git_threads_shutdown();
+
+ return 0;
+}
+
+/**
+ * Unlike regular "git init", this example shows how to create an initial
+ * empty commit in the repository. This is the helper function that does
+ * that.
+ */
+static void create_initial_commit(git_repository *repo)
+{
+ git_signature *sig;
+ git_index *index;
+ git_oid tree_id, commit_id;
+ git_tree *tree;
+
+ /** First use the config to initialize a commit signature for the user. */
+
+ if (git_signature_default(&sig, repo) < 0)
+ fatal("Unable to create a commit signature.",
+ "Perhaps 'user.name' and 'user.email' are not set");
+
+ /* Now let's create an empty tree for this commit */
+
+ if (git_repository_index(&index, repo) < 0)
+ fatal("Could not open repository index", NULL);
+
+ /**
+ * Outside of this example, you could call git_index_add_bypath()
+ * here to put actual files into the index. For our purposes, we'll
+ * leave it empty for now.
+ */
+
+ if (git_index_write_tree(&tree_id, index) < 0)
+ fatal("Unable to write initial tree from index", NULL);
+
+ git_index_free(index);
+
+ if (git_tree_lookup(&tree, repo, &tree_id) < 0)
+ fatal("Could not look up initial tree", NULL);
+
+ /**
+ * Ready to create the initial commit.
+ *
+ * Normally creating a commit would involve looking up the current
+ * HEAD commit and making that be the parent of the initial commit,
+ * but here this is the first commit so there will be no parent.
+ */
+
+ if (git_commit_create_v(
+ &commit_id, repo, "HEAD", sig, sig,
+ NULL, "Initial commit", tree, 0) < 0)
+ fatal("Could not create the initial commit", NULL);
+
+ /** Clean up so we don't leak memory. */
+
+ git_tree_free(tree);
+ git_signature_free(sig);
+}
+
+static void usage(const char *error, const char *arg)
+{
+ fprintf(stderr, "error: %s '%s'\n", error, arg);
+ fprintf(stderr,
+ "usage: init [-q | --quiet] [--bare] [--template=<dir>]\n"
+ " [--shared[=perms]] [--initial-commit]\n"
+ " [--separate-git-dir] <directory>\n");
+ exit(1);
+}
+
+/** Parse the tail of the --shared= argument. */
+static uint32_t parse_shared(const char *shared)
+{
+ if (!strcmp(shared, "false") || !strcmp(shared, "umask"))
+ return GIT_REPOSITORY_INIT_SHARED_UMASK;
+
+ else if (!strcmp(shared, "true") || !strcmp(shared, "group"))
+ return GIT_REPOSITORY_INIT_SHARED_GROUP;
+
+ else if (!strcmp(shared, "all") || !strcmp(shared, "world") ||
+ !strcmp(shared, "everybody"))
+ return GIT_REPOSITORY_INIT_SHARED_ALL;
+
+ else if (shared[0] == '0') {
+ long val;
+ char *end = NULL;
+ val = strtol(shared + 1, &end, 8);
+ if (end == shared + 1 || *end != 0)
+ usage("invalid octal value for --shared", shared);
+ return (uint32_t)val;
+ }
+
+ else
+ usage("unknown value for --shared", shared);
+
+ return 0;
+}
+
+static void parse_opts(struct opts *o, int argc, char *argv[])
+{
+ struct args_info args = ARGS_INFO_INIT;
+ const char *sharedarg;
+
+ /** Process arguments. */
+
+ for (args.pos = 1; args.pos < argc; ++args.pos) {
+ char *a = argv[args.pos];
+
+ if (a[0] == '-')
+ o->no_options = 0;
+
+ if (a[0] != '-') {
+ if (o->dir != NULL)
+ usage("extra argument", a);
+ o->dir = a;
+ }
+ else if (!strcmp(a, "-q") || !strcmp(a, "--quiet"))
+ o->quiet = 1;
+ else if (!strcmp(a, "--bare"))
+ o->bare = 1;
+ else if (!strcmp(a, "--shared"))
+ o->shared = GIT_REPOSITORY_INIT_SHARED_GROUP;
+ else if (!strcmp(a, "--initial-commit"))
+ o->initial_commit = 1;
+ else if (match_str_arg(&sharedarg, &args, "--shared"))
+ o->shared = parse_shared(sharedarg);
+ else if (!match_str_arg(&o->template, &args, "--template") ||
+ !match_str_arg(&o->gitdir, &args, "--separate-git-dir"))
+ usage("unknown option", a);
+ }
+
+ if (!o->dir)
+ usage("must specify directory to init", NULL);
+}
diff --git a/examples/log.c b/examples/log.c
new file mode 100644
index 000000000..d5f75a297
--- /dev/null
+++ b/examples/log.c
@@ -0,0 +1,479 @@
+/*
+ * libgit2 "log" example - shows how to walk history and get commit info
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#include "common.h"
+
+/**
+ * This example demonstrates the libgit2 rev walker APIs to roughly
+ * simulate the output of `git log` and a few of command line arguments.
+ * `git log` has many many options and this only shows a few of them.
+ *
+ * This does not have:
+ *
+ * - Robust error handling
+ * - Colorized or paginated output formatting
+ * - Most of the `git log` options
+ *
+ * This does have:
+ *
+ * - Examples of translating command line arguments to equivalent libgit2
+ * revwalker configuration calls
+ * - Simplified options to apply pathspec limits and to show basic diffs
+ */
+
+/** log_state represents walker being configured while handling options */
+struct log_state {
+ git_repository *repo;
+ const char *repodir;
+ git_revwalk *walker;
+ int hide;
+ int sorting;
+ int revisions;
+};
+
+/** utility functions that are called to configure the walker */
+static void set_sorting(struct log_state *s, unsigned int sort_mode);
+static void push_rev(struct log_state *s, git_object *obj, int hide);
+static int add_revision(struct log_state *s, const char *revstr);
+
+/** log_options holds other command line options that affect log output */
+struct log_options {
+ int show_diff;
+ int skip, limit;
+ int min_parents, max_parents;
+ git_time_t before;
+ git_time_t after;
+ const char *author;
+ const char *committer;
+ const char *grep;
+};
+
+/** utility functions that parse options and help with log output */
+static int parse_options(
+ struct log_state *s, struct log_options *opt, int argc, char **argv);
+static void print_time(const git_time *intime, const char *prefix);
+static void print_commit(git_commit *commit);
+static int match_with_parent(git_commit *commit, int i, git_diff_options *);
+
+/** utility functions for filtering */
+static int signature_matches(const git_signature *sig, const char *filter);
+static int log_message_matches(const git_commit *commit, const char *filter);
+
+int main(int argc, char *argv[])
+{
+ int i, count = 0, printed = 0, parents, last_arg;
+ struct log_state s;
+ struct log_options opt;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_oid oid;
+ git_commit *commit = NULL;
+ git_pathspec *ps = NULL;
+
+ git_threads_init();
+
+ /** Parse arguments and set up revwalker. */
+
+ last_arg = parse_options(&s, &opt, argc, argv);
+
+ diffopts.pathspec.strings = &argv[last_arg];
+ diffopts.pathspec.count = argc - last_arg;
+ if (diffopts.pathspec.count > 0)
+ check_lg2(git_pathspec_new(&ps, &diffopts.pathspec),
+ "Building pathspec", NULL);
+
+ if (!s.revisions)
+ add_revision(&s, NULL);
+
+ /** Use the revwalker to traverse the history. */
+
+ printed = count = 0;
+
+ for (; !git_revwalk_next(&oid, s.walker); git_commit_free(commit)) {
+ check_lg2(git_commit_lookup(&commit, s.repo, &oid),
+ "Failed to look up commit", NULL);
+
+ parents = (int)git_commit_parentcount(commit);
+ if (parents < opt.min_parents)
+ continue;
+ if (opt.max_parents > 0 && parents > opt.max_parents)
+ continue;
+
+ if (diffopts.pathspec.count > 0) {
+ int unmatched = parents;
+
+ if (parents == 0) {
+ git_tree *tree;
+ check_lg2(git_commit_tree(&tree, commit), "Get tree", NULL);
+ if (git_pathspec_match_tree(
+ NULL, tree, GIT_PATHSPEC_NO_MATCH_ERROR, ps) != 0)
+ unmatched = 1;
+ git_tree_free(tree);
+ } else if (parents == 1) {
+ unmatched = match_with_parent(commit, 0, &diffopts) ? 0 : 1;
+ } else {
+ for (i = 0; i < parents; ++i) {
+ if (match_with_parent(commit, i, &diffopts))
+ unmatched--;
+ }
+ }
+
+ if (unmatched > 0)
+ continue;
+ }
+
+ if (!signature_matches(git_commit_author(commit), opt.author))
+ continue;
+
+ if (!signature_matches(git_commit_committer(commit), opt.committer))
+ continue;
+
+ if (!log_message_matches(commit, opt.grep))
+ continue;
+
+ if (count++ < opt.skip)
+ continue;
+ if (opt.limit != -1 && printed++ >= opt.limit) {
+ git_commit_free(commit);
+ break;
+ }
+
+ print_commit(commit);
+
+ if (opt.show_diff) {
+ git_tree *a = NULL, *b = NULL;
+ git_diff *diff = NULL;
+
+ if (parents > 1)
+ continue;
+ check_lg2(git_commit_tree(&b, commit), "Get tree", NULL);
+ if (parents == 1) {
+ git_commit *parent;
+ check_lg2(git_commit_parent(&parent, commit, 0), "Get parent", NULL);
+ check_lg2(git_commit_tree(&a, parent), "Tree for parent", NULL);
+ git_commit_free(parent);
+ }
+
+ check_lg2(git_diff_tree_to_tree(
+ &diff, git_commit_owner(commit), a, b, &diffopts),
+ "Diff commit with parent", NULL);
+ check_lg2(
+ git_diff_print(diff, GIT_DIFF_FORMAT_PATCH, diff_output, NULL),
+ "Displaying diff", NULL);
+
+ git_diff_free(diff);
+ git_tree_free(a);
+ git_tree_free(b);
+ }
+ }
+
+ git_pathspec_free(ps);
+ git_revwalk_free(s.walker);
+ git_repository_free(s.repo);
+ git_threads_shutdown();
+
+ return 0;
+}
+
+/** Determine if the given git_signature does not contain the filter text. */
+static int signature_matches(const git_signature *sig, const char *filter) {
+ if (filter == NULL)
+ return 1;
+
+ if (sig != NULL &&
+ (strstr(sig->name, filter) != NULL ||
+ strstr(sig->email, filter) != NULL))
+ return 1;
+
+ return 0;
+}
+
+static int log_message_matches(const git_commit *commit, const char *filter) {
+ const char *message = NULL;
+
+ if (filter == NULL)
+ return 1;
+
+ if ((message = git_commit_message(commit)) != NULL &&
+ strstr(message, filter) != NULL)
+ return 1;
+
+ return 0;
+}
+
+/** Push object (for hide or show) onto revwalker. */
+static void push_rev(struct log_state *s, git_object *obj, int hide)
+{
+ hide = s->hide ^ hide;
+
+ /** Create revwalker on demand if it doesn't already exist. */
+ if (!s->walker) {
+ check_lg2(git_revwalk_new(&s->walker, s->repo),
+ "Could not create revision walker", NULL);
+ git_revwalk_sorting(s->walker, s->sorting);
+ }
+
+ if (!obj)
+ check_lg2(git_revwalk_push_head(s->walker),
+ "Could not find repository HEAD", NULL);
+ else if (hide)
+ check_lg2(git_revwalk_hide(s->walker, git_object_id(obj)),
+ "Reference does not refer to a commit", NULL);
+ else
+ check_lg2(git_revwalk_push(s->walker, git_object_id(obj)),
+ "Reference does not refer to a commit", NULL);
+
+ git_object_free(obj);
+}
+
+/** Parse revision string and add revs to walker. */
+static int add_revision(struct log_state *s, const char *revstr)
+{
+ git_revspec revs;
+ int hide = 0;
+
+ /** Open repo on demand if it isn't already open. */
+ if (!s->repo) {
+ if (!s->repodir) s->repodir = ".";
+ check_lg2(git_repository_open_ext(&s->repo, s->repodir, 0, NULL),
+ "Could not open repository", s->repodir);
+ }
+
+ if (!revstr) {
+ push_rev(s, NULL, hide);
+ return 0;
+ }
+
+ if (*revstr == '^') {
+ revs.flags = GIT_REVPARSE_SINGLE;
+ hide = !hide;
+
+ if (git_revparse_single(&revs.from, s->repo, revstr + 1) < 0)
+ return -1;
+ } else if (git_revparse(&revs, s->repo, revstr) < 0)
+ return -1;
+
+ if ((revs.flags & GIT_REVPARSE_SINGLE) != 0)
+ push_rev(s, revs.from, hide);
+ else {
+ push_rev(s, revs.to, hide);
+
+ if ((revs.flags & GIT_REVPARSE_MERGE_BASE) != 0) {
+ git_oid base;
+ check_lg2(git_merge_base(&base, s->repo,
+ git_object_id(revs.from), git_object_id(revs.to)),
+ "Could not find merge base", revstr);
+ check_lg2(
+ git_object_lookup(&revs.to, s->repo, &base, GIT_OBJ_COMMIT),
+ "Could not find merge base commit", NULL);
+
+ push_rev(s, revs.to, hide);
+ }
+
+ push_rev(s, revs.from, !hide);
+ }
+
+ return 0;
+}
+
+/** Update revwalker with sorting mode. */
+static void set_sorting(struct log_state *s, unsigned int sort_mode)
+{
+ /** Open repo on demand if it isn't already open. */
+ if (!s->repo) {
+ if (!s->repodir) s->repodir = ".";
+ check_lg2(git_repository_open_ext(&s->repo, s->repodir, 0, NULL),
+ "Could not open repository", s->repodir);
+ }
+
+ /** Create revwalker on demand if it doesn't already exist. */
+ if (!s->walker)
+ check_lg2(git_revwalk_new(&s->walker, s->repo),
+ "Could not create revision walker", NULL);
+
+ if (sort_mode == GIT_SORT_REVERSE)
+ s->sorting = s->sorting ^ GIT_SORT_REVERSE;
+ else
+ s->sorting = sort_mode | (s->sorting & GIT_SORT_REVERSE);
+
+ git_revwalk_sorting(s->walker, s->sorting);
+}
+
+/** Helper to format a git_time value like Git. */
+static void print_time(const git_time *intime, const char *prefix)
+{
+ char sign, out[32];
+ struct tm *intm;
+ int offset, hours, minutes;
+ time_t t;
+
+ offset = intime->offset;
+ if (offset < 0) {
+ sign = '-';
+ offset = -offset;
+ } else {
+ sign = '+';
+ }
+
+ hours = offset / 60;
+ minutes = offset % 60;
+
+ t = (time_t)intime->time + (intime->offset * 60);
+
+ intm = gmtime(&t);
+ strftime(out, sizeof(out), "%a %b %e %T %Y", intm);
+
+ printf("%s%s %c%02d%02d\n", prefix, out, sign, hours, minutes);
+}
+
+/** Helper to print a commit object. */
+static void print_commit(git_commit *commit)
+{
+ char buf[GIT_OID_HEXSZ + 1];
+ int i, count;
+ const git_signature *sig;
+ const char *scan, *eol;
+
+ git_oid_tostr(buf, sizeof(buf), git_commit_id(commit));
+ printf("commit %s\n", buf);
+
+ if ((count = (int)git_commit_parentcount(commit)) > 1) {
+ printf("Merge:");
+ for (i = 0; i < count; ++i) {
+ git_oid_tostr(buf, 8, git_commit_parent_id(commit, i));
+ printf(" %s", buf);
+ }
+ printf("\n");
+ }
+
+ if ((sig = git_commit_author(commit)) != NULL) {
+ printf("Author: %s <%s>\n", sig->name, sig->email);
+ print_time(&sig->when, "Date: ");
+ }
+ printf("\n");
+
+ for (scan = git_commit_message(commit); scan && *scan; ) {
+ for (eol = scan; *eol && *eol != '\n'; ++eol) /* find eol */;
+
+ printf(" %.*s\n", (int)(eol - scan), scan);
+ scan = *eol ? eol + 1 : NULL;
+ }
+ printf("\n");
+}
+
+/** Helper to find how many files in a commit changed from its nth parent. */
+static int match_with_parent(git_commit *commit, int i, git_diff_options *opts)
+{
+ git_commit *parent;
+ git_tree *a, *b;
+ git_diff *diff;
+ int ndeltas;
+
+ check_lg2(
+ git_commit_parent(&parent, commit, (size_t)i), "Get parent", NULL);
+ check_lg2(git_commit_tree(&a, parent), "Tree for parent", NULL);
+ check_lg2(git_commit_tree(&b, commit), "Tree for commit", NULL);
+ check_lg2(
+ git_diff_tree_to_tree(&diff, git_commit_owner(commit), a, b, opts),
+ "Checking diff between parent and commit", NULL);
+
+ ndeltas = (int)git_diff_num_deltas(diff);
+
+ git_diff_free(diff);
+ git_tree_free(a);
+ git_tree_free(b);
+ git_commit_free(parent);
+
+ return ndeltas > 0;
+}
+
+/** Print a usage message for the program. */
+static void usage(const char *message, const char *arg)
+{
+ if (message && arg)
+ fprintf(stderr, "%s: %s\n", message, arg);
+ else if (message)
+ fprintf(stderr, "%s\n", message);
+ fprintf(stderr, "usage: log [<options>]\n");
+ exit(1);
+}
+
+/** Parse some log command line options. */
+static int parse_options(
+ struct log_state *s, struct log_options *opt, int argc, char **argv)
+{
+ struct args_info args = ARGS_INFO_INIT;
+
+ memset(s, 0, sizeof(*s));
+ s->sorting = GIT_SORT_TIME;
+
+ memset(opt, 0, sizeof(*opt));
+ opt->max_parents = -1;
+ opt->limit = -1;
+
+ for (args.pos = 1; args.pos < argc; ++args.pos) {
+ const char *a = argv[args.pos];
+
+ if (a[0] != '-') {
+ if (!add_revision(s, a))
+ s->revisions++;
+ else
+ /** Try failed revision parse as filename. */
+ break;
+ } else if (!strcmp(a, "--")) {
+ ++args.pos;
+ break;
+ }
+ else if (!strcmp(a, "--date-order"))
+ set_sorting(s, GIT_SORT_TIME);
+ else if (!strcmp(a, "--topo-order"))
+ set_sorting(s, GIT_SORT_TOPOLOGICAL);
+ else if (!strcmp(a, "--reverse"))
+ set_sorting(s, GIT_SORT_REVERSE);
+ else if (match_str_arg(&opt->author, &args, "--author"))
+ /** Found valid --author */;
+ else if (match_str_arg(&opt->committer, &args, "--committer"))
+ /** Found valid --committer */;
+ else if (match_str_arg(&opt->grep, &args, "--grep"))
+ /** Found valid --grep */;
+ else if (match_str_arg(&s->repodir, &args, "--git-dir"))
+ /** Found git-dir. */;
+ else if (match_int_arg(&opt->skip, &args, "--skip", 0))
+ /** Found valid --skip. */;
+ else if (match_int_arg(&opt->limit, &args, "--max-count", 0))
+ /** Found valid --max-count. */;
+ else if (a[1] >= '0' && a[1] <= '9')
+ is_integer(&opt->limit, a + 1, 0);
+ else if (match_int_arg(&opt->limit, &args, "-n", 0))
+ /** Found valid -n. */;
+ else if (!strcmp(a, "--merges"))
+ opt->min_parents = 2;
+ else if (!strcmp(a, "--no-merges"))
+ opt->max_parents = 1;
+ else if (!strcmp(a, "--no-min-parents"))
+ opt->min_parents = 0;
+ else if (!strcmp(a, "--no-max-parents"))
+ opt->max_parents = -1;
+ else if (match_int_arg(&opt->max_parents, &args, "--max-parents=", 1))
+ /** Found valid --max-parents. */;
+ else if (match_int_arg(&opt->min_parents, &args, "--min-parents=", 0))
+ /** Found valid --min_parents. */;
+ else if (!strcmp(a, "-p") || !strcmp(a, "-u") || !strcmp(a, "--patch"))
+ opt->show_diff = 1;
+ else
+ usage("Unsupported argument", a);
+ }
+
+ return args.pos;
+}
+
diff --git a/examples/network/.gitignore b/examples/network/.gitignore
new file mode 100644
index 000000000..1b48e66ed
--- /dev/null
+++ b/examples/network/.gitignore
@@ -0,0 +1 @@
+/git2
diff --git a/examples/network/Makefile b/examples/network/Makefile
new file mode 100644
index 000000000..f65c6cb26
--- /dev/null
+++ b/examples/network/Makefile
@@ -0,0 +1,22 @@
+default: all
+
+CC = gcc
+CFLAGS += -g
+CFLAGS += -I../../include
+LDFLAGS += -L../../build -L../..
+LIBRARIES += -lgit2 -lpthread
+
+OBJECTS = \
+ git2.o \
+ ls-remote.o \
+ fetch.o \
+ clone.o \
+ index-pack.o \
+ common.o
+
+all: $(OBJECTS)
+ $(CC) $(CFLAGS) $(LDFLAGS) -o git2 $(OBJECTS) $(LIBRARIES)
+
+clean:
+ $(RM) $(OBJECTS)
+ $(RM) git2
diff --git a/examples/network/clone.c b/examples/network/clone.c
new file mode 100644
index 000000000..182d1c35b
--- /dev/null
+++ b/examples/network/clone.c
@@ -0,0 +1,97 @@
+#include "common.h"
+#include <git2.h>
+#include <git2/clone.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef _WIN32
+# include <pthread.h>
+# include <unistd.h>
+#endif
+
+typedef struct progress_data {
+ git_transfer_progress fetch_progress;
+ size_t completed_steps;
+ size_t total_steps;
+ const char *path;
+} progress_data;
+
+static void print_progress(const progress_data *pd)
+{
+ 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;
+ int kbytes = pd->fetch_progress.received_bytes / 1024;
+
+ if (pd->fetch_progress.received_objects == pd->fetch_progress.total_objects) {
+ printf("Resolving deltas %d/%d\r",
+ pd->fetch_progress.indexed_deltas,
+ pd->fetch_progress.total_deltas);
+ } else {
+ 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);
+ }
+}
+
+static int fetch_progress(const git_transfer_progress *stats, void *payload)
+{
+ progress_data *pd = (progress_data*)payload;
+ pd->fetch_progress = *stats;
+ print_progress(pd);
+ return 0;
+}
+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)
+{
+ progress_data pd = {{0}};
+ git_repository *cloned_repo = NULL;
+ git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT;
+ git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
+ 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]);
+ return -1;
+ }
+
+ // Set up options
+ checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+ checkout_opts.progress_cb = checkout_progress;
+ checkout_opts.progress_payload = &pd;
+ clone_opts.checkout_opts = checkout_opts;
+ clone_opts.remote_callbacks.transfer_progress = &fetch_progress;
+ clone_opts.remote_callbacks.credentials = cred_acquire_cb;
+ clone_opts.remote_callbacks.payload = &pd;
+
+ // Do the clone
+ error = git_clone(&cloned_repo, url, path, &clone_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.c b/examples/network/common.c
new file mode 100644
index 000000000..d123eedbd
--- /dev/null
+++ b/examples/network/common.c
@@ -0,0 +1,34 @@
+#include "common.h"
+#include <stdio.h>
+
+/* Shamelessly borrowed from http://stackoverflow.com/questions/3417837/
+ * with permission of the original author, Martin Pool.
+ * http://sourcefrog.net/weblog/software/languages/C/unused.html
+ */
+#ifdef UNUSED
+#elif defined(__GNUC__)
+# define UNUSED(x) UNUSED_ ## x __attribute__((unused))
+#elif defined(__LCLINT__)
+# define UNUSED(x) /*@unused@*/ x
+#else
+# define UNUSED(x) x
+#endif
+
+int cred_acquire_cb(git_cred **out,
+ const char * UNUSED(url),
+ const char * UNUSED(username_from_url),
+ unsigned int UNUSED(allowed_types),
+ void * UNUSED(payload))
+{
+ char username[128] = {0};
+ char password[128] = {0};
+
+ printf("Username: ");
+ scanf("%s", username);
+
+ /* Yup. Right there on your terminal. Careful where you copy/paste output. */
+ printf("Password: ");
+ scanf("%s", password);
+
+ return git_cred_userpass_plaintext_new(out, username, password);
+}
diff --git a/examples/network/common.h b/examples/network/common.h
new file mode 100644
index 000000000..1b09caad4
--- /dev/null
+++ b/examples/network/common.h
@@ -0,0 +1,30 @@
+#ifndef __COMMON_H__
+#define __COMMON_H__
+
+#include <git2.h>
+
+typedef int (*git_cb)(git_repository *, int , char **);
+
+int ls_remote(git_repository *repo, int argc, char **argv);
+int parse_pkt_line(git_repository *repo, int argc, char **argv);
+int show_remote(git_repository *repo, int argc, char **argv);
+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);
+
+int cred_acquire_cb(git_cred **out,
+ const char * url,
+ const char * username_from_url,
+ unsigned int allowed_types,
+ void *payload);
+
+#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
new file mode 100644
index 000000000..03f1541a4
--- /dev/null
+++ b/examples/network/fetch.c
@@ -0,0 +1,169 @@
+#include "common.h"
+#include <git2.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef _WIN32
+# include <pthread.h>
+# include <unistd.h>
+#endif
+
+struct dl_data {
+ git_remote *remote;
+ int ret;
+ int finished;
+};
+
+static int progress_cb(const char *str, int len, void *data)
+{
+ (void)data;
+ printf("remote: %.*s", len, str);
+ fflush(stdout); /* We don't have the \n to force the flush */
+ return 0;
+}
+
+static void *download(void *ptr)
+{
+ struct dl_data *data = (struct dl_data *)ptr;
+
+ // Connect to the remote end specifying that we want to fetch
+ // information from it.
+ if (git_remote_connect(data->remote, GIT_DIRECTION_FETCH) < 0) {
+ data->ret = -1;
+ goto exit;
+ }
+
+ // 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) < 0) {
+ data->ret = -1;
+ goto exit;
+ }
+
+ data->ret = 0;
+
+exit:
+ data->finished = 1;
+ return &data->ret;
+}
+
+/**
+ * This function gets called for each remote-tracking branch that gets
+ * updated. The message we output depends on whether it's a new one or
+ * an update.
+ */
+static int update_cb(const char *refname, const git_oid *a, const git_oid *b, void *data)
+{
+ char a_str[GIT_OID_HEXSZ+1], b_str[GIT_OID_HEXSZ+1];
+ (void)data;
+
+ git_oid_fmt(b_str, b);
+ b_str[GIT_OID_HEXSZ] = '\0';
+
+ if (git_oid_iszero(a)) {
+ printf("[new] %.20s %s\n", b_str, refname);
+ } else {
+ git_oid_fmt(a_str, a);
+ a_str[GIT_OID_HEXSZ] = '\0';
+ printf("[updated] %.10s..%.10s %s\n", a_str, b_str, refname);
+ }
+
+ return 0;
+}
+
+/** Entry point for this command */
+int fetch(git_repository *repo, int argc, char **argv)
+{
+ git_remote *remote = NULL;
+ const git_transfer_progress *stats;
+ struct dl_data data;
+ git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
+#ifndef _WIN32
+ pthread_t worker;
+#endif
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s fetch <repo>\n", argv[-1]);
+ return EXIT_FAILURE;
+ }
+
+ // Figure out whether it's a named remote or a URL
+ printf("Fetching %s for repo %p\n", argv[1], repo);
+ if (git_remote_load(&remote, repo, argv[1]) < 0) {
+ if (git_remote_create_anonymous(&remote, repo, argv[1], NULL) < 0)
+ return -1;
+ }
+
+ // Set up the callbacks (only update_tips for now)
+ callbacks.update_tips = &update_cb;
+ callbacks.sideband_progress = &progress_cb;
+ callbacks.credentials = cred_acquire_cb;
+ git_remote_set_callbacks(remote, &callbacks);
+
+ // Set up the information for the background worker thread
+ data.remote = remote;
+ data.ret = 0;
+ data.finished = 0;
+
+ stats = git_remote_stats(remote);
+
+#ifdef _WIN32
+ download(&data);
+#else
+ pthread_create(&worker, NULL, download, &data);
+
+ // Loop while the worker thread is still running. Here we show processed
+ // and total objects in the pack and the amount of received
+ // data. Most frontends will probably want to show a percentage and
+ // the download rate.
+ do {
+ usleep(10000);
+
+ if (stats->received_objects == stats->total_objects) {
+ printf("Resolving deltas %d/%d\r",
+ stats->indexed_deltas, stats->total_deltas);
+ } else 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);
+#endif
+
+ /**
+ * If there are local objects (we got a thin pack), then tell
+ * the user how many objects we saved from having to cross the
+ * network.
+ */
+ if (stats->local_objects > 0) {
+ printf("\rReceived %d/%d objects in %zu bytes (used %d local objects)\n",
+ stats->indexed_objects, stats->total_objects, stats->received_bytes, stats->local_objects);
+ } else{
+ 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);
+
+ // Update the references in the remote's namespace to point to the
+ // right commits. This may be needed even if there was no packfile
+ // to download, which can happen e.g. when the branches have been
+ // changed but all the neede objects are available locally.
+ if (git_remote_update_tips(remote, NULL, NULL) < 0)
+ return -1;
+
+ git_remote_free(remote);
+
+ return 0;
+
+ on_error:
+ git_remote_free(remote);
+ return -1;
+}
diff --git a/examples/network/git2.c b/examples/network/git2.c
new file mode 100644
index 000000000..5b32ac809
--- /dev/null
+++ b/examples/network/git2.c
@@ -0,0 +1,66 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "common.h"
+
+// This part is not strictly libgit2-dependent, but you can use this
+// as a starting point for a git-like tool
+
+struct {
+ char *name;
+ git_cb fn;
+} commands[] = {
+ {"ls-remote", ls_remote},
+ {"fetch", fetch},
+ {"clone", do_clone},
+ {"index-pack", index_pack},
+ { NULL, NULL}
+};
+
+static int run_command(git_cb fn, int argc, char **argv)
+{
+ int error;
+ git_repository *repo;
+
+// Before running the actual command, create an instance of the local
+// repository and pass it to the function.
+
+ error = git_repository_open(&repo, ".git");
+ if (error < 0)
+ repo = NULL;
+
+ // Run the command. If something goes wrong, print the error message to stderr
+ error = fn(repo, argc, argv);
+ if (error < 0) {
+ if (giterr_last() == NULL)
+ fprintf(stderr, "Error without message");
+ else
+ fprintf(stderr, "Bad news:\n %s\n", giterr_last()->message);
+ }
+
+ if(repo)
+ git_repository_free(repo);
+
+ return !!error;
+}
+
+int main(int argc, char **argv)
+{
+ int i;
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s <cmd> [repo]\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ git_threads_init();
+
+ for (i = 0; commands[i].name != NULL; ++i) {
+ if (!strcmp(argv[1], commands[i].name))
+ return run_command(commands[i].fn, --argc, ++argv);
+ }
+
+ fprintf(stderr, "Command not found: %s\n", argv[1]);
+ return 1;
+}
diff --git a/examples/network/index-pack.c b/examples/network/index-pack.c
new file mode 100644
index 000000000..314f21160
--- /dev/null
+++ b/examples/network/index-pack.c
@@ -0,0 +1,88 @@
+#include <git2.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#ifdef _WIN32
+# include <io.h>
+# include <Windows.h>
+
+# define open _open
+# define read _read
+# define close _close
+
+#define ssize_t unsigned int
+#else
+# include <unistd.h>
+#endif
+#include "common.h"
+
+// 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_transfer_progress *stats, void *data)
+{
+ (void)data;
+ printf("\rProcessing %d of %d", stats->indexed_objects, stats->total_objects);
+
+ return 0;
+}
+
+int index_pack(git_repository *repo, int argc, char **argv)
+{
+ git_indexer *idx;
+ git_transfer_progress stats = {0, 0};
+ int error;
+ char hash[GIT_OID_HEXSZ + 1] = {0};
+ int fd;
+ ssize_t read_bytes;
+ char buf[512];
+
+ (void)repo;
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s index-pack <packfile>\n", argv[-1]);
+ return EXIT_FAILURE;
+ }
+
+ if (git_indexer_new(&idx, ".", 0, NULL, NULL, NULL) < 0) {
+ puts("bad idx");
+ return -1;
+ }
+
+ if ((fd = open(argv[1], 0)) < 0) {
+ perror("open");
+ return -1;
+ }
+
+ do {
+ read_bytes = read(fd, buf, sizeof(buf));
+ if (read_bytes < 0)
+ break;
+
+ if ((error = git_indexer_append(idx, buf, read_bytes, &stats)) < 0)
+ goto cleanup;
+
+ index_cb(&stats, NULL);
+ } while (read_bytes > 0);
+
+ if (read_bytes < 0) {
+ error = -1;
+ perror("failed reading");
+ goto cleanup;
+ }
+
+ if ((error = git_indexer_commit(idx, &stats)) < 0)
+ goto cleanup;
+
+ printf("\rIndexing %d of %d\n", stats.indexed_objects, stats.total_objects);
+
+ git_oid_fmt(hash, git_indexer_hash(idx));
+ puts(hash);
+
+ cleanup:
+ close(fd);
+ git_indexer_free(idx);
+ return error;
+}
diff --git a/examples/network/ls-remote.c b/examples/network/ls-remote.c
new file mode 100644
index 000000000..a5c14cea8
--- /dev/null
+++ b/examples/network/ls-remote.c
@@ -0,0 +1,65 @@
+#include <git2.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "common.h"
+
+static int use_remote(git_repository *repo, char *name)
+{
+ git_remote *remote = NULL;
+ int error;
+ const git_remote_head **refs;
+ size_t refs_len, i;
+ git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
+
+ // Find the remote by name
+ error = git_remote_load(&remote, repo, name);
+ if (error < 0) {
+ error = git_remote_create_anonymous(&remote, repo, name, NULL);
+ if (error < 0)
+ goto cleanup;
+ }
+
+ /**
+ * Connect to the remote and call the printing function for
+ * each of the remote references.
+ */
+ callbacks.credentials = cred_acquire_cb;
+ git_remote_set_callbacks(remote, &callbacks);
+
+ error = git_remote_connect(remote, GIT_DIRECTION_FETCH);
+ if (error < 0)
+ goto cleanup;
+
+ /**
+ * Get the list of references on the remote and print out
+ * their name next to what they point to.
+ */
+ if (git_remote_ls(&refs, &refs_len, remote) < 0)
+ goto cleanup;
+
+ for (i = 0; i < refs_len; i++) {
+ char oid[GIT_OID_HEXSZ + 1] = {0};
+ git_oid_fmt(oid, &refs[i]->oid);
+ printf("%s\t%s\n", oid, refs[i]->name);
+ }
+
+cleanup:
+ git_remote_free(remote);
+ return error;
+}
+
+/** Entry point for this command */
+int ls_remote(git_repository *repo, int argc, char **argv)
+{
+ int error;
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s ls-remote <remote>\n", argv[-1]);
+ return EXIT_FAILURE;
+ }
+
+ error = use_remote(repo, argv[1]);
+
+ return error;
+}
diff --git a/examples/rev-list.c b/examples/rev-list.c
new file mode 100644
index 000000000..5c0d751a3
--- /dev/null
+++ b/examples/rev-list.c
@@ -0,0 +1,121 @@
+/*
+ * libgit2 "rev-list" example - shows how to transform a rev-spec into a list
+ * of commit ids
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#include "common.h"
+
+static int revwalk_parseopts(git_repository *repo, git_revwalk *walk, int nopts, char **opts);
+
+int main (int argc, char **argv)
+{
+ git_repository *repo;
+ git_revwalk *walk;
+ git_oid oid;
+ char buf[41];
+
+ git_threads_init();
+
+ check_lg2(git_repository_open_ext(&repo, ".", 0, NULL), "opening repository", NULL);
+ check_lg2(git_revwalk_new(&walk, repo), "allocating revwalk", NULL);
+ check_lg2(revwalk_parseopts(repo, walk, argc-1, argv+1), "parsing options", NULL);
+
+ while (!git_revwalk_next(&oid, walk)) {
+ git_oid_fmt(buf, &oid);
+ buf[40] = '\0';
+ printf("%s\n", buf);
+ }
+
+ git_threads_shutdown();
+ return 0;
+}
+
+static int push_commit(git_revwalk *walk, const git_oid *oid, int hide)
+{
+ if (hide)
+ return git_revwalk_hide(walk, oid);
+ else
+ return git_revwalk_push(walk, oid);
+}
+
+static int push_spec(git_repository *repo, git_revwalk *walk, const char *spec, int hide)
+{
+ int error;
+ git_object *obj;
+
+ if ((error = git_revparse_single(&obj, repo, spec)) < 0)
+ return error;
+
+ error = push_commit(walk, git_object_id(obj), hide);
+ git_object_free(obj);
+ return error;
+}
+
+static int push_range(git_repository *repo, git_revwalk *walk, const char *range, int hide)
+{
+ git_revspec revspec;
+ int error = 0;
+
+ if ((error = git_revparse(&revspec, repo, range)))
+ return error;
+
+ if (revspec.flags & GIT_REVPARSE_MERGE_BASE) {
+ /* TODO: support "<commit>...<commit>" */
+ return GIT_EINVALIDSPEC;
+ }
+
+ if ((error = push_commit(walk, git_object_id(revspec.from), !hide)))
+ goto out;
+
+ error = push_commit(walk, git_object_id(revspec.to), hide);
+
+out:
+ git_object_free(revspec.from);
+ git_object_free(revspec.to);
+ return error;
+}
+
+static int revwalk_parseopts(git_repository *repo, git_revwalk *walk, int nopts, char **opts)
+{
+ int hide, i, error;
+ unsigned int sorting = GIT_SORT_NONE;
+
+ hide = 0;
+ for (i = 0; i < nopts; i++) {
+ if (!strcmp(opts[i], "--topo-order")) {
+ sorting = GIT_SORT_TOPOLOGICAL | (sorting & GIT_SORT_REVERSE);
+ git_revwalk_sorting(walk, sorting);
+ } else if (!strcmp(opts[i], "--date-order")) {
+ sorting = GIT_SORT_TIME | (sorting & GIT_SORT_REVERSE);
+ git_revwalk_sorting(walk, sorting);
+ } else if (!strcmp(opts[i], "--reverse")) {
+ sorting = (sorting & ~GIT_SORT_REVERSE)
+ | ((sorting & GIT_SORT_REVERSE) ? 0 : GIT_SORT_REVERSE);
+ git_revwalk_sorting(walk, sorting);
+ } else if (!strcmp(opts[i], "--not")) {
+ hide = !hide;
+ } else if (opts[i][0] == '^') {
+ if ((error = push_spec(repo, walk, opts[i] + 1, !hide)))
+ return error;
+ } else if (strstr(opts[i], "..")) {
+ if ((error = push_range(repo, walk, opts[i], hide)))
+ return error;
+ } else {
+ if ((error = push_spec(repo, walk, opts[i], hide)))
+ return error;
+ }
+ }
+
+ return 0;
+}
+
diff --git a/examples/rev-parse.c b/examples/rev-parse.c
new file mode 100644
index 000000000..a24833767
--- /dev/null
+++ b/examples/rev-parse.c
@@ -0,0 +1,115 @@
+/*
+ * libgit2 "rev-parse" example - shows how to parse revspecs
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#include "common.h"
+
+/** Forward declarations for helpers. */
+struct parse_state {
+ git_repository *repo;
+ const char *repodir;
+ const char *spec;
+ int not;
+};
+static void parse_opts(struct parse_state *ps, int argc, char *argv[]);
+static int parse_revision(struct parse_state *ps);
+
+
+int main(int argc, char *argv[])
+{
+ struct parse_state ps = {0};
+
+ git_threads_init();
+ parse_opts(&ps, argc, argv);
+
+ check_lg2(parse_revision(&ps), "Parsing", NULL);
+
+ git_repository_free(ps.repo);
+ git_threads_shutdown();
+
+ return 0;
+}
+
+static void usage(const char *message, const char *arg)
+{
+ if (message && arg)
+ fprintf(stderr, "%s: %s\n", message, arg);
+ else if (message)
+ fprintf(stderr, "%s\n", message);
+ fprintf(stderr, "usage: rev-parse [ --option ] <args>...\n");
+ exit(1);
+}
+
+static void parse_opts(struct parse_state *ps, int argc, char *argv[])
+{
+ struct args_info args = ARGS_INFO_INIT;
+
+ for (args.pos=1; args.pos < argc; ++args.pos) {
+ const char *a = argv[args.pos];
+
+ if (a[0] != '-') {
+ if (ps->spec)
+ usage("Too many specs", a);
+ ps->spec = a;
+ } else if (!strcmp(a, "--not"))
+ ps->not = !ps->not;
+ else if (!match_str_arg(&ps->repodir, &args, "--git-dir"))
+ usage("Cannot handle argument", a);
+ }
+}
+
+static int parse_revision(struct parse_state *ps)
+{
+ git_revspec rs;
+ char str[GIT_OID_HEXSZ + 1];
+
+ if (!ps->repo) {
+ if (!ps->repodir)
+ ps->repodir = ".";
+ check_lg2(git_repository_open_ext(&ps->repo, ps->repodir, 0, NULL),
+ "Could not open repository from", ps->repodir);
+ }
+
+ check_lg2(git_revparse(&rs, ps->repo, ps->spec), "Could not parse", ps->spec);
+
+ if ((rs.flags & GIT_REVPARSE_SINGLE) != 0) {
+ git_oid_tostr(str, sizeof(str), git_object_id(rs.from));
+ printf("%s\n", str);
+ git_object_free(rs.from);
+ }
+ else if ((rs.flags & GIT_REVPARSE_RANGE) != 0) {
+ git_oid_tostr(str, sizeof(str), git_object_id(rs.to));
+ printf("%s\n", str);
+ git_object_free(rs.to);
+
+ if ((rs.flags & GIT_REVPARSE_MERGE_BASE) != 0) {
+ git_oid base;
+ check_lg2(git_merge_base(&base, ps->repo,
+ git_object_id(rs.from), git_object_id(rs.to)),
+ "Could not find merge base", ps->spec);
+
+ git_oid_tostr(str, sizeof(str), &base);
+ printf("%s\n", str);
+ }
+
+ git_oid_tostr(str, sizeof(str), git_object_id(rs.from));
+ printf("^%s\n", str);
+ git_object_free(rs.from);
+ }
+ else {
+ fatal("Invalid results from git_revparse", ps->spec);
+ }
+
+ return 0;
+}
+
diff --git a/examples/showindex.c b/examples/showindex.c
index 7f2130b90..ad4a16e8e 100644
--- a/examples/showindex.c
+++ b/examples/showindex.c
@@ -1,43 +1,70 @@
-#include <git2.h>
-#include <stdio.h>
+/*
+ * libgit2 "showindex" example - shows how to extract data from the index
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#include "common.h"
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_index *index;
+ unsigned int i, ecount;
+ char *dir = ".";
+ size_t dirlen;
+ char out[41];
+ out[40] = '\0';
+
+ git_threads_init();
+
+ if (argc > 2)
+ fatal("usage: showindex [<repo-dir>]", NULL);
+ if (argc > 1)
+ dir = argv[1];
+
+ dirlen = strlen(dir);
+ if (dirlen > 5 && strcmp(dir + dirlen - 5, "index") == 0) {
+ check_lg2(git_index_open(&index, dir), "could not open index", dir);
+ } else {
+ git_repository *repo;
+ check_lg2(git_repository_open_ext(&repo, dir, 0, NULL), "could not open repository", dir);
+ check_lg2(git_repository_index(&index, repo), "could not open repository index", NULL);
+ git_repository_free(repo);
+ }
+
+ git_index_read(index, 0);
+ 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->id);
+
+ printf("File Path: %s\n", e->path);
+ printf(" Stage: %d\n", git_index_entry_stage(e));
+ printf(" Blob SHA: %s\n", out);
+ printf("File Mode: %07o\n", e->mode);
+ printf("File Size: %d bytes\n", (int)e->file_size);
+ printf("Dev/Inode: %d/%d\n", (int)e->dev, (int)e->ino);
+ printf(" UID/GID: %d/%d\n", (int)e->uid, (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_threads_shutdown();
+
+ return 0;
+}
diff --git a/examples/status.c b/examples/status.c
new file mode 100644
index 000000000..a59f34454
--- /dev/null
+++ b/examples/status.c
@@ -0,0 +1,523 @@
+/*
+ * libgit2 "status" example - shows how to use the status APIs
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#include "common.h"
+#ifdef _WIN32
+# include <Windows.h>
+# define sleep(a) Sleep(a * 1000)
+#else
+# include <unistd.h>
+#endif
+
+/**
+ * This example demonstrates the use of the libgit2 status APIs,
+ * particularly the `git_status_list` object, to roughly simulate the
+ * output of running `git status`. It serves as a simple example of
+ * using those APIs to get basic status information.
+ *
+ * This does not have:
+ *
+ * - Robust error handling
+ * - Colorized or paginated output formatting
+ *
+ * This does have:
+ *
+ * - Examples of translating command line arguments to the status
+ * options settings to mimic `git status` results.
+ * - A sample status formatter that matches the default "long" format
+ * from `git status`
+ * - A sample status formatter that matches the "short" format
+ */
+
+enum {
+ FORMAT_DEFAULT = 0,
+ FORMAT_LONG = 1,
+ FORMAT_SHORT = 2,
+ FORMAT_PORCELAIN = 3,
+};
+
+#define MAX_PATHSPEC 8
+
+struct opts {
+ git_status_options statusopt;
+ char *repodir;
+ char *pathspec[MAX_PATHSPEC];
+ int npaths;
+ int format;
+ int zterm;
+ int showbranch;
+ int showsubmod;
+ int repeat;
+};
+
+static void parse_opts(struct opts *o, int argc, char *argv[]);
+static void show_branch(git_repository *repo, int format);
+static void print_long(git_status_list *status);
+static void print_short(git_repository *repo, git_status_list *status);
+static int print_submod(git_submodule *sm, const char *name, void *payload);
+
+int main(int argc, char *argv[])
+{
+ git_repository *repo = NULL;
+ git_status_list *status;
+ struct opts o = { GIT_STATUS_OPTIONS_INIT, "." };
+
+ git_threads_init();
+
+ o.statusopt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
+ o.statusopt.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
+ GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
+
+ parse_opts(&o, argc, argv);
+
+ /**
+ * Try to open the repository at the given path (or at the current
+ * directory if none was given).
+ */
+ check_lg2(git_repository_open_ext(&repo, o.repodir, 0, NULL),
+ "Could not open repository", o.repodir);
+
+ if (git_repository_is_bare(repo))
+ fatal("Cannot report status on bare repository",
+ git_repository_path(repo));
+
+show_status:
+ if (o.repeat)
+ printf("\033[H\033[2J");
+
+ /**
+ * Run status on the repository
+ *
+ * We use `git_status_list_new()` to generate a list of status
+ * information which lets us iterate over it at our
+ * convenience and extract the data we want to show out of
+ * each entry.
+ *
+ * You can use `git_status_foreach()` or
+ * `git_status_foreach_ext()` if you'd prefer to execute a
+ * callback for each entry. The latter gives you more control
+ * about what results are presented.
+ */
+ check_lg2(git_status_list_new(&status, repo, &o.statusopt),
+ "Could not get status", NULL);
+
+ if (o.showbranch)
+ show_branch(repo, o.format);
+
+ if (o.showsubmod) {
+ int submod_count = 0;
+ check_lg2(git_submodule_foreach(repo, print_submod, &submod_count),
+ "Cannot iterate submodules", o.repodir);
+ }
+
+ if (o.format == FORMAT_LONG)
+ print_long(status);
+ else
+ print_short(repo, status);
+
+ git_status_list_free(status);
+
+ if (o.repeat) {
+ sleep(o.repeat);
+ goto show_status;
+ }
+
+ git_repository_free(repo);
+ git_threads_shutdown();
+
+ return 0;
+}
+
+/**
+ * If the user asked for the branch, let's show the short name of the
+ * branch.
+ */
+static void show_branch(git_repository *repo, int format)
+{
+ int error = 0;
+ const char *branch = NULL;
+ git_reference *head = NULL;
+
+ error = git_repository_head(&head, repo);
+
+ if (error == GIT_EUNBORNBRANCH || error == GIT_ENOTFOUND)
+ branch = NULL;
+ else if (!error) {
+ branch = git_reference_shorthand(head);
+ } else
+ check_lg2(error, "failed to get current branch", NULL);
+
+ if (format == FORMAT_LONG)
+ printf("# On branch %s\n",
+ branch ? branch : "Not currently on any branch.");
+ else
+ printf("## %s\n", branch ? branch : "HEAD (no branch)");
+
+ git_reference_free(head);
+}
+
+/**
+ * This function print out an output similar to git's status command
+ * in long form, including the command-line hints.
+ */
+static void print_long(git_status_list *status)
+{
+ size_t i, maxi = git_status_list_entrycount(status);
+ const git_status_entry *s;
+ int header = 0, changes_in_index = 0;
+ int changed_in_workdir = 0, rm_in_workdir = 0;
+ const char *old_path, *new_path;
+
+ /** Print index changes. */
+
+ for (i = 0; i < maxi; ++i) {
+ char *istatus = NULL;
+
+ s = git_status_byindex(status, i);
+
+ if (s->status == GIT_STATUS_CURRENT)
+ continue;
+
+ if (s->status & GIT_STATUS_WT_DELETED)
+ rm_in_workdir = 1;
+
+ if (s->status & GIT_STATUS_INDEX_NEW)
+ istatus = "new file: ";
+ if (s->status & GIT_STATUS_INDEX_MODIFIED)
+ istatus = "modified: ";
+ if (s->status & GIT_STATUS_INDEX_DELETED)
+ istatus = "deleted: ";
+ if (s->status & GIT_STATUS_INDEX_RENAMED)
+ istatus = "renamed: ";
+ if (s->status & GIT_STATUS_INDEX_TYPECHANGE)
+ istatus = "typechange:";
+
+ if (istatus == NULL)
+ continue;
+
+ if (!header) {
+ printf("# Changes to be committed:\n");
+ printf("# (use \"git reset HEAD <file>...\" to unstage)\n");
+ printf("#\n");
+ header = 1;
+ }
+
+ old_path = s->head_to_index->old_file.path;
+ new_path = s->head_to_index->new_file.path;
+
+ if (old_path && new_path && strcmp(old_path, new_path))
+ printf("#\t%s %s -> %s\n", istatus, old_path, new_path);
+ else
+ printf("#\t%s %s\n", istatus, old_path ? old_path : new_path);
+ }
+
+ if (header) {
+ changes_in_index = 1;
+ printf("#\n");
+ }
+ header = 0;
+
+ /** Print workdir changes to tracked files. */
+
+ for (i = 0; i < maxi; ++i) {
+ char *wstatus = NULL;
+
+ s = git_status_byindex(status, i);
+
+ /**
+ * With `GIT_STATUS_OPT_INCLUDE_UNMODIFIED` (not used in this example)
+ * `index_to_workdir` may not be `NULL` even if there are
+ * no differences, in which case it will be a `GIT_DELTA_UNMODIFIED`.
+ */
+ if (s->status == GIT_STATUS_CURRENT || s->index_to_workdir == NULL)
+ continue;
+
+ /** Print out the output since we know the file has some changes */
+ if (s->status & GIT_STATUS_WT_MODIFIED)
+ wstatus = "modified: ";
+ if (s->status & GIT_STATUS_WT_DELETED)
+ wstatus = "deleted: ";
+ if (s->status & GIT_STATUS_WT_RENAMED)
+ wstatus = "renamed: ";
+ if (s->status & GIT_STATUS_WT_TYPECHANGE)
+ wstatus = "typechange:";
+
+ if (wstatus == NULL)
+ continue;
+
+ if (!header) {
+ printf("# Changes not staged for commit:\n");
+ printf("# (use \"git add%s <file>...\" to update what will be committed)\n", rm_in_workdir ? "/rm" : "");
+ printf("# (use \"git checkout -- <file>...\" to discard changes in working directory)\n");
+ printf("#\n");
+ header = 1;
+ }
+
+ old_path = s->index_to_workdir->old_file.path;
+ new_path = s->index_to_workdir->new_file.path;
+
+ if (old_path && new_path && strcmp(old_path, new_path))
+ printf("#\t%s %s -> %s\n", wstatus, old_path, new_path);
+ else
+ printf("#\t%s %s\n", wstatus, old_path ? old_path : new_path);
+ }
+
+ if (header) {
+ changed_in_workdir = 1;
+ printf("#\n");
+ }
+
+ /** Print untracked files. */
+
+ header = 0;
+
+ for (i = 0; i < maxi; ++i) {
+ s = git_status_byindex(status, i);
+
+ if (s->status == GIT_STATUS_WT_NEW) {
+
+ if (!header) {
+ printf("# Untracked files:\n");
+ printf("# (use \"git add <file>...\" to include in what will be committed)\n");
+ printf("#\n");
+ header = 1;
+ }
+
+ printf("#\t%s\n", s->index_to_workdir->old_file.path);
+ }
+ }
+
+ header = 0;
+
+ /** Print ignored files. */
+
+ for (i = 0; i < maxi; ++i) {
+ s = git_status_byindex(status, i);
+
+ if (s->status == GIT_STATUS_IGNORED) {
+
+ if (!header) {
+ printf("# Ignored files:\n");
+ printf("# (use \"git add -f <file>...\" to include in what will be committed)\n");
+ printf("#\n");
+ header = 1;
+ }
+
+ printf("#\t%s\n", s->index_to_workdir->old_file.path);
+ }
+ }
+
+ if (!changes_in_index && changed_in_workdir)
+ printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
+}
+
+/**
+ * This version of the output prefixes each path with two status
+ * columns and shows submodule status information.
+ */
+static void print_short(git_repository *repo, git_status_list *status)
+{
+ size_t i, maxi = git_status_list_entrycount(status);
+ const git_status_entry *s;
+ char istatus, wstatus;
+ const char *extra, *a, *b, *c;
+
+ for (i = 0; i < maxi; ++i) {
+ s = git_status_byindex(status, i);
+
+ if (s->status == GIT_STATUS_CURRENT)
+ continue;
+
+ a = b = c = NULL;
+ istatus = wstatus = ' ';
+ extra = "";
+
+ if (s->status & GIT_STATUS_INDEX_NEW)
+ istatus = 'A';
+ if (s->status & GIT_STATUS_INDEX_MODIFIED)
+ istatus = 'M';
+ if (s->status & GIT_STATUS_INDEX_DELETED)
+ istatus = 'D';
+ if (s->status & GIT_STATUS_INDEX_RENAMED)
+ istatus = 'R';
+ if (s->status & GIT_STATUS_INDEX_TYPECHANGE)
+ istatus = 'T';
+
+ if (s->status & GIT_STATUS_WT_NEW) {
+ if (istatus == ' ')
+ istatus = '?';
+ wstatus = '?';
+ }
+ if (s->status & GIT_STATUS_WT_MODIFIED)
+ wstatus = 'M';
+ if (s->status & GIT_STATUS_WT_DELETED)
+ wstatus = 'D';
+ if (s->status & GIT_STATUS_WT_RENAMED)
+ wstatus = 'R';
+ if (s->status & GIT_STATUS_WT_TYPECHANGE)
+ wstatus = 'T';
+
+ if (s->status & GIT_STATUS_IGNORED) {
+ istatus = '!';
+ wstatus = '!';
+ }
+
+ if (istatus == '?' && wstatus == '?')
+ continue;
+
+ /**
+ * A commit in a tree is how submodules are stored, so
+ * let's go take a look at its status.
+ */
+ if (s->index_to_workdir &&
+ s->index_to_workdir->new_file.mode == GIT_FILEMODE_COMMIT)
+ {
+ git_submodule *sm = NULL;
+ unsigned int smstatus = 0;
+
+ if (!git_submodule_lookup(
+ &sm, repo, s->index_to_workdir->new_file.path)) {
+
+ if (!git_submodule_status(&smstatus, sm)) {
+ if (smstatus & GIT_SUBMODULE_STATUS_WD_MODIFIED)
+ extra = " (new commits)";
+ else if (smstatus & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED)
+ extra = " (modified content)";
+ else if (smstatus & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED)
+ extra = " (modified content)";
+ else if (smstatus & GIT_SUBMODULE_STATUS_WD_UNTRACKED)
+ extra = " (untracked content)";
+ }
+ }
+
+ git_submodule_free(sm);
+ }
+
+ /**
+ * Now that we have all the information, format the output.
+ */
+
+ if (s->head_to_index) {
+ a = s->head_to_index->old_file.path;
+ b = s->head_to_index->new_file.path;
+ }
+ if (s->index_to_workdir) {
+ if (!a)
+ a = s->index_to_workdir->old_file.path;
+ if (!b)
+ b = s->index_to_workdir->old_file.path;
+ c = s->index_to_workdir->new_file.path;
+ }
+
+ if (istatus == 'R') {
+ if (wstatus == 'R')
+ printf("%c%c %s %s %s%s\n", istatus, wstatus, a, b, c, extra);
+ else
+ printf("%c%c %s %s%s\n", istatus, wstatus, a, b, extra);
+ } else {
+ if (wstatus == 'R')
+ printf("%c%c %s %s%s\n", istatus, wstatus, a, c, extra);
+ else
+ printf("%c%c %s%s\n", istatus, wstatus, a, extra);
+ }
+ }
+
+ for (i = 0; i < maxi; ++i) {
+ s = git_status_byindex(status, i);
+
+ if (s->status == GIT_STATUS_WT_NEW)
+ printf("?? %s\n", s->index_to_workdir->old_file.path);
+ }
+}
+
+static int print_submod(git_submodule *sm, const char *name, void *payload)
+{
+ int *count = payload;
+ (void)name;
+
+ if (*count == 0)
+ printf("# Submodules\n");
+ (*count)++;
+
+ printf("# - submodule '%s' at %s\n",
+ git_submodule_name(sm), git_submodule_path(sm));
+
+ return 0;
+}
+
+/**
+ * Parse options that git's status command supports.
+ */
+static void parse_opts(struct opts *o, int argc, char *argv[])
+{
+ struct args_info args = ARGS_INFO_INIT;
+
+ for (args.pos = 1; args.pos < argc; ++args.pos) {
+ char *a = argv[args.pos];
+
+ if (a[0] != '-') {
+ if (o->npaths < MAX_PATHSPEC)
+ o->pathspec[o->npaths++] = a;
+ else
+ fatal("Example only supports a limited pathspec", NULL);
+ }
+ else if (!strcmp(a, "-s") || !strcmp(a, "--short"))
+ o->format = FORMAT_SHORT;
+ else if (!strcmp(a, "--long"))
+ o->format = FORMAT_LONG;
+ else if (!strcmp(a, "--porcelain"))
+ o->format = FORMAT_PORCELAIN;
+ else if (!strcmp(a, "-b") || !strcmp(a, "--branch"))
+ o->showbranch = 1;
+ else if (!strcmp(a, "-z")) {
+ o->zterm = 1;
+ if (o->format == FORMAT_DEFAULT)
+ o->format = FORMAT_PORCELAIN;
+ }
+ else if (!strcmp(a, "--ignored"))
+ o->statusopt.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED;
+ else if (!strcmp(a, "-uno") ||
+ !strcmp(a, "--untracked-files=no"))
+ o->statusopt.flags &= ~GIT_STATUS_OPT_INCLUDE_UNTRACKED;
+ else if (!strcmp(a, "-unormal") ||
+ !strcmp(a, "--untracked-files=normal"))
+ o->statusopt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
+ else if (!strcmp(a, "-uall") ||
+ !strcmp(a, "--untracked-files=all"))
+ o->statusopt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
+ else if (!strcmp(a, "--ignore-submodules=all"))
+ o->statusopt.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
+ else if (!strncmp(a, "--git-dir=", strlen("--git-dir=")))
+ o->repodir = a + strlen("--git-dir=");
+ else if (!strcmp(a, "--repeat"))
+ o->repeat = 10;
+ else if (match_int_arg(&o->repeat, &args, "--repeat", 0))
+ /* okay */;
+ else if (!strcmp(a, "--list-submodules"))
+ o->showsubmod = 1;
+ else
+ check_lg2(-1, "Unsupported option", a);
+ }
+
+ if (o->format == FORMAT_DEFAULT)
+ o->format = FORMAT_LONG;
+ if (o->format == FORMAT_LONG)
+ o->showbranch = 1;
+ if (o->npaths > 0) {
+ o->statusopt.pathspec.strings = o->pathspec;
+ o->statusopt.pathspec.count = o->npaths;
+ }
+}
diff --git a/examples/tag.c b/examples/tag.c
new file mode 100644
index 000000000..ebb8e37b2
--- /dev/null
+++ b/examples/tag.c
@@ -0,0 +1,319 @@
+/*
+ * libgit2 "tag" example - shows how to list, create and delete tags
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#include "common.h"
+
+/**
+ * The following example partially reimplements the `git tag` command
+ * and some of its options.
+ *
+ * These commands should work:
+
+ * - Tag name listing (`tag`)
+ * - Filtered tag listing with messages (`tag -n3 -l "v0.1*"`)
+ * - Lightweight tag creation (`tag test v0.18.0`)
+ * - Tag creation (`tag -a -m "Test message" test v0.18.0`)
+ * - Tag deletion (`tag -d test`)
+ *
+ * The command line parsing logic is simplified and doesn't handle
+ * all of the use cases.
+ */
+
+/** tag_options represents the parsed command line options */
+typedef struct {
+ const char *message;
+ const char *pattern;
+ const char *tag_name;
+ const char *target;
+ int num_lines;
+ int force;
+} tag_options;
+
+/** tag_state represents the current program state for dragging around */
+typedef struct {
+ git_repository *repo;
+ tag_options *opts;
+} tag_state;
+
+/** An action to execute based on the command line arguments */
+typedef void (*tag_action)(tag_state *state);
+typedef struct args_info args_info;
+
+static void check(int result, const char *message)
+{
+ if (result) fatal(message, NULL);
+}
+
+/** Tag listing: Print individual message lines */
+static void print_list_lines(const char *message, const tag_state *state)
+{
+ const char *msg = message;
+ int num = state->opts->num_lines - 1;
+
+ if (!msg) return;
+
+ /** first line - headline */
+ while(*msg && *msg != '\n') printf("%c", *msg++);
+
+ /** skip over new lines */
+ while(*msg && *msg == '\n') msg++;
+
+ printf("\n");
+
+ /** print just headline? */
+ if (num == 0) return;
+ if (*msg && msg[1]) printf("\n");
+
+ /** print individual commit/tag lines */
+ while (*msg && num-- >= 2) {
+ printf(" ");
+
+ while (*msg && *msg != '\n') printf("%c", *msg++);
+
+ /** handle consecutive new lines */
+ if (*msg && *msg == '\n' && msg[1] == '\n') {
+ num--;
+ printf("\n");
+ }
+ while(*msg && *msg == '\n') msg++;
+
+ printf("\n");
+ }
+}
+
+/** Tag listing: Print an actual tag object */
+static void print_tag(git_tag *tag, const tag_state *state)
+{
+ printf("%-16s", git_tag_name(tag));
+
+ if (state->opts->num_lines) {
+ const char *msg = git_tag_message(tag);
+ print_list_lines(msg, state);
+ } else {
+ printf("\n");
+ }
+}
+
+/** Tag listing: Print a commit (target of a lightweight tag) */
+static void print_commit(git_commit *commit, const char *name,
+ const tag_state *state)
+{
+ printf("%-16s", name);
+
+ if (state->opts->num_lines) {
+ const char *msg = git_commit_message(commit);
+ print_list_lines(msg, state);
+ } else {
+ printf("\n");
+ }
+}
+
+/** Tag listing: Fallback, should not happen */
+static void print_name(const char *name)
+{
+ printf("%s\n", name);
+}
+
+/** Tag listing: Lookup tags based on ref name and dispatch to print */
+static int each_tag(const char *name, tag_state *state)
+{
+ git_repository *repo = state->repo;
+ git_object *obj;
+
+ check_lg2(git_revparse_single(&obj, repo, name),
+ "Failed to lookup rev", name);
+
+ switch (git_object_type(obj)) {
+ case GIT_OBJ_TAG:
+ print_tag((git_tag *) obj, state);
+ break;
+ case GIT_OBJ_COMMIT:
+ print_commit((git_commit *) obj, name, state);
+ break;
+ default:
+ print_name(name);
+ }
+
+ git_object_free(obj);
+ return 0;
+}
+
+static void action_list_tags(tag_state *state)
+{
+ const char *pattern = state->opts->pattern;
+ git_strarray tag_names = {0};
+ size_t i;
+
+ check_lg2(git_tag_list_match(&tag_names, pattern ? pattern : "*", state->repo),
+ "Unable to get list of tags", NULL);
+
+ for(i = 0; i < tag_names.count; i++) {
+ each_tag(tag_names.strings[i], state);
+ }
+
+ git_strarray_free(&tag_names);
+}
+
+static void action_delete_tag(tag_state *state)
+{
+ tag_options *opts = state->opts;
+ git_object *obj;
+ git_buf abbrev_oid = {0};
+
+ check(!opts->tag_name, "Name required");
+
+ check_lg2(git_revparse_single(&obj, state->repo, opts->tag_name),
+ "Failed to lookup rev", opts->tag_name);
+
+ check_lg2(git_object_short_id(&abbrev_oid, obj),
+ "Unable to get abbreviated OID", opts->tag_name);
+
+ check_lg2(git_tag_delete(state->repo, opts->tag_name),
+ "Unable to delete tag", opts->tag_name);
+
+ printf("Deleted tag '%s' (was %s)\n", opts->tag_name, abbrev_oid.ptr);
+
+ git_buf_free(&abbrev_oid);
+ git_object_free(obj);
+}
+
+static void action_create_lighweight_tag(tag_state *state)
+{
+ git_repository *repo = state->repo;
+ tag_options *opts = state->opts;
+ git_oid oid;
+ git_object *target;
+
+ check(!opts->tag_name, "Name required");
+
+ if (!opts->target) opts->target = "HEAD";
+
+ check(!opts->target, "Target required");
+
+ check_lg2(git_revparse_single(&target, repo, opts->target),
+ "Unable to resolve spec", opts->target);
+
+ check_lg2(git_tag_create_lightweight(&oid, repo, opts->tag_name,
+ target, opts->force), "Unable to create tag", NULL);
+
+ git_object_free(target);
+}
+
+static void action_create_tag(tag_state *state)
+{
+ git_repository *repo = state->repo;
+ tag_options *opts = state->opts;
+ git_signature *tagger;
+ git_oid oid;
+ git_object *target;
+
+ check(!opts->tag_name, "Name required");
+ check(!opts->message, "Message required");
+
+ if (!opts->target) opts->target = "HEAD";
+
+ check_lg2(git_revparse_single(&target, repo, opts->target),
+ "Unable to resolve spec", opts->target);
+
+ check_lg2(git_signature_default(&tagger, repo),
+ "Unable to create signature", NULL);
+
+ check_lg2(git_tag_create(&oid, repo, opts->tag_name,
+ target, tagger, opts->message, opts->force), "Unable to create tag", NULL);
+
+ git_object_free(target);
+ git_signature_free(tagger);
+}
+
+static void print_usage()
+{
+ fprintf(stderr, "usage: see `git help tag`\n");
+ exit(1);
+}
+
+/** Parse command line arguments and choose action to run when done */
+static void parse_options(tag_action *action, tag_options *opts, int argc, char **argv)
+{
+ args_info args = ARGS_INFO_INIT;
+ *action = &action_list_tags;
+
+ for (args.pos = 1; args.pos < argc; ++args.pos) {
+ const char *curr = argv[args.pos];
+
+ if (curr[0] != '-') {
+ if (!opts->tag_name)
+ opts->tag_name = curr;
+ else if (!opts->target)
+ opts->target = curr;
+ else
+ print_usage();
+
+ if (*action != &action_create_tag)
+ *action = &action_create_lighweight_tag;
+ } else if (!strcmp(curr, "-n")) {
+ opts->num_lines = 1;
+ *action = &action_list_tags;
+ } else if (!strcmp(curr, "-a")) {
+ *action = &action_create_tag;
+ } else if (!strcmp(curr, "-f")) {
+ opts->force = 1;
+ } else if (match_int_arg(&opts->num_lines, &args, "-n", 0)) {
+ *action = &action_list_tags;
+ } else if (match_str_arg(&opts->pattern, &args, "-l")) {
+ *action = &action_list_tags;
+ } else if (match_str_arg(&opts->tag_name, &args, "-d")) {
+ *action = &action_delete_tag;
+ } else if (match_str_arg(&opts->message, &args, "-m")) {
+ *action = &action_create_tag;
+ }
+ }
+}
+
+/** Initialize tag_options struct */
+static void tag_options_init(tag_options *opts)
+{
+ memset(opts, 0, sizeof(*opts));
+
+ opts->message = NULL;
+ opts->pattern = NULL;
+ opts->tag_name = NULL;
+ opts->target = NULL;
+ opts->num_lines = 0;
+ opts->force = 0;
+}
+
+int main(int argc, char **argv)
+{
+ git_repository *repo;
+ tag_options opts;
+ tag_action action;
+ tag_state state;
+
+ git_threads_init();
+
+ check_lg2(git_repository_open_ext(&repo, ".", 0, NULL),
+ "Could not open repository", NULL);
+
+ tag_options_init(&opts);
+ parse_options(&action, &opts, argc, argv);
+
+ state.repo = repo;
+ state.opts = &opts;
+ action(&state);
+
+ git_repository_free(repo);
+ git_threads_shutdown();
+
+ return 0;
+}
diff --git a/examples/test/test-rev-list.sh b/examples/test/test-rev-list.sh
new file mode 100755
index 000000000..aa645be5e
--- /dev/null
+++ b/examples/test/test-rev-list.sh
@@ -0,0 +1,95 @@
+#!/bin/bash
+
+THIS_FILE="$(readlink -f "$0")"
+ROOT="$(dirname "$(dirname "$(dirname "$THIS_FILE")")")"
+PROGRAM="$ROOT"/examples/rev-list
+LIBDIR="$ROOT"/build
+REPO="$ROOT"/tests/resources/testrepo.git
+
+cd "$REPO"
+
+run () {
+ LD_LIBRARY_PATH="$LIBDIR" "$PROGRAM" "$@"
+}
+
+diff -u - <(run --date-order a4a7dce) <<EOF
+a4a7dce85cf63874e984719f4fdd239f5145052f
+c47800c7266a2be04c571c04d5a6614691ea99bd
+9fd738e8f7967c078dceed8190330fc8648ee56a
+4a202b346bb0fb0db7eff3cffeb3c70babbd2045
+5b5b025afb0b4c913b4c338a42934a3863bf3644
+8496071c1b46c854b31185ea97743be6a8774479
+EOF
+
+out="$(run --topo-order a4a7dce)"
+diff -q - <(echo -n "$out") <<EOF >/dev/null ||
+a4a7dce85cf63874e984719f4fdd239f5145052f
+c47800c7266a2be04c571c04d5a6614691ea99bd
+9fd738e8f7967c078dceed8190330fc8648ee56a
+4a202b346bb0fb0db7eff3cffeb3c70babbd2045
+5b5b025afb0b4c913b4c338a42934a3863bf3644
+8496071c1b46c854b31185ea97743be6a8774479
+EOF
+diff -u - <(echo "$out") <<EOF
+a4a7dce85cf63874e984719f4fdd239f5145052f
+9fd738e8f7967c078dceed8190330fc8648ee56a
+4a202b346bb0fb0db7eff3cffeb3c70babbd2045
+c47800c7266a2be04c571c04d5a6614691ea99bd
+5b5b025afb0b4c913b4c338a42934a3863bf3644
+8496071c1b46c854b31185ea97743be6a8774479
+EOF
+
+diff -u - <(run --date-order --reverse a4a7dce) <<EOF
+8496071c1b46c854b31185ea97743be6a8774479
+5b5b025afb0b4c913b4c338a42934a3863bf3644
+4a202b346bb0fb0db7eff3cffeb3c70babbd2045
+9fd738e8f7967c078dceed8190330fc8648ee56a
+c47800c7266a2be04c571c04d5a6614691ea99bd
+a4a7dce85cf63874e984719f4fdd239f5145052f
+EOF
+
+out=$(run --topo-order --reverse a4a7dce)
+diff -q - <(echo -n "$out") <<EOF >/dev/null ||
+8496071c1b46c854b31185ea97743be6a8774479
+5b5b025afb0b4c913b4c338a42934a3863bf3644
+4a202b346bb0fb0db7eff3cffeb3c70babbd2045
+9fd738e8f7967c078dceed8190330fc8648ee56a
+c47800c7266a2be04c571c04d5a6614691ea99bd
+a4a7dce85cf63874e984719f4fdd239f5145052f
+EOF
+diff -u - <(echo "$out") <<EOF
+8496071c1b46c854b31185ea97743be6a8774479
+5b5b025afb0b4c913b4c338a42934a3863bf3644
+c47800c7266a2be04c571c04d5a6614691ea99bd
+4a202b346bb0fb0db7eff3cffeb3c70babbd2045
+9fd738e8f7967c078dceed8190330fc8648ee56a
+a4a7dce85cf63874e984719f4fdd239f5145052f
+EOF
+
+out="$(run --date-order --topo-order --reverse --reverse a4a7dce)"
+diff -q - <(echo -n "$out") <<EOF >/dev/null ||
+a4a7dce85cf63874e984719f4fdd239f5145052f
+c47800c7266a2be04c571c04d5a6614691ea99bd
+9fd738e8f7967c078dceed8190330fc8648ee56a
+4a202b346bb0fb0db7eff3cffeb3c70babbd2045
+5b5b025afb0b4c913b4c338a42934a3863bf3644
+8496071c1b46c854b31185ea97743be6a8774479
+EOF
+diff -u - <(echo "$out") <<EOF
+a4a7dce85cf63874e984719f4fdd239f5145052f
+9fd738e8f7967c078dceed8190330fc8648ee56a
+4a202b346bb0fb0db7eff3cffeb3c70babbd2045
+c47800c7266a2be04c571c04d5a6614691ea99bd
+5b5b025afb0b4c913b4c338a42934a3863bf3644
+8496071c1b46c854b31185ea97743be6a8774479
+EOF
+
+diff -u - <(run ^9fd738e~2 9fd738e) <<EOF
+9fd738e8f7967c078dceed8190330fc8648ee56a
+4a202b346bb0fb0db7eff3cffeb3c70babbd2045
+EOF
+
+diff -u - <(run --not 9fd738e..9fd738e~2) <<EOF
+9fd738e8f7967c078dceed8190330fc8648ee56a
+4a202b346bb0fb0db7eff3cffeb3c70babbd2045
+EOF
diff --git a/git.git-authors b/git.git-authors
index 086ab3e18..9131a1fa1 100644
--- a/git.git-authors
+++ b/git.git-authors
@@ -28,9 +28,6 @@
# his/her consent on a patch-by-patch basis.
# "???" means the person is a prominent contributor who has
# not yet made his/her standpoint clear.
-# "ign" means the authors consent is ignored for the purpose
-# of libification. This is because the author has contributed
-# to areas that aren't interesting for the library.
#
# Please try to keep the list alphabetically ordered. It will
# help in case we get all 600-ish git.git authors on it.
@@ -39,28 +36,39 @@
# but has otherwise not contributed to git.)
#
ok Adam Simpkins <adam@adamsimpkins.net> (http transport)
+ok Adrian Johnson <ajohnson@redneon.com>
+ok Alexey Shumkin <alex.crezoff@gmail.com>
ok Andreas Ericsson <ae@op5.se>
ok Boyd Lynn Gerber <gerberb@zenez.com>
+ok Brandon Casey <drafnel@gmail.com>
+ok Brian Downing <bdowning@lavos.net>
ok Brian Gernhardt <benji@silverinsanity.com>
ok Christian Couder <chriscool@tuxfamily.org>
ok Daniel Barkalow <barkalow@iabervon.org>
+ok Florian Forster <octo@verplant.org>
+ok Gustaf Hendeby <hendeby@isy.liu.se>
+ok Holger Weiss <holger@zedat.fu-berlin.de>
ok Jeff King <peff@peff.net>
ok Johannes Schindelin <Johannes.Schindelin@gmx.de>
ok Johannes Sixt <j6t@kdbg.org>
+ask Jonathan Nieder <jrnieder@gmail.com>
ok Junio C Hamano <gitster@pobox.com>
ok Kristian Høgsberg <krh@redhat.com>
ok Linus Torvalds <torvalds@linux-foundation.org>
ok Lukas Sandström <lukass@etek.chalmers.se>
ok Matthieu Moy <Matthieu.Moy@imag.fr>
-ign Mike McCormack <mike@codeweavers.com> (imap-send)
-ok Nicolas Pitre <nico@cam.org>
+ok Michael Haggerty <mhagger@alum.mit.edu>
+ok Nicolas Pitre <nico@fluxnic.net> <nico@cam.org>
ok Paolo Bonzini <bonzini@gnu.org>
ok Paul Kocher <paul@cryptography.com>
ok Peter Hagervall <hager@cs.umu.se>
+ok Petr Onderka <gsvick@gmail.com>
ok Pierre Habouzit <madcoder@debian.org>
ok Pieter de Bie <pdebie@ai.rug.nl>
ok René Scharfe <rene.scharfe@lsrfire.ath.cx>
-ign Robert Shearman <rob@codeweavers.com> (imap-send)
+ok Sebastian Schuberth <sschuberth@gmail.com>
ok Shawn O. Pearce <spearce@spearce.org>
ok Steffen Prohaska <prohaska@zib.de>
ok Sven Verdoolaege <skimo@kotnet.org>
+ask Thomas Rast <tr@thomasrast.ch> (ok before 6-Oct-2013)
+ok Torsten Bögershausen <tboegi@web.de>
diff --git a/include/git2.h b/include/git2.h
index 96de524e7..f74976061 100644
--- a/include/git2.h
+++ b/include/git2.h
@@ -1,65 +1,61 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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_git_h__
#define INCLUDE_git_git_h__
-#define LIBGIT2_VERSION "0.14.0"
-#define LIBGIT2_VER_MAJOR 0
-#define LIBGIT2_VER_MINOR 14
-#define LIBGIT2_VER_REVISION 0
-
+#include "git2/attr.h"
+#include "git2/blob.h"
+#include "git2/blame.h"
+#include "git2/branch.h"
+#include "git2/buffer.h"
+#include "git2/checkout.h"
+#include "git2/cherrypick.h"
+#include "git2/clone.h"
+#include "git2/commit.h"
#include "git2/common.h"
+#include "git2/config.h"
+#include "git2/diff.h"
#include "git2/errors.h"
-#include "git2/zlib.h"
-
-#include "git2/types.h"
-
-#include "git2/oid.h"
-#include "git2/signature.h"
+#include "git2/filter.h"
+#include "git2/graph.h"
+#include "git2/ignore.h"
+#include "git2/index.h"
+#include "git2/indexer.h"
+#include "git2/merge.h"
+#include "git2/message.h"
+#include "git2/net.h"
+#include "git2/notes.h"
+#include "git2/object.h"
#include "git2/odb.h"
-
+#include "git2/oid.h"
+#include "git2/pack.h"
+#include "git2/patch.h"
+#include "git2/pathspec.h"
+#include "git2/push.h"
+#include "git2/refdb.h"
+#include "git2/reflog.h"
+#include "git2/refs.h"
+#include "git2/refspec.h"
+#include "git2/remote.h"
#include "git2/repository.h"
+#include "git2/reset.h"
+#include "git2/revert.h"
+#include "git2/revparse.h"
#include "git2/revwalk.h"
-#include "git2/refs.h"
-#include "git2/reflog.h"
-
-#include "git2/object.h"
-#include "git2/blob.h"
-#include "git2/commit.h"
+#include "git2/signature.h"
+#include "git2/stash.h"
+#include "git2/status.h"
+#include "git2/submodule.h"
#include "git2/tag.h"
-#include "git2/tree.h"
-
-#include "git2/index.h"
-#include "git2/config.h"
-#include "git2/remote.h"
-
-#include "git2/refspec.h"
-#include "git2/net.h"
+#include "git2/threads.h"
#include "git2/transport.h"
-#include "git2/status.h"
-#include "git2/indexer.h"
+#include "git2/tree.h"
+#include "git2/types.h"
+#include "git2/version.h"
#endif
diff --git a/include/git2/attr.h b/include/git2/attr.h
new file mode 100644
index 000000000..3adbb2c24
--- /dev/null
+++ b/include/git2/attr.h
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_attr_h__
+#define INCLUDE_git_attr_h__
+
+#include "common.h"
+#include "types.h"
+
+/**
+ * @file git2/attr.h
+ * @brief Git attribute management routines
+ * @defgroup git_attr Git attribute management routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * GIT_ATTR_TRUE checks if an attribute is set on. In core git
+ * parlance, this the value for "Set" attributes.
+ *
+ * For example, if the attribute file contains:
+ *
+ * *.c foo
+ *
+ * Then for file `xyz.c` looking up attribute "foo" gives a value for
+ * which `GIT_ATTR_TRUE(value)` is true.
+ */
+#define GIT_ATTR_TRUE(attr) (git_attr_value(attr) == GIT_ATTR_TRUE_T)
+
+/**
+ * GIT_ATTR_FALSE checks if an attribute is set off. In core git
+ * parlance, this is the value for attributes that are "Unset" (not to
+ * be confused with values that a "Unspecified").
+ *
+ * For example, if the attribute file contains:
+ *
+ * *.h -foo
+ *
+ * Then for file `zyx.h` looking up attribute "foo" gives a value for
+ * which `GIT_ATTR_FALSE(value)` is true.
+ */
+#define GIT_ATTR_FALSE(attr) (git_attr_value(attr) == GIT_ATTR_FALSE_T)
+
+/**
+ * GIT_ATTR_UNSPECIFIED checks if an attribute is unspecified. This
+ * may be due to the attribute not being mentioned at all or because
+ * the attribute was explicitly set unspecified via the `!` operator.
+ *
+ * For example, if the attribute file contains:
+ *
+ * *.c foo
+ * *.h -foo
+ * onefile.c !foo
+ *
+ * Then for `onefile.c` looking up attribute "foo" yields a value with
+ * `GIT_ATTR_UNSPECIFIED(value)` of true. Also, looking up "foo" on
+ * file `onefile.rb` or looking up "bar" on any file will all give
+ * `GIT_ATTR_UNSPECIFIED(value)` of true.
+ */
+#define GIT_ATTR_UNSPECIFIED(attr) (git_attr_value(attr) == GIT_ATTR_UNSPECIFIED_T)
+
+/**
+ * GIT_ATTR_HAS_VALUE checks if an attribute is set to a value (as
+ * opposed to TRUE, FALSE or UNSPECIFIED). This would be the case if
+ * for a file with something like:
+ *
+ * *.txt eol=lf
+ *
+ * Given this, looking up "eol" for `onefile.txt` will give back the
+ * string "lf" and `GIT_ATTR_SET_TO_VALUE(attr)` will return true.
+ */
+#define GIT_ATTR_HAS_VALUE(attr) (git_attr_value(attr) == GIT_ATTR_VALUE_T)
+
+typedef enum {
+ GIT_ATTR_UNSPECIFIED_T = 0,
+ GIT_ATTR_TRUE_T,
+ GIT_ATTR_FALSE_T,
+ GIT_ATTR_VALUE_T,
+} git_attr_t;
+
+/*
+ * Return the value type for a given attribute.
+ *
+ * This can be either `TRUE`, `FALSE`, `UNSPECIFIED` (if the attribute
+ * was not set at all), or `VALUE`, if the attribute was set to
+ * an actual string.
+ *
+ * If the attribute has a `VALUE` string, it can be accessed normally
+ * as a NULL-terminated C string.
+ *
+ * @param attr The attribute
+ * @return the value type for the attribute
+ */
+GIT_EXTERN(git_attr_t) git_attr_value(const char *attr);
+
+/**
+ * Check attribute flags: Reading values from index and working directory.
+ *
+ * When checking attributes, it is possible to check attribute files
+ * in both the working directory (if there is one) and the index (if
+ * there is one). You can explicitly choose where to check and in
+ * which order using the following flags.
+ *
+ * Core git usually checks the working directory then the index,
+ * except during a checkout when it checks the index first. It will
+ * use index only for creating archives or for a bare repo (if an
+ * index has been specified for the bare repo).
+ */
+#define GIT_ATTR_CHECK_FILE_THEN_INDEX 0
+#define GIT_ATTR_CHECK_INDEX_THEN_FILE 1
+#define GIT_ATTR_CHECK_INDEX_ONLY 2
+
+/**
+ * Check attribute flags: Using the system attributes file.
+ *
+ * Normally, attribute checks include looking in the /etc (or system
+ * equivalent) directory for a `gitattributes` file. Passing this
+ * flag will cause attribute checks to ignore that file.
+ */
+#define GIT_ATTR_CHECK_NO_SYSTEM (1 << 2)
+
+/**
+ * Look up the value of one git attribute for path.
+ *
+ * @param value_out Output of the value of the attribute. Use the GIT_ATTR_...
+ * macros to test for TRUE, FALSE, UNSPECIFIED, etc. or just
+ * use the string value for attributes set to a value. You
+ * should NOT modify or free this value.
+ * @param repo The repository containing the path.
+ * @param flags A combination of GIT_ATTR_CHECK... flags.
+ * @param path The path to check for attributes. Relative paths are
+ * interpreted relative to the repo root. The file does
+ * not have to exist, but if it does not, then it will be
+ * treated as a plain file (not a directory).
+ * @param name The name of the attribute to look up.
+ */
+GIT_EXTERN(int) git_attr_get(
+ const char **value_out,
+ git_repository *repo,
+ uint32_t flags,
+ const char *path,
+ const char *name);
+
+/**
+ * Look up a list of git attributes for path.
+ *
+ * Use this if you have a known list of attributes that you want to
+ * look up in a single call. This is somewhat more efficient than
+ * calling `git_attr_get()` multiple times.
+ *
+ * For example, you might write:
+ *
+ * const char *attrs[] = { "crlf", "diff", "foo" };
+ * const char **values[3];
+ * git_attr_get_many(values, repo, 0, "my/fun/file.c", 3, attrs);
+ *
+ * Then you could loop through the 3 values to get the settings for
+ * the three attributes you asked about.
+ *
+ * @param values_out An array of num_attr entries that will have string
+ * pointers written into it for the values of the attributes.
+ * You should not modify or free the values that are written
+ * into this array (although of course, you should free the
+ * array itself if you allocated it).
+ * @param repo The repository containing the path.
+ * @param flags A combination of GIT_ATTR_CHECK... flags.
+ * @param path The path inside the repo to check attributes. This
+ * does not have to exist, but if it does not, then
+ * it will be treated as a plain file (i.e. not a directory).
+ * @param num_attr The number of attributes being looked up
+ * @param names An array of num_attr strings containing attribute names.
+ */
+GIT_EXTERN(int) git_attr_get_many(
+ const char **values_out,
+ git_repository *repo,
+ uint32_t flags,
+ const char *path,
+ size_t num_attr,
+ const char **names);
+
+typedef int (*git_attr_foreach_cb)(const char *name, const char *value, void *payload);
+
+/**
+ * Loop over all the git attributes for a path.
+ *
+ * @param repo The repository containing the path.
+ * @param flags A combination of GIT_ATTR_CHECK... flags.
+ * @param path Path inside the repo to check attributes. This does not have
+ * to exist, but if it does not, then it will be treated as a
+ * plain file (i.e. not a directory).
+ * @param callback Function to invoke on each attribute name and value. The
+ * value may be NULL is the attribute is explicitly set to
+ * UNSPECIFIED using the '!' sign. Callback will be invoked
+ * only once per attribute name, even if there are multiple
+ * rules for a given file. The highest priority rule will be
+ * used. Return a non-zero value from this to stop looping.
+ * The value will be returned from `git_attr_foreach`.
+ * @param payload Passed on as extra parameter to callback function.
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_attr_foreach(
+ git_repository *repo,
+ uint32_t flags,
+ const char *path,
+ git_attr_foreach_cb callback,
+ void *payload);
+
+/**
+ * Flush the gitattributes cache.
+ *
+ * Call this if you have reason to believe that the attributes files on
+ * disk no longer match the cached contents of memory. This will cause
+ * the attributes files to be reloaded the next time that an attribute
+ * access function is called.
+ */
+GIT_EXTERN(void) git_attr_cache_flush(
+ git_repository *repo);
+
+/**
+ * Add a macro definition.
+ *
+ * Macros will automatically be loaded from the top level `.gitattributes`
+ * file of the repository (plus the build-in "binary" macro). This
+ * function allows you to add others. For example, to add the default
+ * macro, you would call:
+ *
+ * git_attr_add_macro(repo, "binary", "-diff -crlf");
+ */
+GIT_EXTERN(int) git_attr_add_macro(
+ git_repository *repo,
+ const char *name,
+ const char *values);
+
+/** @} */
+GIT_END_DECL
+#endif
+
diff --git a/include/git2/blame.h b/include/git2/blame.h
new file mode 100644
index 000000000..7f0de1731
--- /dev/null
+++ b/include/git2/blame.h
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_blame_h__
+#define INCLUDE_git_blame_h__
+
+#include "common.h"
+#include "oid.h"
+
+/**
+ * @file git2/blame.h
+ * @brief Git blame routines
+ * @defgroup git_blame Git blame routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Flags for indicating option behavior for git_blame APIs.
+ */
+typedef enum {
+ /** Normal blame, the default */
+ GIT_BLAME_NORMAL = 0,
+ /** Track lines that have moved within a file (like `git blame -M`).
+ * NOT IMPLEMENTED. */
+ GIT_BLAME_TRACK_COPIES_SAME_FILE = (1<<0),
+ /** Track lines that have moved across files in the same commit (like `git blame -C`).
+ * NOT IMPLEMENTED. */
+ GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES = (1<<1),
+ /** Track lines that have been copied from another file that exists in the
+ * same commit (like `git blame -CC`). Implies SAME_FILE.
+ * NOT IMPLEMENTED. */
+ GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES = (1<<2),
+ /** Track lines that have been copied from another file that exists in *any*
+ * commit (like `git blame -CCC`). Implies SAME_COMMIT_COPIES.
+ * NOT IMPLEMENTED. */
+ GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES = (1<<3),
+ /** Restrict the search of commits to those reachable following only the
+ * first parents. */
+ GIT_BLAME_FIRST_PARENT = (1<<4),
+} git_blame_flag_t;
+
+/**
+ * Blame options structure
+ *
+ * Use zeros to indicate default settings. It's easiest to use the
+ * `GIT_BLAME_OPTIONS_INIT` macro:
+ * git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
+ *
+ * - `flags` is a combination of the `git_blame_flag_t` values above.
+ * - `min_match_characters` is the lower bound on the number of alphanumeric
+ * characters that must be detected as moving/copying within a file for it to
+ * associate those lines with the parent commit. The default value is 20.
+ * This value only takes effect if any of the `GIT_BLAME_TRACK_COPIES_*`
+ * flags are specified.
+ * - `newest_commit` is the id of the newest commit to consider. The default
+ * is HEAD.
+ * - `oldest_commit` is the id of the oldest commit to consider. The default
+ * is the first commit encountered with a NULL parent.
+ * - `min_line` is the first line in the file to blame. The default is 1 (line
+ * numbers start with 1).
+ * - `max_line` is the last line in the file to blame. The default is the last
+ * line of the file.
+ */
+
+typedef struct git_blame_options {
+ unsigned int version;
+
+ uint32_t flags;
+ uint16_t min_match_characters;
+ git_oid newest_commit;
+ git_oid oldest_commit;
+ uint32_t min_line;
+ uint32_t max_line;
+} git_blame_options;
+
+#define GIT_BLAME_OPTIONS_VERSION 1
+#define GIT_BLAME_OPTIONS_INIT {GIT_BLAME_OPTIONS_VERSION}
+
+/**
+ * Initializes a `git_blame_options` with default values. Equivalent to
+ * creating an instance with GIT_BLAME_OPTIONS_INIT.
+ *
+ * @param opts The `git_blame_options` struct to initialize
+ * @param version Version of struct; pass `GIT_BLAME_OPTIONS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_blame_init_options(
+ git_blame_options *opts,
+ unsigned int version);
+
+/**
+ * Structure that represents a blame hunk.
+ *
+ * - `lines_in_hunk` is the number of lines in this hunk
+ * - `final_commit_id` is the OID of the commit where this line was last
+ * changed.
+ * - `final_start_line_number` is the 1-based line number where this hunk
+ * begins, in the final version of the file
+ * - `orig_commit_id` is the OID of the commit where this hunk was found. This
+ * will usually be the same as `final_commit_id`, except when
+ * `GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES` has been specified.
+ * - `orig_path` is the path to the file where this hunk originated, as of the
+ * commit specified by `orig_commit_id`.
+ * - `orig_start_line_number` is the 1-based line number where this hunk begins
+ * in the file named by `orig_path` in the commit specified by
+ * `orig_commit_id`.
+ * - `boundary` is 1 iff the hunk has been tracked to a boundary commit (the
+ * root, or the commit specified in git_blame_options.oldest_commit)
+ */
+typedef struct git_blame_hunk {
+ uint16_t lines_in_hunk;
+
+ git_oid final_commit_id;
+ uint16_t final_start_line_number;
+ git_signature *final_signature;
+
+ git_oid orig_commit_id;
+ const char *orig_path;
+ uint16_t orig_start_line_number;
+ git_signature *orig_signature;
+
+ char boundary;
+} git_blame_hunk;
+
+
+/* Opaque structure to hold blame results */
+typedef struct git_blame git_blame;
+
+/**
+ * Gets the number of hunks that exist in the blame structure.
+ */
+GIT_EXTERN(uint32_t) git_blame_get_hunk_count(git_blame *blame);
+
+/**
+ * Gets the blame hunk at the given index.
+ *
+ * @param blame the blame structure to query
+ * @param index index of the hunk to retrieve
+ * @return the hunk at the given index, or NULL on error
+ */
+GIT_EXTERN(const git_blame_hunk*) git_blame_get_hunk_byindex(
+ git_blame *blame,
+ uint32_t index);
+
+/**
+ * Gets the hunk that relates to the given line number in the newest commit.
+ *
+ * @param blame the blame structure to query
+ * @param lineno the (1-based) line number to find a hunk for
+ * @return the hunk that contains the given line, or NULL on error
+ */
+GIT_EXTERN(const git_blame_hunk*) git_blame_get_hunk_byline(
+ git_blame *blame,
+ uint32_t lineno);
+
+/**
+ * Get the blame for a single file.
+ *
+ * @param out pointer that will receive the blame object
+ * @param repo repository whose history is to be walked
+ * @param path path to file to consider
+ * @param options options for the blame operation. If NULL, this is treated as
+ * though GIT_BLAME_OPTIONS_INIT were passed.
+ * @return 0 on success, or an error code. (use giterr_last for information
+ * about the error.)
+ */
+GIT_EXTERN(int) git_blame_file(
+ git_blame **out,
+ git_repository *repo,
+ const char *path,
+ git_blame_options *options);
+
+
+/**
+ * Get blame data for a file that has been modified in memory. The `reference`
+ * parameter is a pre-calculated blame for the in-odb history of the file. This
+ * means that once a file blame is completed (which can be expensive), updating
+ * the buffer blame is very fast.
+ *
+ * Lines that differ between the buffer and the committed version are marked as
+ * having a zero OID for their final_commit_id.
+ *
+ * @param out pointer that will receive the resulting blame data
+ * @param reference cached blame from the history of the file (usually the output
+ * from git_blame_file)
+ * @param buffer the (possibly) modified contents of the file
+ * @param buffer_len number of valid bytes in the buffer
+ * @return 0 on success, or an error code. (use giterr_last for information
+ * about the error)
+ */
+GIT_EXTERN(int) git_blame_buffer(
+ git_blame **out,
+ git_blame *reference,
+ const char *buffer,
+ size_t buffer_len);
+
+/**
+ * Free memory allocated by git_blame_file or git_blame_buffer.
+ *
+ * @param blame the blame structure to free
+ */
+GIT_EXTERN(void) git_blame_free(git_blame *blame);
+
+/** @} */
+GIT_END_DECL
+#endif
+
diff --git a/include/git2/blob.h b/include/git2/blob.h
index e366ce880..c24ff7e7f 100644
--- a/include/git2/blob.h
+++ b/include/git2/blob.h
@@ -1,26 +1,8 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_blob_h__
#define INCLUDE_git_blob_h__
@@ -29,6 +11,7 @@
#include "types.h"
#include "oid.h"
#include "object.h"
+#include "buffer.h"
/**
* @file git2/blob.h
@@ -45,12 +28,9 @@ GIT_BEGIN_DECL
* @param blob pointer to the looked up blob
* @param repo the repo to use when locating the blob.
* @param id identity of the blob to locate.
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_INLINE(int) git_blob_lookup(git_blob **blob, git_repository *repo, const git_oid *id)
-{
- return git_object_lookup((git_object **)blob, repo, id, GIT_OBJ_BLOB);
-}
+GIT_EXTERN(int) git_blob_lookup(git_blob **blob, git_repository *repo, const git_oid *id);
/**
* Lookup a blob object from a repository,
@@ -62,17 +42,14 @@ GIT_INLINE(int) git_blob_lookup(git_blob **blob, git_repository *repo, const git
* @param repo the repo to use when locating the blob.
* @param id identity of the blob to locate.
* @param len the length of the short identifier
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_INLINE(int) git_blob_lookup_prefix(git_blob **blob, git_repository *repo, const git_oid *id, unsigned int len)
-{
- return git_object_lookup_prefix((git_object **)blob, repo, id, len, GIT_OBJ_BLOB);
-}
+GIT_EXTERN(int) git_blob_lookup_prefix(git_blob **blob, git_repository *repo, const git_oid *id, size_t len);
/**
* Close an open blob
*
- * This is a wrapper around git_object_close()
+ * This is a wrapper around git_object_free()
*
* IMPORTANT:
* It *is* necessary to call this method when you stop
@@ -80,12 +57,23 @@ GIT_INLINE(int) git_blob_lookup_prefix(git_blob **blob, git_repository *repo, co
*
* @param blob the blob to close
*/
+GIT_EXTERN(void) git_blob_free(git_blob *blob);
-GIT_INLINE(void) git_blob_close(git_blob *blob)
-{
- git_object_close((git_object *) blob);
-}
+/**
+ * Get the id of a blob.
+ *
+ * @param blob a previously loaded blob.
+ * @return SHA1 hash for this blob.
+ */
+GIT_EXTERN(const git_oid *) git_blob_id(const git_blob *blob);
+/**
+ * Get the repository that contains the blob.
+ *
+ * @param blob A previously loaded blob.
+ * @return Repository that contains this blob.
+ */
+GIT_EXTERN(git_repository *) git_blob_owner(const git_blob *blob);
/**
* Get a read-only buffer with the raw content of a blob.
@@ -96,9 +84,9 @@ GIT_INLINE(void) git_blob_close(git_blob *blob)
* time.
*
* @param blob pointer to the blob
- * @return the pointer; NULL if the blob has no contents
+ * @return the pointer
*/
-GIT_EXTERN(const void *) git_blob_rawcontent(git_blob *blob);
+GIT_EXTERN(const void *) git_blob_rawcontent(const git_blob *blob);
/**
* Get the size in bytes of the contents of a blob
@@ -106,32 +94,129 @@ GIT_EXTERN(const void *) git_blob_rawcontent(git_blob *blob);
* @param blob pointer to the blob
* @return size on bytes
*/
-GIT_EXTERN(int) git_blob_rawsize(git_blob *blob);
+GIT_EXTERN(git_off_t) git_blob_rawsize(const git_blob *blob);
+
+/**
+ * Get a buffer with the filtered content of a blob.
+ *
+ * This applies filters as if the blob was being checked out to the
+ * working directory under the specified filename. This may apply
+ * CRLF filtering or other types of changes depending on the file
+ * attributes set for the blob and the content detected in it.
+ *
+ * The output is written into a `git_buf` which the caller must free
+ * when done (via `git_buf_free`).
+ *
+ * If no filters need to be applied, then the `out` buffer will just be
+ * populated with a pointer to the raw content of the blob. In that case,
+ * be careful to *not* free the blob until done with the buffer. To keep
+ * the data detached from the blob, call `git_buf_grow` on the buffer
+ * with a `want_size` of 0 and the buffer will be reallocated to be
+ * detached from the blob.
+ *
+ * @param out The git_buf to be filled in
+ * @param blob Pointer to the blob
+ * @param as_path Path used for file attribute lookups, etc.
+ * @param check_for_binary_data Should this test if blob content contains
+ * NUL bytes / looks like binary data before applying filters?
+ * @return 0 on success or an error code
+ */
+GIT_EXTERN(int) git_blob_filtered_content(
+ git_buf *out,
+ git_blob *blob,
+ const char *as_path,
+ int check_for_binary_data);
/**
* Read a file from the working folder of a repository
* and write it to the Object Database as a loose blob
*
- * @param oid return the id of the written blob
+ * @param id return the id of the written blob
* @param repo repository where the blob will be written.
* this repository cannot be bare
- * @param path file from which the blob will be created,
+ * @param relative_path file from which the blob will be created,
* relative to the repository's working dir
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *path);
+GIT_EXTERN(int) git_blob_create_fromworkdir(git_oid *id, git_repository *repo, const char *relative_path);
+/**
+ * Read a file from the filesystem and write its content
+ * to the Object Database as a loose blob
+ *
+ * @param id return the id of the written blob
+ * @param repo repository where the blob will be written.
+ * this repository can be bare or not
+ * @param path file from which the blob will be created
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_blob_create_fromdisk(git_oid *id, git_repository *repo, const char *path);
+
+
+typedef int (*git_blob_chunk_cb)(char *content, size_t max_length, void *payload);
+
+/**
+ * Write a loose blob to the Object Database from a
+ * provider of chunks of data.
+ *
+ * If the `hintpath` parameter is filled, it will be used to determine
+ * what git filters should be applied to the object before it is written
+ * to the object database.
+ *
+ * The implementation of the callback MUST respect the following rules:
+ *
+ * - `content` must be filled by the callback. The maximum number of
+ * bytes that the buffer can accept per call is defined by the
+ * `max_length` parameter. Allocation and freeing of the buffer will
+ * be taken care of by libgit2.
+ *
+ * - The `callback` must return the number of bytes that have been
+ * written to the `content` buffer.
+ *
+ * - When there is no more data to stream, `callback` should return
+ * 0. This will prevent it from being invoked anymore.
+ *
+ * - If an error occurs, the callback should return a negative value.
+ * This value will be returned to the caller.
+ *
+ * @param id Return the id of the written blob
+ * @param repo Repository where the blob will be written.
+ * This repository can be bare or not.
+ * @param hintpath If not NULL, will be used to select data filters
+ * to apply onto the content of the blob to be created.
+ * @return 0 or error code (from either libgit2 or callback function)
+ */
+GIT_EXTERN(int) git_blob_create_fromchunks(
+ git_oid *id,
+ git_repository *repo,
+ const char *hintpath,
+ git_blob_chunk_cb callback,
+ void *payload);
/**
* Write an in-memory buffer to the ODB as a blob
*
- * @param oid return the oid of the written blob
+ * @param id return the id of the written blob
* @param repo repository where to blob will be written
* @param buffer data to be written into the blob
* @param len length of the data
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_blob_create_frombuffer(
+ git_oid *id, git_repository *repo, const void *buffer, size_t len);
+
+/**
+ * Determine if the blob content is most certainly binary or not.
+ *
+ * The heuristic used to guess if a file is binary is taken from core git:
+ * Searching for NUL bytes and looking for a reasonable ratio of printable
+ * to non-printable characters among the first 8000 bytes.
+ *
+ * @param blob The blob which content should be analyzed
+ * @return 1 if the content of the blob is detected
+ * as binary; 0 otherwise.
*/
-GIT_EXTERN(int) git_blob_create_frombuffer(git_oid *oid, git_repository *repo, const void *buffer, size_t len);
+GIT_EXTERN(int) git_blob_is_binary(const git_blob *blob);
/** @} */
GIT_END_DECL
diff --git a/include/git2/branch.h b/include/git2/branch.h
index 456b7d1ac..f28410000 100644
--- a/include/git2/branch.h
+++ b/include/git2/branch.h
@@ -1,9 +1,265 @@
-#ifndef INCLUDE_branch_h__
-#define INCLUDE_branch_h__
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_branch_h__
+#define INCLUDE_git_branch_h__
-struct git_branch {
- char *remote; /* TODO: Make this a git_remote */
- char *merge;
-};
+#include "common.h"
+#include "oid.h"
+#include "types.h"
+/**
+ * @file git2/branch.h
+ * @brief Git branch parsing routines
+ * @defgroup git_branch Git branch management
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Create a new branch pointing at a target commit
+ *
+ * A new direct reference will be created pointing to
+ * this target commit. If `force` is true and a reference
+ * already exists with the given name, it'll be replaced.
+ *
+ * The returned reference must be freed by the user.
+ *
+ * The branch name will be checked for validity.
+ * See `git_tag_create()` for rules about valid names.
+ *
+ * @param out Pointer where to store the underlying reference.
+ *
+ * @param branch_name Name for the branch; this name is
+ * validated for consistency. It should also not conflict with
+ * an already existing branch name.
+ *
+ * @param target Commit to which this branch should point. This object
+ * must belong to the given `repo`.
+ *
+ * @param force Overwrite existing branch.
+ *
+ * @param signature The identity that will used to populate the reflog entry
+ *
+ * @param log_message The one line long message to be appended to the reflog.
+ * If NULL, the default is "Branch: created"; if you want something more
+ * useful, provide a message.
+ *
+ * @return 0, GIT_EINVALIDSPEC or an error code.
+ * A proper reference is written in the refs/heads namespace
+ * pointing to the provided target commit.
+ */
+GIT_EXTERN(int) git_branch_create(
+ git_reference **out,
+ git_repository *repo,
+ const char *branch_name,
+ const git_commit *target,
+ int force,
+ const git_signature *signature,
+ const char *log_message);
+
+/**
+ * Delete an existing branch reference.
+ *
+ * If the branch is successfully deleted, the passed reference
+ * object will be invalidated. The reference must be freed manually
+ * by the user.
+ *
+ * @param branch A valid reference representing a branch
+ * @return 0 on success, or an error code.
+ */
+GIT_EXTERN(int) git_branch_delete(git_reference *branch);
+
+/** Iterator type for branches */
+typedef struct git_branch_iterator git_branch_iterator;
+
+/**
+ * Create an iterator which loops over the requested branches.
+ *
+ * @param out the iterator
+ * @param repo Repository where to find the branches.
+ * @param list_flags Filtering flags for the branch
+ * listing. Valid values are GIT_BRANCH_LOCAL, GIT_BRANCH_REMOTE
+ * or GIT_BRANCH_ALL.
+ *
+ * @return 0 on success or an error code
+ */
+GIT_EXTERN(int) git_branch_iterator_new(
+ git_branch_iterator **out,
+ git_repository *repo,
+ git_branch_t list_flags);
+
+/**
+ * Retrieve the next branch from the iterator
+ *
+ * @param out the reference
+ * @param out_type the type of branch (local or remote-tracking)
+ * @param iter the branch iterator
+ * @return 0 on success, GIT_ITEROVER if there are no more branches or an error code.
+ */
+GIT_EXTERN(int) git_branch_next(git_reference **out, git_branch_t *out_type, git_branch_iterator *iter);
+
+/**
+ * Free a branch iterator
+ *
+ * @param iter the iterator to free
+ */
+GIT_EXTERN(void) git_branch_iterator_free(git_branch_iterator *iter);
+
+/**
+ * Move/rename an existing local branch reference.
+ *
+ * The new branch name will be checked for validity.
+ * See `git_tag_create()` for rules about valid names.
+ *
+ * @param branch Current underlying reference of the branch.
+ *
+ * @param new_branch_name Target name of the branch once the move
+ * is performed; this name is validated for consistency.
+ *
+ * @param force Overwrite existing branch.
+ *
+ * @param signature The identity that will used to populate the reflog entry
+ *
+ * @param log_message The one line long message to be appended to the reflog
+ *
+ * @return 0 on success, GIT_EINVALIDSPEC or an error code.
+ */
+GIT_EXTERN(int) git_branch_move(
+ git_reference **out,
+ git_reference *branch,
+ const char *new_branch_name,
+ int force,
+ const git_signature *signature,
+ const char *log_message);
+
+/**
+ * Lookup a branch by its name in a repository.
+ *
+ * The generated reference must be freed by the user.
+ *
+ * The branch name will be checked for validity.
+ * See `git_tag_create()` for rules about valid names.
+ *
+ * @param out pointer to the looked-up branch reference
+ *
+ * @param repo the repository to look up the branch
+ *
+ * @param branch_name Name of the branch to be looked-up;
+ * this name is validated for consistency.
+ *
+ * @param branch_type Type of the considered branch. This should
+ * be valued with either GIT_BRANCH_LOCAL or GIT_BRANCH_REMOTE.
+ *
+ * @return 0 on success; GIT_ENOTFOUND when no matching branch
+ * exists, GIT_EINVALIDSPEC, otherwise an error code.
+ */
+GIT_EXTERN(int) git_branch_lookup(
+ git_reference **out,
+ git_repository *repo,
+ const char *branch_name,
+ git_branch_t branch_type);
+
+/**
+ * Return the name of the given local or remote branch.
+ *
+ * The name of the branch matches the definition of the name
+ * for git_branch_lookup. That is, if the returned name is given
+ * to git_branch_lookup() then the reference is returned that
+ * was given to this function.
+ *
+ * @param out where the pointer of branch name is stored;
+ * this is valid as long as the ref is not freed.
+ * @param ref the reference ideally pointing to a branch
+ *
+ * @return 0 on success; otherwise an error code (e.g., if the
+ * ref is no local or remote branch).
+ */
+GIT_EXTERN(int) git_branch_name(
+ const char **out,
+ const git_reference *ref);
+
+/**
+ * Return the reference supporting the remote tracking branch,
+ * given a local branch reference.
+ *
+ * @param out Pointer where to store the retrieved
+ * reference.
+ *
+ * @param branch Current underlying reference of the branch.
+ *
+ * @return 0 on success; GIT_ENOTFOUND when no remote tracking
+ * reference exists, otherwise an error code.
+ */
+GIT_EXTERN(int) git_branch_upstream(
+ git_reference **out,
+ const git_reference *branch);
+
+/**
+ * Set the upstream configuration for a given local branch
+ *
+ * @param branch the branch to configure
+ *
+ * @param upstream_name remote-tracking or local branch to set as
+ * upstream. Pass NULL to unset.
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_branch_set_upstream(git_reference *branch, const char *upstream_name);
+
+/**
+ * Return the name of the reference supporting the remote tracking branch,
+ * given the name of a local branch reference.
+ *
+ * @param out Pointer to the user-allocated git_buf which will be
+ * filled with the name of the reference.
+ *
+ * @param repo the repository where the branches live
+ *
+ * @param refname reference name of the local branch.
+ *
+ * @return 0, GIT_ENOTFOUND when no remote tracking reference exists,
+ * otherwise an error code.
+ */
+GIT_EXTERN(int) git_branch_upstream_name(
+ git_buf *out,
+ git_repository *repo,
+ const char *refname);
+
+/**
+ * Determine if the current local branch is pointed at by HEAD.
+ *
+ * @param branch Current underlying reference of the branch.
+ *
+ * @return 1 if HEAD points at the branch, 0 if it isn't,
+ * error code otherwise.
+ */
+GIT_EXTERN(int) git_branch_is_head(
+ const git_reference *branch);
+
+/**
+ * Return the name of remote that the remote tracking branch belongs to.
+ *
+ * @param out Pointer to the user-allocated git_buf which will be filled iwth the name of the remote.
+ *
+ * @param repo The repository where the branch lives.
+ *
+ * @param canonical_branch_name name of the remote tracking branch.
+ *
+ * @return 0, GIT_ENOTFOUND
+ * when no remote matching remote was found,
+ * GIT_EAMBIGUOUS when the branch maps to several remotes,
+ * otherwise an error code.
+ */
+GIT_EXTERN(int) git_branch_remote_name(
+ git_buf *out,
+ git_repository *repo,
+ const char *canonical_branch_name);
+
+/** @} */
+GIT_END_DECL
#endif
diff --git a/include/git2/buffer.h b/include/git2/buffer.h
new file mode 100644
index 000000000..36a61e6c9
--- /dev/null
+++ b/include/git2/buffer.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_buf_h__
+#define INCLUDE_git_buf_h__
+
+#include "common.h"
+
+/**
+ * @file git2/buffer.h
+ * @brief Buffer export structure
+ *
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * A data buffer for exporting data from libgit2
+ *
+ * Sometimes libgit2 wants to return an allocated data buffer to the
+ * caller and have the caller take responsibility for freeing that memory.
+ * This can be awkward if the caller does not have easy access to the same
+ * allocation functions that libgit2 is using. In those cases, libgit2
+ * will fill in a `git_buf` and the caller can use `git_buf_free()` to
+ * release it when they are done.
+ *
+ * A `git_buf` may also be used for the caller to pass in a reference to
+ * a block of memory they hold. In this case, libgit2 will not resize or
+ * free the memory, but will read from it as needed.
+ *
+ * A `git_buf` is a public structure with three fields:
+ *
+ * - `ptr` points to the start of the allocated memory. If it is NULL,
+ * then the `git_buf` is considered empty and libgit2 will feel free
+ * to overwrite it with new data.
+ *
+ * - `size` holds the size (in bytes) of the data that is actually used.
+ *
+ * - `asize` holds the known total amount of allocated memory if the `ptr`
+ * was allocated by libgit2. It may be larger than `size`. If `ptr`
+ * was not allocated by libgit2 and should not be resized and/or freed,
+ * then `asize` will be set to zero.
+ *
+ * Some APIs may occasionally do something slightly unusual with a buffer,
+ * such as setting `ptr` to a value that was passed in by the user. In
+ * those cases, the behavior will be clearly documented by the API.
+ */
+typedef struct {
+ char *ptr;
+ size_t asize, size;
+} git_buf;
+
+/**
+ * Static initializer for git_buf from static buffer
+ */
+#define GIT_BUF_INIT_CONST(STR,LEN) { (char *)(STR), 0, (size_t)(LEN) }
+
+/**
+ * Free the memory referred to by the git_buf.
+ *
+ * Note that this does not free the `git_buf` itself, just the memory
+ * pointed to by `buffer->ptr`. This will not free the memory if it looks
+ * like it was not allocated internally, but it will clear the buffer back
+ * to the empty state.
+ *
+ * @param buffer The buffer to deallocate
+ */
+GIT_EXTERN(void) git_buf_free(git_buf *buffer);
+
+/**
+ * Resize the buffer allocation to make more space.
+ *
+ * This will attempt to grow the buffer to accomodate the target size.
+ *
+ * If the buffer refers to memory that was not allocated by libgit2 (i.e.
+ * the `asize` field is zero), then `ptr` will be replaced with a newly
+ * allocated block of data. Be careful so that memory allocated by the
+ * caller is not lost. As a special variant, if you pass `target_size` as
+ * 0 and the memory is not allocated by libgit2, this will allocate a new
+ * buffer of size `size` and copy the external data into it.
+ *
+ * Currently, this will never shrink a buffer, only expand it.
+ *
+ * If the allocation fails, this will return an error and the buffer will be
+ * marked as invalid for future operations, invaliding the contents.
+ *
+ * @param buffer The buffer to be resized; may or may not be allocated yet
+ * @param target_size The desired available size
+ * @return 0 on success, -1 on allocation failure
+ */
+GIT_EXTERN(int) git_buf_grow(git_buf *buffer, size_t target_size);
+
+/**
+ * Set buffer to a copy of some raw data.
+ *
+ * @param buffer The buffer to set
+ * @param data The data to copy into the buffer
+ * @param datalen The length of the data to copy into the buffer
+ * @return 0 on success, -1 on allocation failure
+ */
+GIT_EXTERN(int) git_buf_set(
+ git_buf *buffer, const void *data, size_t datalen);
+
+GIT_END_DECL
+
+/** @} */
+
+#endif
diff --git a/include/git2/checkout.h b/include/git2/checkout.h
new file mode 100644
index 000000000..ad44173e6
--- /dev/null
+++ b/include/git2/checkout.h
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_checkout_h__
+#define INCLUDE_git_checkout_h__
+
+#include "common.h"
+#include "types.h"
+#include "diff.h"
+
+/**
+ * @file git2/checkout.h
+ * @brief Git checkout routines
+ * @defgroup git_checkout Git checkout routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Checkout behavior flags
+ *
+ * In libgit2, checkout is used to update the working directory and index
+ * to match a target tree. Unlike git checkout, it does not move the HEAD
+ * commit for you - use `git_repository_set_head` or the like to do that.
+ *
+ * Checkout looks at (up to) four things: the "target" tree you want to
+ * check out, the "baseline" tree of what was checked out previously, the
+ * working directory for actual files, and the index for staged changes.
+ *
+ * You give checkout one of four strategies for update:
+ *
+ * - `GIT_CHECKOUT_NONE` is a dry-run strategy that checks for conflicts,
+ * etc., but doesn't make any actual changes.
+ *
+ * - `GIT_CHECKOUT_FORCE` is at the opposite extreme, taking any action to
+ * make the working directory match the target (including potentially
+ * discarding modified files).
+ *
+ * In between those are `GIT_CHECKOUT_SAFE` and `GIT_CHECKOUT_SAFE_CREATE`
+ * both of which only make modifications that will not lose changes.
+ *
+ * | target == baseline | target != baseline |
+ * ---------------------|-----------------------|----------------------|
+ * workdir == baseline | no action | create, update, or |
+ * | | delete file |
+ * ---------------------|-----------------------|----------------------|
+ * workdir exists and | no action | conflict (notify |
+ * is != baseline | notify dirty MODIFIED | and cancel checkout) |
+ * ---------------------|-----------------------|----------------------|
+ * workdir missing, | create if SAFE_CREATE | create file |
+ * baseline present | notify dirty DELETED | |
+ * ---------------------|-----------------------|----------------------|
+ *
+ * The only difference between SAFE and SAFE_CREATE is that SAFE_CREATE
+ * will cause a file to be checked out if it is missing from the working
+ * directory even if it is not modified between the target and baseline.
+ *
+ *
+ * To emulate `git checkout`, use `GIT_CHECKOUT_SAFE` with a checkout
+ * notification callback (see below) that displays information about dirty
+ * files. The default behavior will cancel checkout on conflicts.
+ *
+ * To emulate `git checkout-index`, use `GIT_CHECKOUT_SAFE_CREATE` with a
+ * notification callback that cancels the operation if a dirty-but-existing
+ * file is found in the working directory. This core git command isn't
+ * quite "force" but is sensitive about some types of changes.
+ *
+ * To emulate `git checkout -f`, use `GIT_CHECKOUT_FORCE`.
+ *
+ * To emulate `git clone` use `GIT_CHECKOUT_SAFE_CREATE` in the options.
+ *
+ *
+ * There are some additional flags to modified the behavior of checkout:
+ *
+ * - GIT_CHECKOUT_ALLOW_CONFLICTS makes SAFE mode apply safe file updates
+ * even if there are conflicts (instead of cancelling the checkout).
+ *
+ * - GIT_CHECKOUT_REMOVE_UNTRACKED means remove untracked files (i.e. not
+ * in target, baseline, or index, and not ignored) from the working dir.
+ *
+ * - GIT_CHECKOUT_REMOVE_IGNORED means remove ignored files (that are also
+ * untracked) from the working directory as well.
+ *
+ * - GIT_CHECKOUT_UPDATE_ONLY means to only update the content of files that
+ * already exist. Files will not be created nor deleted. This just skips
+ * applying adds, deletes, and typechanges.
+ *
+ * - GIT_CHECKOUT_DONT_UPDATE_INDEX prevents checkout from writing the
+ * updated files' information to the index.
+ *
+ * - Normally, checkout will reload the index and git attributes from disk
+ * before any operations. GIT_CHECKOUT_NO_REFRESH prevents this reload.
+ *
+ * - Unmerged index entries are conflicts. GIT_CHECKOUT_SKIP_UNMERGED skips
+ * files with unmerged index entries instead. GIT_CHECKOUT_USE_OURS and
+ * GIT_CHECKOUT_USE_THEIRS to proceed with the checkout using either the
+ * stage 2 ("ours") or stage 3 ("theirs") version of files in the index.
+ *
+ * - GIT_CHECKOUT_DONT_OVERWRITE_IGNORED prevents ignored files from being
+ * overwritten. Normally, files that are ignored in the working directory
+ * are not considered "precious" and may be overwritten if the checkout
+ * target contains that file.
+ */
+typedef enum {
+ GIT_CHECKOUT_NONE = 0, /**< default is a dry run, no actual updates */
+
+ /** Allow safe updates that cannot overwrite uncommitted data */
+ GIT_CHECKOUT_SAFE = (1u << 0),
+
+ /** Allow safe updates plus creation of missing files */
+ GIT_CHECKOUT_SAFE_CREATE = (1u << 1),
+
+ /** Allow all updates to force working directory to look like index */
+ GIT_CHECKOUT_FORCE = (1u << 2),
+
+
+ /** Allow checkout to make safe updates even if conflicts are found */
+ GIT_CHECKOUT_ALLOW_CONFLICTS = (1u << 4),
+
+ /** Remove untracked files not in index (that are not ignored) */
+ GIT_CHECKOUT_REMOVE_UNTRACKED = (1u << 5),
+
+ /** Remove ignored files not in index */
+ GIT_CHECKOUT_REMOVE_IGNORED = (1u << 6),
+
+ /** Only update existing files, don't create new ones */
+ GIT_CHECKOUT_UPDATE_ONLY = (1u << 7),
+
+ /** Normally checkout updates index entries as it goes; this stops that */
+ GIT_CHECKOUT_DONT_UPDATE_INDEX = (1u << 8),
+
+ /** Don't refresh index/config/etc before doing checkout */
+ GIT_CHECKOUT_NO_REFRESH = (1u << 9),
+
+ /** Allow checkout to skip unmerged files */
+ GIT_CHECKOUT_SKIP_UNMERGED = (1u << 10),
+ /** For unmerged files, checkout stage 2 from index */
+ GIT_CHECKOUT_USE_OURS = (1u << 11),
+ /** For unmerged files, checkout stage 3 from index */
+ GIT_CHECKOUT_USE_THEIRS = (1u << 12),
+
+ /** Treat pathspec as simple list of exact match file paths */
+ GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH = (1u << 13),
+
+ /** Ignore directories in use, they will be left empty */
+ GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES = (1u << 18),
+
+ /** Don't overwrite ignored files that exist in the checkout target */
+ GIT_CHECKOUT_DONT_OVERWRITE_IGNORED = (1u << 19),
+
+ /** Write normal merge files for conflicts */
+ GIT_CHECKOUT_CONFLICT_STYLE_MERGE = (1u << 20),
+
+ /** Include common ancestor data in diff3 format files for conflicts */
+ GIT_CHECKOUT_CONFLICT_STYLE_DIFF3 = (1u << 21),
+
+ /**
+ * THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED
+ */
+
+ /** Recursively checkout submodules with same options (NOT IMPLEMENTED) */
+ GIT_CHECKOUT_UPDATE_SUBMODULES = (1u << 16),
+ /** Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED) */
+ GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED = (1u << 17),
+
+} git_checkout_strategy_t;
+
+/**
+ * Checkout notification flags
+ *
+ * Checkout will invoke an options notification callback (`notify_cb`) for
+ * certain cases - you pick which ones via `notify_flags`:
+ *
+ * - GIT_CHECKOUT_NOTIFY_CONFLICT invokes checkout on conflicting paths.
+ *
+ * - GIT_CHECKOUT_NOTIFY_DIRTY notifies about "dirty" files, i.e. those that
+ * do not need an update but no longer match the baseline. Core git
+ * displays these files when checkout runs, but won't stop the checkout.
+ *
+ * - GIT_CHECKOUT_NOTIFY_UPDATED sends notification for any file changed.
+ *
+ * - GIT_CHECKOUT_NOTIFY_UNTRACKED notifies about untracked files.
+ *
+ * - GIT_CHECKOUT_NOTIFY_IGNORED notifies about ignored files.
+ *
+ * Returning a non-zero value from this callback will cancel the checkout.
+ * The non-zero return value will be propagated back and returned by the
+ * git_checkout_... call.
+ *
+ * Notification callbacks are made prior to modifying any files on disk,
+ * so canceling on any notification will still happen prior to any files
+ * being modified.
+ */
+typedef enum {
+ GIT_CHECKOUT_NOTIFY_NONE = 0,
+ GIT_CHECKOUT_NOTIFY_CONFLICT = (1u << 0),
+ GIT_CHECKOUT_NOTIFY_DIRTY = (1u << 1),
+ GIT_CHECKOUT_NOTIFY_UPDATED = (1u << 2),
+ GIT_CHECKOUT_NOTIFY_UNTRACKED = (1u << 3),
+ GIT_CHECKOUT_NOTIFY_IGNORED = (1u << 4),
+
+ GIT_CHECKOUT_NOTIFY_ALL = 0x0FFFFu
+} git_checkout_notify_t;
+
+/** Checkout notification callback function */
+typedef int (*git_checkout_notify_cb)(
+ git_checkout_notify_t why,
+ const char *path,
+ const git_diff_file *baseline,
+ const git_diff_file *target,
+ const git_diff_file *workdir,
+ void *payload);
+
+/** Checkout progress notification function */
+typedef void (*git_checkout_progress_cb)(
+ const char *path,
+ size_t completed_steps,
+ size_t total_steps,
+ void *payload);
+
+/**
+ * Checkout options structure
+ *
+ * Zero out for defaults. Initialize with `GIT_CHECKOUT_OPTIONS_INIT` macro to
+ * correctly set the `version` field. E.g.
+ *
+ * git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ */
+typedef struct git_checkout_options {
+ unsigned int version;
+
+ unsigned int checkout_strategy; /** default will be a dry run */
+
+ int disable_filters; /** don't apply filters like CRLF conversion */
+ unsigned int dir_mode; /** default is 0755 */
+ unsigned int file_mode; /** default is 0644 or 0755 as dictated by blob */
+ int file_open_flags; /** default is O_CREAT | O_TRUNC | O_WRONLY */
+
+ unsigned int notify_flags; /** see `git_checkout_notify_t` above */
+ git_checkout_notify_cb notify_cb;
+ void *notify_payload;
+
+ /* Optional callback to notify the consumer of checkout progress. */
+ git_checkout_progress_cb progress_cb;
+ void *progress_payload;
+
+ /** When not zeroed out, array of fnmatch patterns specifying which
+ * paths should be taken into account, otherwise all files. Use
+ * GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH to treat as simple list.
+ */
+ git_strarray paths;
+
+ git_tree *baseline; /** expected content of workdir, defaults to HEAD */
+
+ const char *target_directory; /** alternative checkout path to workdir */
+
+ const char *ancestor_label; /** the name of the common ancestor side of conflicts */
+ const char *our_label; /** the name of the "our" side of conflicts */
+ const char *their_label; /** the name of the "their" side of conflicts */
+} git_checkout_options;
+
+#define GIT_CHECKOUT_OPTIONS_VERSION 1
+#define GIT_CHECKOUT_OPTIONS_INIT {GIT_CHECKOUT_OPTIONS_VERSION}
+
+/**
+* Initializes a `git_checkout_options` with default values. Equivalent to
+* creating an instance with GIT_CHECKOUT_OPTIONS_INIT.
+*
+* @param opts the `git_checkout_options` struct to initialize.
+* @param version Version of struct; pass `GIT_CHECKOUT_OPTIONS_VERSION`
+* @return Zero on success; -1 on failure.
+*/
+GIT_EXTERN(int) git_checkout_init_options(
+ git_checkout_options *opts,
+ unsigned int version);
+
+/**
+ * Updates files in the index and the working tree to match the content of
+ * the commit pointed at by HEAD.
+ *
+ * @param repo repository to check out (must be non-bare)
+ * @param opts specifies checkout options (may be NULL)
+ * @return 0 on success, GIT_EUNBORNBRANCH if HEAD points to a non
+ * existing branch, non-zero value returned by `notify_cb`, or
+ * other error code < 0 (use giterr_last for error details)
+ */
+GIT_EXTERN(int) git_checkout_head(
+ git_repository *repo,
+ const git_checkout_options *opts);
+
+/**
+ * Updates files in the working tree to match the content of the index.
+ *
+ * @param repo repository into which to check out (must be non-bare)
+ * @param index index to be checked out (or NULL to use repository index)
+ * @param opts specifies checkout options (may be NULL)
+ * @return 0 on success, non-zero return value from `notify_cb`, or error
+ * code < 0 (use giterr_last for error details)
+ */
+GIT_EXTERN(int) git_checkout_index(
+ git_repository *repo,
+ git_index *index,
+ const git_checkout_options *opts);
+
+/**
+ * Updates files in the index and working tree to match the content of the
+ * tree pointed at by the treeish.
+ *
+ * @param repo repository to check out (must be non-bare)
+ * @param treeish a commit, tag or tree which content will be used to update
+ * the working directory (or NULL to use HEAD)
+ * @param opts specifies checkout options (may be NULL)
+ * @return 0 on success, non-zero return value from `notify_cb`, or error
+ * code < 0 (use giterr_last for error details)
+ */
+GIT_EXTERN(int) git_checkout_tree(
+ git_repository *repo,
+ const git_object *treeish,
+ const git_checkout_options *opts);
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/cherrypick.h b/include/git2/cherrypick.h
new file mode 100644
index 000000000..9eccb0af9
--- /dev/null
+++ b/include/git2/cherrypick.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_cherrypick_h__
+#define INCLUDE_git_cherrypick_h__
+
+#include "common.h"
+#include "types.h"
+#include "merge.h"
+
+/**
+ * @file git2/cherrypick.h
+ * @brief Git cherry-pick routines
+ * @defgroup git_cherrypick Git cherry-pick routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+typedef struct {
+ unsigned int version;
+
+ /** For merge commits, the "mainline" is treated as the parent. */
+ unsigned int mainline;
+
+ git_merge_options merge_opts;
+ git_checkout_options checkout_opts;
+} git_cherry_pick_options;
+
+#define GIT_CHERRY_PICK_OPTIONS_VERSION 1
+#define GIT_CHERRY_PICK_OPTIONS_INIT {GIT_CHERRY_PICK_OPTIONS_VERSION, 0, GIT_MERGE_OPTIONS_INIT, GIT_CHECKOUT_OPTIONS_INIT}
+
+/**
+ * Initializes a `git_cherry_pick_options` with default values. Equivalent to
+ * creating an instance with GIT_CHERRY_PICK_OPTIONS_INIT.
+ *
+ * @param opts the `git_cherry_pick_options` struct to initialize
+ * @param version Version of struct; pass `GIT_CHERRY_PICK_OPTIONS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_cherry_pick_init_options(
+ git_cherry_pick_options *opts,
+ unsigned int version);
+
+/**
+ * Cherry-picks the given commit against the given "our" commit, producing an
+ * index that reflects the result of the cherry-pick.
+ *
+ * The returned index must be freed explicitly with `git_index_free`.
+ *
+ * @param out pointer to store the index result in
+ * @param repo the repository that contains the given commits
+ * @param cherry_pick_commit the commit to cherry-pick
+ * @param our_commit the commit to revert against (eg, HEAD)
+ * @param mainline the parent of the revert commit, if it is a merge
+ * @param merge_options the merge options (or null for defaults)
+ * @return zero on success, -1 on failure.
+ */
+GIT_EXTERN(int) git_cherry_pick_commit(
+ git_index **out,
+ git_repository *repo,
+ git_commit *cherry_pick_commit,
+ git_commit *our_commit,
+ unsigned int mainline,
+ const git_merge_options *merge_options);
+
+/**
+ * Cherry-pick the given commit, producing changes in the index and working directory.
+ *
+ * @param repo the repository to cherry-pick
+ * @param commit the commit to cherry-pick
+ * @param cherry_pick_options the cherry-pick options (or null for defaults)
+ * @return zero on success, -1 on failure.
+ */
+GIT_EXTERN(int) git_cherry_pick(
+ git_repository *repo,
+ git_commit *commit,
+ const git_cherry_pick_options *cherry_pick_options);
+
+/** @} */
+GIT_END_DECL
+
+#endif
+
diff --git a/include/git2/clone.h b/include/git2/clone.h
new file mode 100644
index 000000000..b2c944a78
--- /dev/null
+++ b/include/git2/clone.h
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_clone_h__
+#define INCLUDE_git_clone_h__
+
+#include "common.h"
+#include "types.h"
+#include "indexer.h"
+#include "checkout.h"
+#include "remote.h"
+
+
+/**
+ * @file git2/clone.h
+ * @brief Git cloning routines
+ * @defgroup git_clone Git cloning routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+typedef enum {
+ GIT_CLONE_LOCAL_AUTO,
+ GIT_CLONE_LOCAL,
+ GIT_CLONE_NO_LOCAL,
+ GIT_CLONE_LOCAL_NO_LINKS,
+} git_clone_local_t;
+
+/**
+ * Clone options structure
+ *
+ * Use the GIT_CLONE_OPTIONS_INIT to get the default settings, like this:
+ *
+ * git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
+ *
+ * - `checkout_opts` are option passed to the checkout step. To disable
+ * checkout, set the `checkout_strategy` to GIT_CHECKOUT_NONE.
+ * Generally you will want the use GIT_CHECKOUT_SAFE_CREATE to create
+ * all files in the working directory for the newly cloned repository.
+ * - `bare` should be set to zero (false) to create a standard repo,
+ * or non-zero for a bare repo
+ * - `ignore_cert_errors` should be set to 1 if errors validating the
+ * remote host's certificate should be ignored.
+ *
+ * ** "origin" remote options: **
+ *
+ * - `remote_name` is the name to be given to the "origin" remote. The
+ * default is "origin".
+ * - `checkout_branch` gives the name of the branch to checkout. NULL
+ * means use the remote's HEAD.
+ * - `signature` is the identity used when updating the reflog. NULL means to
+ * use the default signature using the config.
+ */
+
+typedef struct git_clone_options {
+ unsigned int version;
+
+ git_checkout_options checkout_opts;
+ git_remote_callbacks remote_callbacks;
+
+ int bare;
+ int ignore_cert_errors;
+ git_clone_local_t local;
+ const char *remote_name;
+ const char* checkout_branch;
+ git_signature *signature;
+} git_clone_options;
+
+#define GIT_CLONE_OPTIONS_VERSION 1
+#define GIT_CLONE_OPTIONS_INIT {GIT_CLONE_OPTIONS_VERSION, {GIT_CHECKOUT_OPTIONS_VERSION, GIT_CHECKOUT_SAFE_CREATE}, GIT_REMOTE_CALLBACKS_INIT}
+
+/**
+ * Initializes a `git_clone_options` with default values. Equivalent to
+ * creating an instance with GIT_CLONE_OPTIONS_INIT.
+ *
+ * @param opts The `git_clone_options` struct to initialize
+ * @param version Version of struct; pass `GIT_CLONE_OPTIONS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_clone_init_options(
+ git_clone_options *opts,
+ unsigned int version);
+
+/**
+ * Clone a remote repository.
+ *
+ * This version handles the simple case. If you'd like to create the
+ * repository or remote with non-default settings, you can create and
+ * configure them and then use `git_clone_into()`.
+ *
+ * @param out pointer that will receive the resulting repository object
+ * @param url the remote repository to clone
+ * @param local_path local directory to clone to
+ * @param options configuration options for the clone. If NULL, the
+ * function works as though GIT_OPTIONS_INIT were passed.
+ * @return 0 on success, any non-zero return value from a callback
+ * function, or a negative value to indicate an error (use
+ * `giterr_last` for a detailed error message)
+ */
+GIT_EXTERN(int) git_clone(
+ git_repository **out,
+ const char *url,
+ const char *local_path,
+ const git_clone_options *options);
+
+/**
+ * Clone into a repository
+ *
+ * After creating the repository and remote and configuring them for
+ * paths and callbacks respectively, you can call this function to
+ * perform the clone operation and optionally checkout files.
+ *
+ * @param repo the repository to use
+ * @param remote the remote repository to clone from
+ * @param co_opts options to use during checkout
+ * @param branch the branch to checkout after the clone, pass NULL for the
+ * remote's default branch
+ * @param signature The identity used when updating the reflog.
+ * @return 0 on success, any non-zero return value from a callback
+ * function, or a negative value to indicate an error (use
+ * `giterr_last` for a detailed error message)
+ */
+GIT_EXTERN(int) git_clone_into(
+ git_repository *repo,
+ git_remote *remote,
+ const git_checkout_options *co_opts,
+ const char *branch,
+ const git_signature *signature);
+
+/**
+ * Perform a local clone into a repository
+ *
+ * A "local clone" bypasses any git-aware protocols and simply copies
+ * over the object database from the source repository. It is often
+ * faster than a git-aware clone, but no verification of the data is
+ * performed, and can copy over too much data.
+ *
+ * @param repo the repository to use
+ * @param remote the remote repository to clone from
+ * @param co_opts options to use during checkout
+ * @param branch the branch to checkout after the clone, pass NULL for the
+ * remote's default branch
+ * @param link wether to use hardlinks instead of copying
+ * objects. This is only possible if both repositories are on the same
+ * filesystem.
+ * @param signature the identity used when updating the reflog
+ * @return 0 on success, any non-zero return value from a callback
+ * function, or a negative value to indicate an error (use
+ * `giterr_last` for a detailed error message)
+ */
+GIT_EXTERN(int) git_clone_local_into(
+ git_repository *repo,
+ git_remote *remote,
+ const git_checkout_options *co_opts,
+ const char *branch,
+ int link,
+ const git_signature *signature);
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/commit.h b/include/git2/commit.h
index 84adef596..fb53a701b 100644
--- a/include/git2/commit.h
+++ b/include/git2/commit.h
@@ -1,26 +1,8 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_commit_h__
#define INCLUDE_git_commit_h__
@@ -42,39 +24,41 @@ GIT_BEGIN_DECL
/**
* Lookup a commit object from a repository.
*
+ * The returned object should be released with `git_commit_free` when no
+ * longer needed.
+ *
* @param commit pointer to the looked up commit
* @param repo the repo to use when locating the commit.
- * @param id identity of the commit to locate. If the object is
- * an annotated tag it will be peeled back to the commit.
- * @return 0 on success; error code otherwise
+ * @param id identity of the commit to locate. If the object is
+ * an annotated tag it will be peeled back to the commit.
+ * @return 0 or an error code
*/
-GIT_INLINE(int) git_commit_lookup(git_commit **commit, git_repository *repo, const git_oid *id)
-{
- return git_object_lookup((git_object **)commit, repo, id, GIT_OBJ_COMMIT);
-}
+GIT_EXTERN(int) git_commit_lookup(
+ git_commit **commit, git_repository *repo, const git_oid *id);
/**
- * Lookup a commit object from a repository,
- * given a prefix of its identifier (short id).
+ * Lookup a commit object from a repository, given a prefix of its
+ * identifier (short id).
+ *
+ * The returned object should be released with `git_commit_free` when no
+ * longer needed.
*
* @see git_object_lookup_prefix
*
* @param commit pointer to the looked up commit
* @param repo the repo to use when locating the commit.
- * @param id identity of the commit to locate. If the object is
- * an annotated tag it will be peeled back to the commit.
+ * @param id identity of the commit to locate. If the object is
+ * an annotated tag it will be peeled back to the commit.
* @param len the length of the short identifier
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_INLINE(int) git_commit_lookup_prefix(git_commit **commit, git_repository *repo, const git_oid *id, unsigned len)
-{
- return git_object_lookup_prefix((git_object **)commit, repo, id, len, GIT_OBJ_COMMIT);
-}
+GIT_EXTERN(int) git_commit_lookup_prefix(
+ git_commit **commit, git_repository *repo, const git_oid *id, size_t len);
/**
* Close an open commit
*
- * This is a wrapper around git_object_close()
+ * This is a wrapper around git_object_free()
*
* IMPORTANT:
* It *is* necessary to call this method when you stop
@@ -83,10 +67,7 @@ GIT_INLINE(int) git_commit_lookup_prefix(git_commit **commit, git_repository *re
* @param commit the commit to close
*/
-GIT_INLINE(void) git_commit_close(git_commit *commit)
-{
- git_object_close((git_object *) commit);
-}
+GIT_EXTERN(void) git_commit_free(git_commit *commit);
/**
* Get the id of a commit.
@@ -94,23 +75,57 @@ GIT_INLINE(void) git_commit_close(git_commit *commit)
* @param commit a previously loaded commit.
* @return object identity for the commit.
*/
-GIT_EXTERN(const git_oid *) git_commit_id(git_commit *commit);
+GIT_EXTERN(const git_oid *) git_commit_id(const git_commit *commit);
/**
- * Get the short (one line) message of a commit.
+ * Get the repository that contains the commit.
+ *
+ * @param commit A previously loaded commit.
+ * @return Repository that contains this commit.
+ */
+GIT_EXTERN(git_repository *) git_commit_owner(const git_commit *commit);
+
+/**
+ * Get the encoding for the message of a commit,
+ * as a string representing a standard encoding name.
+ *
+ * The encoding may be NULL if the `encoding` header
+ * in the commit is missing; in that case UTF-8 is assumed.
*
* @param commit a previously loaded commit.
- * @return the short message of a commit
+ * @return NULL, or the encoding
*/
-GIT_EXTERN(const char *) git_commit_message_short(git_commit *commit);
+GIT_EXTERN(const char *) git_commit_message_encoding(const git_commit *commit);
/**
* Get the full message of a commit.
*
+ * The returned message will be slightly prettified by removing any
+ * potential leading newlines.
+ *
* @param commit a previously loaded commit.
* @return the message of a commit
*/
-GIT_EXTERN(const char *) git_commit_message(git_commit *commit);
+GIT_EXTERN(const char *) git_commit_message(const git_commit *commit);
+
+/**
+ * Get the full raw message of a commit.
+ *
+ * @param commit a previously loaded commit.
+ * @return the raw message of a commit
+ */
+GIT_EXTERN(const char *) git_commit_message_raw(const git_commit *commit);
+
+/**
+ * Get the short "summary" of the git commit message.
+ *
+ * The returned message is the summary of the commit, comprising the
+ * first paragraph of the message with whitespace trimmed and squashed.
+ *
+ * @param commit a previously loaded commit.
+ * @return the summary of a commit or NULL on error
+ */
+GIT_EXTERN(const char *) git_commit_summary(git_commit *commit);
/**
* Get the commit time (i.e. committer time) of a commit.
@@ -118,7 +133,7 @@ GIT_EXTERN(const char *) git_commit_message(git_commit *commit);
* @param commit a previously loaded commit.
* @return the time of a commit
*/
-GIT_EXTERN(git_time_t) git_commit_time(git_commit *commit);
+GIT_EXTERN(git_time_t) git_commit_time(const git_commit *commit);
/**
* Get the commit timezone offset (i.e. committer's preferred timezone) of a commit.
@@ -126,7 +141,7 @@ GIT_EXTERN(git_time_t) git_commit_time(git_commit *commit);
* @param commit a previously loaded commit.
* @return positive or negative timezone offset, in minutes from UTC
*/
-GIT_EXTERN(int) git_commit_time_offset(git_commit *commit);
+GIT_EXTERN(int) git_commit_time_offset(const git_commit *commit);
/**
* Get the committer of a commit.
@@ -134,7 +149,7 @@ GIT_EXTERN(int) git_commit_time_offset(git_commit *commit);
* @param commit a previously loaded commit.
* @return the committer of a commit
*/
-GIT_EXTERN(const git_signature *) git_commit_committer(git_commit *commit);
+GIT_EXTERN(const git_signature *) git_commit_committer(const git_commit *commit);
/**
* Get the author of a commit.
@@ -142,16 +157,24 @@ GIT_EXTERN(const git_signature *) git_commit_committer(git_commit *commit);
* @param commit a previously loaded commit.
* @return the author of a commit
*/
-GIT_EXTERN(const git_signature *) git_commit_author(git_commit *commit);
+GIT_EXTERN(const git_signature *) git_commit_author(const git_commit *commit);
+
+/**
+ * Get the full raw text of the commit header.
+ *
+ * @param commit a previously loaded commit
+ * @return the header text of the commit
+ */
+GIT_EXTERN(const char *) git_commit_raw_header(const git_commit *commit);
/**
* Get the tree pointed to by a commit.
*
* @param tree_out pointer where to store the tree object
* @param commit a previously loaded commit.
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_commit_tree(git_tree **tree_out, git_commit *commit);
+GIT_EXTERN(int) git_commit_tree(git_tree **tree_out, const git_commit *commit);
/**
* Get the id of the tree pointed to by a commit. This differs from
@@ -161,7 +184,7 @@ GIT_EXTERN(int) git_commit_tree(git_tree **tree_out, git_commit *commit);
* @param commit a previously loaded commit.
* @return the id of tree pointed to by commit.
*/
-GIT_EXTERN(const git_oid *) git_commit_tree_oid(git_commit *commit);
+GIT_EXTERN(const git_oid *) git_commit_tree_id(const git_commit *commit);
/**
* Get the number of parents of this commit
@@ -169,17 +192,20 @@ GIT_EXTERN(const git_oid *) git_commit_tree_oid(git_commit *commit);
* @param commit a previously loaded commit.
* @return integer of count of parents
*/
-GIT_EXTERN(unsigned int) git_commit_parentcount(git_commit *commit);
+GIT_EXTERN(unsigned int) git_commit_parentcount(const git_commit *commit);
/**
* Get the specified parent of the commit.
*
- * @param parent Pointer where to store the parent commit
+ * @param out Pointer where to store the parent commit
* @param commit a previously loaded commit.
* @param n the position of the parent (from 0 to `parentcount`)
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n);
+GIT_EXTERN(int) git_commit_parent(
+ git_commit **out,
+ const git_commit *commit,
+ unsigned int n);
/**
* Get the oid of a specified parent for a commit. This is different from
@@ -190,14 +216,36 @@ GIT_EXTERN(int) git_commit_parent(git_commit **parent, git_commit *commit, unsig
* @param n the position of the parent (from 0 to `parentcount`)
* @return the id of the parent, NULL on error.
*/
-GIT_EXTERN(const git_oid *) git_commit_parent_oid(git_commit *commit, unsigned int n);
+GIT_EXTERN(const git_oid *) git_commit_parent_id(
+ const git_commit *commit,
+ unsigned int n);
/**
- * Create a new commit in the repository using `git_object`
- * instances as parameters.
+ * Get the commit object that is the <n>th generation ancestor
+ * of the named commit object, following only the first parents.
+ * The returned commit has to be freed by the caller.
*
- * @param oid Pointer where to store the OID of the
- * newly created commit
+ * Passing `0` as the generation number returns another instance of the
+ * base commit itself.
+ *
+ * @param ancestor Pointer where to store the ancestor commit
+ * @param commit a previously loaded commit.
+ * @param n the requested generation
+ * @return 0 on success; GIT_ENOTFOUND if no matching ancestor exists
+ * or an error code
+ */
+GIT_EXTERN(int) git_commit_nth_gen_ancestor(
+ git_commit **ancestor,
+ const git_commit *commit,
+ unsigned int n);
+
+/**
+ * Create new commit in the repository from a list of `git_object` pointers
+ *
+ * The message will **not** be cleaned up automatically. You can do that
+ * with the `git_message_prettify()` function.
+ *
+ * @param id Pointer in which to store the OID of the newly created commit
*
* @param repo Repository where to store the commit
*
@@ -205,65 +253,104 @@ GIT_EXTERN(const git_oid *) git_commit_parent_oid(git_commit *commit, unsigned i
* will be updated to point to this commit. If the reference
* is not direct, it will be resolved to a direct reference.
* Use "HEAD" to update the HEAD of the current branch and
- * make it point to this commit
+ * make it point to this commit. If the reference doesn't
+ * exist yet, it will be created. If it does exist, the first
+ * parent must be the tip of this branch.
+ *
+ * @param author Signature with author and author time of commit
*
- * @param author Signature representing the author and the authory
- * time of this commit
+ * @param committer Signature with committer and * commit time of commit
*
- * @param committer Signature representing the committer and the
- * commit time of this commit
+ * @param message_encoding The encoding for the message in the
+ * commit, represented with a standard encoding name.
+ * E.g. "UTF-8". If NULL, no encoding header is written and
+ * UTF-8 is assumed.
*
* @param message Full message for this commit
*
* @param tree An instance of a `git_tree` object that will
- * be used as the tree for the commit. This tree object must
- * also be owned by the given `repo`.
+ * be used as the tree for the commit. This tree object must
+ * also be owned by the given `repo`.
*
* @param parent_count Number of parents for this commit
*
- * @param parents[] Array of `parent_count` pointers to `git_commit`
- * objects that will be used as the parents for this commit. This
- * array may be NULL if `parent_count` is 0 (root commit). All the
- * given commits must be owned by the `repo`.
+ * @param parents Array of `parent_count` pointers to `git_commit`
+ * objects that will be used as the parents for this commit. This
+ * array may be NULL if `parent_count` is 0 (root commit). All the
+ * given commits must be owned by the `repo`.
*
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
* The created commit will be written to the Object Database and
* the given reference will be updated to point to it
*/
GIT_EXTERN(int) git_commit_create(
- git_oid *oid,
- git_repository *repo,
- const char *update_ref,
- const git_signature *author,
- const git_signature *committer,
- const char *message,
- const git_tree *tree,
- int parent_count,
- const git_commit *parents[]);
+ git_oid *id,
+ git_repository *repo,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_tree *tree,
+ size_t parent_count,
+ const git_commit *parents[]);
/**
- * Create a new commit in the repository using a variable
- * argument list.
+ * Create new commit in the repository using a variable argument list.
*
- * The parents for the commit are specified as a variable
- * list of pointers to `const git_commit *`. Note that this
- * is a convenience method which may not be safe to export
- * for certain languages or compilers
+ * The message will **not** be cleaned up automatically. You can do that
+ * with the `git_message_prettify()` function.
*
- * All other parameters remain the same
+ * The parents for the commit are specified as a variable list of pointers
+ * to `const git_commit *`. Note that this is a convenience method which may
+ * not be safe to export for certain languages or compilers
+ *
+ * All other parameters remain the same as `git_commit_create()`.
*
* @see git_commit_create
*/
GIT_EXTERN(int) git_commit_create_v(
- git_oid *oid,
- git_repository *repo,
- const char *update_ref,
- const git_signature *author,
- const git_signature *committer,
- const char *message,
- const git_tree *tree,
- int parent_count,
- ...);
+ git_oid *id,
+ git_repository *repo,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_tree *tree,
+ size_t parent_count,
+ ...);
+
+/**
+ * Amend an existing commit by replacing only non-NULL values.
+ *
+ * This creates a new commit that is exactly the same as the old commit,
+ * except that any non-NULL values will be updated. The new commit has
+ * the same parents as the old commit.
+ *
+ * The `update_ref` value works as in the regular `git_commit_create()`,
+ * updating the ref to point to the newly rewritten commit. If you want
+ * to amend a commit that is not currently the tip of the branch and then
+ * rewrite the following commits to reach a ref, pass this as NULL and
+ * update the rest of the commit chain and ref separately.
+ *
+ * Unlike `git_commit_create()`, the `author`, `committer`, `message`,
+ * `message_encoding`, and `tree` parameters can be NULL in which case this
+ * will use the values from the original `commit_to_amend`.
+ *
+ * All parameters have the same meanings as in `git_commit_create()`.
+ *
+ * @see git_commit_create
+ */
+GIT_EXTERN(int) git_commit_amend(
+ git_oid *id,
+ const git_commit *commit_to_amend,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_tree *tree);
/** @} */
GIT_END_DECL
diff --git a/include/git2/common.h b/include/git2/common.h
index 58cb1f200..32237efed 100644
--- a/include/git2/common.h
+++ b/include/git2/common.h
@@ -1,74 +1,42 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_common_h__
#define INCLUDE_git_common_h__
-#include "thread-utils.h"
#include <time.h>
#include <stdlib.h>
+#ifdef _MSC_VER
+# include "inttypes.h"
+#else
+# include <inttypes.h>
+#endif
+
#ifdef __cplusplus
-# define GIT_BEGIN_DECL extern "C" {
-# define GIT_END_DECL }
+# define GIT_BEGIN_DECL extern "C" {
+# define GIT_END_DECL }
#else
- /** Start declarations in C mode */
-# define GIT_BEGIN_DECL /* empty */
- /** End declarations in C mode */
-# define GIT_END_DECL /* empty */
+ /** Start declarations in C mode */
+# define GIT_BEGIN_DECL /* empty */
+ /** End declarations in C mode */
+# define GIT_END_DECL /* empty */
#endif
/** Declare a public function exported for application use. */
-#ifdef __GNUC__
+#if __GNUC__ >= 4
# define GIT_EXTERN(type) extern \
- __attribute__((visibility("default"))) \
- type
+ __attribute__((visibility("default"))) \
+ type
#elif defined(_MSC_VER)
# define GIT_EXTERN(type) __declspec(dllexport) type
#else
# define GIT_EXTERN(type) extern type
#endif
-/** Declare a public TLS symbol exported for application use. */
-#ifdef __GNUC__
-# define GIT_EXTERN_TLS(type) extern \
- __attribute__((visibility("default"))) \
- GIT_TLS \
- type
-#elif defined(_MSC_VER)
-# define GIT_EXTERN_TLS(type) __declspec(dllexport) GIT_TLS type
-#else
-# define GIT_EXTERN_TLS(type) extern GIT_TLS type
-#endif
-
-/** Declare a function as always inlined. */
-#if defined(_MSC_VER)
-# define GIT_INLINE(type) static __inline type
-#else
-# define GIT_INLINE(type) static inline type
-#endif
-
/** Declare a function's takes printf style arguments. */
#ifdef __GNUC__
# define GIT_FORMAT_PRINTF(a,b) __attribute__((format (printf, a, b)))
@@ -76,10 +44,14 @@
# define GIT_FORMAT_PRINTF(a,b) /* empty */
#endif
-#if defined(_WIN32) && !defined(__CYGWIN__)
+#if (defined(_WIN32)) && !defined(__CYGWIN__)
#define GIT_WIN32 1
#endif
+#ifdef __amigaos4__
+#include <netinet/in.h>
+#endif
+
/**
* @file git2/common.h
* @brief Git common platform definitions
@@ -102,16 +74,14 @@ GIT_BEGIN_DECL
#endif
/**
- * The maximum length of a git valid git path.
+ * The maximum length of a valid git path.
*/
#define GIT_PATH_MAX 4096
-typedef struct {
- char **strings;
- size_t count;
-} git_strarray;
-
-GIT_EXTERN(void) git_strarray_free(git_strarray *array);
+/**
+ * The string representation of the null object ID.
+ */
+#define GIT_OID_HEX_ZERO "0000000000000000000000000000000000000000"
/**
* Return the version of the libgit2 library
@@ -123,6 +93,141 @@ GIT_EXTERN(void) git_strarray_free(git_strarray *array);
*/
GIT_EXTERN(void) git_libgit2_version(int *major, int *minor, int *rev);
+/**
+ * Combinations of these values describe the features with which libgit2
+ * was compiled
+ */
+typedef enum {
+ GIT_FEATURE_THREADS = (1 << 0),
+ GIT_FEATURE_HTTPS = (1 << 1),
+ GIT_FEATURE_SSH = (1 << 2),
+} git_feature_t;
+
+/**
+ * Query compile time options for libgit2.
+ *
+ * @return A combination of GIT_FEATURE_* values.
+ *
+ * - GIT_FEATURE_THREADS
+ * Libgit2 was compiled with thread support. Note that thread support is
+ * still to be seen as a 'work in progress' - basic object lookups are
+ * believed to be threadsafe, but other operations may not be.
+ *
+ * - GIT_FEATURE_HTTPS
+ * Libgit2 supports the https:// protocol. This requires the openssl
+ * library to be found when compiling libgit2.
+ *
+ * - GIT_FEATURE_SSH
+ * Libgit2 supports the SSH protocol for network operations. This requires
+ * the libssh2 library to be found when compiling libgit2
+ */
+GIT_EXTERN(int) git_libgit2_features(void);
+
+
+typedef enum {
+ GIT_OPT_GET_MWINDOW_SIZE,
+ GIT_OPT_SET_MWINDOW_SIZE,
+ GIT_OPT_GET_MWINDOW_MAPPED_LIMIT,
+ GIT_OPT_SET_MWINDOW_MAPPED_LIMIT,
+ GIT_OPT_GET_SEARCH_PATH,
+ GIT_OPT_SET_SEARCH_PATH,
+ GIT_OPT_SET_CACHE_OBJECT_LIMIT,
+ GIT_OPT_SET_CACHE_MAX_SIZE,
+ GIT_OPT_ENABLE_CACHING,
+ GIT_OPT_GET_CACHED_MEMORY,
+ GIT_OPT_GET_TEMPLATE_PATH,
+ GIT_OPT_SET_TEMPLATE_PATH
+} git_libgit2_opt_t;
+
+/**
+ * Set or query a library global option
+ *
+ * Available options:
+ *
+ * * opts(GIT_OPT_GET_MWINDOW_SIZE, size_t *):
+ *
+ * > Get the maximum mmap window size
+ *
+ * * opts(GIT_OPT_SET_MWINDOW_SIZE, size_t):
+ *
+ * > Set the maximum mmap window size
+ *
+ * * opts(GIT_OPT_GET_MWINDOW_MAPPED_LIMIT, size_t *):
+ *
+ * > Get the maximum memory that will be mapped in total by the library
+ *
+ * * opts(GIT_OPT_SET_MWINDOW_MAPPED_LIMIT, size_t):
+ *
+ * >Set the maximum amount of memory that can be mapped at any time
+ * by the library
+ *
+ * * opts(GIT_OPT_GET_SEARCH_PATH, int level, git_buf *buf)
+ *
+ * > Get the search path for a given level of config data. "level" must
+ * > be one of `GIT_CONFIG_LEVEL_SYSTEM`, `GIT_CONFIG_LEVEL_GLOBAL`, or
+ * > `GIT_CONFIG_LEVEL_XDG`. The search path is written to the `out`
+ * > buffer.
+ *
+ * * opts(GIT_OPT_SET_SEARCH_PATH, int level, const char *path)
+ *
+ * > Set the search path for a level of config data. The search path
+ * > applied to shared attributes and ignore files, too.
+ * >
+ * > - `path` lists directories delimited by GIT_PATH_LIST_SEPARATOR.
+ * > Pass NULL to reset to the default (generally based on environment
+ * > variables). Use magic path `$PATH` to include the old value
+ * > of the path (if you want to prepend or append, for instance).
+ * >
+ * > - `level` must be GIT_CONFIG_LEVEL_SYSTEM, GIT_CONFIG_LEVEL_GLOBAL,
+ * > or GIT_CONFIG_LEVEL_XDG.
+ *
+ * * opts(GIT_OPT_SET_CACHE_OBJECT_LIMIT, git_otype type, size_t size)
+ *
+ * > Set the maximum data size for the given type of object to be
+ * > considered eligible for caching in memory. Setting to value to
+ * > zero means that that type of object will not be cached.
+ * > Defaults to 0 for GIT_OBJ_BLOB (i.e. won't cache blobs) and 4k
+ * > for GIT_OBJ_COMMIT, GIT_OBJ_TREE, and GIT_OBJ_TAG.
+ *
+ * * opts(GIT_OPT_SET_CACHE_MAX_SIZE, ssize_t max_storage_bytes)
+ *
+ * > Set the maximum total data size that will be cached in memory
+ * > across all repositories before libgit2 starts evicting objects
+ * > from the cache. This is a soft limit, in that the library might
+ * > briefly exceed it, but will start aggressively evicting objects
+ * > from cache when that happens. The default cache size is 256MB.
+ *
+ * * opts(GIT_OPT_ENABLE_CACHING, int enabled)
+ *
+ * > Enable or disable caching completely.
+ * >
+ * > Because caches are repository-specific, disabling the cache
+ * > cannot immediately clear all cached objects, but each cache will
+ * > be cleared on the next attempt to update anything in it.
+ *
+ * * opts(GIT_OPT_GET_CACHED_MEMORY, ssize_t *current, ssize_t *allowed)
+ *
+ * > Get the current bytes in cache and the maximum that would be
+ * > allowed in the cache.
+ *
+ * * opts(GIT_OPT_GET_TEMPLATE_PATH, git_buf *out)
+ *
+ * > Get the default template path.
+ * > The path is written to the `out` buffer.
+ *
+ * * opts(GIT_OPT_SET_TEMPLATE_PATH, const char *path)
+ *
+ * > Set the default template path.
+ * >
+ * > - `path` directory of template.
+ *
+ * @param option Option key
+ * @param ... value to set the option
+ * @return 0 on success, <0 on failure
+ */
+GIT_EXTERN(int) git_libgit2_opts(int option, ...);
+
/** @} */
GIT_END_DECL
+
#endif
diff --git a/include/git2/config.h b/include/git2/config.h
index e05d23694..21a5825a5 100644
--- a/include/git2/config.h
+++ b/include/git2/config.h
@@ -1,32 +1,15 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_config_h__
#define INCLUDE_git_config_h__
#include "common.h"
#include "types.h"
+#include "buffer.h"
/**
* @file git2/config.h
@@ -38,19 +21,61 @@
GIT_BEGIN_DECL
/**
- * Generic backend that implements the interface to
- * access a configuration file
+ * Priority level of a config file.
+ * These priority levels correspond to the natural escalation logic
+ * (from higher to lower) when searching for config entries in git.git.
+ *
+ * git_config_open_default() and git_repository_config() honor those
+ * priority levels as well.
*/
-struct git_config_file {
- struct git_config *cfg;
+typedef enum {
+ /** System-wide configuration file; /etc/gitconfig on Linux systems */
+ GIT_CONFIG_LEVEL_SYSTEM = 1,
+
+ /** XDG compatible configuration file; typically ~/.config/git/config */
+ GIT_CONFIG_LEVEL_XDG = 2,
+
+ /** User-specific configuration file (also called Global configuration
+ * file); typically ~/.gitconfig
+ */
+ GIT_CONFIG_LEVEL_GLOBAL = 3,
+
+ /** Repository specific configuration file; $WORK_DIR/.git/config on
+ * non-bare repos
+ */
+ GIT_CONFIG_LEVEL_LOCAL = 4,
+
+ /** Application specific configuration file; freely defined by applications
+ */
+ GIT_CONFIG_LEVEL_APP = 5,
- /* Open means open the file/database and parse if necessary */
- int (*open)(struct git_config_file *);
- int (*get)(struct git_config_file *, const char *key, const char **value);
- int (*set)(struct git_config_file *, const char *key, const char *value);
- int (*foreach)(struct git_config_file *, int (*fn)(const char *, const char *, void *), void *data);
- void (*free)(struct git_config_file *);
-};
+ /** Represents the highest level available config file (i.e. the most
+ * specific config file available that actually is loaded)
+ */
+ GIT_CONFIG_HIGHEST_LEVEL = -1,
+} git_config_level_t;
+
+typedef struct {
+ const char *name;
+ const char *value;
+ git_config_level_t level;
+} git_config_entry;
+
+typedef int (*git_config_foreach_cb)(const git_config_entry *, void *);
+typedef struct git_config_iterator git_config_iterator;
+
+typedef enum {
+ GIT_CVAR_FALSE = 0,
+ GIT_CVAR_TRUE = 1,
+ GIT_CVAR_INT32,
+ GIT_CVAR_STRING
+} git_cvar_t;
+
+typedef struct {
+ git_cvar_t cvar_type;
+ const char *str_match;
+ int map_value;
+} git_cvar_map;
/**
* Locate the path to the global configuration file
@@ -63,63 +88,65 @@ struct git_config_file {
* may be used on any `git_config` call to load the
* global configuration file.
*
- * @param global_config_path Buffer of GIT_PATH_MAX length to store the path
- * @return GIT_SUCCESS if a global configuration file has been
- * found. Its path will be stored in `buffer`.
+ * This method will not guess the path to the xdg compatible
+ * config file (.config/git/config).
+ *
+ * @param out Pointer to a user-allocated git_buf in which to store the path
+ * @return 0 if a global configuration file has been found. Its path will be stored in `out`.
*/
-GIT_EXTERN(int) git_config_find_global(char *global_config_path);
+GIT_EXTERN(int) git_config_find_global(git_buf *out);
/**
- * Open the global configuration file
+ * Locate the path to the global xdg compatible configuration file
*
- * Utility wrapper that calls `git_config_find_global`
- * and opens the located file, if it exists.
+ * The xdg compatible configuration file is usually
+ * located in `$HOME/.config/git/config`.
*
- * @param out Pointer to store the config instance
- * @return GIT_SUCCESS on success; error code otherwise
+ * This method will try to guess the full path to that
+ * file, if the file exists. The returned path
+ * may be used on any `git_config` call to load the
+ * xdg compatible configuration file.
+ *
+ * @param out Pointer to a user-allocated git_buf in which to store the path
+ * @return 0 if a xdg compatible configuration file has been
+ * found. Its path will be stored in `out`.
*/
-GIT_EXTERN(int) git_config_open_global(git_config **out);
+GIT_EXTERN(int) git_config_find_xdg(git_buf *out);
/**
- * Create a configuration file backend for ondisk files
+ * Locate the path to the system configuration file
*
- * 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.
+ * If /etc/gitconfig doesn't exist, it will look for
+ * %PROGRAMFILES%\Git\etc\gitconfig.
*
- * @param out the new backend
- * @param path where the config file is located
+ * @param out Pointer to a user-allocated git_buf in which to store the path
+ * @return 0 if a system configuration file has been
+ * found. Its path will be stored in `out`.
*/
-GIT_EXTERN(int) git_config_file__ondisk(struct git_config_file **out, const char *path);
+GIT_EXTERN(int) git_config_find_system(git_buf *out);
/**
- * Allocate a new configuration object
+ * Open the global, XDG and system configuration files
*
- * This object is empty, so you have to add a file to it before you
- * can do anything with it.
+ * Utility wrapper that finds the global, XDG and system configuration files
+ * and opens them into a single prioritized config object that can be
+ * used when accessing default config data outside a repository.
*
- * @param out pointer to the new configuration
- * @return GIT_SUCCESS on success; error code otherwise
+ * @param out Pointer to store the config instance
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_config_new(git_config **out);
+GIT_EXTERN(int) git_config_open_default(git_config **out);
/**
- * Add a generic config file instance to an existing config
- *
- * Note that the configuration object will free the file
- * automatically.
+ * Allocate a new configuration object
*
- * Further queries on this config object will access each
- * of the config file instances in order (instances with
- * a higher priority will be accessed first).
+ * This object is empty, so you have to add a file to it before you
+ * can do anything with it.
*
- * @param cfg the configuration to add the file to
- * @param file the configuration file (backend) to add
- * @param priority the priority the backend should have
- * @return GIT_SUCCESS on success; error code otherwise
+ * @param out pointer to the new configuration
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_config_add_file(git_config *cfg, git_config_file *file, int priority);
+GIT_EXTERN(int) git_config_new(git_config **out);
/**
* Add an on-disk config file instance to an existing config
@@ -133,15 +160,21 @@ GIT_EXTERN(int) git_config_add_file(git_config *cfg, git_config_file *file, int
*
* Further queries on this config object will access each
* of the config file instances in order (instances with
- * a higher priority will be accessed first).
+ * a higher priority level will be accessed first).
*
* @param cfg the configuration to add the file to
- * @param path path to the configuration file (backend) to add
- * @param priority the priority the backend should have
- * @return GIT_SUCCESS on success; error code otherwise
+ * @param path path to the configuration file to add
+ * @param level the priority level of the backend
+ * @param force replace config file at the given priority level
+ * @return 0 on success, GIT_EEXISTS when adding more than one file
+ * for a given priority level (and force_replace set to 0),
+ * GIT_ENOTFOUND when the file doesn't exist or error code
*/
-GIT_EXTERN(int) git_config_add_file_ondisk(git_config *cfg, const char *path, int priority);
-
+GIT_EXTERN(int) git_config_add_file_ondisk(
+ git_config *cfg,
+ const char *path,
+ git_config_level_t level,
+ int force);
/**
* Create a new config instance containing a single on-disk file
@@ -151,11 +184,77 @@ GIT_EXTERN(int) git_config_add_file_ondisk(git_config *cfg, const char *path, in
* - 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 GIT_SUCCESS on success; error code otherwise
+ * @return 0 on success, GIT_ENOTFOUND when the file doesn't exist
+ * or an error code
+ */
+GIT_EXTERN(int) git_config_open_ondisk(git_config **out, const char *path);
+
+/**
+ * Build a single-level focused config object from a multi-level one.
+ *
+ * The returned config object can be used to perform get/set/delete operations
+ * on a single specific level.
+ *
+ * Getting several times the same level from the same parent multi-level config
+ * will return different config instances, but containing the same config_file
+ * instance.
+ *
+ * @param out The configuration instance to create
+ * @param parent Multi-level config to search for the given level
+ * @param level Configuration level to search for
+ * @return 0, GIT_ENOTFOUND if the passed level cannot be found in the
+ * multi-level parent config, or an error code
+ */
+GIT_EXTERN(int) git_config_open_level(
+ git_config **out,
+ const git_config *parent,
+ git_config_level_t level);
+
+/**
+ * Open the global/XDG configuration file according to git's rules
+ *
+ * Git allows you to store your global configuration at
+ * `$HOME/.config` or `$XDG_CONFIG_HOME/git/config`. For backwards
+ * compatability, the XDG file shouldn't be used unless the use has
+ * created it explicitly. With this function you'll open the correct
+ * one to write to.
+ *
+ * @param out pointer in which to store the config object
+ * @param config the config object in which to look
+ */
+GIT_EXTERN(int) git_config_open_global(git_config **out, git_config *config);
+
+/**
+ * Create a snapshot of the configuration
+ *
+ * Create a snapshot of the current state of a configuration, which
+ * allows you to look into a consistent view of the configuration for
+ * looking up complex values (e.g. a remote, submodule).
+ *
+ * The string returned when querying such a config object is valid
+ * until it is freed.
+ *
+ * @param out pointer in which to store the snapshot config object
+ * @param config configuration to snapshot
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_config_snapshot(git_config **out, git_config *config);
+
+
+/**
+ * Reload changed config files
+ *
+ * A config file may be changed on disk out from under the in-memory
+ * config object. This function causes us to look for files that have
+ * been modified since we last loaded them and refresh the config with
+ * the latest information.
+ *
+ * @param cfg The configuration to refresh
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_config_open_ondisk(git_config **cfg, const char *path);
+GIT_EXTERN(int) git_config_refresh(git_config *cfg);
/**
* Free the configuration and its associated memory and files
@@ -165,24 +264,48 @@ GIT_EXTERN(int) git_config_open_ondisk(git_config **cfg, const char *path);
GIT_EXTERN(void) git_config_free(git_config *cfg);
/**
- * Get the value of an integer config variable.
+ * Get the git_config_entry of a config variable.
*
+ * The git_config_entry is owned by the config and should not be freed by the
+ * user.
+
+ * @param out pointer to the variable git_config_entry
* @param cfg where to look for the variable
* @param name the variable's name
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_config_get_entry(
+ const git_config_entry **out,
+ const git_config *cfg,
+ const char *name);
+
+/**
+ * Get the value of an integer config variable.
+ *
+ * All config files will be looked into, in the order of their
+ * defined level. A higher level means a higher priority. The
+ * first occurence of the variable will be returned here.
+ *
* @param out pointer to the variable where the value should be stored
- * @return GIT_SUCCESS on success; error code otherwise
+ * @param cfg where to look for the variable
+ * @param name the variable's name
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_config_get_int(git_config *cfg, const char *name, int *out);
+GIT_EXTERN(int) git_config_get_int32(int32_t *out, const git_config *cfg, const char *name);
/**
* Get the value of a long integer config variable.
*
+ * All config files will be looked into, in the order of their
+ * defined level. A higher level means a higher priority. The
+ * first occurrence of the variable will be returned here.
+ *
+ * @param out pointer to the variable where the value should be stored
* @param cfg where to look for the variable
* @param name the variable's name
- * @param out pointer to the variable where the value should be stored
- * @return GIT_SUCCESS on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_config_get_long(git_config *cfg, const char *name, long int *out);
+GIT_EXTERN(int) git_config_get_int64(int64_t *out, const git_config *cfg, const char *name);
/**
* Get the value of a boolean config variable.
@@ -190,58 +313,115 @@ GIT_EXTERN(int) git_config_get_long(git_config *cfg, const char *name, long int
* This function uses the usual C convention of 0 being false and
* anything else true.
*
+ * All config files will be looked into, in the order of their
+ * defined level. A higher level means a higher priority. The
+ * first occurrence of the variable will be returned here.
+ *
+ * @param out pointer to the variable where the value should be stored
* @param cfg where to look for the variable
* @param name the variable's name
- * @param out pointer to the variable where the value should be stored
- * @return GIT_SUCCESS on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_config_get_bool(git_config *cfg, const char *name, int *out);
+GIT_EXTERN(int) git_config_get_bool(int *out, const git_config *cfg, const char *name);
/**
* Get the value of a string config variable.
*
* The string is owned by the variable and should not be freed by the
- * user.
+ * user. The pointer will be valid until the next operation on this
+ * config object.
+ *
+ * All config files will be looked into, in the order of their
+ * defined level. A higher level means a higher priority. The
+ * first occurrence of the variable will be returned here.
*
+ * @param out pointer to the variable's value
* @param cfg where to look for the variable
* @param name the variable's name
- * @param out pointer to the variable's value
- * @return GIT_SUCCESS on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_config_get_string(git_config *cfg, const char *name, const char **out);
+GIT_EXTERN(int) git_config_get_string(const char **out, const git_config *cfg, const char *name);
/**
- * Set the value of an integer config variable.
+ * Get each value of a multivar in a foreach callback
+ *
+ * The callback will be called on each variable found
+ *
+ * @param cfg where to look for the variable
+ * @param name the variable's name
+ * @param regexp regular expression to filter which variables we're
+ * interested in. Use NULL to indicate all
+ * @param callback the function to be called on each value of the variable
+ * @param payload opaque pointer to pass to the callback
+ */
+GIT_EXTERN(int) git_config_get_multivar_foreach(const git_config *cfg, const char *name, const char *regexp, git_config_foreach_cb callback, void *payload);
+
+/**
+ * Get each value of a multivar
+ *
+ * @param out pointer to store the iterator
+ * @param cfg where to look for the variable
+ * @param name the variable's name
+ * @param regexp regular expression to filter which variables we're
+ * interested in. Use NULL to indicate all
+ */
+GIT_EXTERN(int) git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp);
+
+/**
+ * Return the current entry and advance the iterator
+ *
+ * The pointers returned by this function are valid until the iterator
+ * is freed.
+ *
+ * @param entry pointer to store the entry
+ * @param iter the iterator
+ * @return 0 or an error code. GIT_ITEROVER if the iteration has completed
+ */
+GIT_EXTERN(int) git_config_next(git_config_entry **entry, git_config_iterator *iter);
+
+/**
+ * Free a config iterator
+ *
+ * @param iter the iterator to free
+ */
+GIT_EXTERN(void) git_config_iterator_free(git_config_iterator *iter);
+
+/**
+ * Set the value of an integer config variable in the config file
+ * with the highest level (usually the local one).
*
* @param cfg where to look for the variable
* @param name the variable's name
* @param value Integer value for the variable
- * @return GIT_SUCCESS on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_config_set_int(git_config *cfg, const char *name, int value);
+GIT_EXTERN(int) git_config_set_int32(git_config *cfg, const char *name, int32_t value);
/**
- * Set the value of a long integer config variable.
+ * Set the value of a long integer config variable in the config file
+ * with the highest level (usually the local one).
*
* @param cfg where to look for the variable
* @param name the variable's name
* @param value Long integer value for the variable
- * @return GIT_SUCCESS on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_config_set_long(git_config *cfg, const char *name, long int value);
+GIT_EXTERN(int) git_config_set_int64(git_config *cfg, const char *name, int64_t value);
/**
- * Set the value of a boolean config variable.
+ * Set the value of a boolean config variable in the config file
+ * with the highest level (usually the local one).
*
* @param cfg where to look for the variable
* @param name the variable's name
* @param value the value to store
- * @return GIT_SUCCESS on success; error code otherwise
+ * @return 0 or an error code
*/
GIT_EXTERN(int) git_config_set_bool(git_config *cfg, const char *name, int value);
/**
- * Set the value of a string config variable.
+ * Set the value of a string config variable in the config file
+ * with the highest level (usually the local one).
*
* A copy of the string is made and the user is free to use it
* afterwards.
@@ -249,36 +429,217 @@ GIT_EXTERN(int) git_config_set_bool(git_config *cfg, const char *name, int value
* @param cfg where to look for the variable
* @param name the variable's name
* @param value the string to store.
- * @return GIT_SUCCESS on success; error code otherwise
+ * @return 0 or an error code
*/
GIT_EXTERN(int) git_config_set_string(git_config *cfg, const char *name, const char *value);
/**
- * Delete a config variable
+ * Set a multivar in the local config file.
+ *
+ * @param cfg where to look for the variable
+ * @param name the variable's name
+ * @param regexp a regular expression to indicate which values to replace
+ * @param value the new value.
+ */
+GIT_EXTERN(int) git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value);
+
+/**
+ * Delete a config variable from the config file
+ * with the highest level (usually the local one).
*
* @param cfg the configuration
* @param name the variable to delete
*/
-GIT_EXTERN(int) git_config_delete(git_config *cfg, const char *name);
+GIT_EXTERN(int) git_config_delete_entry(git_config *cfg, const char *name);
+
+/**
+ * Deletes one or several entries from a multivar in the local config file.
+ *
+ * @param cfg where to look for the variables
+ * @param name the variable's name
+ * @param regexp a regular expression to indicate which values to delete
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_config_delete_multivar(git_config *cfg, const char *name, const char *regexp);
/**
* Perform an operation on each config variable.
*
* The callback receives the normalized name and value of each variable
* in the config backend, and the data pointer passed to this function.
- * As soon as one of the callback functions returns something other than 0,
- * this function returns that value.
+ * If the callback returns a non-zero value, the function stops iterating
+ * and returns that value to the caller.
+ *
+ * The pointers passed to the callback are only valid as long as the
+ * iteration is ongoing.
*
* @param cfg where to get the variables from
* @param callback the function to call on each variable
* @param payload the data to pass to the callback
- * @return GIT_SUCCESS or the return value of the callback which didn't return 0
+ * @return 0 on success, non-zero callback return value, or error code
*/
GIT_EXTERN(int) git_config_foreach(
- git_config *cfg,
- int (*callback)(const char *var_name, const char *value, void *payload),
+ const git_config *cfg,
+ git_config_foreach_cb callback,
void *payload);
+/**
+ * Iterate over all the config variables
+ *
+ * Use `git_config_next` to advance the iteration and
+ * `git_config_iterator_free` when done.
+ *
+ * @param out pointer to store the iterator
+ * @param cfg where to ge the variables from
+ */
+GIT_EXTERN(int) git_config_iterator_new(git_config_iterator **out, const git_config *cfg);
+
+/**
+ * Iterate over all the config variables whose name matches a pattern
+ *
+ * Use `git_config_next` to advance the iteration and
+ * `git_config_iterator_free` when done.
+ *
+ * @param out pointer to store the iterator
+ * @param cfg where to ge the variables from
+ * @param regexp regular expression to match the names
+ */
+GIT_EXTERN(int) git_config_iterator_glob_new(git_config_iterator **out, const git_config *cfg, const char *regexp);
+
+/**
+ * Perform an operation on each config variable matching a regular expression.
+ *
+ * This behaviors like `git_config_foreach` with an additional filter of a
+ * regular expression that filters which config keys are passed to the
+ * callback.
+ *
+ * The pointers passed to the callback are only valid as long as the
+ * iteration is ongoing.
+ *
+ * @param cfg where to get the variables from
+ * @param regexp regular expression to match against config names
+ * @param callback the function to call on each variable
+ * @param payload the data to pass to the callback
+ * @return 0 or the return value of the callback which didn't return 0
+ */
+GIT_EXTERN(int) git_config_foreach_match(
+ const git_config *cfg,
+ const char *regexp,
+ git_config_foreach_cb callback,
+ void *payload);
+
+/**
+ * Query the value of a config variable and return it mapped to
+ * an integer constant.
+ *
+ * This is a helper method to easily map different possible values
+ * to a variable to integer constants that easily identify them.
+ *
+ * A mapping array looks as follows:
+ *
+ * git_cvar_map autocrlf_mapping[] = {
+ * {GIT_CVAR_FALSE, NULL, GIT_AUTO_CRLF_FALSE},
+ * {GIT_CVAR_TRUE, NULL, GIT_AUTO_CRLF_TRUE},
+ * {GIT_CVAR_STRING, "input", GIT_AUTO_CRLF_INPUT},
+ * {GIT_CVAR_STRING, "default", GIT_AUTO_CRLF_DEFAULT}};
+ *
+ * On any "false" value for the variable (e.g. "false", "FALSE", "no"), the
+ * mapping will store `GIT_AUTO_CRLF_FALSE` in the `out` parameter.
+ *
+ * The same thing applies for any "true" value such as "true", "yes" or "1", storing
+ * the `GIT_AUTO_CRLF_TRUE` variable.
+ *
+ * Otherwise, if the value matches the string "input" (with case insensitive comparison),
+ * the given constant will be stored in `out`, and likewise for "default".
+ *
+ * If not a single match can be made to store in `out`, an error code will be
+ * returned.
+ *
+ * @param out place to store the result of the mapping
+ * @param cfg config file to get the variables from
+ * @param name name of the config variable to lookup
+ * @param maps array of `git_cvar_map` objects specifying the possible mappings
+ * @param map_n number of mapping objects in `maps`
+ * @return 0 on success, error code otherwise
+ */
+GIT_EXTERN(int) git_config_get_mapped(
+ int *out,
+ const git_config *cfg,
+ const char *name,
+ const git_cvar_map *maps,
+ size_t map_n);
+
+/**
+ * Maps a string value to an integer constant
+ *
+ * @param out place to store the result of the parsing
+ * @param maps array of `git_cvar_map` objects specifying the possible mappings
+ * @param map_n number of mapping objects in `maps`
+ * @param value value to parse
+ */
+GIT_EXTERN(int) git_config_lookup_map_value(
+ int *out,
+ const git_cvar_map *maps,
+ size_t map_n,
+ const char *value);
+
+/**
+ * Parse a string value as a bool.
+ *
+ * Valid values for true are: 'true', 'yes', 'on', 1 or any
+ * number different from 0
+ * Valid values for false are: 'false', 'no', 'off', 0
+ *
+ * @param out place to store the result of the parsing
+ * @param value value to parse
+ */
+GIT_EXTERN(int) git_config_parse_bool(int *out, const char *value);
+
+/**
+ * Parse a string value as an int32.
+ *
+ * An optional value suffix of 'k', 'm', or 'g' will
+ * cause the value to be multiplied by 1024, 1048576,
+ * or 1073741824 prior to output.
+ *
+ * @param out place to store the result of the parsing
+ * @param value value to parse
+ */
+GIT_EXTERN(int) git_config_parse_int32(int32_t *out, const char *value);
+
+/**
+ * Parse a string value as an int64.
+ *
+ * An optional value suffix of 'k', 'm', or 'g' will
+ * cause the value to be multiplied by 1024, 1048576,
+ * or 1073741824 prior to output.
+ *
+ * @param out place to store the result of the parsing
+ * @param value value to parse
+ */
+GIT_EXTERN(int) git_config_parse_int64(int64_t *out, const char *value);
+
+
+/**
+ * Perform an operation on each config variable in given config backend
+ * matching a regular expression.
+ *
+ * This behaviors like `git_config_foreach_match` except instead of all config
+ * entries it just enumerates through the given backend entry.
+ *
+ * @param backend where to get the variables from
+ * @param regexp regular expression to match against config names (can be NULL)
+ * @param callback the function to call on each variable
+ * @param payload the data to pass to the callback
+ */
+GIT_EXTERN(int) git_config_backend_foreach_match(
+ git_config_backend *backend,
+ const char *regexp,
+ git_config_foreach_cb callback,
+ void *payload);
+
+
/** @} */
GIT_END_DECL
#endif
diff --git a/include/git2/cred_helpers.h b/include/git2/cred_helpers.h
new file mode 100644
index 000000000..1d8809211
--- /dev/null
+++ b/include/git2/cred_helpers.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_cred_helpers_h__
+#define INCLUDE_git_cred_helpers_h__
+
+#include "transport.h"
+
+/**
+ * @file git2/cred_helpers.h
+ * @brief Utility functions for credential management
+ * @defgroup git_cred_helpers credential management helpers
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Payload for git_cred_stock_userpass_plaintext.
+ */
+typedef struct git_cred_userpass_payload {
+ char *username;
+ char *password;
+} git_cred_userpass_payload;
+
+
+/**
+ * Stock callback usable as a git_cred_acquire_cb. This calls
+ * git_cred_userpass_plaintext_new unless the protocol has not specified
+ * `GIT_CREDTYPE_USERPASS_PLAINTEXT` as an allowed type.
+ *
+ * @param cred The newly created credential object.
+ * @param url The resource for which we are demanding a credential.
+ * @param user_from_url The username that was embedded in a "user@host"
+ * remote url, or NULL if not included.
+ * @param allowed_types A bitmask stating which cred types are OK to return.
+ * @param payload The payload provided when specifying this callback. (This is
+ * interpreted as a `git_cred_userpass_payload*`.)
+ */
+GIT_EXTERN(int) git_cred_userpass(
+ git_cred **cred,
+ const char *url,
+ const char *user_from_url,
+ unsigned int allowed_types,
+ void *payload);
+
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/diff.h b/include/git2/diff.h
new file mode 100644
index 000000000..675c209be
--- /dev/null
+++ b/include/git2/diff.h
@@ -0,0 +1,1240 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_diff_h__
+#define INCLUDE_git_diff_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+#include "tree.h"
+#include "refs.h"
+
+/**
+ * @file git2/diff.h
+ * @brief Git tree and file differencing routines.
+ *
+ * Overview
+ * --------
+ *
+ * Calculating diffs is generally done in two phases: building a list of
+ * diffs then traversing it. This makes is easier to share logic across
+ * the various types of diffs (tree vs tree, workdir vs index, etc.), and
+ * also allows you to insert optional diff post-processing phases,
+ * such as rename detection, in between the steps. When you are done with
+ * a diff object, it must be freed.
+ *
+ * Terminology
+ * -----------
+ *
+ * To understand the diff APIs, you should know the following terms:
+ *
+ * - A `diff` represents the cumulative list of differences between two
+ * snapshots of a repository (possibly filtered by a set of file name
+ * patterns). This is the `git_diff` object.
+ *
+ * - A `delta` is a file pair with an old and new revision. The old version
+ * may be absent if the file was just created and the new version may be
+ * absent if the file was deleted. A diff is mostly just a list of deltas.
+ *
+ * - A `binary` file / delta is a file (or pair) for which no text diffs
+ * should be generated. A diff can contain delta entries that are
+ * binary, but no diff content will be output for those files. There is
+ * a base heuristic for binary detection and you can further tune the
+ * behavior with git attributes or diff flags and option settings.
+ *
+ * - A `hunk` is a span of modified lines in a delta along with some stable
+ * surrounding context. You can configure the amount of context and other
+ * properties of how hunks are generated. Each hunk also comes with a
+ * header that described where it starts and ends in both the old and new
+ * versions in the delta.
+ *
+ * - A `line` is a range of characters inside a hunk. It could be a context
+ * line (i.e. in both old and new versions), an added line (i.e. only in
+ * the new version), or a removed line (i.e. only in the old version).
+ * Unfortunately, we don't know anything about the encoding of data in the
+ * file being diffed, so we cannot tell you much about the line content.
+ * Line data will not be NUL-byte terminated, however, because it will be
+ * just a span of bytes inside the larger file.
+ *
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Flags for diff options. A combination of these flags can be passed
+ * in via the `flags` value in the `git_diff_options`.
+ */
+typedef enum {
+ /** Normal diff, the default */
+ GIT_DIFF_NORMAL = 0,
+
+ /*
+ * Options controlling which files will be in the diff
+ */
+
+ /** Reverse the sides of the diff */
+ GIT_DIFF_REVERSE = (1u << 0),
+
+ /** Include ignored files in the diff */
+ GIT_DIFF_INCLUDE_IGNORED = (1u << 1),
+
+ /** Even with GIT_DIFF_INCLUDE_IGNORED, an entire ignored directory
+ * will be marked with only a single entry in the diff; this flag
+ * adds all files under the directory as IGNORED entries, too.
+ */
+ GIT_DIFF_RECURSE_IGNORED_DIRS = (1u << 2),
+
+ /** Include untracked files in the diff */
+ GIT_DIFF_INCLUDE_UNTRACKED = (1u << 3),
+
+ /** Even with GIT_DIFF_INCLUDE_UNTRACKED, an entire untracked
+ * directory will be marked with only a single entry in the diff
+ * (a la what core Git does in `git status`); this flag adds *all*
+ * files under untracked directories as UNTRACKED entries, too.
+ */
+ GIT_DIFF_RECURSE_UNTRACKED_DIRS = (1u << 4),
+
+ /** Include unmodified files in the diff */
+ GIT_DIFF_INCLUDE_UNMODIFIED = (1u << 5),
+
+ /** Normally, a type change between files will be converted into a
+ * DELETED record for the old and an ADDED record for the new; this
+ * options enabled the generation of TYPECHANGE delta records.
+ */
+ GIT_DIFF_INCLUDE_TYPECHANGE = (1u << 6),
+
+ /** Even with GIT_DIFF_INCLUDE_TYPECHANGE, blob->tree changes still
+ * generally show as a DELETED blob. This flag tries to correctly
+ * label blob->tree transitions as TYPECHANGE records with new_file's
+ * mode set to tree. Note: the tree SHA will not be available.
+ */
+ GIT_DIFF_INCLUDE_TYPECHANGE_TREES = (1u << 7),
+
+ /** Ignore file mode changes */
+ GIT_DIFF_IGNORE_FILEMODE = (1u << 8),
+
+ /** Treat all submodules as unmodified */
+ GIT_DIFF_IGNORE_SUBMODULES = (1u << 9),
+
+ /** Use case insensitive filename comparisons */
+ GIT_DIFF_IGNORE_CASE = (1u << 10),
+
+ /** If the pathspec is set in the diff options, this flags means to
+ * apply it as an exact match instead of as an fnmatch pattern.
+ */
+ GIT_DIFF_DISABLE_PATHSPEC_MATCH = (1u << 12),
+
+ /** Disable updating of the `binary` flag in delta records. This is
+ * useful when iterating over a diff if you don't need hunk and data
+ * callbacks and want to avoid having to load file completely.
+ */
+ GIT_DIFF_SKIP_BINARY_CHECK = (1u << 13),
+
+ /** When diff finds an untracked directory, to match the behavior of
+ * core Git, it scans the contents for IGNORED and UNTRACKED files.
+ * If *all* contents are IGNORED, then the directory is IGNORED; if
+ * any contents are not IGNORED, then the directory is UNTRACKED.
+ * This is extra work that may not matter in many cases. This flag
+ * turns off that scan and immediately labels an untracked directory
+ * as UNTRACKED (changing the behavior to not match core Git).
+ */
+ GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS = (1u << 14),
+
+ /** When diff finds a file in the working directory with stat
+ * information different from the index, but the OID ends up being the
+ * same, write the correct stat information into the index. Note:
+ * without this flag, diff will always leave the index untouched.
+ */
+ GIT_DIFF_UPDATE_INDEX = (1u << 15),
+
+ /*
+ * Options controlling how output will be generated
+ */
+
+ /** Treat all files as text, disabling binary attributes & detection */
+ GIT_DIFF_FORCE_TEXT = (1u << 20),
+ /** Treat all files as binary, disabling text diffs */
+ GIT_DIFF_FORCE_BINARY = (1u << 21),
+
+ /** Ignore all whitespace */
+ GIT_DIFF_IGNORE_WHITESPACE = (1u << 22),
+ /** Ignore changes in amount of whitespace */
+ GIT_DIFF_IGNORE_WHITESPACE_CHANGE = (1u << 23),
+ /** Ignore whitespace at end of line */
+ GIT_DIFF_IGNORE_WHITESPACE_EOL = (1u << 24),
+
+ /** When generating patch text, include the content of untracked
+ * files. This automatically turns on GIT_DIFF_INCLUDE_UNTRACKED but
+ * it does not turn on GIT_DIFF_RECURSE_UNTRACKED_DIRS. Add that
+ * flag if you want the content of every single UNTRACKED file.
+ */
+ GIT_DIFF_SHOW_UNTRACKED_CONTENT = (1u << 25),
+
+ /** When generating output, include the names of unmodified files if
+ * they are included in the git_diff. Normally these are skipped in
+ * the formats that list files (e.g. name-only, name-status, raw).
+ * Even with this, these will not be included in patch format.
+ */
+ GIT_DIFF_SHOW_UNMODIFIED = (1u << 26),
+
+ /** Use the "patience diff" algorithm */
+ GIT_DIFF_PATIENCE = (1u << 28),
+ /** Take extra time to find minimal diff */
+ GIT_DIFF_MINIMAL = (1 << 29),
+
+ /** Include the necessary deflate / delta information so that `git-apply`
+ * can apply given diff information to binary files.
+ */
+ GIT_DIFF_SHOW_BINARY = (1 << 30),
+} git_diff_option_t;
+
+/**
+ * The diff object that contains all individual file deltas.
+ *
+ * This is an opaque structure which will be allocated by one of the diff
+ * generator functions below (such as `git_diff_tree_to_tree`). You are
+ * responsible for releasing the object memory when done, using the
+ * `git_diff_free()` function.
+ */
+typedef struct git_diff git_diff;
+
+/**
+ * Flags for the delta object and the file objects on each side.
+ *
+ * These flags are used for both the `flags` value of the `git_diff_delta`
+ * and the flags for the `git_diff_file` objects representing the old and
+ * new sides of the delta. Values outside of this public range should be
+ * considered reserved for internal or future use.
+ */
+typedef enum {
+ GIT_DIFF_FLAG_BINARY = (1u << 0), /**< file(s) treated as binary data */
+ GIT_DIFF_FLAG_NOT_BINARY = (1u << 1), /**< file(s) treated as text data */
+ GIT_DIFF_FLAG_VALID_ID = (1u << 2), /**< `id` value is known correct */
+} git_diff_flag_t;
+
+/**
+ * What type of change is described by a git_diff_delta?
+ *
+ * `GIT_DELTA_RENAMED` and `GIT_DELTA_COPIED` will only show up if you run
+ * `git_diff_find_similar()` on the diff object.
+ *
+ * `GIT_DELTA_TYPECHANGE` only shows up given `GIT_DIFF_INCLUDE_TYPECHANGE`
+ * in the option flags (otherwise type changes will be split into ADDED /
+ * DELETED pairs).
+ */
+typedef enum {
+ GIT_DELTA_UNMODIFIED = 0, /**< no changes */
+ GIT_DELTA_ADDED = 1, /**< entry does not exist in old version */
+ GIT_DELTA_DELETED = 2, /**< entry does not exist in new version */
+ GIT_DELTA_MODIFIED = 3, /**< entry content changed between old and new */
+ GIT_DELTA_RENAMED = 4, /**< entry was renamed between old and new */
+ GIT_DELTA_COPIED = 5, /**< entry was copied from another old entry */
+ GIT_DELTA_IGNORED = 6, /**< entry is ignored item in workdir */
+ GIT_DELTA_UNTRACKED = 7, /**< entry is untracked item in workdir */
+ GIT_DELTA_TYPECHANGE = 8, /**< type of entry changed between old and new */
+} git_delta_t;
+
+/**
+ * Description of one side of a delta.
+ *
+ * Although this is called a "file", it could represent a file, a symbolic
+ * link, a submodule commit id, or even a tree (although that only if you
+ * are tracking type changes or ignored/untracked directories).
+ *
+ * The `oid` is the `git_oid` of the item. If the entry represents an
+ * absent side of a diff (e.g. the `old_file` of a `GIT_DELTA_ADDED` delta),
+ * then the oid will be zeroes.
+ *
+ * `path` is the NUL-terminated path to the entry relative to the working
+ * directory of the repository.
+ *
+ * `size` is the size of the entry in bytes.
+ *
+ * `flags` is a combination of the `git_diff_flag_t` types
+ *
+ * `mode` is, roughly, the stat() `st_mode` value for the item. This will
+ * be restricted to one of the `git_filemode_t` values.
+ */
+typedef struct {
+ git_oid id;
+ const char *path;
+ git_off_t size;
+ uint32_t flags;
+ uint16_t mode;
+} git_diff_file;
+
+/**
+ * Description of changes to one entry.
+ *
+ * When iterating over a diff, this will be passed to most callbacks and
+ * you can use the contents to understand exactly what has changed.
+ *
+ * The `old_file` represents the "from" side of the diff and the `new_file`
+ * represents to "to" side of the diff. What those means depend on the
+ * function that was used to generate the diff and will be documented below.
+ * You can also use the `GIT_DIFF_REVERSE` flag to flip it around.
+ *
+ * Although the two sides of the delta are named "old_file" and "new_file",
+ * they actually may correspond to entries that represent a file, a symbolic
+ * link, a submodule commit id, or even a tree (if you are tracking type
+ * changes or ignored/untracked directories).
+ *
+ * Under some circumstances, in the name of efficiency, not all fields will
+ * be filled in, but we generally try to fill in as much as possible. One
+ * example is that the "flags" field may not have either the `BINARY` or the
+ * `NOT_BINARY` flag set to avoid examining file contents if you do not pass
+ * in hunk and/or line callbacks to the diff foreach iteration function. It
+ * will just use the git attributes for those files.
+ *
+ * The similarity score is zero unless you call `git_diff_find_similar()`
+ * which does a similarity analysis of files in the diff. Use that
+ * function to do rename and copy detection, and to split heavily modified
+ * files in add/delete pairs. After that call, deltas with a status of
+ * GIT_DELTA_RENAMED or GIT_DELTA_COPIED will have a similarity score
+ * between 0 and 100 indicating how similar the old and new sides are.
+ *
+ * If you ask `git_diff_find_similar` to find heavily modified files to
+ * break, but to not *actually* break the records, then GIT_DELTA_MODIFIED
+ * records may have a non-zero similarity score if the self-similarity is
+ * below the split threshold. To display this value like core Git, invert
+ * the score (a la `printf("M%03d", 100 - delta->similarity)`).
+ */
+typedef struct {
+ git_delta_t status;
+ uint32_t flags; /**< git_diff_flag_t values */
+ uint16_t similarity; /**< for RENAMED and COPIED, value 0-100 */
+ uint16_t nfiles; /**< number of files in this delta */
+ git_diff_file old_file;
+ git_diff_file new_file;
+} git_diff_delta;
+
+/**
+ * Diff notification callback function.
+ *
+ * The callback will be called for each file, just before the `git_delta_t`
+ * gets inserted into the diff.
+ *
+ * When the callback:
+ * - returns < 0, the diff process will be aborted.
+ * - returns > 0, the delta will not be inserted into the diff, but the
+ * diff process continues.
+ * - returns 0, the delta is inserted into the diff, and the diff process
+ * continues.
+ */
+typedef int (*git_diff_notify_cb)(
+ const git_diff *diff_so_far,
+ const git_diff_delta *delta_to_add,
+ const char *matched_pathspec,
+ void *payload);
+
+/**
+ * Structure describing options about how the diff should be executed.
+ *
+ * Setting all values of the structure to zero will yield the default
+ * values. Similarly, passing NULL for the options structure will
+ * give the defaults. The default values are marked below.
+ *
+ * - `flags` is a combination of the `git_diff_option_t` values above
+ * - `context_lines` is the number of unchanged lines that define the
+ * boundary of a hunk (and to display before and after)
+ * - `interhunk_lines` is the maximum number of unchanged lines between
+ * hunk boundaries before the hunks will be merged into a one.
+ * - `old_prefix` is the virtual "directory" to prefix to old file names
+ * in hunk headers (default "a")
+ * - `new_prefix` is the virtual "directory" to prefix to new file names
+ * in hunk headers (default "b")
+ * - `pathspec` is an array of paths / fnmatch patterns to constrain diff
+ * - `max_size` is a file size (in bytes) above which a blob will be marked
+ * as binary automatically; pass a negative value to disable.
+ * - `notify_cb` is an optional callback function, notifying the consumer of
+ * which files are being examined as the diff is generated
+ * - `notify_payload` is the payload data to pass to the `notify_cb` function
+ * - `ignore_submodules` overrides the submodule ignore setting for all
+ * submodules in the diff.
+ */
+typedef struct {
+ unsigned int version; /**< version for the struct */
+ uint32_t flags; /**< defaults to GIT_DIFF_NORMAL */
+
+ /* options controlling which files are in the diff */
+
+ git_submodule_ignore_t ignore_submodules; /**< submodule ignore rule */
+ git_strarray pathspec; /**< defaults to include all paths */
+ git_diff_notify_cb notify_cb;
+ void *notify_payload;
+
+ /* options controlling how to diff text is generated */
+
+ uint16_t context_lines; /**< defaults to 3 */
+ uint16_t interhunk_lines; /**< defaults to 0 */
+ uint16_t id_abbrev; /**< default 'core.abbrev' or 7 if unset */
+ git_off_t max_size; /**< defaults to 512MB */
+ const char *old_prefix; /**< defaults to "a" */
+ const char *new_prefix; /**< defaults to "b" */
+} git_diff_options;
+
+/* The current version of the diff options structure */
+#define GIT_DIFF_OPTIONS_VERSION 1
+
+/* Stack initializer for diff options. Alternatively use
+ * `git_diff_options_init` programmatic initialization.
+ */
+#define GIT_DIFF_OPTIONS_INIT \
+ {GIT_DIFF_OPTIONS_VERSION, 0, GIT_SUBMODULE_IGNORE_DEFAULT, {NULL,0}, NULL, NULL, 3}
+
+/**
+ * Initializes a `git_diff_options` with default values. Equivalent to
+ * creating an instance with GIT_DIFF_OPTIONS_INIT.
+ *
+ * @param opts The `git_diff_options` struct to initialize
+ * @param version Version of struct; pass `GIT_DIFF_OPTIONS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_diff_init_options(
+ git_diff_options *opts,
+ unsigned int version);
+
+/**
+ * When iterating over a diff, callback that will be made per file.
+ *
+ * @param delta A pointer to the delta data for the file
+ * @param progress Goes from 0 to 1 over the diff
+ * @param payload User-specified pointer from foreach function
+ */
+typedef int (*git_diff_file_cb)(
+ const git_diff_delta *delta,
+ float progress,
+ void *payload);
+
+/**
+ * Structure describing a hunk of a diff.
+ */
+typedef struct git_diff_hunk git_diff_hunk;
+struct git_diff_hunk {
+ int old_start; /**< Starting line number in old_file */
+ int old_lines; /**< Number of lines in old_file */
+ int new_start; /**< Starting line number in new_file */
+ int new_lines; /**< Number of lines in new_file */
+ size_t header_len; /**< Number of bytes in header text */
+ char header[128]; /**< Header text, NUL-byte terminated */
+};
+
+/**
+ * When iterating over a diff, callback that will be made per hunk.
+ */
+typedef int (*git_diff_hunk_cb)(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk,
+ void *payload);
+
+/**
+ * Line origin constants.
+ *
+ * These values describe where a line came from and will be passed to
+ * the git_diff_line_cb when iterating over a diff. There are some
+ * special origin constants at the end that are used for the text
+ * output callbacks to demarcate lines that are actually part of
+ * the file or hunk headers.
+ */
+typedef enum {
+ /* These values will be sent to `git_diff_line_cb` along with the line */
+ GIT_DIFF_LINE_CONTEXT = ' ',
+ GIT_DIFF_LINE_ADDITION = '+',
+ GIT_DIFF_LINE_DELETION = '-',
+
+ GIT_DIFF_LINE_CONTEXT_EOFNL = '=', /**< Both files have no LF at end */
+ GIT_DIFF_LINE_ADD_EOFNL = '>', /**< Old has no LF at end, new does */
+ GIT_DIFF_LINE_DEL_EOFNL = '<', /**< Old has LF at end, new does not */
+
+ /* The following values will only be sent to a `git_diff_line_cb` when
+ * the content of a diff is being formatted through `git_diff_print`.
+ */
+ GIT_DIFF_LINE_FILE_HDR = 'F',
+ GIT_DIFF_LINE_HUNK_HDR = 'H',
+ GIT_DIFF_LINE_BINARY = 'B' /**< For "Binary files x and y differ" */
+} git_diff_line_t;
+
+/**
+ * Structure describing a line (or data span) of a diff.
+ */
+typedef struct git_diff_line git_diff_line;
+struct git_diff_line {
+ char origin; /**< A git_diff_line_t value */
+ int old_lineno; /**< Line number in old file or -1 for added line */
+ int new_lineno; /**< Line number in new file or -1 for deleted line */
+ int num_lines; /**< Number of newline characters in content */
+ size_t content_len; /**< Number of bytes of data */
+ git_off_t content_offset; /**< Offset in the original file to the content */
+ const char *content; /**< Pointer to diff text, not NUL-byte terminated */
+};
+
+/**
+ * When iterating over a diff, callback that will be made per text diff
+ * line. In this context, the provided range will be NULL.
+ *
+ * When printing a diff, callback that will be made to output each line
+ * of text. This uses some extra GIT_DIFF_LINE_... constants for output
+ * of lines of file and hunk headers.
+ */
+typedef int (*git_diff_line_cb)(
+ const git_diff_delta *delta, /**< delta that contains this data */
+ const git_diff_hunk *hunk, /**< hunk containing this data */
+ const git_diff_line *line, /**< line data */
+ void *payload); /**< user reference data */
+
+/**
+ * Flags to control the behavior of diff rename/copy detection.
+ */
+typedef enum {
+ /** Obey `diff.renames`. Overridden by any other GIT_DIFF_FIND_... flag. */
+ GIT_DIFF_FIND_BY_CONFIG = 0,
+
+ /** Look for renames? (`--find-renames`) */
+ GIT_DIFF_FIND_RENAMES = (1u << 0),
+
+ /** Consider old side of MODIFIED for renames? (`--break-rewrites=N`) */
+ GIT_DIFF_FIND_RENAMES_FROM_REWRITES = (1u << 1),
+
+ /** Look for copies? (a la `--find-copies`). */
+ GIT_DIFF_FIND_COPIES = (1u << 2),
+
+ /** Consider UNMODIFIED as copy sources? (`--find-copies-harder`).
+ *
+ * For this to work correctly, use GIT_DIFF_INCLUDE_UNMODIFIED when
+ * the initial `git_diff` is being generated.
+ */
+ GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED = (1u << 3),
+
+ /** Mark significant rewrites for split (`--break-rewrites=/M`) */
+ GIT_DIFF_FIND_REWRITES = (1u << 4),
+ /** Actually split large rewrites into delete/add pairs */
+ GIT_DIFF_BREAK_REWRITES = (1u << 5),
+ /** Mark rewrites for split and break into delete/add pairs */
+ GIT_DIFF_FIND_AND_BREAK_REWRITES =
+ (GIT_DIFF_FIND_REWRITES | GIT_DIFF_BREAK_REWRITES),
+
+ /** Find renames/copies for UNTRACKED items in working directory.
+ *
+ * For this to work correctly, use GIT_DIFF_INCLUDE_UNTRACKED when the
+ * initial `git_diff` is being generated (and obviously the diff must
+ * be against the working directory for this to make sense).
+ */
+ GIT_DIFF_FIND_FOR_UNTRACKED = (1u << 6),
+
+ /** Turn on all finding features. */
+ GIT_DIFF_FIND_ALL = (0x0ff),
+
+ /** Measure similarity ignoring leading whitespace (default) */
+ GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE = 0,
+ /** Measure similarity ignoring all whitespace */
+ GIT_DIFF_FIND_IGNORE_WHITESPACE = (1u << 12),
+ /** Measure similarity including all data */
+ GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE = (1u << 13),
+ /** Measure similarity only by comparing SHAs (fast and cheap) */
+ GIT_DIFF_FIND_EXACT_MATCH_ONLY = (1u << 14),
+
+ /** Do not break rewrites unless they contribute to a rename.
+ *
+ * Normally, GIT_DIFF_FIND_AND_BREAK_REWRITES will measure the self-
+ * similarity of modified files and split the ones that have changed a
+ * lot into a DELETE / ADD pair. Then the sides of that pair will be
+ * considered candidates for rename and copy detection.
+ *
+ * If you add this flag in and the split pair is *not* used for an
+ * actual rename or copy, then the modified record will be restored to
+ * a regular MODIFIED record instead of being split.
+ */
+ GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY = (1u << 15),
+
+ /** Remove any UNMODIFIED deltas after find_similar is done.
+ *
+ * Using GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED to emulate the
+ * --find-copies-harder behavior requires building a diff with the
+ * GIT_DIFF_INCLUDE_UNMODIFIED flag. If you do not want UNMODIFIED
+ * records in the final result, pass this flag to have them removed.
+ */
+ GIT_DIFF_FIND_REMOVE_UNMODIFIED = (1u << 16),
+} git_diff_find_t;
+
+/**
+ * Pluggable similarity metric
+ */
+typedef struct {
+ int (*file_signature)(
+ void **out, const git_diff_file *file,
+ const char *fullpath, void *payload);
+ int (*buffer_signature)(
+ void **out, const git_diff_file *file,
+ const char *buf, size_t buflen, void *payload);
+ void (*free_signature)(void *sig, void *payload);
+ int (*similarity)(int *score, void *siga, void *sigb, void *payload);
+ void *payload;
+} git_diff_similarity_metric;
+
+/**
+ * Control behavior of rename and copy detection
+ *
+ * These options mostly mimic parameters that can be passed to git-diff.
+ *
+ * - `rename_threshold` is the same as the -M option with a value
+ * - `copy_threshold` is the same as the -C option with a value
+ * - `rename_from_rewrite_threshold` matches the top of the -B option
+ * - `break_rewrite_threshold` matches the bottom of the -B option
+ * - `rename_limit` is the maximum number of matches to consider for
+ * a particular file. This is a little different from the `-l` option
+ * to regular Git because we will still process up to this many matches
+ * before abandoning the search.
+ *
+ * The `metric` option allows you to plug in a custom similarity metric.
+ * Set it to NULL for the default internal metric which is based on sampling
+ * hashes of ranges of data in the file. The default metric is a pretty
+ * good similarity approximation that should work fairly well for both text
+ * and binary data, and is pretty fast with fixed memory overhead.
+ */
+typedef struct {
+ unsigned int version;
+
+ /**
+ * Combination of git_diff_find_t values (default GIT_DIFF_FIND_BY_CONFIG).
+ * NOTE: if you don't explicitly set this, `diff.renames` could be set
+ * to false, resulting in `git_diff_find_similar` doing nothing.
+ */
+ uint32_t flags;
+
+ /** Similarity to consider a file renamed (default 50) */
+ uint16_t rename_threshold;
+ /** Similarity of modified to be eligible rename source (default 50) */
+ uint16_t rename_from_rewrite_threshold;
+ /** Similarity to consider a file a copy (default 50) */
+ uint16_t copy_threshold;
+ /** Similarity to split modify into delete/add pair (default 60) */
+ uint16_t break_rewrite_threshold;
+
+ /** Maximum similarity sources to examine for a file (somewhat like
+ * git-diff's `-l` option or `diff.renameLimit` config) (default 200)
+ */
+ size_t rename_limit;
+
+ /** Pluggable similarity metric; pass NULL to use internal metric */
+ git_diff_similarity_metric *metric;
+} git_diff_find_options;
+
+#define GIT_DIFF_FIND_OPTIONS_VERSION 1
+#define GIT_DIFF_FIND_OPTIONS_INIT {GIT_DIFF_FIND_OPTIONS_VERSION}
+
+/**
+ * Initializes a `git_diff_find_options` with default values. Equivalent to
+ * creating an instance with GIT_DIFF_FIND_OPTIONS_INIT.
+ *
+ * @param opts The `git_diff_find_options` struct to initialize
+ * @param version Version of struct; pass `GIT_DIFF_FIND_OPTIONS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_diff_find_init_options(
+ git_diff_find_options *opts,
+ unsigned int version);
+
+/** @name Diff Generator Functions
+ *
+ * These are the functions you would use to create (or destroy) a
+ * git_diff from various objects in a repository.
+ */
+/**@{*/
+
+/**
+ * Deallocate a diff.
+ *
+ * @param diff The previously created diff; cannot be used after free.
+ */
+GIT_EXTERN(void) git_diff_free(git_diff *diff);
+
+/**
+ * Create a diff with the difference between two tree objects.
+ *
+ * This is equivalent to `git diff <old-tree> <new-tree>`
+ *
+ * The first tree will be used for the "old_file" side of the delta and the
+ * second tree will be used for the "new_file" side of the delta. You can
+ * pass NULL to indicate an empty tree, although it is an error to pass
+ * NULL for both the `old_tree` and `new_tree`.
+ *
+ * @param diff Output pointer to a git_diff pointer to be allocated.
+ * @param repo The repository containing the trees.
+ * @param old_tree A git_tree object to diff from, or NULL for empty tree.
+ * @param new_tree A git_tree object to diff to, or NULL for empty tree.
+ * @param opts Structure with options to influence diff or NULL for defaults.
+ */
+GIT_EXTERN(int) git_diff_tree_to_tree(
+ git_diff **diff,
+ git_repository *repo,
+ git_tree *old_tree,
+ git_tree *new_tree,
+ const git_diff_options *opts); /**< can be NULL for defaults */
+
+/**
+ * Create a diff between a tree and repository index.
+ *
+ * This is equivalent to `git diff --cached <treeish>` or if you pass
+ * the HEAD tree, then like `git diff --cached`.
+ *
+ * The tree you pass will be used for the "old_file" side of the delta, and
+ * the index will be used for the "new_file" side of the delta.
+ *
+ * If you pass NULL for the index, then the existing index of the `repo`
+ * will be used. In this case, the index will be refreshed from disk
+ * (if it has changed) before the diff is generated.
+ *
+ * @param diff Output pointer to a git_diff pointer to be allocated.
+ * @param repo The repository containing the tree and index.
+ * @param old_tree A git_tree object to diff from, or NULL for empty tree.
+ * @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_tree_to_index(
+ git_diff **diff,
+ git_repository *repo,
+ git_tree *old_tree,
+ git_index *index,
+ const git_diff_options *opts); /**< can be NULL for defaults */
+
+/**
+ * Create a diff between the repository index and the workdir directory.
+ *
+ * This matches the `git diff` command. See the note below on
+ * `git_diff_tree_to_workdir` for a discussion of the difference between
+ * `git diff` and `git diff HEAD` and how to emulate a `git diff <treeish>`
+ * using libgit2.
+ *
+ * The index will be used for the "old_file" side of the delta, and the
+ * working directory will be used for the "new_file" side of the delta.
+ *
+ * If you pass NULL for the index, then the existing index of the `repo`
+ * will be used. In this case, the index will be refreshed from disk
+ * (if it has changed) before the diff is generated.
+ *
+ * @param diff Output pointer to a git_diff 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.
+ */
+GIT_EXTERN(int) git_diff_index_to_workdir(
+ git_diff **diff,
+ git_repository *repo,
+ git_index *index,
+ const git_diff_options *opts); /**< can be NULL for defaults */
+
+/**
+ * Create a diff between a tree and the working directory.
+ *
+ * The tree you provide will be used for the "old_file" side of the delta,
+ * and the working directory will be used for the "new_file" side.
+ *
+ * This is not the same as `git diff <treeish>` or `git diff-index
+ * <treeish>`. Those commands use information from the index, whereas this
+ * function strictly returns the differences between the tree and the files
+ * in the working directory, regardless of the state of the index. Use
+ * `git_diff_tree_to_workdir_with_index` to emulate those commands.
+ *
+ * To see difference between this and `git_diff_tree_to_workdir_with_index`,
+ * consider the example of a staged file deletion where the file has then
+ * been put back into the working dir and further modified. The
+ * tree-to-workdir diff for that file is 'modified', but `git diff` would
+ * show status 'deleted' since there is a staged delete.
+ *
+ * @param diff A pointer to a git_diff pointer that will be allocated.
+ * @param repo The repository containing the tree.
+ * @param old_tree A git_tree object to diff from, or NULL for empty tree.
+ * @param opts Structure with options to influence diff or NULL for defaults.
+ */
+GIT_EXTERN(int) git_diff_tree_to_workdir(
+ git_diff **diff,
+ git_repository *repo,
+ git_tree *old_tree,
+ const git_diff_options *opts); /**< can be NULL for defaults */
+
+/**
+ * Create a diff between a tree and the working directory using index data
+ * to account for staged deletes, tracked files, etc.
+ *
+ * This emulates `git diff <tree>` by diffing the tree to the index and
+ * the index to the working directory and blending the results into a
+ * single diff that includes staged deleted, etc.
+ *
+ * @param diff A pointer to a git_diff pointer that will be allocated.
+ * @param repo The repository containing the tree.
+ * @param old_tree A git_tree object to diff from, or NULL for empty tree.
+ * @param opts Structure with options to influence diff or NULL for defaults.
+ */
+GIT_EXTERN(int) git_diff_tree_to_workdir_with_index(
+ git_diff **diff,
+ git_repository *repo,
+ git_tree *old_tree,
+ const git_diff_options *opts); /**< can be NULL for defaults */
+
+/**
+ * Merge one diff into another.
+ *
+ * This merges items from the "from" list into the "onto" list. The
+ * resulting diff will have all items that appear in either list.
+ * If an item appears in both lists, then it will be "merged" to appear
+ * as if the old version was from the "onto" list and the new version
+ * is from the "from" list (with the exception that if the item has a
+ * pending DELETE in the middle, then it will show as deleted).
+ *
+ * @param onto Diff to merge into.
+ * @param from Diff to merge.
+ */
+GIT_EXTERN(int) git_diff_merge(
+ git_diff *onto,
+ const git_diff *from);
+
+/**
+ * Transform a diff marking file renames, copies, etc.
+ *
+ * This modifies a diff 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 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 *diff,
+ const git_diff_find_options *options);
+
+/**@}*/
+
+
+/** @name Diff Processor Functions
+ *
+ * These are the functions you apply to a diff to process it
+ * or read it in some way.
+ */
+/**@{*/
+
+/**
+ * Query how many diff records are there in a diff.
+ *
+ * @param diff A git_diff generated by one of the above functions
+ * @return Count of number of deltas in the list
+ */
+GIT_EXTERN(size_t) git_diff_num_deltas(const git_diff *diff);
+
+/**
+ * Query how many diff deltas are there in a diff filtered by type.
+ *
+ * This works just like `git_diff_entrycount()` with an extra parameter
+ * that is a `git_delta_t` and returns just the count of how many deltas
+ * match that particular type.
+ *
+ * @param diff A git_diff generated by one of the above functions
+ * @param type A git_delta_t value to filter the count
+ * @return Count of number of deltas matching delta_t type
+ */
+GIT_EXTERN(size_t) git_diff_num_deltas_of_type(
+ const git_diff *diff, git_delta_t type);
+
+/**
+ * Return the diff delta for an entry in the diff list.
+ *
+ * The `git_delta` pointer points to internal data and you do not have
+ * to release it when you are done with it. It will go away when the
+ * `git_diff` (or any associated `git_patch`) goes away.
+ *
+ * Note that the flags on the delta related to whether it has binary
+ * content or not may not be set if there are no attributes set for the
+ * file and there has been no reason to load the file data at this point.
+ * For now, if you need those flags to be up to date, your only option is
+ * to either use `git_diff_foreach` or create a `git_patch`.
+ *
+ * @param diff Diff list object
+ * @param idx Index into diff list
+ * @return Pointer to git_diff_delta (or NULL if `idx` out of range)
+ */
+GIT_EXTERN(const git_diff_delta *) git_diff_get_delta(
+ const git_diff *diff, size_t idx);
+
+/**
+ * Check if deltas are sorted case sensitively or insensitively.
+ *
+ * @param diff diff to check
+ * @return 0 if case sensitive, 1 if case is ignored
+ */
+GIT_EXTERN(int) git_diff_is_sorted_icase(const git_diff *diff);
+
+/**
+ * Loop over all deltas in a diff issuing callbacks.
+ *
+ * This will iterate through all of the files described in a diff. You
+ * should provide a file callback to learn about each file.
+ *
+ * The "hunk" and "line" callbacks are optional, and the text diff of the
+ * files will only be calculated if they are not NULL. Of course, these
+ * callbacks will not be invoked for binary files on the diff or for
+ * files whose only changed is a file mode change.
+ *
+ * Returning a non-zero value from any of the callbacks will terminate
+ * the iteration and return the value to the user.
+ *
+ * @param diff A git_diff generated by one of the above functions.
+ * @param file_cb Callback function to make per file in the diff.
+ * @param hunk_cb Optional callback to make per hunk of text diff. This
+ * callback is called to describe a range of lines in the
+ * diff. It will not be issued for binary files.
+ * @param line_cb Optional callback to make per line of diff text. This
+ * same callback will be made for context lines, added, and
+ * removed lines, and even for a deleted trailing newline.
+ * @param payload Reference pointer that will be passed to your callbacks.
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_diff_foreach(
+ git_diff *diff,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb line_cb,
+ void *payload);
+
+/**
+ * Look up the single character abbreviation for a delta status code.
+ *
+ * When you run `git diff --name-status` it uses single letter codes in
+ * the output such as 'A' for added, 'D' for deleted, 'M' for modified,
+ * etc. This function converts a git_delta_t value into these letters for
+ * your own purposes. GIT_DELTA_UNTRACKED will return a space (i.e. ' ').
+ *
+ * @param status The git_delta_t value to look up
+ * @return The single character label for that code
+ */
+GIT_EXTERN(char) git_diff_status_char(git_delta_t status);
+
+/**
+ * Possible output formats for diff data
+ */
+typedef enum {
+ GIT_DIFF_FORMAT_PATCH = 1u, /**< full git diff */
+ GIT_DIFF_FORMAT_PATCH_HEADER = 2u, /**< just the file headers of patch */
+ GIT_DIFF_FORMAT_RAW = 3u, /**< like git diff --raw */
+ GIT_DIFF_FORMAT_NAME_ONLY = 4u, /**< like git diff --name-only */
+ GIT_DIFF_FORMAT_NAME_STATUS = 5u, /**< like git diff --name-status */
+} git_diff_format_t;
+
+/**
+ * Iterate over a diff generating formatted text output.
+ *
+ * Returning a non-zero value from the callbacks will terminate the
+ * iteration and return the non-zero value to the caller.
+ *
+ * @param diff A git_diff generated by one of the above functions.
+ * @param format A git_diff_format_t value to pick the text format.
+ * @param print_cb Callback to make per line of diff text.
+ * @param payload Reference pointer that will be passed to your callback.
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_diff_print(
+ git_diff *diff,
+ git_diff_format_t format,
+ git_diff_line_cb print_cb,
+ void *payload);
+
+/**@}*/
+
+
+/*
+ * Misc
+ */
+
+/**
+ * Directly run a diff on two blobs.
+ *
+ * Compared to a file, a blob lacks some contextual information. As such,
+ * the `git_diff_file` given to the callback will have some fake data; i.e.
+ * `mode` will be 0 and `path` will be NULL.
+ *
+ * NULL is allowed for either `old_blob` or `new_blob` and will be treated
+ * as an empty blob, with the `oid` set to NULL in the `git_diff_file` data.
+ * Passing NULL for both blobs is a noop; no callbacks will be made at all.
+ *
+ * We do run a binary content check on the blob content and if either blob
+ * looks like binary data, the `git_diff_delta` binary attribute will be set
+ * to 1 and no call to the hunk_cb nor line_cb will be made (unless you pass
+ * `GIT_DIFF_FORCE_TEXT` of course).
+ *
+ * @param old_blob Blob for old side of diff, or NULL for empty blob
+ * @param old_as_path Treat old blob as if it had this filename; can be NULL
+ * @param new_blob Blob for new side of diff, or NULL for empty blob
+ * @param new_as_path Treat new blob as if it had this filename; can be NULL
+ * @param options Options for diff, or NULL for default options
+ * @param file_cb Callback for "file"; made once if there is a diff; can be NULL
+ * @param hunk_cb Callback for each hunk in diff; can be NULL
+ * @param line_cb Callback for each line in diff; can be NULL
+ * @param payload Payload passed to each callback function
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_diff_blobs(
+ const git_blob *old_blob,
+ const char *old_as_path,
+ const git_blob *new_blob,
+ const char *new_as_path,
+ const git_diff_options *options,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb line_cb,
+ void *payload);
+
+/**
+ * Directly run a diff between a blob and a buffer.
+ *
+ * As with `git_diff_blobs`, comparing a blob and buffer lacks some context,
+ * so the `git_diff_file` parameters to the callbacks will be faked a la the
+ * rules for `git_diff_blobs()`.
+ *
+ * Passing NULL for `old_blob` will be treated as an empty blob (i.e. the
+ * `file_cb` will be invoked with GIT_DELTA_ADDED and the diff will be the
+ * entire content of the buffer added). Passing NULL to the buffer will do
+ * the reverse, with GIT_DELTA_REMOVED and blob content removed.
+ *
+ * @param old_blob Blob for old side of diff, or NULL for empty blob
+ * @param old_as_path Treat old blob as if it had this filename; can be NULL
+ * @param buffer Raw data for new side of diff, or NULL for empty
+ * @param buffer_len Length of raw data for new side of diff
+ * @param buffer_as_path Treat buffer as if it had this filename; can be NULL
+ * @param options Options for diff, or NULL for default options
+ * @param file_cb Callback for "file"; made once if there is a diff; can be NULL
+ * @param hunk_cb Callback for each hunk in diff; can be NULL
+ * @param line_cb Callback for each line in diff; can be NULL
+ * @param payload Payload passed to each callback function
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_diff_blob_to_buffer(
+ const git_blob *old_blob,
+ const char *old_as_path,
+ const char *buffer,
+ size_t buffer_len,
+ const char *buffer_as_path,
+ const git_diff_options *options,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb line_cb,
+ void *payload);
+
+/**
+ * Directly run a diff between two buffers.
+ *
+ * Even more than with `git_diff_blobs`, comparing two buffer lacks
+ * context, so the `git_diff_file` parameters to the callbacks will be
+ * faked a la the rules for `git_diff_blobs()`.
+ *
+ * @param old_buffer Raw data for old side of diff, or NULL for empty
+ * @param old_len Length of the raw data for old side of the diff
+ * @param old_as_path Treat old buffer as if it had this filename; can be NULL
+ * @param new_buffer Raw data for new side of diff, or NULL for empty
+ * @param new_len Length of raw data for new side of diff
+ * @param new_as_path Treat buffer as if it had this filename; can be NULL
+ * @param options Options for diff, or NULL for default options
+ * @param file_cb Callback for "file"; made once if there is a diff; can be NULL
+ * @param hunk_cb Callback for each hunk in diff; can be NULL
+ * @param line_cb Callback for each line in diff; can be NULL
+ * @param payload Payload passed to each callback function
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_diff_buffers(
+ const void *old_buffer,
+ size_t old_len,
+ const char *old_as_path,
+ const void *new_buffer,
+ size_t new_len,
+ const char *new_as_path,
+ const git_diff_options *options,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb line_cb,
+ void *payload);
+
+/**
+ * This is an opaque structure which is allocated by `git_diff_get_stats`.
+ * You are responsible for releasing the object memory when done, using the
+ * `git_diff_stats_free()` function.
+ */
+typedef struct git_diff_stats git_diff_stats;
+
+/**
+ * Formatting options for diff stats
+ */
+typedef enum {
+ /** No stats*/
+ GIT_DIFF_STATS_NONE = 0,
+
+ /** Full statistics, equivalent of `--stat` */
+ GIT_DIFF_STATS_FULL = (1u << 0),
+
+ /** Short statistics, equivalent of `--shortstat` */
+ GIT_DIFF_STATS_SHORT = (1u << 1),
+
+ /** Number statistics, equivalent of `--numstat` */
+ GIT_DIFF_STATS_NUMBER = (1u << 2),
+
+ /** Extended header information such as creations, renames and mode changes, equivalent of `--summary` */
+ GIT_DIFF_STATS_INCLUDE_SUMMARY = (1u << 3),
+} git_diff_stats_format_t;
+
+/**
+ * Accumlate diff statistics for all patches.
+ *
+ * @param out Structure containg the diff statistics.
+ * @param diff A git_diff generated by one of the above functions.
+ * @return 0 on success; non-zero on error
+ */
+GIT_EXTERN(int) git_diff_get_stats(
+ git_diff_stats **out,
+ git_diff *diff);
+
+/**
+ * Get the total number of files changed in a diff
+ *
+ * @param stats A `git_diff_stats` generated by one of the above functions.
+ * @return total number of files changed in the diff
+ */
+GIT_EXTERN(size_t) git_diff_stats_files_changed(
+ const git_diff_stats *stats);
+
+/**
+ * Get the total number of insertions in a diff
+ *
+ * @param stats A `git_diff_stats` generated by one of the above functions.
+ * @return total number of insertions in the diff
+ */
+GIT_EXTERN(size_t) git_diff_stats_insertions(
+ const git_diff_stats *stats);
+
+/**
+ * Get the total number of deletions in a diff
+ *
+ * @param stats A `git_diff_stats` generated by one of the above functions.
+ * @return total number of deletions in the diff
+ */
+GIT_EXTERN(size_t) git_diff_stats_deletions(
+ const git_diff_stats *stats);
+
+/**
+ * Print diff statistics to a `git_buf`.
+ *
+ * @param out buffer to store the formatted diff statistics in.
+ * @param stats A `git_diff_stats` generated by one of the above functions.
+ * @param format Formatting option.
+ * @param width Target width for output (only affects GIT_DIFF_STATS_FULL)
+ * @return 0 on success; non-zero on error
+ */
+GIT_EXTERN(int) git_diff_stats_to_buf(
+ git_buf *out,
+ const git_diff_stats *stats,
+ git_diff_stats_format_t format,
+ size_t width);
+
+/**
+ * Deallocate a `git_diff_stats`.
+ *
+ * @param stats The previously created statistics object;
+ * cannot be used after free.
+ */
+GIT_EXTERN(void) git_diff_stats_free(git_diff_stats *stats);
+
+/**
+ * Formatting options for diff e-mail generation
+ */
+typedef enum {
+ /** Normal patch, the default */
+ GIT_DIFF_FORMAT_EMAIL_NONE = 0,
+
+ /** Don't insert "[PATCH]" in the subject header*/
+ GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER = (1 << 0),
+
+} git_diff_format_email_flags_t;
+
+/**
+ * Options for controlling the formatting of the generated e-mail.
+ */
+typedef struct {
+ unsigned int version;
+
+ git_diff_format_email_flags_t flags;
+
+ /** This patch number */
+ size_t patch_no;
+
+ /** Total number of patches in this series */
+ size_t total_patches;
+
+ /** id to use for the commit */
+ const git_oid *id;
+
+ /** Summary of the change */
+ const char *summary;
+
+ /** Author of the change */
+ const git_signature *author;
+} git_diff_format_email_options;
+
+#define GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION 1
+#define GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT {GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION, 0, 1, 1, NULL, NULL, NULL}
+
+/**
+ * Create an e-mail ready patch from a diff.
+ *
+ * @param out buffer to store the e-mail patch in
+ * @param diff containing the commit
+ * @param opts structure with options to influence content and formatting.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_diff_format_email(
+ git_buf *out,
+ git_diff *diff,
+ const git_diff_format_email_options *opts);
+
+/**
+ * Create an e-mail ready patch for a commit.
+ *
+ * Does not support creating patches for merge commits (yet).
+ *
+ * @param out buffer to store the e-mail patch in
+ * @param repo containing the commit
+ * @param commit pointer to up commit
+ * @param patch_no patch number of the commit
+ * @param total_patches total number of patches in the patch set
+ * @param flags determines the formatting of the e-mail
+ * @param diff_opts structure with options to influence diff or NULL for defaults.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_diff_commit_as_email(
+ git_buf *out,
+ git_repository *repo,
+ git_commit *commit,
+ size_t patch_no,
+ size_t total_patches,
+ git_diff_format_email_flags_t flags,
+ const git_diff_options *diff_opts);
+
+/**
+ * Initializes a `git_diff_format_email_options` with default values.
+ *
+ * Equivalent to creating an instance with GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT.
+ *
+ * @param opts The `git_diff_format_email_options` struct to initialize
+ * @param version Version of struct; pass `GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_diff_format_email_init_options(
+ git_diff_format_email_options *opts,
+ unsigned int version);
+
+GIT_END_DECL
+
+/** @} */
+
+#endif
diff --git a/include/git2/errors.h b/include/git2/errors.h
index 710ac244b..c914653fc 100644
--- a/include/git2/errors.h
+++ b/include/git2/errors.h
@@ -1,26 +1,8 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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_errors_h__
#define INCLUDE_git_errors_h__
@@ -35,127 +17,136 @@
*/
GIT_BEGIN_DECL
+/** Generic return codes */
typedef enum {
- GIT_SUCCESS = 0,
- GIT_ERROR = -1,
-
- /** Input was not a properly formatted Git object id. */
- GIT_ENOTOID = -2,
-
- /** Input does not exist in the scope searched. */
- GIT_ENOTFOUND = -3,
-
- /** Not enough space available. */
- GIT_ENOMEM = -4,
-
- /** Consult the OS error information. */
- GIT_EOSERR = -5,
-
- /** The specified object is of invalid type */
- GIT_EOBJTYPE = -6,
-
- /** The specified repository is invalid */
- GIT_ENOTAREPO = -7,
-
- /** The object type is invalid or doesn't match */
- GIT_EINVALIDTYPE = -8,
-
- /** The object cannot be written because it's missing internal data */
- GIT_EMISSINGOBJDATA = -9,
-
- /** The packfile for the ODB is corrupted */
- GIT_EPACKCORRUPTED = -10,
-
- /** Failed to acquire or release a file lock */
- GIT_EFLOCKFAIL = -11,
-
- /** The Z library failed to inflate/deflate an object's data */
- GIT_EZLIB = -12,
-
- /** The queried object is currently busy */
- GIT_EBUSY = -13,
-
- /** The index file is not backed up by an existing repository */
- GIT_EBAREINDEX = -14,
-
- /** The name of the reference is not valid */
- GIT_EINVALIDREFNAME = -15,
-
- /** The specified reference has its data corrupted */
- GIT_EREFCORRUPTED = -16,
-
- /** The specified symbolic reference is too deeply nested */
- GIT_ETOONESTEDSYMREF = -17,
-
- /** The pack-refs file is either corrupted or its format is not currently supported */
- GIT_EPACKEDREFSCORRUPTED = -18,
-
- /** The path is invalid */
- GIT_EINVALIDPATH = -19,
-
- /** The revision walker is empty; there are no more commits left to iterate */
- GIT_EREVWALKOVER = -20,
-
- /** The state of the reference is not valid */
- GIT_EINVALIDREFSTATE = -21,
-
- /** This feature has not been implemented yet */
- GIT_ENOTIMPLEMENTED = -22,
+ GIT_OK = 0, /**< No error */
+
+ GIT_ERROR = -1, /**< Generic error */
+ GIT_ENOTFOUND = -3, /**< Requested object could not be found */
+ GIT_EEXISTS = -4, /**< Object exists preventing operation */
+ GIT_EAMBIGUOUS = -5, /**< More than one object matches */
+ GIT_EBUFS = -6, /**< Output buffer too short to hold data */
+
+ /* GIT_EUSER is a special error that is never generated by libgit2
+ * code. You can return it from a callback (e.g to stop an iteration)
+ * to know that it was generated by the callback and not by libgit2.
+ */
+ GIT_EUSER = -7,
+
+ GIT_EBAREREPO = -8, /**< Operation not allowed on bare repository */
+ GIT_EUNBORNBRANCH = -9, /**< HEAD refers to branch with no commits */
+ GIT_EUNMERGED = -10, /**< Merge in progress prevented operation */
+ GIT_ENONFASTFORWARD = -11, /**< Reference was not fast-forwardable */
+ GIT_EINVALIDSPEC = -12, /**< Name/ref spec was not in a valid format */
+ GIT_EMERGECONFLICT = -13, /**< Merge conflicts prevented operation */
+ GIT_ELOCKED = -14, /**< Lock file prevented operation */
+ GIT_EMODIFIED = -15, /**< Reference value does not match expected */
+
+ GIT_PASSTHROUGH = -30, /**< Internal only */
+ GIT_ITEROVER = -31, /**< Signals end of iteration with iterator */
+} git_error_code;
- /** A reference with this name already exists */
- GIT_EEXISTS = -23,
-
- /** The given integer literal is too large to be parsed */
- GIT_EOVERFLOW = -24,
-
- /** The given literal is not a valid number */
- GIT_ENOTNUM = -25,
-
- /** Streaming error */
- GIT_ESTREAM = -26,
-
- /** invalid arguments to function */
- GIT_EINVALIDARGS = -27,
-
- /** The specified object has its data corrupted */
- GIT_EOBJCORRUPTED = -28,
-
- /** The given short oid is ambiguous */
- GIT_EAMBIGUOUSOIDPREFIX = -29,
+/**
+ * Structure to store extra details of the last error that occurred.
+ *
+ * This is kept on a per-thread basis if GIT_THREADS was defined when the
+ * library was build, otherwise one is kept globally for the library
+ */
+typedef struct {
+ char *message;
+ int klass;
+} git_error;
- /** Skip and passthrough the given ODB backend */
- GIT_EPASSTHROUGH = -30,
+/** Error classes */
+typedef enum {
+ GITERR_NONE = 0,
+ GITERR_NOMEMORY,
+ GITERR_OS,
+ GITERR_INVALID,
+ GITERR_REFERENCE,
+ GITERR_ZLIB,
+ GITERR_REPOSITORY,
+ GITERR_CONFIG,
+ GITERR_REGEX,
+ GITERR_ODB,
+ GITERR_INDEX,
+ GITERR_OBJECT,
+ GITERR_NET,
+ GITERR_TAG,
+ GITERR_TREE,
+ GITERR_INDEXER,
+ GITERR_SSL,
+ GITERR_SUBMODULE,
+ GITERR_THREAD,
+ GITERR_STASH,
+ GITERR_CHECKOUT,
+ GITERR_FETCHHEAD,
+ GITERR_MERGE,
+ GITERR_SSH,
+ GITERR_FILTER,
+ GITERR_REVERT,
+ GITERR_CALLBACK,
+ GITERR_CHERRYPICK,
+} git_error_t;
- /** The path pattern and string did not match */
- GIT_ENOMATCH = -31,
+/**
+ * Return the last `git_error` object that was generated for the
+ * current thread or NULL if no error has occurred.
+ *
+ * @return A git_error object.
+ */
+GIT_EXTERN(const git_error *) giterr_last(void);
- /** The buffer is too short to satisfy the request */
- GIT_ESHORTBUFFER = -32,
-} git_error;
+/**
+ * Clear the last library error that occurred for this thread.
+ */
+GIT_EXTERN(void) giterr_clear(void);
/**
- * Return a detailed error string with the latest error
- * that occurred in the library.
- * @return a string explaining the error
+ * Get the last error data and clear it.
+ *
+ * This copies the last error into the given `git_error` struct
+ * and returns 0 if the copy was successful, leaving the error
+ * cleared as if `giterr_clear` had been called.
+ *
+ * If there was no existing error in the library, -1 will be returned
+ * and the contents of `cpy` will be left unmodified.
*/
-GIT_EXTERN(const char *) git_lasterror(void);
+GIT_EXTERN(int) giterr_detach(git_error *cpy);
/**
- * strerror() for the Git library
+ * Set the error message string for this thread.
+ *
+ * This function is public so that custom ODB backends and the like can
+ * relay an error message through libgit2. Most regular users of libgit2
+ * will never need to call this function -- actually, calling it in most
+ * circumstances (for example, calling from within a callback function)
+ * will just end up having the value overwritten by libgit2 internals.
*
- * Get a string description for a given error code.
- * NOTE: This method will be eventually deprecated in favor
- * of the new `git_lasterror`.
+ * This error message is stored in thread-local storage and only applies
+ * to the particular thread that this libgit2 call is made from.
*
- * @param num The error code to explain
- * @return a string explaining the error code
+ * NOTE: Passing the `error_class` as GITERR_OS has a special behavior: we
+ * attempt to append the system default error message for the last OS error
+ * that occurred and then clear the last error. The specific implementation
+ * of looking up and clearing this last OS error will vary by platform.
+ *
+ * @param error_class One of the `git_error_t` enum above describing the
+ * general subsystem that is responsible for the error.
+ * @param string The formatted error message to keep
*/
-GIT_EXTERN(const char *) git_strerror(int num);
+GIT_EXTERN(void) giterr_set_str(int error_class, const char *string);
/**
- * Clear the latest library error
+ * Set the error message to a special value for memory allocation failure.
+ *
+ * The normal `giterr_set_str()` function attempts to `strdup()` the string
+ * that is passed in. This is not a good idea when the error in question
+ * is a memory allocation failure. That circumstance has a special setter
+ * function that sets the error string to a known and statically allocated
+ * internal value.
*/
-GIT_EXTERN(void) git_clearerror(void);
+GIT_EXTERN(void) giterr_set_oom(void);
/** @} */
GIT_END_DECL
diff --git a/include/git2/filter.h b/include/git2/filter.h
new file mode 100644
index 000000000..e57a67e73
--- /dev/null
+++ b/include/git2/filter.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_filter_h__
+#define INCLUDE_git_filter_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+#include "buffer.h"
+
+/**
+ * @file git2/filter.h
+ * @brief Git filter APIs
+ *
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Filters are applied in one of two directions: smudging - which is
+ * exporting a file from the Git object database to the working directory,
+ * and cleaning - which is importing a file from the working directory to
+ * the Git object database. These values control which direction of
+ * change is being applied.
+ */
+typedef enum {
+ GIT_FILTER_TO_WORKTREE = 0,
+ GIT_FILTER_SMUDGE = GIT_FILTER_TO_WORKTREE,
+ GIT_FILTER_TO_ODB = 1,
+ GIT_FILTER_CLEAN = GIT_FILTER_TO_ODB,
+} git_filter_mode_t;
+
+typedef enum {
+ GIT_FILTER_OPT_DEFAULT = 0u,
+ GIT_FILTER_OPT_ALLOW_UNSAFE = (1u << 0),
+} git_filter_opt_t;
+
+/**
+ * A filter that can transform file data
+ *
+ * This represents a filter that can be used to transform or even replace
+ * file data. Libgit2 includes one built in filter and it is possible to
+ * write your own (see git2/sys/filter.h for information on that).
+ *
+ * The two builtin filters are:
+ *
+ * * "crlf" which uses the complex rules with the "text", "eol", and
+ * "crlf" file attributes to decide how to convert between LF and CRLF
+ * line endings
+ * * "ident" which replaces "$Id$" in a blob with "$Id: <blob OID>$" upon
+ * checkout and replaced "$Id: <anything>$" with "$Id$" on checkin.
+ */
+typedef struct git_filter git_filter;
+
+/**
+ * List of filters to be applied
+ *
+ * This represents a list of filters to be applied to a file / blob. You
+ * can build the list with one call, apply it with another, and dispose it
+ * with a third. In typical usage, there are not many occasions where a
+ * git_filter_list is needed directly since the library will generally
+ * handle conversions for you, but it can be convenient to be able to
+ * build and apply the list sometimes.
+ */
+typedef struct git_filter_list git_filter_list;
+
+/**
+ * Load the filter list for a given path.
+ *
+ * This will return 0 (success) but set the output git_filter_list to NULL
+ * if no filters are requested for the given file.
+ *
+ * @param filters Output newly created git_filter_list (or NULL)
+ * @param repo Repository object that contains `path`
+ * @param blob The blob to which the filter will be applied (if known)
+ * @param path Relative path of the file to be filtered
+ * @param mode Filtering direction (WT->ODB or ODB->WT)
+ * @param options Combination of `git_filter_opt_t` flags
+ * @return 0 on success (which could still return NULL if no filters are
+ * needed for the requested file), <0 on error
+ */
+GIT_EXTERN(int) git_filter_list_load(
+ git_filter_list **filters,
+ git_repository *repo,
+ git_blob *blob, /* can be NULL */
+ const char *path,
+ git_filter_mode_t mode,
+ uint32_t options);
+
+/**
+ * Apply filter list to a data buffer.
+ *
+ * See `git2/buffer.h` for background on `git_buf` objects.
+ *
+ * If the `in` buffer holds data allocated by libgit2 (i.e. `in->asize` is
+ * not zero), then it will be overwritten when applying the filters. If
+ * not, then it will be left untouched.
+ *
+ * If there are no filters to apply (or `filters` is NULL), then the `out`
+ * buffer will reference the `in` buffer data (with `asize` set to zero)
+ * instead of allocating data. This keeps allocations to a minimum, but
+ * it means you have to be careful about freeing the `in` data since `out`
+ * may be pointing to it!
+ *
+ * @param out Buffer to store the result of the filtering
+ * @param filters A loaded git_filter_list (or NULL)
+ * @param in Buffer containing the data to filter
+ * @return 0 on success, an error code otherwise
+ */
+GIT_EXTERN(int) git_filter_list_apply_to_data(
+ git_buf *out,
+ git_filter_list *filters,
+ git_buf *in);
+
+/**
+ * Apply filter list to the contents of a file on disk
+ */
+GIT_EXTERN(int) git_filter_list_apply_to_file(
+ git_buf *out,
+ git_filter_list *filters,
+ git_repository *repo,
+ const char *path);
+
+/**
+ * Apply filter list to the contents of a blob
+ */
+GIT_EXTERN(int) git_filter_list_apply_to_blob(
+ git_buf *out,
+ git_filter_list *filters,
+ git_blob *blob);
+
+/**
+ * Free a git_filter_list
+ *
+ * @param filters A git_filter_list created by `git_filter_list_load`
+ */
+GIT_EXTERN(void) git_filter_list_free(git_filter_list *filters);
+
+
+GIT_END_DECL
+
+/** @} */
+
+#endif
diff --git a/include/git2/graph.h b/include/git2/graph.h
new file mode 100644
index 000000000..c997d8ca9
--- /dev/null
+++ b/include/git2/graph.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_graph_h__
+#define INCLUDE_git_graph_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+
+/**
+ * @file git2/graph.h
+ * @brief Git graph traversal routines
+ * @defgroup git_revwalk Git graph traversal routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Count the number of unique commits between two commit objects
+ *
+ * There is no need for branches containing the commits to have any
+ * upstream relationship, but it helps to think of one as a branch and
+ * the other as its upstream, the `ahead` and `behind` values will be
+ * what git would report for the branches.
+ *
+ * @param ahead number of unique from commits in `upstream`
+ * @param behind number of unique from commits in `local`
+ * @param repo the repository where the commits exist
+ * @param local the commit for local
+ * @param upstream the commit for upstream
+ */
+GIT_EXTERN(int) git_graph_ahead_behind(size_t *ahead, size_t *behind, git_repository *repo, const git_oid *local, const git_oid *upstream);
+
+
+/**
+ * Determine if a commit is the descendant of another commit.
+ *
+ * @param commit a previously loaded commit.
+ * @param ancestor a potential ancestor commit.
+ * @return 1 if the given commit is a descendant of the potential ancestor,
+ * 0 if not, error code otherwise.
+ */
+GIT_EXTERN(int) git_graph_descendant_of(
+ git_repository *repo,
+ const git_oid *commit,
+ const git_oid *ancestor);
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/ignore.h b/include/git2/ignore.h
new file mode 100644
index 000000000..d0c1877a8
--- /dev/null
+++ b/include/git2/ignore.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_ignore_h__
+#define INCLUDE_git_ignore_h__
+
+#include "common.h"
+#include "types.h"
+
+GIT_BEGIN_DECL
+
+/**
+ * Add ignore rules for a repository.
+ *
+ * Excludesfile rules (i.e. .gitignore rules) are generally read from
+ * .gitignore files in the repository tree or from a shared system file
+ * only if a "core.excludesfile" config value is set. The library also
+ * keeps a set of per-repository internal ignores that can be configured
+ * in-memory and will not persist. This function allows you to add to
+ * that internal rules list.
+ *
+ * Example usage:
+ *
+ * error = git_ignore_add_rule(myrepo, "*.c\ndir/\nFile with space\n");
+ *
+ * This would add three rules to the ignores.
+ *
+ * @param repo The repository to add ignore rules to.
+ * @param rules Text of rules, a la the contents of a .gitignore file.
+ * It is okay to have multiple rules in the text; if so,
+ * each rule should be terminated with a newline.
+ * @return 0 on success
+ */
+GIT_EXTERN(int) git_ignore_add_rule(
+ git_repository *repo,
+ const char *rules);
+
+/**
+ * Clear ignore rules that were explicitly added.
+ *
+ * Resets to the default internal ignore rules. This will not turn off
+ * rules in .gitignore files that actually exist in the filesystem.
+ *
+ * The default internal ignores ignore ".", ".." and ".git" entries.
+ *
+ * @param repo The repository to remove ignore rules from.
+ * @return 0 on success
+ */
+GIT_EXTERN(int) git_ignore_clear_internal_rules(
+ git_repository *repo);
+
+/**
+ * Test if the ignore rules apply to a given path.
+ *
+ * This function checks the ignore rules to see if they would apply to the
+ * given file. This indicates if the file would be ignored regardless of
+ * whether the file is already in the index or committed to the repository.
+ *
+ * One way to think of this is if you were to do "git add ." on the
+ * directory containing the file, would it be added or not?
+ *
+ * @param ignored boolean returning 0 if the file is not ignored, 1 if it is
+ * @param repo a repository object
+ * @param path the file to check ignores for, relative to the repo's workdir.
+ * @return 0 if ignore rules could be processed for the file (regardless
+ * of whether it exists or not), or an error < 0 if they could not.
+ */
+GIT_EXTERN(int) git_ignore_path_is_ignored(
+ int *ignored,
+ git_repository *repo,
+ const char *path);
+
+GIT_END_DECL
+
+#endif
diff --git a/include/git2/index.h b/include/git2/index.h
index 18ab9b858..0b4476b4e 100644
--- a/include/git2/index.h
+++ b/include/git2/index.h
@@ -1,33 +1,17 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_index_h__
#define INCLUDE_git_index_h__
#include "common.h"
+#include "indexer.h"
#include "types.h"
#include "oid.h"
+#include "strarray.h"
/**
* @file git2/index.h
@@ -38,50 +22,29 @@
*/
GIT_BEGIN_DECL
-#define GIT_IDXENTRY_NAMEMASK (0x0fff)
-#define GIT_IDXENTRY_STAGEMASK (0x3000)
-#define GIT_IDXENTRY_EXTENDED (0x4000)
-#define GIT_IDXENTRY_VALID (0x8000)
-#define GIT_IDXENTRY_STAGESHIFT 12
-
-/*
- * Flags are divided into two parts: in-memory flags and
- * on-disk ones. Flags in GIT_IDXENTRY_EXTENDED_FLAGS
- * will get saved on-disk.
- *
- * In-memory only flags:
- */
-#define GIT_IDXENTRY_UPDATE (1 << 0)
-#define GIT_IDXENTRY_REMOVE (1 << 1)
-#define GIT_IDXENTRY_UPTODATE (1 << 2)
-#define GIT_IDXENTRY_ADDED (1 << 3)
-
-#define GIT_IDXENTRY_HASHED (1 << 4)
-#define GIT_IDXENTRY_UNHASHED (1 << 5)
-#define GIT_IDXENTRY_WT_REMOVE (1 << 6) /* remove in work directory */
-#define GIT_IDXENTRY_CONFLICTED (1 << 7)
-
-#define GIT_IDXENTRY_UNPACKED (1 << 8)
-#define GIT_IDXENTRY_NEW_SKIP_WORKTREE (1 << 9)
-
-/*
- * Extended on-disk flags:
- */
-#define GIT_IDXENTRY_INTENT_TO_ADD (1 << 13)
-#define GIT_IDXENTRY_SKIP_WORKTREE (1 << 14)
-/* GIT_IDXENTRY_EXTENDED2 is for future extension */
-#define GIT_IDXENTRY_EXTENDED2 (1 << 15)
-
-#define GIT_IDXENTRY_EXTENDED_FLAGS (GIT_IDXENTRY_INTENT_TO_ADD | GIT_IDXENTRY_SKIP_WORKTREE)
-
-/** Time used in a git index entry */
+/** Time structure used in a git index entry */
typedef struct {
git_time_t seconds;
/* nsec should not be stored as time_t compatible */
unsigned int nanoseconds;
} git_index_time;
-/** Memory representation of a file entry in the index. */
+/**
+ * In-memory representation of a file entry in the index.
+ *
+ * This is a public structure that represents a file entry in the index.
+ * The meaning of the fields corresponds to core Git's documentation (in
+ * "Documentation/technical/index-format.txt").
+ *
+ * The `flags` field consists of a number of bit fields which can be
+ * accessed via the first set of `GIT_IDXENTRY_...` bitmasks below. These
+ * flags are all read from and persisted to disk.
+ *
+ * The `flags_extended` field also has a number of bit fields which can be
+ * accessed via the later `GIT_IDXENTRY_...` bitmasks below. Some of
+ * these flags are read from and written to disk, but some are set aside
+ * for in-memory only reference.
+ */
typedef struct git_index_entry {
git_index_time ctime;
git_index_time mtime;
@@ -93,20 +56,109 @@ typedef struct git_index_entry {
unsigned int gid;
git_off_t file_size;
- git_oid oid;
+ git_oid id;
unsigned short flags;
unsigned short flags_extended;
- char *path;
+ const char *path;
} git_index_entry;
-/** Representation of an unmerged file entry in the index. */
-typedef struct git_index_entry_unmerged {
- unsigned int mode[3];
- git_oid oid[3];
- char *path;
-} git_index_entry_unmerged;
+/**
+ * Bitmasks for on-disk fields of `git_index_entry`'s `flags`
+ *
+ * These bitmasks match the four fields in the `git_index_entry` `flags`
+ * value both in memory and on disk. You can use them to interpret the
+ * data in the `flags`.
+ */
+#define GIT_IDXENTRY_NAMEMASK (0x0fff)
+#define GIT_IDXENTRY_STAGEMASK (0x3000)
+#define GIT_IDXENTRY_STAGESHIFT 12
+
+typedef enum {
+ GIT_IDXENTRY_EXTENDED = (0x4000),
+ GIT_IDXENTRY_VALID = (0x8000),
+} git_indxentry_flag_t;
+
+#define GIT_IDXENTRY_STAGE(E) \
+ (((E)->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT)
+
+#define GIT_IDXENTRY_STAGE_SET(E,S) do { \
+ (E)->flags = ((E)->flags & ~GIT_IDXENTRY_STAGEMASK) | \
+ (((S) & 0x03) << GIT_IDXENTRY_STAGESHIFT); } while (0)
+
+/**
+ * Bitmasks for on-disk fields of `git_index_entry`'s `flags_extended`
+ *
+ * In memory, the `flags_extended` fields are divided into two parts: the
+ * fields that are read from and written to disk, and other fields that
+ * in-memory only and used by libgit2. Only the flags in
+ * `GIT_IDXENTRY_EXTENDED_FLAGS` will get saved on-disk.
+ *
+ * Thee first three bitmasks match the three fields in the
+ * `git_index_entry` `flags_extended` value that belong on disk. You
+ * can use them to interpret the data in the `flags_extended`.
+ *
+ * The rest of the bitmasks match the other fields in the `git_index_entry`
+ * `flags_extended` value that are only used in-memory by libgit2.
+ * You can use them to interpret the data in the `flags_extended`.
+ *
+ */
+typedef enum {
+
+ GIT_IDXENTRY_INTENT_TO_ADD = (1 << 13),
+ GIT_IDXENTRY_SKIP_WORKTREE = (1 << 14),
+ /** Reserved for future extension */
+ GIT_IDXENTRY_EXTENDED2 = (1 << 15),
+
+ GIT_IDXENTRY_EXTENDED_FLAGS = (GIT_IDXENTRY_INTENT_TO_ADD | GIT_IDXENTRY_SKIP_WORKTREE),
+ GIT_IDXENTRY_UPDATE = (1 << 0),
+ GIT_IDXENTRY_REMOVE = (1 << 1),
+ GIT_IDXENTRY_UPTODATE = (1 << 2),
+ GIT_IDXENTRY_ADDED = (1 << 3),
+
+ GIT_IDXENTRY_HASHED = (1 << 4),
+ GIT_IDXENTRY_UNHASHED = (1 << 5),
+ GIT_IDXENTRY_WT_REMOVE = (1 << 6), /**< remove in work directory */
+ GIT_IDXENTRY_CONFLICTED = (1 << 7),
+
+ GIT_IDXENTRY_UNPACKED = (1 << 8),
+ GIT_IDXENTRY_NEW_SKIP_WORKTREE = (1 << 9),
+} git_idxentry_extended_flag_t;
+
+/** Capabilities of system that affect index actions. */
+typedef enum {
+ GIT_INDEXCAP_IGNORE_CASE = 1,
+ GIT_INDEXCAP_NO_FILEMODE = 2,
+ GIT_INDEXCAP_NO_SYMLINKS = 4,
+ GIT_INDEXCAP_FROM_OWNER = -1,
+} git_indexcap_t;
+
+/** Callback for APIs that add/remove/update files matching pathspec */
+typedef int (*git_index_matched_path_cb)(
+ const char *path, const char *matched_pathspec, void *payload);
+
+/** Flags for APIs that add files matching pathspec */
+typedef enum {
+ GIT_INDEX_ADD_DEFAULT = 0,
+ GIT_INDEX_ADD_FORCE = (1u << 0),
+ GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH = (1u << 1),
+ GIT_INDEX_ADD_CHECK_PATHSPEC = (1u << 2),
+} git_index_add_option_t;
+
+/**
+ * Match any index stage.
+ *
+ * Some index APIs take a stage to match; pass this value to match
+ * any entry matching the path regardless of stage.
+ */
+#define GIT_INDEX_STAGE_ANY -1
+
+/** @name Index File Functions
+ *
+ * These functions work on the index file itself.
+ */
+/**@{*/
/**
* Create a new bare Git index object as a memory representation
@@ -114,28 +166,32 @@ typedef struct git_index_entry_unmerged {
* to back it.
*
* Since there is no ODB or working directory behind this index,
- * any Index methods which rely on these (e.g. index_add) will
- * fail with the GIT_EBAREINDEX error code.
+ * any Index methods which rely on these (e.g. index_add_bypath)
+ * will fail with the GIT_ERROR error code.
*
* If you need to access the index of an actual repository,
* use the `git_repository_index` wrapper.
*
* The index must be freed once it's no longer in use.
*
- * @param index the pointer for the new index
+ * @param out the pointer for the new index
* @param index_path the path to the index file in disk
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_index_open(git_index **index, const char *index_path);
+GIT_EXTERN(int) git_index_open(git_index **out, const char *index_path);
/**
- * Clear the contents (all the entries) of an index object.
- * This clears the index object in memory; changes must be manually
- * written to disk for them to take effect.
+ * Create an in-memory index object.
*
- * @param index an existing index object
+ * This index object cannot be read/written to the filesystem,
+ * but may be used to perform in-memory index operations.
+ *
+ * The index must be freed once it's no longer in use.
+ *
+ * @param out the pointer for the new index
+ * @return 0 or an error code
*/
-GIT_EXTERN(void) git_index_clear(git_index *index);
+GIT_EXTERN(int) git_index_new(git_index **out);
/**
* Free an existing index object.
@@ -145,179 +201,498 @@ GIT_EXTERN(void) git_index_clear(git_index *index);
GIT_EXTERN(void) git_index_free(git_index *index);
/**
- * Update the contents of an existing index object in memory
- * by reading from the hard disk.
+ * Get the repository this index relates to
+ *
+ * @param index The index
+ * @return A pointer to the repository
+ */
+GIT_EXTERN(git_repository *) git_index_owner(const git_index *index);
+
+/**
+ * Read index capabilities flags.
+ *
+ * @param index An existing index object
+ * @return A combination of GIT_INDEXCAP values
+ */
+GIT_EXTERN(int) git_index_caps(const git_index *index);
+
+/**
+ * Set index capabilities flags.
+ *
+ * If you pass `GIT_INDEXCAP_FROM_OWNER` for the caps, then the
+ * capabilities will be read from the config of the owner object,
+ * looking at `core.ignorecase`, `core.filemode`, `core.symlinks`.
+ *
+ * @param index An existing index object
+ * @param caps A combination of GIT_INDEXCAP values
+ * @return 0 on success, -1 on failure
+ */
+GIT_EXTERN(int) git_index_set_caps(git_index *index, int caps);
+
+/**
+ * Update the contents of an existing index object in memory by reading
+ * from the hard disk.
+ *
+ * If `force` is true, this performs a "hard" read that discards in-memory
+ * changes and always reloads the on-disk index data. If there is no
+ * on-disk version, the index will be cleared.
+ *
+ * If `force` is false, this does a "soft" read that reloads the index
+ * data from disk only if it has changed since the last time it was
+ * loaded. Purely in-memory index data will be untouched. Be aware: if
+ * there are changes on disk, unwritten in-memory changes are discarded.
*
* @param index an existing index object
- * @return 0 on success, otherwise an error code
+ * @param force if true, always reload, vs. only read if file has changed
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_index_read(git_index *index);
+GIT_EXTERN(int) git_index_read(git_index *index, int force);
/**
* Write an existing index object from memory back to disk
* using an atomic file lock.
*
* @param index an existing index object
- * @return 0 on success, otherwise an error code
+ * @return 0 or an error code
*/
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.
+ * Get the full path to the index file on disk.
*
* @param index an existing index object
- * @param path path to search
- * @return an index >= 0 if found, -1 otherwise
+ * @return path to index file or NULL for in-memory index
*/
-GIT_EXTERN(int) git_index_find(git_index *index, const char *path);
+GIT_EXTERN(const char *) git_index_path(const git_index *index);
/**
- * Remove all entries with equal path except last added
+ * 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 tree tree to read
+ * @return 0 or an error code
*/
-GIT_EXTERN(void) git_index_uniq(git_index *index);
+GIT_EXTERN(int) git_index_read_tree(git_index *index, const git_tree *tree);
/**
- * Add or update an index entry from a file in disk
+ * Write the index as a tree
*
- * The file `path` must be relative to the repository's
- * working folder and must be readable.
+ * 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.
*
- * This method will fail in bare index instances.
+ * The index instance cannot be bare, and needs to be associated
+ * to an existing repository.
+ *
+ * The index must not contain any file in conflict.
+ *
+ * @param out Pointer where to store the OID of the written tree
+ * @param index Index to write
+ * @return 0 on success, GIT_EUNMERGED when the index is not clean
+ * or an error code
+ */
+GIT_EXTERN(int) git_index_write_tree(git_oid *out, git_index *index);
+
+/**
+ * Write the index as a tree to the given repository
+ *
+ * This method will do the same as `git_index_write_tree`, but
+ * letting the user choose the repository where the tree will
+ * be written.
+ *
+ * The index must not contain any file in conflict.
+ *
+ * @param out Pointer where to store OID of the the written tree
+ * @param index Index to write
+ * @param repo Repository where to write the tree
+ * @return 0 on success, GIT_EUNMERGED when the index is not clean
+ * or an error code
+ */
+GIT_EXTERN(int) git_index_write_tree_to(git_oid *out, git_index *index, git_repository *repo);
+
+/**@}*/
+
+/** @name Raw Index Entry Functions
+ *
+ * These functions work on index entries, and allow for raw manipulation
+ * of the entries.
+ */
+/**@{*/
+
+/* Index entry manipulation */
+
+/**
+ * Get the count of entries currently in the index
*
* @param index an existing index object
- * @param path filename to add
- * @param stage stage for the entry
- * @return 0 on success, otherwise an error code
+ * @return integer of count of current entries
*/
-GIT_EXTERN(int) git_index_add(git_index *index, const char *path, int stage);
+GIT_EXTERN(size_t) git_index_entrycount(const git_index *index);
+
+/**
+ * Clear the contents (all the entries) of an index object.
+ *
+ * This clears the index object in memory; changes must be explicitly
+ * written to disk for them to take effect persistently.
+ *
+ * @param index an existing index object
+ * @return 0 on success, error code < 0 on failure
+ */
+GIT_EXTERN(int) git_index_clear(git_index *index);
+
+/**
+ * Get a pointer to one of the entries in the index
+ *
+ * The entry is not modifiable and should not be freed. Because the
+ * `git_index_entry` struct is a publicly defined struct, you should
+ * be able to make your own permanent copy of the data if necessary.
+ *
+ * @param index an existing index object
+ * @param n the position of the entry
+ * @return a pointer to the entry; NULL if out of bounds
+ */
+GIT_EXTERN(const git_index_entry *) git_index_get_byindex(
+ git_index *index, size_t n);
+
+/**
+ * Get a pointer to one of the entries in the index
+ *
+ * The entry is not modifiable and should not be freed. Because the
+ * `git_index_entry` struct is a publicly defined struct, you should
+ * be able to make your own permanent copy of the data if necessary.
+ *
+ * @param index an existing index object
+ * @param path path to search
+ * @param stage stage to search
+ * @return a pointer to the entry; NULL if it was not found
+ */
+GIT_EXTERN(const git_index_entry *) git_index_get_bypath(
+ git_index *index, const char *path, int stage);
+
+/**
+ * Remove an entry from the index
+ *
+ * @param index an existing index object
+ * @param path path to search
+ * @param stage stage to search
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_remove(git_index *index, const char *path, int stage);
+
+/**
+ * Remove all entries from the index under a given directory
+ *
+ * @param index an existing index object
+ * @param dir container directory path
+ * @param stage stage to search
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_remove_directory(
+ git_index *index, const char *dir, int stage);
/**
* Add or update an index entry from an in-memory struct
*
+ * If a previous index entry exists that has the same path and stage
+ * as the given 'source_entry', it will be replaced. Otherwise, the
+ * 'source_entry' will be added.
+ *
* A full copy (including the 'path' string) of the given
* 'source_entry' will be inserted on the index.
*
* @param index an existing index object
* @param source_entry new entry object
- * @return 0 on success, otherwise an error code
+ * @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
+ *
+ * This entry is calculated from the entry's flag attribute like this:
*
- * 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.
+ * (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT
+ *
+ * @param entry The entry
+ * @return the stage number
+ */
+GIT_EXTERN(int) git_index_entry_stage(const git_index_entry *entry);
+
+/**@}*/
+
+/** @name Workdir Index Entry Functions
+ *
+ * These functions work on index entries specifically in the working
+ * directory (ie, stage 0).
+ */
+/**@{*/
+
+/**
+ * Add or update an index entry from a file on disk
*
* The file `path` must be relative to the repository's
* working folder and must be readable.
*
* This method will fail in bare index instances.
*
+ * This forces the file to be added to the index, not looking
+ * at gitignore rules. Those rules can be evaluated through
+ * the git_status APIs (in status.h) before calling this.
+ *
+ * If this file currently is the result of a merge conflict, this
+ * file will no longer be marked as conflicting. The data about
+ * the conflict will be moved to the "resolve undo" (REUC) section.
+ *
* @param index an existing index object
* @param path filename to add
- * @param stage stage for the entry
- * @return 0 on success, otherwise an error code
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_index_append(git_index *index, const char *path, int stage);
+GIT_EXTERN(int) git_index_add_bypath(git_index *index, const char *path);
/**
- * Add (append) an index entry from an in-memory struct
+ * Remove an index entry corresponding to a file on disk
*
- * A new entry will always be inserted into the index;
- * if the index already contains an entry for the path
- * in the `entry` struct, the old entry will **not** be
- * replaced.
+ * The file `path` must be relative to the repository's
+ * working folder. It may exist.
*
- * A full copy (including the 'path' string) of the given
- * 'source_entry' will be inserted on the index.
+ * If this file currently is the result of a merge conflict, this
+ * file will no longer be marked as conflicting. The data about
+ * the conflict will be moved to the "resolve undo" (REUC) section.
*
* @param index an existing index object
- * @param source_entry new entry object
- * @return 0 on success, otherwise an error code
+ * @param path filename to remove
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_index_append2(git_index *index, const git_index_entry *source_entry);
+GIT_EXTERN(int) git_index_remove_bypath(git_index *index, const char *path);
/**
- * Remove an entry from the index
+ * Add or update index entries matching files in the working directory.
+ *
+ * This method will fail in bare index instances.
+ *
+ * The `pathspec` is a list of file names or shell glob patterns that will
+ * matched against files in the repository's working directory. Each file
+ * that matches will be added to the index (either updating an existing
+ * entry or adding a new entry). You can disable glob expansion and force
+ * exact matching with the `GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH` flag.
+ *
+ * Files that are ignored will be skipped (unlike `git_index_add_bypath`).
+ * If a file is already tracked in the index, then it *will* be updated
+ * even if it is ignored. Pass the `GIT_INDEX_ADD_FORCE` flag to
+ * skip the checking of ignore rules.
+ *
+ * To emulate `git add -A` and generate an error if the pathspec contains
+ * the exact path of an ignored file (when not using FORCE), add the
+ * `GIT_INDEX_ADD_CHECK_PATHSPEC` flag. This checks that each entry
+ * in the `pathspec` that is an exact match to a filename on disk is
+ * either not ignored or already in the index. If this check fails, the
+ * function will return GIT_EINVALIDSPEC.
+ *
+ * To emulate `git add -A` with the "dry-run" option, just use a callback
+ * function that always returns a positive value. See below for details.
+ *
+ * If any files are currently the result of a merge conflict, those files
+ * will no longer be marked as conflicting. The data about the conflicts
+ * will be moved to the "resolve undo" (REUC) section.
+ *
+ * If you provide a callback function, it will be invoked on each matching
+ * item in the working directory immediately *before* it is added to /
+ * updated in the index. Returning zero will add the item to the index,
+ * greater than zero will skip the item, and less than zero will abort the
+ * scan and return that value to the caller.
*
* @param index an existing index object
- * @param position position of the entry to remove
- * @return 0 on success, otherwise an error code
+ * @param pathspec array of path patterns
+ * @param flags combination of git_index_add_option_t flags
+ * @param callback notification callback for each added/updated path (also
+ * gets index of matching pathspec entry); can be NULL;
+ * return 0 to add, >0 to skip, <0 to abort scan.
+ * @param payload payload passed through to callback function
+ * @return 0 on success, negative callback return value, or error code
*/
-GIT_EXTERN(int) git_index_remove(git_index *index, int position);
+GIT_EXTERN(int) git_index_add_all(
+ git_index *index,
+ const git_strarray *pathspec,
+ unsigned int flags,
+ git_index_matched_path_cb callback,
+ void *payload);
+/**
+ * Remove all matching index entries.
+ *
+ * If you provide a callback function, it will be invoked on each matching
+ * item in the index immediately *before* it is removed. Return 0 to
+ * remove the item, > 0 to skip the item, and < 0 to abort the scan.
+ *
+ * @param index An existing index object
+ * @param pathspec array of path patterns
+ * @param callback notification callback for each removed path (also
+ * gets index of matching pathspec entry); can be NULL;
+ * return 0 to add, >0 to skip, <0 to abort scan.
+ * @param payload payload passed through to callback function
+ * @return 0 on success, negative callback return value, or error code
+ */
+GIT_EXTERN(int) git_index_remove_all(
+ git_index *index,
+ const git_strarray *pathspec,
+ git_index_matched_path_cb callback,
+ void *payload);
/**
- * Get a pointer to one of the entries in the index
+ * Update all index entries to match the working directory
*
- * This entry can be modified, and the changes will be written
- * back to disk on the next write() call.
- *
- * The entry should not be freed by the caller.
+ * This method will fail in bare index instances.
*
- * @param index an existing index object
- * @param n the position of the entry
- * @return a pointer to the entry; NULL if out of bounds
+ * This scans the existing index entries and synchronizes them with the
+ * working directory, deleting them if the corresponding working directory
+ * file no longer exists otherwise updating the information (including
+ * adding the latest version of file to the ODB if needed).
+ *
+ * If you provide a callback function, it will be invoked on each matching
+ * item in the index immediately *before* it is updated (either refreshed
+ * or removed depending on working directory state). Return 0 to proceed
+ * with updating the item, > 0 to skip the item, and < 0 to abort the scan.
+ *
+ * @param index An existing index object
+ * @param pathspec array of path patterns
+ * @param callback notification callback for each updated path (also
+ * gets index of matching pathspec entry); can be NULL;
+ * return 0 to add, >0 to skip, <0 to abort scan.
+ * @param payload payload passed through to callback function
+ * @return 0 on success, negative callback return value, or error code
*/
-GIT_EXTERN(git_index_entry *) git_index_get(git_index *index, unsigned int n);
+GIT_EXTERN(int) git_index_update_all(
+ git_index *index,
+ const git_strarray *pathspec,
+ git_index_matched_path_cb callback,
+ void *payload);
/**
- * Get the count of entries currently in the index
+ * Find the first position of any entries which point to given
+ * path in the Git index.
*
+ * @param at_pos the address to which the position of the index entry is written (optional)
* @param index an existing index object
- * @return integer of count of current entries
+ * @param path path to search
+ * @return a zero-based position in the index if found; GIT_ENOTFOUND otherwise
*/
-GIT_EXTERN(unsigned int) git_index_entrycount(git_index *index);
+GIT_EXTERN(int) git_index_find(size_t *at_pos, git_index *index, const char *path);
+
+/**@}*/
+
+/** @name Conflict Index Entry Functions
+ *
+ * These functions work on conflict index entries specifically (ie, stages 1-3)
+ */
+/**@{*/
/**
- * Get the count of unmerged entries currently in the index
+ * Add or update index entries to represent a conflict
+ *
+ * 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
- * @return integer of count of current unmerged entries
+ * @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(unsigned int) git_index_entrycount_unmerged(git_index *index);
+GIT_EXTERN(int) git_index_conflict_add(
+ git_index *index,
+ const git_index_entry *ancestor_entry,
+ const git_index_entry *our_entry,
+ const git_index_entry *their_entry);
/**
- * Get an unmerged entry from the index.
+ * Get the index entries that represent a conflict of a single file.
*
- * The returned entry is read-only and should not be modified
- * of freed by the caller.
+ * The entries are not modifiable and should not be freed. Because the
+ * `git_index_entry` struct is a publicly defined struct, you should
+ * be able to make your own permanent copy of the data if necessary.
*
+ * @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 path path to search
- * @return the unmerged entry; NULL if not found
+ * @return 0 or an error code
*/
-GIT_EXTERN(const git_index_entry_unmerged *) git_index_get_unmerged_bypath(git_index *index, const char *path);
+GIT_EXTERN(int) git_index_conflict_get(
+ const git_index_entry **ancestor_out,
+ const git_index_entry **our_out,
+ const git_index_entry **their_out,
+ git_index *index,
+ const char *path);
/**
- * Get an unmerged entry from the index.
+ * Removes the index entries that represent a conflict of a single file.
*
- * 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 remove conflicts for
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_conflict_remove(git_index *index, const char *path);
+
+/**
+ * Remove all conflicts in the index (entries with a stage greater than 0).
*
* @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 0 or an error code
*/
-GIT_EXTERN(const git_index_entry_unmerged *) git_index_get_unmerged_byindex(git_index *index, unsigned int n);
+GIT_EXTERN(int) git_index_conflict_cleanup(git_index *index);
/**
- * Return the stage number from a git index entry
+ * Determine if the index contains entries representing file conflicts.
+ *
+ * @return 1 if at least one conflict is found, 0 otherwise.
+ */
+GIT_EXTERN(int) git_index_has_conflicts(const git_index *index);
+
+/**
+ * Create an iterator for the conflicts in the index.
*
- * This entry is calculated from the entrie's flag
- * attribute like this:
+ * The index must not be modified while iterating; the results are undefined.
*
- * (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT
+ * @param iterator_out The newly created conflict iterator
+ * @param index The index to scan
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_conflict_iterator_new(
+ git_index_conflict_iterator **iterator_out,
+ git_index *index);
+
+/**
+ * Returns the current conflict (ancestor, ours and theirs entry) and
+ * advance the iterator internally to the next value.
+ *
+ * @param ancestor_out Pointer to store the ancestor side of the conflict
+ * @param our_out Pointer to store our side of the conflict
+ * @param their_out Pointer to store their side of the conflict
+ * @return 0 (no error), GIT_ITEROVER (iteration is done) or an error code
+ * (negative value)
+ */
+GIT_EXTERN(int) git_index_conflict_next(
+ const git_index_entry **ancestor_out,
+ const git_index_entry **our_out,
+ const git_index_entry **their_out,
+ git_index_conflict_iterator *iterator);
+
+/**
+ * Frees a `git_index_conflict_iterator`.
*
- * @param entry The entry
- * @returns the stage number
+ * @param iterator pointer to the iterator
*/
-GIT_EXTERN(int) git_index_entry_stage(const git_index_entry *entry);
+GIT_EXTERN(void) git_index_conflict_iterator_free(
+ git_index_conflict_iterator *iterator);
+
+/**@}*/
/** @} */
GIT_END_DECL
diff --git a/include/git2/indexer.h b/include/git2/indexer.h
index 6c31956b1..d2d315e47 100644
--- a/include/git2/indexer.h
+++ b/include/git2/indexer.h
@@ -1,18 +1,17 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_indexer_h__
#define _INCLUDE_git_indexer_h__
-#include "git2/common.h"
-#include "git2/oid.h"
-
-/**
- * This is passed as the first argument to the callback to allow the
- * user to see the progress.
- */
-typedef struct git_indexer_stats {
- unsigned int total;
- unsigned int processed;
-} git_indexer_stats;
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+GIT_BEGIN_DECL
typedef struct git_indexer git_indexer;
@@ -20,41 +19,50 @@ typedef struct git_indexer git_indexer;
* Create a new indexer instance
*
* @param out where to store the indexer instance
- * @param packname the absolute filename of the packfile to index
+ * @param path to the directory where the packfile should be stored
+ * @param mode permissions to use creating packfile or 0 for defaults
+ * @param odb object database from which to read base objects when
+ * fixing thin packs. Pass NULL if no thin pack is expected (an error
+ * will be returned if there are bases missing)
+ * @param progress_cb function to call with progress information
+ * @param progress_cb_payload payload for the progress callback
*/
-GIT_EXTERN(int) git_indexer_new(git_indexer **out, const char *packname);
+GIT_EXTERN(int) git_indexer_new(
+ git_indexer **out,
+ const char *path,
+ unsigned int mode,
+ git_odb *odb,
+ git_transfer_progress_cb progress_cb,
+ void *progress_cb_payload);
/**
- * Iterate over the objects in the packfile and extract the information
- *
- * Indexing a packfile can be very expensive so this function is
- * expected to be run in a worker thread and the stats used to provide
- * feedback the user.
+ * Add data to the indexer
*
- * @param idx the indexer instance
- * @param stats storage for the running state
+ * @param idx the indexer
+ * @param data the data to add
+ * @param size the size of the data in bytes
+ * @param stats stat storage
*/
-GIT_EXTERN(int) git_indexer_run(git_indexer *idx, git_indexer_stats *stats);
+GIT_EXTERN(int) git_indexer_append(git_indexer *idx, const void *data, size_t size, git_transfer_progress *stats);
/**
- * Write the index file to disk.
+ * Finalize the pack and index
*
- * The file will be stored as pack-$hash.idx in the same directory as
- * the packfile.
+ * Resolve any pending deltas and write out the index file
*
- * @param idx the indexer instance
+ * @param idx the indexer
*/
-GIT_EXTERN(int) git_indexer_write(git_indexer *idx);
+GIT_EXTERN(int) git_indexer_commit(git_indexer *idx, git_transfer_progress *stats);
/**
* Get the packfile's hash
*
* A packfile's name is derived from the sorted hashing of all object
- * names. This is only correct after the index has been written to disk.
+ * names. This is only correct after the index has been finalized.
*
* @param idx the indexer instance
*/
-GIT_EXTERN(const git_oid *) git_indexer_hash(git_indexer *idx);
+GIT_EXTERN(const git_oid *) git_indexer_hash(const git_indexer *idx);
/**
* Free the indexer and its resources
@@ -63,5 +71,6 @@ GIT_EXTERN(const git_oid *) git_indexer_hash(git_indexer *idx);
*/
GIT_EXTERN(void) git_indexer_free(git_indexer *idx);
+GIT_END_DECL
#endif
diff --git a/include/git2/inttypes.h b/include/git2/inttypes.h
new file mode 100644
index 000000000..17364c7f8
--- /dev/null
+++ b/include/git2/inttypes.h
@@ -0,0 +1,309 @@
+// ISO C9x compliant inttypes.h for Microsoft Visual Studio
+// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124
+//
+// Copyright (c) 2006 Alexander Chemeris
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. The name of the author may be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef _MSC_VER // [
+#error "Use this header only with Microsoft Visual C++ compilers!"
+#endif // _MSC_VER ]
+
+#ifndef _MSC_INTTYPES_H_ // [
+#define _MSC_INTTYPES_H_
+
+#if _MSC_VER > 1000
+#pragma once
+#endif
+
+#if _MSC_VER >= 1600
+#include <stdint.h>
+#else
+#include "stdint.h"
+#endif
+
+// 7.8 Format conversion of integer types
+
+typedef struct {
+ intmax_t quot;
+ intmax_t rem;
+} imaxdiv_t;
+
+// 7.8.1 Macros for format specifiers
+
+#if !defined(__cplusplus) || defined(__STDC_FORMAT_MACROS) // [ See footnote 185 at page 198
+
+// The fprintf macros for signed integers are:
+#define PRId8 "d"
+#define PRIi8 "i"
+#define PRIdLEAST8 "d"
+#define PRIiLEAST8 "i"
+#define PRIdFAST8 "d"
+#define PRIiFAST8 "i"
+
+#define PRId16 "hd"
+#define PRIi16 "hi"
+#define PRIdLEAST16 "hd"
+#define PRIiLEAST16 "hi"
+#define PRIdFAST16 "hd"
+#define PRIiFAST16 "hi"
+
+#define PRId32 "I32d"
+#define PRIi32 "I32i"
+#define PRIdLEAST32 "I32d"
+#define PRIiLEAST32 "I32i"
+#define PRIdFAST32 "I32d"
+#define PRIiFAST32 "I32i"
+
+#define PRId64 "I64d"
+#define PRIi64 "I64i"
+#define PRIdLEAST64 "I64d"
+#define PRIiLEAST64 "I64i"
+#define PRIdFAST64 "I64d"
+#define PRIiFAST64 "I64i"
+
+#define PRIdMAX "I64d"
+#define PRIiMAX "I64i"
+
+#define PRIdPTR "Id"
+#define PRIiPTR "Ii"
+
+// The fprintf macros for unsigned integers are:
+#define PRIo8 "o"
+#define PRIu8 "u"
+#define PRIx8 "x"
+#define PRIX8 "X"
+#define PRIoLEAST8 "o"
+#define PRIuLEAST8 "u"
+#define PRIxLEAST8 "x"
+#define PRIXLEAST8 "X"
+#define PRIoFAST8 "o"
+#define PRIuFAST8 "u"
+#define PRIxFAST8 "x"
+#define PRIXFAST8 "X"
+
+#define PRIo16 "ho"
+#define PRIu16 "hu"
+#define PRIx16 "hx"
+#define PRIX16 "hX"
+#define PRIoLEAST16 "ho"
+#define PRIuLEAST16 "hu"
+#define PRIxLEAST16 "hx"
+#define PRIXLEAST16 "hX"
+#define PRIoFAST16 "ho"
+#define PRIuFAST16 "hu"
+#define PRIxFAST16 "hx"
+#define PRIXFAST16 "hX"
+
+#define PRIo32 "I32o"
+#define PRIu32 "I32u"
+#define PRIx32 "I32x"
+#define PRIX32 "I32X"
+#define PRIoLEAST32 "I32o"
+#define PRIuLEAST32 "I32u"
+#define PRIxLEAST32 "I32x"
+#define PRIXLEAST32 "I32X"
+#define PRIoFAST32 "I32o"
+#define PRIuFAST32 "I32u"
+#define PRIxFAST32 "I32x"
+#define PRIXFAST32 "I32X"
+
+#define PRIo64 "I64o"
+#define PRIu64 "I64u"
+#define PRIx64 "I64x"
+#define PRIX64 "I64X"
+#define PRIoLEAST64 "I64o"
+#define PRIuLEAST64 "I64u"
+#define PRIxLEAST64 "I64x"
+#define PRIXLEAST64 "I64X"
+#define PRIoFAST64 "I64o"
+#define PRIuFAST64 "I64u"
+#define PRIxFAST64 "I64x"
+#define PRIXFAST64 "I64X"
+
+#define PRIoMAX "I64o"
+#define PRIuMAX "I64u"
+#define PRIxMAX "I64x"
+#define PRIXMAX "I64X"
+
+#define PRIoPTR "Io"
+#define PRIuPTR "Iu"
+#define PRIxPTR "Ix"
+#define PRIXPTR "IX"
+
+// The fscanf macros for signed integers are:
+#define SCNd8 "d"
+#define SCNi8 "i"
+#define SCNdLEAST8 "d"
+#define SCNiLEAST8 "i"
+#define SCNdFAST8 "d"
+#define SCNiFAST8 "i"
+
+#define SCNd16 "hd"
+#define SCNi16 "hi"
+#define SCNdLEAST16 "hd"
+#define SCNiLEAST16 "hi"
+#define SCNdFAST16 "hd"
+#define SCNiFAST16 "hi"
+
+#define SCNd32 "ld"
+#define SCNi32 "li"
+#define SCNdLEAST32 "ld"
+#define SCNiLEAST32 "li"
+#define SCNdFAST32 "ld"
+#define SCNiFAST32 "li"
+
+#define SCNd64 "I64d"
+#define SCNi64 "I64i"
+#define SCNdLEAST64 "I64d"
+#define SCNiLEAST64 "I64i"
+#define SCNdFAST64 "I64d"
+#define SCNiFAST64 "I64i"
+
+#define SCNdMAX "I64d"
+#define SCNiMAX "I64i"
+
+#ifdef _WIN64 // [
+# define SCNdPTR "I64d"
+# define SCNiPTR "I64i"
+#else // _WIN64 ][
+# define SCNdPTR "ld"
+# define SCNiPTR "li"
+#endif // _WIN64 ]
+
+// The fscanf macros for unsigned integers are:
+#define SCNo8 "o"
+#define SCNu8 "u"
+#define SCNx8 "x"
+#define SCNX8 "X"
+#define SCNoLEAST8 "o"
+#define SCNuLEAST8 "u"
+#define SCNxLEAST8 "x"
+#define SCNXLEAST8 "X"
+#define SCNoFAST8 "o"
+#define SCNuFAST8 "u"
+#define SCNxFAST8 "x"
+#define SCNXFAST8 "X"
+
+#define SCNo16 "ho"
+#define SCNu16 "hu"
+#define SCNx16 "hx"
+#define SCNX16 "hX"
+#define SCNoLEAST16 "ho"
+#define SCNuLEAST16 "hu"
+#define SCNxLEAST16 "hx"
+#define SCNXLEAST16 "hX"
+#define SCNoFAST16 "ho"
+#define SCNuFAST16 "hu"
+#define SCNxFAST16 "hx"
+#define SCNXFAST16 "hX"
+
+#define SCNo32 "lo"
+#define SCNu32 "lu"
+#define SCNx32 "lx"
+#define SCNX32 "lX"
+#define SCNoLEAST32 "lo"
+#define SCNuLEAST32 "lu"
+#define SCNxLEAST32 "lx"
+#define SCNXLEAST32 "lX"
+#define SCNoFAST32 "lo"
+#define SCNuFAST32 "lu"
+#define SCNxFAST32 "lx"
+#define SCNXFAST32 "lX"
+
+#define SCNo64 "I64o"
+#define SCNu64 "I64u"
+#define SCNx64 "I64x"
+#define SCNX64 "I64X"
+#define SCNoLEAST64 "I64o"
+#define SCNuLEAST64 "I64u"
+#define SCNxLEAST64 "I64x"
+#define SCNXLEAST64 "I64X"
+#define SCNoFAST64 "I64o"
+#define SCNuFAST64 "I64u"
+#define SCNxFAST64 "I64x"
+#define SCNXFAST64 "I64X"
+
+#define SCNoMAX "I64o"
+#define SCNuMAX "I64u"
+#define SCNxMAX "I64x"
+#define SCNXMAX "I64X"
+
+#ifdef _WIN64 // [
+# define SCNoPTR "I64o"
+# define SCNuPTR "I64u"
+# define SCNxPTR "I64x"
+# define SCNXPTR "I64X"
+#else // _WIN64 ][
+# define SCNoPTR "lo"
+# define SCNuPTR "lu"
+# define SCNxPTR "lx"
+# define SCNXPTR "lX"
+#endif // _WIN64 ]
+
+#endif // __STDC_FORMAT_MACROS ]
+
+// 7.8.2 Functions for greatest-width integer types
+
+// 7.8.2.1 The imaxabs function
+#define imaxabs _abs64
+
+// 7.8.2.2 The imaxdiv function
+
+// This is modified version of div() function from Microsoft's div.c found
+// in %MSVC.NET%\crt\src\div.c
+#ifdef STATIC_IMAXDIV // [
+static
+#else // STATIC_IMAXDIV ][
+_inline
+#endif // STATIC_IMAXDIV ]
+imaxdiv_t __cdecl imaxdiv(intmax_t numer, intmax_t denom)
+{
+ imaxdiv_t result;
+
+ result.quot = numer / denom;
+ result.rem = numer % denom;
+
+ if (numer < 0 && result.rem > 0) {
+ // did division wrong; must fix up
+ ++result.quot;
+ result.rem -= denom;
+ }
+
+ return result;
+}
+
+// 7.8.2.3 The strtoimax and strtoumax functions
+#define strtoimax _strtoi64
+#define strtoumax _strtoui64
+
+// 7.8.2.4 The wcstoimax and wcstoumax functions
+#define wcstoimax _wcstoi64
+#define wcstoumax _wcstoui64
+
+
+#endif // _MSC_INTTYPES_H_ ]
diff --git a/include/git2/merge.h b/include/git2/merge.h
new file mode 100644
index 000000000..9eb14ccb1
--- /dev/null
+++ b/include/git2/merge.h
@@ -0,0 +1,536 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_merge_h__
+#define INCLUDE_git_merge_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+#include "checkout.h"
+#include "index.h"
+
+/**
+ * @file git2/merge.h
+ * @brief Git merge routines
+ * @defgroup git_merge Git merge routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * The file inputs to `git_merge_file`. Callers should populate the
+ * `git_merge_file_input` structure with descriptions of the files in
+ * each side of the conflict for use in producing the merge file.
+ */
+typedef struct {
+ unsigned int version;
+
+ /** Pointer to the contents of the file. */
+ const char *ptr;
+
+ /** Size of the contents pointed to in `ptr`. */
+ size_t size;
+
+ /** File name of the conflicted file, or `NULL` to not merge the path. */
+ const char *path;
+
+ /** File mode of the conflicted file, or `0` to not merge the mode. */
+ unsigned int mode;
+} git_merge_file_input;
+
+#define GIT_MERGE_FILE_INPUT_VERSION 1
+#define GIT_MERGE_FILE_INPUT_INIT {GIT_MERGE_FILE_INPUT_VERSION}
+
+/**
+ * Initializes a `git_merge_file_input` with default values. Equivalent to
+ * creating an instance with GIT_MERGE_FILE_INPUT_INIT.
+ *
+ * @param opts the `git_merge_file_input` instance to initialize.
+ * @param version the version of the struct; you should pass
+ * `GIT_MERGE_FILE_INPUT_VERSION` here.
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_merge_file_init_input(
+ git_merge_file_input *opts,
+ unsigned int version);
+
+/**
+ * Flags for `git_merge_tree` options. A combination of these flags can be
+ * passed in via the `flags` value in the `git_merge_options`.
+ */
+typedef enum {
+ /**
+ * Detect renames that occur between the common ancestor and the "ours"
+ * side or the common ancestor and the "theirs" side. This will enable
+ * the ability to merge between a modified and renamed file.
+ */
+ GIT_MERGE_TREE_FIND_RENAMES = (1 << 0),
+} git_merge_tree_flag_t;
+
+/**
+ * Merge file favor options for `git_merge_options` instruct the file-level
+ * merging functionality how to deal with conflicting regions of the files.
+ */
+typedef enum {
+ /**
+ * When a region of a file is changed in both branches, a conflict
+ * will be recorded in the index so that `git_checkout` can produce
+ * a merge file with conflict markers in the working directory.
+ * This is the default.
+ */
+ GIT_MERGE_FILE_FAVOR_NORMAL = 0,
+
+ /**
+ * When a region of a file is changed in both branches, the file
+ * created in the index will contain the "ours" side of any conflicting
+ * region. The index will not record a conflict.
+ */
+ GIT_MERGE_FILE_FAVOR_OURS = 1,
+
+ /**
+ * When a region of a file is changed in both branches, the file
+ * created in the index will contain the "theirs" side of any conflicting
+ * region. The index will not record a conflict.
+ */
+ GIT_MERGE_FILE_FAVOR_THEIRS = 2,
+
+ /**
+ * When a region of a file is changed in both branches, the file
+ * created in the index will contain each unique line from each side,
+ * which has the result of combining both files. The index will not
+ * record a conflict.
+ */
+ GIT_MERGE_FILE_FAVOR_UNION = 3,
+} git_merge_file_favor_t;
+
+typedef enum {
+ /* Defaults */
+ GIT_MERGE_FILE_DEFAULT = 0,
+
+ /* Create standard conflicted merge files */
+ GIT_MERGE_FILE_STYLE_MERGE = (1 << 0),
+
+ /* Create diff3-style files */
+ GIT_MERGE_FILE_STYLE_DIFF3 = (1 << 1),
+
+ /* Condense non-alphanumeric regions for simplified diff file */
+ GIT_MERGE_FILE_SIMPLIFY_ALNUM = (1 << 2),
+} git_merge_file_flags_t;
+
+typedef struct {
+ unsigned int version;
+
+ /**
+ * Label for the ancestor file side of the conflict which will be prepended
+ * to labels in diff3-format merge files.
+ */
+ const char *ancestor_label;
+
+ /**
+ * Label for our file side of the conflict which will be prepended
+ * to labels in merge files.
+ */
+ const char *our_label;
+
+ /**
+ * Label for their file side of the conflict which will be prepended
+ * to labels in merge files.
+ */
+ const char *their_label;
+
+ /** The file to favor in region conflicts. */
+ git_merge_file_favor_t favor;
+
+ /** Merge file flags. */
+ git_merge_file_flags_t flags;
+} git_merge_file_options;
+
+#define GIT_MERGE_FILE_OPTIONS_VERSION 1
+#define GIT_MERGE_FILE_OPTIONS_INIT {GIT_MERGE_FILE_OPTIONS_VERSION}
+
+/**
+ * Initializes a `git_merge_file_options` with default values. Equivalent to
+ * creating an instance with GIT_MERGE_FILE_OPTIONS_INIT.
+ *
+ * @param opts the `git_merge_file_options` instance to initialize.
+ * @param version the version of the struct; you should pass
+ * `GIT_MERGE_FILE_OPTIONS_VERSION` here.
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_merge_file_init_options(
+ git_merge_file_options *opts,
+ unsigned int version);
+
+typedef struct {
+ /**
+ * True if the output was automerged, false if the output contains
+ * conflict markers.
+ */
+ unsigned int automergeable;
+
+ /**
+ * The path that the resultant merge file should use, or NULL if a
+ * filename conflict would occur.
+ */
+ const char *path;
+
+ /** The mode that the resultant merge file should use. */
+ unsigned int mode;
+
+ /** The contents of the merge. */
+ const char *ptr;
+
+ /** The length of the merge contents. */
+ size_t len;
+} git_merge_file_result;
+
+typedef struct {
+ unsigned int version;
+ git_merge_tree_flag_t flags;
+
+ /**
+ * Similarity to consider a file renamed (default 50). If
+ * `GIT_MERGE_TREE_FIND_RENAMES` is enabled, added files will be compared
+ * with deleted files to determine their similarity. Files that are
+ * more similar than the rename threshold (percentage-wise) will be
+ * treated as a rename.
+ */
+ unsigned int rename_threshold;
+
+ /**
+ * Maximum similarity sources to examine for renames (default 200).
+ * If the number of rename candidates (add / delete pairs) is greater
+ * than this value, inexact rename detection is aborted.
+ *
+ * This setting overrides the `merge.renameLimit` configuration value.
+ */
+ unsigned int target_limit;
+
+ /** Pluggable similarity metric; pass NULL to use internal metric */
+ git_diff_similarity_metric *metric;
+
+ /** Flags for handling conflicting content. */
+ git_merge_file_favor_t file_favor;
+} git_merge_options;
+
+#define GIT_MERGE_OPTIONS_VERSION 1
+#define GIT_MERGE_OPTIONS_INIT {GIT_MERGE_OPTIONS_VERSION}
+
+/**
+ * Initializes a `git_merge_options` with default values. Equivalent to
+ * creating an instance with GIT_MERGE_OPTIONS_INIT.
+ *
+ * @param opts the `git_merge_options` instance to initialize.
+ * @param version the version of the struct; you should pass
+ * `GIT_MERGE_OPTIONS_VERSION` here.
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_merge_init_options(
+ git_merge_options *opts,
+ unsigned int version);
+
+/**
+ * The results of `git_merge_analysis` indicate the merge opportunities.
+ */
+typedef enum {
+ /** No merge is possible. (Unused.) */
+ GIT_MERGE_ANALYSIS_NONE = 0,
+
+ /**
+ * A "normal" merge; both HEAD and the given merge input have diverged
+ * from their common ancestor. The divergent commits must be merged.
+ */
+ GIT_MERGE_ANALYSIS_NORMAL = (1 << 0),
+
+ /**
+ * All given merge inputs are reachable from HEAD, meaning the
+ * repository is up-to-date and no merge needs to be performed.
+ */
+ GIT_MERGE_ANALYSIS_UP_TO_DATE = (1 << 1),
+
+ /**
+ * The given merge input is a fast-forward from HEAD and no merge
+ * needs to be performed. Instead, the client can check out the
+ * given merge input.
+ */
+ GIT_MERGE_ANALYSIS_FASTFORWARD = (1 << 2),
+
+ /**
+ * The HEAD of the current repository is "unborn" and does not point to
+ * a valid commit. No merge can be performed, but the caller may wish
+ * to simply set HEAD to the target commit(s).
+ */
+ GIT_MERGE_ANALYSIS_UNBORN = (1 << 3),
+} git_merge_analysis_t;
+
+typedef enum {
+ /*
+ * No configuration was found that suggests a preferred behavior for
+ * merge.
+ */
+ GIT_MERGE_PREFERENCE_NONE = 0,
+
+ /**
+ * There is a `merge.ff=false` configuration setting, suggesting that
+ * the user does not want to allow a fast-forward merge.
+ */
+ GIT_MERGE_PREFERENCE_NO_FASTFORWARD = (1 << 0),
+
+ /**
+ * There is a `merge.ff=only` configuration setting, suggesting that
+ * the user only wants fast-forward merges.
+ */
+ GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY = (1 << 1),
+} git_merge_preference_t;
+
+/**
+ * Analyzes the given branch(es) and determines the opportunities for
+ * merging them into the HEAD of the repository.
+ *
+ * @param analysis_out analysis enumeration that the result is written into
+ * @param repo the repository to merge
+ * @param their_heads the heads to merge into
+ * @param their_heads_len the number of heads to merge
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_merge_analysis(
+ git_merge_analysis_t *analysis_out,
+ git_merge_preference_t *preference_out,
+ git_repository *repo,
+ const git_merge_head **their_heads,
+ size_t their_heads_len);
+
+/**
+ * Find a merge base between two commits
+ *
+ * @param out the OID of a merge base between 'one' and 'two'
+ * @param repo the repository where the commits exist
+ * @param one one of the commits
+ * @param two the other commit
+ * @return 0 on success, GIT_ENOTFOUND if not found or error code
+ */
+GIT_EXTERN(int) git_merge_base(
+ git_oid *out,
+ git_repository *repo,
+ const git_oid *one,
+ const git_oid *two);
+
+/**
+ * Find a merge base given a list of commits
+ *
+ * @param out the OID of a merge base considering all the commits
+ * @param repo the repository where the commits exist
+ * @param length The number of commits in the provided `input_array`
+ * @param input_array oids of the commits
+ * @return Zero on success; GIT_ENOTFOUND or -1 on failure.
+ */
+GIT_EXTERN(int) git_merge_base_many(
+ git_oid *out,
+ git_repository *repo,
+ size_t length,
+ const git_oid input_array[]);
+
+/**
+ * Find a merge base in preparation for an octopus merge
+ *
+ * @param out the OID of a merge base considering all the commits
+ * @param repo the repository where the commits exist
+ * @param length The number of commits in the provided `input_array`
+ * @param input_array oids of the commits
+ * @return Zero on success; GIT_ENOTFOUND or -1 on failure.
+ */
+GIT_EXTERN(int) git_merge_base_octopus(
+ git_oid *out,
+ git_repository *repo,
+ size_t length,
+ const git_oid input_array[]);
+
+/**
+ * Creates a `git_merge_head` from the given reference. The resulting
+ * git_merge_head must be freed with `git_merge_head_free`.
+ *
+ * @param out pointer to store the git_merge_head result in
+ * @param repo repository that contains the given reference
+ * @param ref reference to use as a merge input
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_merge_head_from_ref(
+ git_merge_head **out,
+ git_repository *repo,
+ const git_reference *ref);
+
+/**
+ * Creates a `git_merge_head` from the given fetch head data. The resulting
+ * git_merge_head must be freed with `git_merge_head_free`.
+ *
+ * @param out pointer to store the git_merge_head result in
+ * @param repo repository that contains the given commit
+ * @param branch_name name of the (remote) branch
+ * @param remote_url url of the remote
+ * @param oid the commit object id to use as a merge input
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_merge_head_from_fetchhead(
+ git_merge_head **out,
+ git_repository *repo,
+ const char *branch_name,
+ const char *remote_url,
+ const git_oid *oid);
+
+/**
+ * Creates a `git_merge_head` from the given commit id. The resulting
+ * git_merge_head must be freed with `git_merge_head_free`.
+ *
+ * @param out pointer to store the git_merge_head result in
+ * @param repo repository that contains the given commit
+ * @param id the commit object id to use as a merge input
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_merge_head_from_id(
+ git_merge_head **out,
+ git_repository *repo,
+ const git_oid *id);
+
+/**
+ * Gets the commit ID that the given `git_merge_head` refers to.
+ *
+ * @param head the given merge head
+ * @return commit id
+ */
+GIT_EXTERN(const git_oid *) git_merge_head_id(
+ const git_merge_head *head);
+
+/**
+ * Frees a `git_merge_head`.
+ *
+ * @param head merge head to free
+ */
+GIT_EXTERN(void) git_merge_head_free(
+ git_merge_head *head);
+
+/**
+ * Merge two files as they exist in the in-memory data structures, using
+ * the given common ancestor as the baseline, producing a
+ * `git_merge_file_result` that reflects the merge result. The
+ * `git_merge_file_result` must be freed with `git_merge_file_result_free`.
+ *
+ * Note that this function does not reference a repository and any
+ * configuration must be passed as `git_merge_file_options`.
+ *
+ * @param out The git_merge_file_result to be filled in
+ * @param ancestor The contents of the ancestor file
+ * @param ours The contents of the file in "our" side
+ * @param theirs The contents of the file in "their" side
+ * @param opts The merge file options or `NULL` for defaults
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_merge_file(
+ git_merge_file_result *out,
+ const git_merge_file_input *ancestor,
+ const git_merge_file_input *ours,
+ const git_merge_file_input *theirs,
+ const git_merge_file_options *opts);
+
+/**
+ * Merge two files as they exist in the index, using the given common
+ * ancestor as the baseline, producing a `git_merge_file_result` that
+ * reflects the merge result. The `git_merge_file_result` must be freed with
+ * `git_merge_file_result_free`.
+ *
+ * @param out The git_merge_file_result to be filled in
+ * @param repo The repository
+ * @param ancestor The index entry for the ancestor file (stage level 1)
+ * @param ours The index entry for our file (stage level 2)
+ * @param theirs The index entry for their file (stage level 3)
+ * @param opts The merge file options or NULL
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_merge_file_from_index(
+ git_merge_file_result *out,
+ git_repository *repo,
+ const git_index_entry *ancestor,
+ const git_index_entry *ours,
+ const git_index_entry *theirs,
+ const git_merge_file_options *opts);
+
+/**
+ * Frees a `git_merge_file_result`.
+ *
+ * @param result The result to free or `NULL`
+ */
+GIT_EXTERN(void) git_merge_file_result_free(git_merge_file_result *result);
+
+/**
+ * Merge two trees, producing a `git_index` that reflects the result of
+ * the merge. The index may be written as-is to the working directory
+ * or checked out. If the index is to be converted to a tree, the caller
+ * should resolve any conflicts that arose as part of the merge.
+ *
+ * The returned index must be freed explicitly with `git_index_free`.
+ *
+ * @param out pointer to store the index result in
+ * @param repo repository that contains the given trees
+ * @param ancestor_tree the common ancestor between the trees (or null if none)
+ * @param our_tree the tree that reflects the destination tree
+ * @param their_tree the tree to merge in to `our_tree`
+ * @param opts the merge tree options (or null for defaults)
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_merge_trees(
+ git_index **out,
+ git_repository *repo,
+ const git_tree *ancestor_tree,
+ const git_tree *our_tree,
+ const git_tree *their_tree,
+ const git_merge_options *opts);
+
+/**
+ * Merge two commits, producing a `git_index` that reflects the result of
+ * the merge. The index may be written as-is to the working directory
+ * or checked out. If the index is to be converted to a tree, the caller
+ * should resolve any conflicts that arose as part of the merge.
+ *
+ * The returned index must be freed explicitly with `git_index_free`.
+ *
+ * @param out pointer to store the index result in
+ * @param repo repository that contains the given trees
+ * @param our_commit the commit that reflects the destination tree
+ * @param their_commit the commit to merge in to `our_commit`
+ * @param opts the merge tree options (or null for defaults)
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_merge_commits(
+ git_index **out,
+ git_repository *repo,
+ const git_commit *our_commit,
+ const git_commit *their_commit,
+ const git_merge_options *opts);
+
+/**
+ * Merges the given commit(s) into HEAD, writing the results into the working
+ * directory. Any changes are staged for commit and any conflicts are written
+ * to the index. Callers should inspect the repository's index after this
+ * completes, resolve any conflicts and prepare a commit.
+ *
+ * @param repo the repository to merge
+ * @param their_heads the heads to merge into
+ * @param their_heads_len the number of heads to merge
+ * @param merge_opts merge options
+ * @param checkout_opts checkout options
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_merge(
+ git_repository *repo,
+ const git_merge_head **their_heads,
+ size_t their_heads_len,
+ const git_merge_options *merge_opts,
+ const git_checkout_options *checkout_opts);
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/message.h b/include/git2/message.h
new file mode 100644
index 000000000..d78b1dce5
--- /dev/null
+++ b/include/git2/message.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_message_h__
+#define INCLUDE_git_message_h__
+
+#include "common.h"
+#include "buffer.h"
+
+/**
+ * @file git2/message.h
+ * @brief Git message management routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Clean up message from excess whitespace and make sure that the last line
+ * ends with a '\n'.
+ *
+ * Optionally, can remove lines starting with a "#".
+ *
+ * @param out The user-allocated git_buf which will be filled with the
+ * cleaned up message.
+ *
+ * @param message The message to be prettified.
+ *
+ * @param strip_comments Non-zero to remove comment lines, 0 to leave them in.
+ *
+ * @param comment_char Comment character. Lines starting with this character
+ * are considered to be comments and removed if `strip_comments` is non-zero.
+ *
+ * @return 0 or an error code.
+ */
+GIT_EXTERN(int) git_message_prettify(git_buf *out, const char *message, int strip_comments, char comment_char);
+
+/** @} */
+GIT_END_DECL
+
+#endif /* INCLUDE_git_message_h__ */
diff --git a/include/git2/net.h b/include/git2/net.h
index 164f0ac0a..a727696a2 100644
--- a/include/git2/net.h
+++ b/include/git2/net.h
@@ -1,29 +1,11 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
*/
-#ifndef INCLUDE_net_h__
-#define INCLUDE_net_h__
+#ifndef INCLUDE_git_net_h__
+#define INCLUDE_git_net_h__
#include "common.h"
#include "oid.h"
@@ -45,24 +27,31 @@ GIT_BEGIN_DECL
* gets called.
*/
-#define GIT_DIR_FETCH 0
-#define GIT_DIR_PUSH 1
+typedef enum {
+ GIT_DIRECTION_FETCH = 0,
+ GIT_DIRECTION_PUSH = 1
+} git_direction;
+
/**
* Remote head description, given out on `ls` calls.
*/
struct git_remote_head {
+ int local; /* available locally */
git_oid oid;
+ git_oid loid;
char *name;
+ /**
+ * If the server send a symref mapping for this ref, this will
+ * point to the target.
+ */
+ char *symref_target;
};
/**
- * Array of remote heads
+ * Callback for listing the remote heads
*/
-struct git_headarray {
- unsigned int len;
- struct git_remote_head **heads;
-};
+typedef int (*git_headlist_cb)(git_remote_head *rhead, void *payload);
/** @} */
GIT_END_DECL
diff --git a/include/git2/notes.h b/include/git2/notes.h
new file mode 100644
index 000000000..98eb2aef3
--- /dev/null
+++ b/include/git2/notes.h
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_note_h__
+#define INCLUDE_git_note_h__
+
+#include "oid.h"
+
+/**
+ * @file git2/notes.h
+ * @brief Git notes management routines
+ * @defgroup git_note Git notes management routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Callback for git_note_foreach.
+ *
+ * Receives:
+ * - blob_id: Oid of the blob containing the message
+ * - annotated_object_id: Oid of the git object being annotated
+ * - payload: Payload data passed to `git_note_foreach`
+ */
+typedef int (*git_note_foreach_cb)(
+ const git_oid *blob_id, const git_oid *annotated_object_id, void *payload);
+
+/**
+ * note iterator
+ */
+typedef struct git_iterator git_note_iterator;
+
+/**
+ * Creates a new iterator for notes
+ *
+ * The iterator must be freed manually by the user.
+ *
+ * @param out pointer to the iterator
+ * @param repo repository where to look up the note
+ * @param notes_ref canonical name of the reference to use (optional); defaults to
+ * "refs/notes/commits"
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_note_iterator_new(
+ git_note_iterator **out,
+ git_repository *repo,
+ const char *notes_ref);
+
+/**
+ * Frees an git_note_iterator
+ *
+ * @param it pointer to the iterator
+ */
+GIT_EXTERN(void) git_note_iterator_free(git_note_iterator *it);
+
+/**
+ * Returns the current item (note_id and annotated_id) and advance the iterator
+ * internally to the next value
+ *
+ * The notes must not be freed manually by the user.
+ *
+ * @param it pointer to the iterator
+ * @param note_id id of blob containing the message
+ * @param annotated_id id of the git object being annotated
+ *
+ * @return 0 (no error), GIT_ITEROVER (iteration is done) or an error code
+ * (negative value)
+ */
+GIT_EXTERN(int) git_note_next(
+ git_oid* note_id,
+ git_oid* annotated_id,
+ git_note_iterator *it);
+
+
+/**
+ * Read the note for an object
+ *
+ * The note must be freed manually by the user.
+ *
+ * @param out pointer to the read note; NULL in case of error
+ * @param repo repository where to look up the note
+ * @param notes_ref canonical name of the reference to use (optional); defaults to
+ * "refs/notes/commits"
+ * @param oid OID of the git object to read the note from
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_note_read(
+ git_note **out,
+ git_repository *repo,
+ const char *notes_ref,
+ const git_oid *oid);
+
+/**
+ * Get the note message
+ *
+ * @param note the note
+ * @return the note message
+ */
+GIT_EXTERN(const char *) git_note_message(const git_note *note);
+
+
+/**
+ * Get the note object's id
+ *
+ * @param note the note
+ * @return the note object's id
+ */
+GIT_EXTERN(const git_oid *) git_note_id(const git_note *note);
+
+/**
+ * Add a note for an object
+ *
+ * @param out pointer to store the OID (optional); NULL in case of error
+ * @param repo repository where to store the note
+ * @param author signature of the notes commit author
+ * @param committer signature of the notes commit committer
+ * @param notes_ref canonical name of the reference to use (optional);
+ * defaults to "refs/notes/commits"
+ * @param oid OID of the git object to decorate
+ * @param note Content of the note to add for object oid
+ * @param force Overwrite existing note
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_note_create(
+ git_oid *out,
+ git_repository *repo,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *notes_ref,
+ const git_oid *oid,
+ const char *note,
+ int force);
+
+
+/**
+ * Remove the note for an object
+ *
+ * @param repo repository where the note lives
+ * @param notes_ref canonical name of the reference to use (optional);
+ * defaults to "refs/notes/commits"
+ * @param author signature of the notes commit author
+ * @param committer signature of the notes commit committer
+ * @param oid OID of the git object to remove the note from
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_note_remove(
+ git_repository *repo,
+ const char *notes_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const git_oid *oid);
+
+/**
+ * Free a git_note object
+ *
+ * @param note git_note object
+ */
+GIT_EXTERN(void) git_note_free(git_note *note);
+
+/**
+ * Get the default notes reference for a repository
+ *
+ * @param out Pointer to the default notes reference
+ * @param repo The Git repository
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_note_default_ref(const char **out, git_repository *repo);
+
+/**
+ * Loop over all the notes within a specified namespace
+ * and issue a callback for each one.
+ *
+ * @param repo Repository where to find the notes.
+ *
+ * @param notes_ref Reference to read from (optional); defaults to
+ * "refs/notes/commits".
+ *
+ * @param note_cb Callback to invoke per found annotation. Return non-zero
+ * to stop looping.
+ *
+ * @param payload Extra parameter to callback function.
+ *
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_note_foreach(
+ git_repository *repo,
+ const char *notes_ref,
+ git_note_foreach_cb note_cb,
+ void *payload);
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/object.h b/include/git2/object.h
index 07ba1a1c7..9b13d824e 100644
--- a/include/git2/object.h
+++ b/include/git2/object.h
@@ -1,26 +1,8 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_object_h__
#define INCLUDE_git_object_h__
@@ -28,6 +10,7 @@
#include "common.h"
#include "types.h"
#include "oid.h"
+#include "buffer.h"
/**
* @file git2/object.h
@@ -39,10 +22,10 @@
GIT_BEGIN_DECL
/**
- * Lookup a reference to one of the objects in a repostory.
+ * Lookup a reference to one of the objects in a repository.
*
* The generated reference is owned by the repository and
- * should be closed with the `git_object_close` method
+ * should be closed with the `git_object_free` method
* instead of free'd manually.
*
* The 'type' parameter must match the type of the object
@@ -54,7 +37,7 @@ GIT_BEGIN_DECL
* @param repo the repository to look up the object
* @param id the unique identifier for the object
* @param type the type of the object
- * @return a reference to the object
+ * @return 0 or an error code
*/
GIT_EXTERN(int) git_object_lookup(
git_object **object,
@@ -63,7 +46,7 @@ GIT_EXTERN(int) git_object_lookup(
git_otype type);
/**
- * Lookup a reference to one of the objects in a repostory,
+ * Lookup a reference to one of the objects in a repository,
* given a prefix of its identifier (short id).
*
* The object obtained will be so that its identifier
@@ -74,7 +57,7 @@ GIT_EXTERN(int) git_object_lookup(
* the prefix; otherwise the method will fail.
*
* The generated reference is owned by the repository and
- * should be closed with the `git_object_close` method
+ * should be closed with the `git_object_free` method
* instead of free'd manually.
*
* The 'type' parameter must match the type of the object
@@ -87,13 +70,30 @@ GIT_EXTERN(int) git_object_lookup(
* @param id a short identifier for the object
* @param len the length of the short identifier
* @param type the type of the object
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
GIT_EXTERN(int) git_object_lookup_prefix(
git_object **object_out,
git_repository *repo,
const git_oid *id,
- unsigned int len,
+ size_t len,
+ git_otype type);
+
+
+/**
+ * Lookup an object that represents a tree entry.
+ *
+ * @param out buffer that receives a pointer to the object (which must be freed
+ * by the caller)
+ * @param treeish root object that can be peeled to a tree
+ * @param path relative path from the root object to the desired object
+ * @param type type of object desired
+ * @return 0 on success, or an error code
+ */
+GIT_EXTERN(int) git_object_lookup_bypath(
+ git_object **out,
+ const git_object *treeish,
+ const char *path,
git_otype type);
/**
@@ -105,6 +105,20 @@ GIT_EXTERN(int) git_object_lookup_prefix(
GIT_EXTERN(const git_oid *) git_object_id(const git_object *obj);
/**
+ * Get a short abbreviated OID string for the object
+ *
+ * This starts at the "core.abbrev" length (default 7 characters) and
+ * iteratively extends to a longer string if that length is ambiguous.
+ * The result will be unambiguous (at least until new objects are added to
+ * the repository).
+ *
+ * @param out Buffer to write string into
+ * @param obj The object to get an ID for
+ * @return 0 on success, <0 for error
+ */
+GIT_EXTERN(int) git_object_short_id(git_buf *out, const git_object *obj);
+
+/**
* Get the object type of an object
*
* @param obj the repository object
@@ -132,7 +146,7 @@ GIT_EXTERN(git_repository *) git_object_owner(const git_object *obj);
* This method instructs the library to close an existing
* object; note that git_objects are owned and cached by the repository
* so the object may or may not be freed after this library call,
- * depending on how agressive is the caching mechanism used
+ * depending on how aggressive is the caching mechanism used
* by the repository.
*
* IMPORTANT:
@@ -141,10 +155,10 @@ GIT_EXTERN(git_repository *) git_object_owner(const git_object *obj);
*
* @param object the object to close
*/
-GIT_EXTERN(void) git_object_close(git_object *object);
+GIT_EXTERN(void) git_object_free(git_object *object);
/**
- * Convert an object type to it's string representation.
+ * Convert an object type to its string representation.
*
* The result is a pointer to a string in static memory and
* should not be free()'ed.
@@ -185,6 +199,36 @@ GIT_EXTERN(int) git_object_typeisloose(git_otype type);
*/
GIT_EXTERN(size_t) git_object__size(git_otype type);
+/**
+ * Recursively peel an object until an object of the specified type is met.
+ *
+ * The retrieved `peeled` object is owned by the repository and should be
+ * closed with the `git_object_free` method.
+ *
+ * If you pass `GIT_OBJ_ANY` as the target type, then the object will be
+ * peeled until the type changes (e.g. a tag will be chased until the
+ * referenced object is no longer a tag).
+ *
+ * @param peeled Pointer to the peeled git_object
+ * @param object The object to be processed
+ * @param target_type The type of the requested object (GIT_OBJ_COMMIT,
+ * GIT_OBJ_TAG, GIT_OBJ_TREE, GIT_OBJ_BLOB or GIT_OBJ_ANY).
+ * @return 0 on success, GIT_EAMBIGUOUS, GIT_ENOTFOUND or an error code
+ */
+GIT_EXTERN(int) git_object_peel(
+ git_object **peeled,
+ const git_object *object,
+ git_otype target_type);
+
+/**
+ * Create an in-memory copy of a Git object. The copy must be
+ * explicitly free'd or it will leak.
+ *
+ * @param dest Pointer to store the copy of the object
+ * @param source Original object to copy
+ */
+GIT_EXTERN(int) git_object_dup(git_object **dest, git_object *source);
+
/** @} */
GIT_END_DECL
diff --git a/include/git2/odb.h b/include/git2/odb.h
index d0c369055..114f6b317 100644
--- a/include/git2/odb.h
+++ b/include/git2/odb.h
@@ -1,26 +1,8 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_odb_h__
#define INCLUDE_git_odb_h__
@@ -28,7 +10,6 @@
#include "common.h"
#include "types.h"
#include "oid.h"
-#include "odb_backend.h"
/**
* @file git2/odb.h
@@ -40,15 +21,19 @@
GIT_BEGIN_DECL
/**
+ * Function type for callbacks from git_odb_foreach.
+ */
+typedef int (*git_odb_foreach_cb)(const git_oid *id, void *payload);
+
+/**
* Create a new object database with no backends.
*
* Before the ODB can be used for read/writing, a custom database
* backend must be manually added using `git_odb_add_backend()`
*
* @param out location to store the database pointer, if opened.
- * Set to NULL if the open failed.
- * @return GIT_SUCCESS if the database was created; otherwise an error
- * code describing why the open was not possible.
+ * Set to NULL if the open failed.
+ * @return 0 or an error code
*/
GIT_EXTERN(int) git_odb_new(git_odb **out);
@@ -64,55 +49,35 @@ GIT_EXTERN(int) git_odb_new(git_odb **out);
* contains a 'pack/' folder with the corresponding data
*
* @param out location to store the database pointer, if opened.
- * Set to NULL if the open failed.
+ * Set to NULL if the open failed.
* @param objects_dir path of the backends' "objects" directory.
- * @return GIT_SUCCESS if the database opened; otherwise an error
- * code describing why the open was not possible.
+ * @return 0 or an error code
*/
GIT_EXTERN(int) git_odb_open(git_odb **out, const char *objects_dir);
/**
- * Add a custom backend to an existing Object DB
- *
- * The backends are checked in relative ordering, based on the
- * value of the `priority` parameter.
+ * Add an on-disk alternate to an existing Object DB.
*
- * Read <odb_backends.h> for more information.
- *
- * @param odb database to add the backend to
- * @param backend pointer to a git_odb_backend instance
- * @param priority Value for ordering the backends queue
- * @return 0 on sucess; error code otherwise
- */
-GIT_EXTERN(int) git_odb_add_backend(git_odb *odb, git_odb_backend *backend, int priority);
-
-/**
- * Add a custom backend to an existing Object DB; this
- * backend will work as an alternate.
+ * Note that the added path must point to an `objects`, not
+ * to a full repository, to use it as an alternate store.
*
* Alternate backends are always checked for objects *after*
* all the main backends have been exhausted.
*
- * The backends are checked in relative ordering, based on the
- * value of the `priority` parameter.
- *
* Writing is disabled on alternate backends.
*
- * Read <odb_backends.h> for more information.
- *
* @param odb database to add the backend to
- * @param backend pointer to a git_odb_backend instance
- * @param priority Value for ordering the backends queue
- * @return 0 on sucess; error code otherwise
+ * @param path path to the objects folder for the alternate
+ * @return 0 on success; error code otherwise
*/
-GIT_EXTERN(int) git_odb_add_alternate(git_odb *odb, git_odb_backend *backend, int priority);
+GIT_EXTERN(int) git_odb_add_disk_alternate(git_odb *odb, const char *path);
/**
* Close an open object database.
*
- * @param db database pointer to close. If NULL no action is taken.
+ * @param db database pointer to close. If NULL no action is taken.
*/
-GIT_EXTERN(void) git_odb_close(git_odb *db);
+GIT_EXTERN(void) git_odb_free(git_odb *db);
/**
* Read an object from the database.
@@ -128,7 +93,7 @@ GIT_EXTERN(void) git_odb_close(git_odb *db);
* @param db database to search for the object in.
* @param id identity of the object to read.
* @return
- * - GIT_SUCCESS if the object was read;
+ * - 0 if the object was read;
* - GIT_ENOTFOUND if the object is not in the database.
*/
GIT_EXTERN(int) git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id);
@@ -155,11 +120,12 @@ GIT_EXTERN(int) git_odb_read(git_odb_object **out, git_odb *db, const git_oid *i
* @param db database to search for the object in.
* @param short_id a prefix of the id of the object to read.
* @param len the length of the prefix
- * @return GIT_SUCCESS if the object was read;
- * GIT_ENOTFOUND if the object is not in the database.
- * GIT_EAMBIGUOUS if the prefix is ambiguous (several objects match the prefix)
+ * @return
+ * - 0 if the object was read;
+ * - GIT_ENOTFOUND if the object is not in the database.
+ * - GIT_EAMBIGUOUS if the prefix is ambiguous (several objects match the prefix)
*/
-GIT_EXTERN(int) git_odb_read_prefix(git_odb_object **out, git_odb *db, const git_oid *short_id, unsigned int len);
+GIT_EXTERN(int) git_odb_read_prefix(git_odb_object **out, git_odb *db, const git_oid *short_id, size_t len);
/**
* Read the header of an object from the database, without
@@ -171,15 +137,15 @@ GIT_EXTERN(int) git_odb_read_prefix(git_odb_object **out, git_odb *db, const git
* of an object, so the whole object will be read and then the
* header will be returned.
*
- * @param len_p pointer where to store the length
- * @param type_p pointer where to store the type
+ * @param len_out pointer where to store the length
+ * @param type_out pointer where to store the type
* @param db database to search for the object in.
* @param id identity of the object to read.
* @return
- * - GIT_SUCCESS if the object was read;
+ * - 0 if the object was read;
* - GIT_ENOTFOUND if the object is not in the database.
*/
-GIT_EXTERN(int) git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git_oid *id);
+GIT_EXTERN(int) git_odb_read_header(size_t *len_out, git_otype *type_out, git_odb *db, const git_oid *id);
/**
* Determine if the given object can be found in the object database.
@@ -193,6 +159,54 @@ GIT_EXTERN(int) git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *d
GIT_EXTERN(int) git_odb_exists(git_odb *db, const git_oid *id);
/**
+ * Determine if objects can be found in the object database from a short OID.
+ *
+ * @param out The full OID of the found object if just one is found.
+ * @param db The database to be searched for the given object.
+ * @param short_id A prefix of the id of the object to read.
+ * @param len The length of the prefix.
+ * @return 0 if found, GIT_ENOTFOUND if not found, GIT_EAMBIGUOUS if multiple
+ * matches were found, other value < 0 if there was a read error.
+ */
+GIT_EXTERN(int) git_odb_exists_prefix(
+ git_oid *out, git_odb *db, const git_oid *short_id, size_t len);
+
+/**
+ * Refresh the object database to load newly added files.
+ *
+ * If the object databases have changed on disk while the library
+ * is running, this function will force a reload of the underlying
+ * indexes.
+ *
+ * Use this function when you're confident that an external
+ * application has tampered with the ODB.
+ *
+ * NOTE that it is not necessary to call this function at all. The
+ * library will automatically attempt to refresh the ODB
+ * when a lookup fails, to see if the looked up object exists
+ * on disk but hasn't been loaded yet.
+ *
+ * @param db database to refresh
+ * @return 0 on success, error code otherwise
+ */
+GIT_EXTERN(int) git_odb_refresh(struct git_odb *db);
+
+/**
+ * List all objects available in the database
+ *
+ * The callback will be called for each object available in the
+ * database. Note that the objects are likely to be returned in the index
+ * order, which would make accessing the objects in that order inefficient.
+ * Return a non-zero value from the callback to stop looping.
+ *
+ * @param db database to use
+ * @param cb the callback to call for each object
+ * @param payload data to pass to the callback
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_odb_foreach(git_odb *db, git_odb_foreach_cb cb, void *payload);
+
+/**
* Write an object directly into the ODB
*
* This method writes a full object straight into the ODB.
@@ -203,14 +217,14 @@ GIT_EXTERN(int) git_odb_exists(git_odb *db, const git_oid *id);
* This method is provided for compatibility with custom backends
* which are not able to support streaming writes
*
- * @param oid pointer to store the OID result of the write
+ * @param out pointer to store the OID result of the write
* @param odb object database where to store the object
- * @param data buffer with the data to storr
+ * @param data buffer with the data to store
* @param len size of the buffer
* @param type type of the data to store
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_odb_write(git_oid *oid, git_odb *odb, const void *data, size_t len, git_otype type);
+GIT_EXTERN(int) git_odb_write(git_oid *out, git_odb *odb, const void *data, size_t len, git_otype type);
/**
* Open a stream to write an object into the ODB
@@ -218,28 +232,64 @@ GIT_EXTERN(int) git_odb_write(git_oid *oid, git_odb *odb, const void *data, size
* The type and final length of the object must be specified
* when opening the stream.
*
- * The returned stream will be of type `GIT_STREAM_WRONLY` and
- * will have the following methods:
- *
- * - stream->write: write `n` bytes into the stream
- * - stream->finalize_write: close the stream and store the object in
- * the odb
- * - stream->free: free the stream
- *
- * The streaming write won't be effective until `stream->finalize_write`
- * is called and returns without an error
+ * The returned stream will be of type `GIT_STREAM_WRONLY`, and it
+ * won't be effective until `git_odb_stream_finalize_write` is called
+ * and returns without an error
*
- * The stream must always be free'd or will leak memory.
+ * The stream must always be freed when done with `git_odb_stream_free` or
+ * will leak memory.
*
* @see git_odb_stream
*
- * @param stream pointer where to store the stream
+ * @param out pointer where to store the stream
* @param db object database where the stream will write
* @param size final size of the object that will be written
* @param type type of the object that will be written
* @return 0 if the stream was created; error code otherwise
*/
-GIT_EXTERN(int) git_odb_open_wstream(git_odb_stream **stream, git_odb *db, size_t size, git_otype type);
+GIT_EXTERN(int) git_odb_open_wstream(git_odb_stream **out, git_odb *db, size_t size, git_otype type);
+
+/**
+ * Write to an odb stream
+ *
+ * This method will fail if the total number of received bytes exceeds the
+ * size declared with `git_odb_open_wstream()`
+ *
+ * @param stream the stream
+ * @param buffer the data to write
+ * @param len the buffer's length
+ * @return 0 if the write succeeded; error code otherwise
+ */
+GIT_EXTERN(int) git_odb_stream_write(git_odb_stream *stream, const char *buffer, size_t len);
+
+/**
+ * Finish writing to an odb stream
+ *
+ * The object will take its final name and will be available to the
+ * odb.
+ *
+ * This method will fail if the total number of received bytes
+ * differs from the size declared with `git_odb_open_wstream()`
+ *
+ * @param out pointer to store the resulting object's id
+ * @param stream the stream
+ * @return 0 on success; an error code otherwise
+ */
+GIT_EXTERN(int) git_odb_stream_finalize_write(git_oid *out, git_odb_stream *stream);
+
+/**
+ * Read from an odb stream
+ *
+ * Most backends don't implement streaming reads
+ */
+GIT_EXTERN(int) git_odb_stream_read(git_odb_stream *stream, char *buffer, size_t len);
+
+/**
+ * Free an odb stream
+ *
+ * @param stream the stream to free
+ */
+GIT_EXTERN(void) git_odb_stream_free(git_odb_stream *stream);
/**
* Open a stream to read an object from the ODB
@@ -260,41 +310,81 @@ GIT_EXTERN(int) git_odb_open_wstream(git_odb_stream **stream, git_odb *db, size_
*
* @see git_odb_stream
*
- * @param stream pointer where to store the stream
+ * @param out pointer where to store the stream
* @param db object database where the stream will read from
* @param oid oid of the object the stream will read from
* @return 0 if the stream was created; error code otherwise
*/
-GIT_EXTERN(int) git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oid);
+GIT_EXTERN(int) git_odb_open_rstream(git_odb_stream **out, git_odb *db, const git_oid *oid);
+
+/**
+ * Open a stream for writing a pack file to the ODB.
+ *
+ * If the ODB layer understands pack files, then the given
+ * packfile will likely be streamed directly to disk (and a
+ * corresponding index created). If the ODB layer does not
+ * understand pack files, the objects will be stored in whatever
+ * format the ODB layer uses.
+ *
+ * @see git_odb_writepack
+ *
+ * @param out pointer to the writepack functions
+ * @param db object database where the stream will read from
+ * @param progress_cb function to call with progress information.
+ * Be aware that this is called inline with network and indexing operations,
+ * so performance may be affected.
+ * @param progress_payload payload for the progress callback
+ */
+GIT_EXTERN(int) git_odb_write_pack(
+ git_odb_writepack **out,
+ git_odb *db,
+ git_transfer_progress_cb progress_cb,
+ void *progress_payload);
/**
* Determine the object-ID (sha1 hash) of a data buffer
*
- * The resulting SHA-1 OID will the itentifier for the data
+ * The resulting SHA-1 OID will be the identifier for the data
* buffer as if the data buffer it were to written to the ODB.
*
- * @param id the resulting object-ID.
+ * @param out the resulting object-ID.
* @param data data to hash
* @param len size of the data
* @param type of the data to hash
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_odb_hash(git_oid *id, const void *data, size_t len, git_otype type);
+GIT_EXTERN(int) git_odb_hash(git_oid *out, const void *data, size_t len, git_otype type);
/**
* Read a file from disk and fill a git_oid with the object id
* that the file would have if it were written to the Object
- * Database as an object of the given type. Similar functionality
- * to git.git's `git hash-object` without the `-w` flag.
+ * Database as an object of the given type (w/o applying filters).
+ * Similar functionality to git.git's `git hash-object` without
+ * the `-w` flag, however, with the --no-filters flag.
+ * If you need filters, see git_repository_hashfile.
*
* @param out oid structure the result is written into.
* @param path file to read and determine object id for
* @param type the type of the object that will be hashed
- * @return GIT_SUCCESS if valid; error code otherwise
+ * @return 0 or an error code
*/
GIT_EXTERN(int) git_odb_hashfile(git_oid *out, const char *path, git_otype type);
/**
+ * Create a copy of an odb_object
+ *
+ * The returned copy must be manually freed with `git_odb_object_free`.
+ * Note that because of an implementation detail, the returned copy will be
+ * the same pointer as `source`: the object is internally refcounted, so the
+ * copy still needs to be freed twice.
+ *
+ * @param dest pointer where to store the copy
+ * @param source object to copy
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_odb_object_dup(git_odb_object **dest, git_odb_object *source);
+
+/**
* Close an ODB object
*
* This method must always be called once a `git_odb_object` is no
@@ -302,7 +392,7 @@ GIT_EXTERN(int) git_odb_hashfile(git_oid *out, const char *path, git_otype type)
*
* @param object object to close
*/
-GIT_EXTERN(void) git_odb_object_close(git_odb_object *object);
+GIT_EXTERN(void) git_odb_object_free(git_odb_object *object);
/**
* Return the OID of an ODB object
@@ -346,6 +436,60 @@ GIT_EXTERN(size_t) git_odb_object_size(git_odb_object *object);
*/
GIT_EXTERN(git_otype) git_odb_object_type(git_odb_object *object);
+/**
+ * Add a custom backend to an existing Object DB
+ *
+ * The backends are checked in relative ordering, based on the
+ * value of the `priority` parameter.
+ *
+ * Read <odb_backends.h> for more information.
+ *
+ * @param odb database to add the backend to
+ * @param backend pointer to a git_odb_backend instance
+ * @param priority Value for ordering the backends queue
+ * @return 0 on success; error code otherwise
+ */
+GIT_EXTERN(int) git_odb_add_backend(git_odb *odb, git_odb_backend *backend, int priority);
+
+/**
+ * Add a custom backend to an existing Object DB; this
+ * backend will work as an alternate.
+ *
+ * Alternate backends are always checked for objects *after*
+ * all the main backends have been exhausted.
+ *
+ * The backends are checked in relative ordering, based on the
+ * value of the `priority` parameter.
+ *
+ * Writing is disabled on alternate backends.
+ *
+ * Read <odb_backends.h> for more information.
+ *
+ * @param odb database to add the backend to
+ * @param backend pointer to a git_odb_backend instance
+ * @param priority Value for ordering the backends queue
+ * @return 0 on success; error code otherwise
+ */
+GIT_EXTERN(int) git_odb_add_alternate(git_odb *odb, git_odb_backend *backend, int priority);
+
+/**
+ * Get the number of ODB backend objects
+ *
+ * @param odb object database
+ * @return number of backends in the ODB
+ */
+GIT_EXTERN(size_t) git_odb_num_backends(git_odb *odb);
+
+/**
+ * Lookup an ODB backend object by index
+ *
+ * @param out output pointer to ODB backend at pos
+ * @param odb object database
+ * @param pos index into object database backend list
+ * @return 0 on success; GIT_ENOTFOUND if pos is invalid; other errors < 0
+ */
+GIT_EXTERN(int) git_odb_get_backend(git_odb_backend **out, git_odb *odb, size_t pos);
+
/** @} */
GIT_END_DECL
#endif
diff --git a/include/git2/odb_backend.h b/include/git2/odb_backend.h
index 43a1c2d21..4d772cab9 100644
--- a/include/git2/odb_backend.h
+++ b/include/git2/odb_backend.h
@@ -1,117 +1,133 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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_odb_backend_h__
#define INCLUDE_git_odb_backend_h__
#include "common.h"
#include "types.h"
-#include "oid.h"
/**
* @file git2/backend.h
* @brief Git custom backend functions
- * @defgroup git_backend Git custom backend API
+ * @defgroup git_odb Git object database routines
* @ingroup Git
* @{
*/
GIT_BEGIN_DECL
-struct git_odb_stream;
-
-/** An instance for a custom backend */
-struct git_odb_backend {
- git_odb *odb;
-
- int (* read)(
- void **, size_t *, git_otype *,
- struct git_odb_backend *,
- const git_oid *);
+/*
+ * Constructors for in-box ODB backends.
+ */
- /* To find a unique object given a prefix
- * of its oid.
- * The oid given must be so that the
- * remaining (GIT_OID_HEXSZ - len)*4 bits
- * are 0s.
- */
- int (* read_prefix)(
- git_oid *,
- void **, size_t *, git_otype *,
- struct git_odb_backend *,
- const git_oid *,
- unsigned int);
-
- int (* read_header)(
- size_t *, git_otype *,
- struct git_odb_backend *,
- const git_oid *);
-
- int (* write)(
- git_oid *,
- struct git_odb_backend *,
- const void *,
- size_t,
- git_otype);
-
- int (* writestream)(
- struct git_odb_stream **,
- struct git_odb_backend *,
- size_t,
- git_otype);
-
- int (* readstream)(
- struct git_odb_stream **,
- struct git_odb_backend *,
- const git_oid *);
-
- int (* exists)(
- struct git_odb_backend *,
- const git_oid *);
-
- void (* free)(struct git_odb_backend *);
-};
+/**
+ * Create a backend for the packfiles.
+ *
+ * @param out location to store the odb backend pointer
+ * @param objects_dir the Git repository's objects directory
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_odb_backend_pack(git_odb_backend **out, const char *objects_dir);
-/** A stream to read/write from a backend */
-struct git_odb_stream {
- struct git_odb_backend *backend;
- int mode;
+/**
+ * Create a backend for loose objects
+ *
+ * @param out location to store the odb backend pointer
+ * @param objects_dir the Git repository's objects directory
+ * @param compression_level zlib compression level to use
+ * @param do_fsync whether to do an fsync() after writing (currently ignored)
+ * @param dir_mode permissions to use creating a directory or 0 for defaults
+ * @param file_mode permissions to use creating a file or 0 for defaults
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_odb_backend_loose(
+ git_odb_backend **out,
+ const char *objects_dir,
+ int compression_level,
+ int do_fsync,
+ unsigned int dir_mode,
+ unsigned int file_mode);
- int (*read)(struct git_odb_stream *stream, char *buffer, size_t len);
- int (*write)(struct git_odb_stream *stream, const char *buffer, size_t len);
- int (*finalize_write)(git_oid *oid_p, struct git_odb_stream *stream);
- void (*free)(struct git_odb_stream *stream);
-};
+/**
+ * Create a backend out of a single packfile
+ *
+ * This can be useful for inspecting the contents of a single
+ * packfile.
+ *
+ * @param out location to store the odb backend pointer
+ * @param index_file path to the packfile's .idx file
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_odb_backend_one_pack(git_odb_backend **out, const char *index_file);
/** Streaming mode */
typedef enum {
GIT_STREAM_RDONLY = (1 << 1),
GIT_STREAM_WRONLY = (1 << 2),
GIT_STREAM_RW = (GIT_STREAM_RDONLY | GIT_STREAM_WRONLY),
-} git_odb_streammode;
+} git_odb_stream_t;
+
+/**
+ * A stream to read/write from a backend.
+ *
+ * This represents a stream of data being written to or read from a
+ * backend. When writing, the frontend functions take care of
+ * calculating the object's id and all `finalize_write` needs to do is
+ * store the object with the id it is passed.
+ */
+struct git_odb_stream {
+ git_odb_backend *backend;
+ unsigned int mode;
+ void *hash_ctx;
-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);
+ size_t declared_size;
+ size_t received_bytes;
+
+ /**
+ * Write at most `len` bytes into `buffer` and advance the stream.
+ */
+ int (*read)(git_odb_stream *stream, char *buffer, size_t len);
+
+ /**
+ * Write `len` bytes from `buffer` into the stream.
+ */
+ int (*write)(git_odb_stream *stream, const char *buffer, size_t len);
+
+ /**
+ * Store the contents of the stream as an object with the id
+ * specified in `oid`.
+ *
+ * This method might not be invoked if:
+ * - an error occurs earlier with the `write` callback,
+ * - the object referred to by `oid` already exists in any backend, or
+ * - the final number of received bytes differs from the size declared
+ * with `git_odb_open_wstream()`
+ */
+ int (*finalize_write)(git_odb_stream *stream, const git_oid *oid);
+
+ /**
+ * Free the stream's memory.
+ *
+ * This method might be called without a call to `finalize_write` if
+ * an error occurs or if the object is already present in the ODB.
+ */
+ void (*free)(git_odb_stream *stream);
+};
+
+/** A stream to write a pack file to the ODB */
+struct git_odb_writepack {
+ git_odb_backend *backend;
+
+ int (*append)(git_odb_writepack *writepack, const void *data, size_t size, git_transfer_progress *stats);
+ int (*commit)(git_odb_writepack *writepack, git_transfer_progress *stats);
+ void (*free)(git_odb_writepack *writepack);
+};
GIT_END_DECL
diff --git a/include/git2/oid.h b/include/git2/oid.h
index 8a0f134b9..1cfd4e5e2 100644
--- a/include/git2/oid.h
+++ b/include/git2/oid.h
@@ -1,26 +1,8 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_oid_h__
#define INCLUDE_git_oid_h__
@@ -48,24 +30,33 @@ GIT_BEGIN_DECL
#define GIT_OID_MINPREFIXLEN 4
/** Unique identity of any object (commit, tree, blob, tag). */
-typedef struct _git_oid git_oid;
-struct _git_oid {
+typedef struct git_oid {
/** raw binary formatted id */
unsigned char id[GIT_OID_RAWSZ];
-};
+} git_oid;
/**
* Parse a hex formatted object id into a git_oid.
*
* @param out oid structure the result is written into.
* @param str input hex string; must be pointing at the start of
- * the hex sequence and have at least the number of bytes
- * needed for an oid encoded in hex (40 bytes).
- * @return GIT_SUCCESS if valid; GIT_ENOTOID on failure.
+ * the hex sequence and have at least the number of bytes
+ * needed for an oid encoded in hex (40 bytes).
+ * @return 0 or an error code
*/
GIT_EXTERN(int) git_oid_fromstr(git_oid *out, const char *str);
/**
+ * Parse a hex formatted null-terminated string into a git_oid.
+ *
+ * @param out oid structure the result is written into.
+ * @param str input hex string; must be at least 4 characters
+ * long and null-terminated.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_oid_fromstrp(git_oid *out, const char *str);
+
+/**
* Parse N characters of a hex formatted object id into a git_oid
*
* If N is odd, N-1 characters will be parsed instead.
@@ -74,7 +65,7 @@ GIT_EXTERN(int) git_oid_fromstr(git_oid *out, const char *str);
* @param out oid structure the result is written into.
* @param str input hex string of at least size `length`
* @param length length of the input string
- * @return GIT_SUCCESS if valid; GIT_ENOTOID on failure.
+ * @return 0 or an error code
*/
GIT_EXTERN(int) git_oid_fromstrn(git_oid *out, const char *str, size_t length);
@@ -89,55 +80,68 @@ GIT_EXTERN(void) git_oid_fromraw(git_oid *out, const unsigned char *raw);
/**
* Format a git_oid into a hex string.
*
- * @param str output hex string; must be pointing at the start of
- * the hex sequence and have at least the number of bytes
- * needed for an oid encoded in hex (40 bytes). Only the
- * oid digits are written; a '\\0' terminator must be added
- * by the caller if it is required.
- * @param oid oid structure to format.
+ * @param out output hex string; must be pointing at the start of
+ * the hex sequence and have at least the number of bytes
+ * needed for an oid encoded in hex (40 bytes). Only the
+ * oid digits are written; a '\\0' terminator must be added
+ * by the caller if it is required.
+ * @param id oid structure to format.
*/
-GIT_EXTERN(void) git_oid_fmt(char *str, const git_oid *oid);
+GIT_EXTERN(void) git_oid_fmt(char *out, const git_oid *id);
+
+/**
+ * Format a git_oid into a partial hex string.
+ *
+ * @param out output hex string; you say how many bytes to write.
+ * If the number of bytes is > GIT_OID_HEXSZ, extra bytes
+ * will be zeroed; if not, a '\0' terminator is NOT added.
+ * @param n number of characters to write into out string
+ * @param id oid structure to format.
+ */
+GIT_EXTERN(void) git_oid_nfmt(char *out, size_t n, const git_oid *id);
/**
* Format a git_oid into a loose-object path string.
*
* The resulting string is "aa/...", where "aa" is the first two
- * hex digitis of the oid and "..." is the remaining 38 digits.
+ * hex digits of the oid and "..." is the remaining 38 digits.
*
- * @param str output hex string; must be pointing at the start of
- * the hex sequence and have at least the number of bytes
- * needed for an oid encoded in hex (41 bytes). Only the
- * oid digits are written; a '\\0' terminator must be added
- * by the caller if it is required.
- * @param oid oid structure to format.
+ * @param out output hex string; must be pointing at the start of
+ * the hex sequence and have at least the number of bytes
+ * needed for an oid encoded in hex (41 bytes). Only the
+ * oid digits are written; a '\\0' terminator must be added
+ * by the caller if it is required.
+ * @param id oid structure to format.
*/
-GIT_EXTERN(void) git_oid_pathfmt(char *str, const git_oid *oid);
+GIT_EXTERN(void) git_oid_pathfmt(char *out, const git_oid *id);
/**
* Format a git_oid into a newly allocated c-string.
*
- * @param oid the oid structure to format
- * @return the c-string; NULL if memory is exhausted. Caller must
- * deallocate the string with free().
+ * @param id the oid structure to format
+ * @return the c-string; NULL if memory is exhausted. Caller must
+ * deallocate the string with git__free().
*/
-GIT_EXTERN(char *) git_oid_allocfmt(const git_oid *oid);
+GIT_EXTERN(char *) git_oid_allocfmt(const git_oid *id);
/**
* Format a git_oid into a buffer as a hex format c-string.
*
* If the buffer is smaller than GIT_OID_HEXSZ+1, then the resulting
- * oid c-string will be truncated to n-1 characters. If there are
- * any input parameter errors (out == NULL, n == 0, oid == NULL),
- * then a pointer to an empty string is returned, so that the return
- * value can always be printed.
+ * oid c-string will be truncated to n-1 characters (but will still be
+ * NUL-byte terminated).
+ *
+ * If there are any input parameter errors (out == NULL, n == 0, oid ==
+ * NULL), then a pointer to an empty string is returned, so that the
+ * return value can always be printed.
*
* @param out the buffer into which the oid string is output.
* @param n the size of the out buffer.
- * @param oid the oid structure to format.
+ * @param id the oid structure to format.
* @return the out buffer pointer, assuming no input parameter
- * errors, otherwise a pointer to an empty string.
+ * errors, otherwise a pointer to an empty string.
*/
-GIT_EXTERN(char *) git_oid_to_string(char *out, size_t n, const git_oid *oid);
+GIT_EXTERN(char *) git_oid_tostr(char *out, size_t n, const git_oid *id);
/**
* Copy an oid from one structure to another.
@@ -157,6 +161,15 @@ GIT_EXTERN(void) git_oid_cpy(git_oid *out, const git_oid *src);
GIT_EXTERN(int) git_oid_cmp(const git_oid *a, const git_oid *b);
/**
+ * Compare two oid structures for equality
+ *
+ * @param a first oid structure.
+ * @param b second oid structure.
+ * @return true if equal, false otherwise
+ */
+GIT_EXTERN(int) git_oid_equal(const git_oid *a, const git_oid *b);
+
+/**
* Compare the first 'len' hexadecimal characters (packets of 4 bits)
* of two oid structures.
*
@@ -165,7 +178,33 @@ GIT_EXTERN(int) git_oid_cmp(const git_oid *a, const git_oid *b);
* @param len the number of hex chars to compare
* @return 0 in case of a match
*/
-GIT_EXTERN(int) git_oid_ncmp(const git_oid *a, const git_oid *b, unsigned int len);
+GIT_EXTERN(int) git_oid_ncmp(const git_oid *a, const git_oid *b, size_t len);
+
+/**
+ * Check if an oid equals an hex formatted object id.
+ *
+ * @param id oid structure.
+ * @param str input hex string of an object id.
+ * @return 0 in case of a match, -1 otherwise.
+ */
+GIT_EXTERN(int) git_oid_streq(const git_oid *id, const char *str);
+
+/**
+ * Compare an oid to an hex formatted object id.
+ *
+ * @param id oid structure.
+ * @param str input hex string of an object id.
+ * @return -1 if str is not valid, <0 if id sorts before str,
+ * 0 if id matches str, >0 if id sorts after str.
+ */
+GIT_EXTERN(int) git_oid_strcmp(const git_oid *id, const char *str);
+
+/**
+ * Check is an oid is all zeros.
+ *
+ * @return 1 if all zeros, 0 otherwise.
+ */
+GIT_EXTERN(int) git_oid_iszero(const git_oid *id);
/**
* OID Shortener object
@@ -186,7 +225,7 @@ typedef struct git_oid_shorten git_oid_shorten;
* be unique.
* @return a `git_oid_shorten` instance, NULL if OOM
*/
-git_oid_shorten *git_oid_shorten_new(size_t min_length);
+GIT_EXTERN(git_oid_shorten *) git_oid_shorten_new(size_t min_length);
/**
* Add a new OID to set of shortened OIDs and calculate
@@ -198,28 +237,28 @@ git_oid_shorten *git_oid_shorten_new(size_t min_length);
* or freed.
*
* For performance reasons, there is a hard-limit of how many
- * OIDs can be added to a single set (around ~22000, assuming
+ * OIDs can be added to a single set (around ~32000, assuming
* a mostly randomized distribution), which should be enough
* for any kind of program, and keeps the algorithm fast and
* memory-efficient.
*
* Attempting to add more than those OIDs will result in a
- * GIT_ENOMEM error
+ * GITERR_INVALID error
*
* @param os a `git_oid_shorten` instance
- * @param text_oid an OID in text form
+ * @param text_id an OID in text form
* @return the minimal length to uniquely identify all OIDs
* added so far to the set; or an error code (<0) if an
* error occurs.
*/
-int git_oid_shorten_add(git_oid_shorten *os, const char *text_oid);
+GIT_EXTERN(int) git_oid_shorten_add(git_oid_shorten *os, const char *text_id);
/**
* Free an OID shortener instance
*
* @param os a `git_oid_shorten` instance
*/
-void git_oid_shorten_free(git_oid_shorten *os);
+GIT_EXTERN(void) git_oid_shorten_free(git_oid_shorten *os);
/** @} */
GIT_END_DECL
diff --git a/include/git2/pack.h b/include/git2/pack.h
new file mode 100644
index 000000000..e7f060d12
--- /dev/null
+++ b/include/git2/pack.h
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_pack_h__
+#define INCLUDE_git_pack_h__
+
+#include "common.h"
+#include "oid.h"
+
+/**
+ * @file git2/pack.h
+ * @brief Git pack management routines
+ *
+ * Packing objects
+ * ---------------
+ *
+ * Creation of packfiles requires two steps:
+ *
+ * - First, insert all the objects you want to put into the packfile
+ * using `git_packbuilder_insert` and `git_packbuilder_insert_tree`.
+ * It's important to add the objects in recency order ("in the order
+ * that they are 'reachable' from head").
+ *
+ * "ANY order will give you a working pack, ... [but it is] the thing
+ * that gives packs good locality. It keeps the objects close to the
+ * head (whether they are old or new, but they are _reachable_ from the
+ * head) at the head of the pack. So packs actually have absolutely
+ * _wonderful_ IO patterns." - Linus Torvalds
+ * git.git/Documentation/technical/pack-heuristics.txt
+ *
+ * - Second, use `git_packbuilder_write` or `git_packbuilder_foreach` to
+ * write the resulting packfile.
+ *
+ * libgit2 will take care of the delta ordering and generation.
+ * `git_packbuilder_set_threads` can be used to adjust the number of
+ * threads used for the process.
+ *
+ * See tests/pack/packbuilder.c for an example.
+ *
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Stages that are reported by the packbuilder progress callback.
+ */
+typedef enum {
+ GIT_PACKBUILDER_ADDING_OBJECTS = 0,
+ GIT_PACKBUILDER_DELTAFICATION = 1,
+} git_packbuilder_stage_t;
+
+/**
+ * Initialize a new packbuilder
+ *
+ * @param out The new packbuilder object
+ * @param repo The repository
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_packbuilder_new(git_packbuilder **out, git_repository *repo);
+
+/**
+ * Set number of threads to spawn
+ *
+ * By default, libgit2 won't spawn any threads at all;
+ * when set to 0, libgit2 will autodetect the number of
+ * CPUs.
+ *
+ * @param pb The packbuilder
+ * @param n Number of threads to spawn
+ * @return number of actual threads to be used
+ */
+GIT_EXTERN(unsigned int) git_packbuilder_set_threads(git_packbuilder *pb, unsigned int n);
+
+/**
+ * Insert a single object
+ *
+ * For an optimal pack it's mandatory to insert objects in recency order,
+ * commits followed by trees and blobs.
+ *
+ * @param pb The packbuilder
+ * @param id The oid of the commit
+ * @param name The name; might be NULL
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_packbuilder_insert(git_packbuilder *pb, const git_oid *id, const char *name);
+
+/**
+ * Insert a root tree object
+ *
+ * This will add the tree as well as all referenced trees and blobs.
+ *
+ * @param pb The packbuilder
+ * @param id The oid of the root tree
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_packbuilder_insert_tree(git_packbuilder *pb, const git_oid *id);
+
+/**
+ * Insert a commit object
+ *
+ * This will add a commit as well as the completed referenced tree.
+ *
+ * @param pb The packbuilder
+ * @param id The oid of the commit
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_packbuilder_insert_commit(git_packbuilder *pb, const git_oid *id);
+
+/**
+ * Write the contents of the packfile to an in-memory buffer
+ *
+ * The contents of the buffer will become a valid packfile, even though there
+ * will be no attached index
+ *
+ * @param buf Buffer where to write the packfile
+ * @param pb The packbuilder
+ */
+GIT_EXTERN(int) git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb);
+
+/**
+ * Write the new pack and corresponding index file to path.
+ *
+ * @param pb The packbuilder
+ * @param path to the directory where the packfile and index should be stored
+ * @param mode permissions to use creating a packfile or 0 for defaults
+ * @param progress_cb function to call with progress information from the indexer (optional)
+ * @param progress_cb_payload payload for the progress callback (optional)
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_packbuilder_write(
+ git_packbuilder *pb,
+ const char *path,
+ unsigned int mode,
+ git_transfer_progress_cb progress_cb,
+ void *progress_cb_payload);
+
+/**
+* Get the packfile's hash
+*
+* A packfile's name is derived from the sorted hashing of all object
+* names. This is only correct after the packfile has been written.
+*
+* @param pb The packbuilder object
+*/
+GIT_EXTERN(const git_oid *) git_packbuilder_hash(git_packbuilder *pb);
+
+typedef int (*git_packbuilder_foreach_cb)(void *buf, size_t size, void *payload);
+
+/**
+ * Create the new pack and pass each object to the callback
+ *
+ * @param pb the packbuilder
+ * @param cb the callback to call with each packed object's buffer
+ * @param payload the callback's data
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_packbuilder_foreach(git_packbuilder *pb, git_packbuilder_foreach_cb cb, void *payload);
+
+/**
+ * Get the total number of objects the packbuilder will write out
+ *
+ * @param pb the packbuilder
+ * @return the number of objects in the packfile
+ */
+GIT_EXTERN(uint32_t) git_packbuilder_object_count(git_packbuilder *pb);
+
+/**
+ * Get the number of objects the packbuilder has already written out
+ *
+ * @param pb the packbuilder
+ * @return the number of objects which have already been written
+ */
+GIT_EXTERN(uint32_t) git_packbuilder_written(git_packbuilder *pb);
+
+/** Packbuilder progress notification function */
+typedef int (*git_packbuilder_progress)(
+ int stage,
+ unsigned int current,
+ unsigned int total,
+ void *payload);
+
+/**
+ * Set the callbacks for a packbuilder
+ *
+ * @param pb The packbuilder object
+ * @param progress_cb Function to call with progress information during
+ * pack building. Be aware that this is called inline with pack building
+ * operations, so performance may be affected.
+ * @param progress_cb_payload Payload for progress callback.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_packbuilder_set_callbacks(
+ git_packbuilder *pb,
+ git_packbuilder_progress progress_cb,
+ void *progress_cb_payload);
+
+/**
+ * Free the packbuilder and all associated data
+ *
+ * @param pb The packbuilder
+ */
+GIT_EXTERN(void) git_packbuilder_free(git_packbuilder *pb);
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/patch.h b/include/git2/patch.h
new file mode 100644
index 000000000..47c395669
--- /dev/null
+++ b/include/git2/patch.h
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_patch_h__
+#define INCLUDE_git_patch_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+#include "diff.h"
+
+/**
+ * @file git2/patch.h
+ * @brief Patch handling routines.
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * The diff patch is used to store all the text diffs for a delta.
+ *
+ * You can easily loop over the content of patches and get information about
+ * them.
+ */
+typedef struct git_patch git_patch;
+
+/**
+ * Return the diff delta and patch for an entry in the diff list.
+ *
+ * The `git_patch` is a newly created object contains the text diffs
+ * for the delta. You have to call `git_patch_free()` when you are
+ * done with it. You can use the patch object to loop over all the hunks
+ * and lines in the diff of the one delta.
+ *
+ * For an unchanged file or a binary file, no `git_patch` will be
+ * created, the output will be set to NULL, and the `binary` flag will be
+ * set true in the `git_diff_delta` structure.
+ *
+ * The `git_diff_delta` pointer points to internal data and you do not have
+ * to release it when you are done with it. It will go away when the
+ * `git_diff` and `git_patch` go away.
+ *
+ * It is okay to pass NULL for either of the output parameters; if you pass
+ * NULL for the `git_patch`, then the text diff will not be calculated.
+ *
+ * @param out Output parameter for the delta patch object
+ * @param diff Diff list object
+ * @param idx Index into diff list
+ * @return 0 on success, other value < 0 on error
+ */
+GIT_EXTERN(int) git_patch_from_diff(
+ git_patch **out, git_diff *diff, size_t idx);
+
+/**
+ * Directly generate a patch from the difference between two blobs.
+ *
+ * This is just like `git_diff_blobs()` except it generates a patch object
+ * for the difference instead of directly making callbacks. You can use the
+ * standard `git_patch` accessor functions to read the patch data, and
+ * you must call `git_patch_free()` on the patch when done.
+ *
+ * @param out The generated patch; NULL on error
+ * @param old_blob Blob for old side of diff, or NULL for empty blob
+ * @param old_as_path Treat old blob as if it had this filename; can be NULL
+ * @param new_blob Blob for new side of diff, or NULL for empty blob
+ * @param new_as_path Treat new blob as if it had this filename; can be NULL
+ * @param opts Options for diff, or NULL for default options
+ * @return 0 on success or error code < 0
+ */
+GIT_EXTERN(int) git_patch_from_blobs(
+ git_patch **out,
+ const git_blob *old_blob,
+ const char *old_as_path,
+ const git_blob *new_blob,
+ const char *new_as_path,
+ const git_diff_options *opts);
+
+/**
+ * Directly generate a patch from the difference between a blob and a buffer.
+ *
+ * This is just like `git_diff_blob_to_buffer()` except it generates a patch
+ * object for the difference instead of directly making callbacks. You can
+ * use the standard `git_patch` accessor functions to read the patch
+ * data, and you must call `git_patch_free()` on the patch when done.
+ *
+ * @param out The generated patch; NULL on error
+ * @param old_blob Blob for old side of diff, or NULL for empty blob
+ * @param old_as_path Treat old blob as if it had this filename; can be NULL
+ * @param buffer Raw data for new side of diff, or NULL for empty
+ * @param buffer_len Length of raw data for new side of diff
+ * @param buffer_as_path Treat buffer as if it had this filename; can be NULL
+ * @param opts Options for diff, or NULL for default options
+ * @return 0 on success or error code < 0
+ */
+GIT_EXTERN(int) git_patch_from_blob_and_buffer(
+ git_patch **out,
+ const git_blob *old_blob,
+ const char *old_as_path,
+ const char *buffer,
+ size_t buffer_len,
+ const char *buffer_as_path,
+ const git_diff_options *opts);
+
+/**
+ * Directly generate a patch from the difference between two buffers.
+ *
+ * This is just like `git_diff_buffers()` except it generates a patch
+ * object for the difference instead of directly making callbacks. You can
+ * use the standard `git_patch` accessor functions to read the patch
+ * data, and you must call `git_patch_free()` on the patch when done.
+ *
+ * @param out The generated patch; NULL on error
+ * @param old_buffer Raw data for old side of diff, or NULL for empty
+ * @param old_len Length of the raw data for old side of the diff
+ * @param old_as_path Treat old buffer as if it had this filename; can be NULL
+ * @param new_buffer Raw data for new side of diff, or NULL for empty
+ * @param new_len Length of raw data for new side of diff
+ * @param new_as_path Treat buffer as if it had this filename; can be NULL
+ * @param opts Options for diff, or NULL for default options
+ * @return 0 on success or error code < 0
+ */
+GIT_EXTERN(int) git_patch_from_buffers(
+ git_patch **out,
+ const void *old_buffer,
+ size_t old_len,
+ const char *old_as_path,
+ const char *new_buffer,
+ size_t new_len,
+ const char *new_as_path,
+ const git_diff_options *opts);
+
+/**
+ * Free a git_patch object.
+ */
+GIT_EXTERN(void) git_patch_free(git_patch *patch);
+
+/**
+ * Get the delta associated with a patch
+ */
+GIT_EXTERN(const git_diff_delta *) git_patch_get_delta(const git_patch *patch);
+
+/**
+ * Get the number of hunks in a patch
+ */
+GIT_EXTERN(size_t) git_patch_num_hunks(const git_patch *patch);
+
+/**
+ * Get line counts of each type in a patch.
+ *
+ * This helps imitate a diff --numstat type of output. For that purpose,
+ * you only need the `total_additions` and `total_deletions` values, but we
+ * include the `total_context` line count in case you want the total number
+ * of lines of diff output that will be generated.
+ *
+ * All outputs are optional. Pass NULL if you don't need a particular count.
+ *
+ * @param total_context Count of context lines in output, can be NULL.
+ * @param total_additions Count of addition lines in output, can be NULL.
+ * @param total_deletions Count of deletion lines in output, can be NULL.
+ * @param patch The git_patch object
+ * @return 0 on success, <0 on error
+ */
+GIT_EXTERN(int) git_patch_line_stats(
+ size_t *total_context,
+ size_t *total_additions,
+ size_t *total_deletions,
+ const git_patch *patch);
+
+/**
+ * Get the information about a hunk in a patch
+ *
+ * Given a patch and a hunk index into the patch, this returns detailed
+ * information about that hunk. Any of the output pointers can be passed
+ * as NULL if you don't care about that particular piece of information.
+ *
+ * @param out Output pointer to git_diff_hunk of hunk
+ * @param lines_in_hunk Output count of total lines in this hunk
+ * @param patch Input pointer to patch object
+ * @param hunk_idx Input index of hunk to get information about
+ * @return 0 on success, GIT_ENOTFOUND if hunk_idx out of range, <0 on error
+ */
+GIT_EXTERN(int) git_patch_get_hunk(
+ const git_diff_hunk **out,
+ size_t *lines_in_hunk,
+ git_patch *patch,
+ size_t hunk_idx);
+
+/**
+ * Get the number of lines in a hunk.
+ *
+ * @param patch The git_patch object
+ * @param hunk_idx Index of the hunk
+ * @return Number of lines in hunk or -1 if invalid hunk index
+ */
+GIT_EXTERN(int) git_patch_num_lines_in_hunk(
+ const git_patch *patch,
+ size_t hunk_idx);
+
+/**
+ * Get data about a line in a hunk of a patch.
+ *
+ * Given a patch, a hunk index, and a line index in the hunk, this
+ * will return a lot of details about that line. If you pass a hunk
+ * index larger than the number of hunks or a line index larger than
+ * the number of lines in the hunk, this will return -1.
+ *
+ * @param out The git_diff_line data for this line
+ * @param patch The patch to look in
+ * @param hunk_idx The index of the hunk
+ * @param line_of_hunk The index of the line in the hunk
+ * @return 0 on success, <0 on failure
+ */
+GIT_EXTERN(int) git_patch_get_line_in_hunk(
+ const git_diff_line **out,
+ git_patch *patch,
+ size_t hunk_idx,
+ size_t line_of_hunk);
+
+/**
+ * Look up size of patch diff data in bytes
+ *
+ * This returns the raw size of the patch data. This only includes the
+ * actual data from the lines of the diff, not the file or hunk headers.
+ *
+ * If you pass `include_context` as true (non-zero), this will be the size
+ * of all of the diff output; if you pass it as false (zero), this will
+ * only include the actual changed lines (as if `context_lines` was 0).
+ *
+ * @param patch A git_patch representing changes to one file
+ * @param include_context Include context lines in size if non-zero
+ * @param include_hunk_headers Include hunk header lines if non-zero
+ * @param include_file_headers Include file header lines if non-zero
+ * @return The number of bytes of data
+ */
+GIT_EXTERN(size_t) git_patch_size(
+ git_patch *patch,
+ int include_context,
+ int include_hunk_headers,
+ int include_file_headers);
+
+/**
+ * Serialize the patch to text via callback.
+ *
+ * Returning a non-zero value from the callback will terminate the iteration
+ * and return that value to the caller.
+ *
+ * @param patch A git_patch representing changes to one file
+ * @param print_cb Callback function to output lines of the patch. Will be
+ * called for file headers, hunk headers, and diff lines.
+ * @param payload Reference pointer that will be passed to your callbacks.
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_patch_print(
+ git_patch *patch,
+ git_diff_line_cb print_cb,
+ void *payload);
+
+/**
+ * Get the content of a patch as a single diff text.
+ *
+ * @param out The git_buf to be filled in
+ * @param patch A git_patch representing changes to one file
+ * @return 0 on success, <0 on failure.
+ */
+GIT_EXTERN(int) git_patch_to_buf(
+ git_buf *out,
+ git_patch *patch);
+
+GIT_END_DECL
+
+/**@}*/
+
+#endif
diff --git a/include/git2/pathspec.h b/include/git2/pathspec.h
new file mode 100644
index 000000000..de6f027c5
--- /dev/null
+++ b/include/git2/pathspec.h
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_pathspec_h__
+#define INCLUDE_git_pathspec_h__
+
+#include "common.h"
+#include "types.h"
+#include "strarray.h"
+#include "diff.h"
+
+GIT_BEGIN_DECL
+
+/**
+ * Compiled pathspec
+ */
+typedef struct git_pathspec git_pathspec;
+
+/**
+ * List of filenames matching a pathspec
+ */
+typedef struct git_pathspec_match_list git_pathspec_match_list;
+
+/**
+ * Options controlling how pathspec match should be executed
+ *
+ * - GIT_PATHSPEC_IGNORE_CASE forces match to ignore case; otherwise
+ * match will use native case sensitivity of platform filesystem
+ * - GIT_PATHSPEC_USE_CASE forces case sensitive match; otherwise
+ * match will use native case sensitivity of platform filesystem
+ * - GIT_PATHSPEC_NO_GLOB disables glob patterns and just uses simple
+ * string comparison for matching
+ * - GIT_PATHSPEC_NO_MATCH_ERROR means the match functions return error
+ * code GIT_ENOTFOUND if no matches are found; otherwise no matches is
+ * still success (return 0) but `git_pathspec_match_list_entrycount`
+ * will indicate 0 matches.
+ * - GIT_PATHSPEC_FIND_FAILURES means that the `git_pathspec_match_list`
+ * should track which patterns matched which files so that at the end of
+ * the match we can identify patterns that did not match any files.
+ * - GIT_PATHSPEC_FAILURES_ONLY means that the `git_pathspec_match_list`
+ * does not need to keep the actual matching filenames. Use this to
+ * just test if there were any matches at all or in combination with
+ * GIT_PATHSPEC_FIND_FAILURES to validate a pathspec.
+ */
+typedef enum {
+ GIT_PATHSPEC_DEFAULT = 0,
+ GIT_PATHSPEC_IGNORE_CASE = (1u << 0),
+ GIT_PATHSPEC_USE_CASE = (1u << 1),
+ GIT_PATHSPEC_NO_GLOB = (1u << 2),
+ GIT_PATHSPEC_NO_MATCH_ERROR = (1u << 3),
+ GIT_PATHSPEC_FIND_FAILURES = (1u << 4),
+ GIT_PATHSPEC_FAILURES_ONLY = (1u << 5),
+} git_pathspec_flag_t;
+
+/**
+ * Compile a pathspec
+ *
+ * @param out Output of the compiled pathspec
+ * @param pathspec A git_strarray of the paths to match
+ * @return 0 on success, <0 on failure
+ */
+GIT_EXTERN(int) git_pathspec_new(
+ git_pathspec **out, const git_strarray *pathspec);
+
+/**
+ * Free a pathspec
+ *
+ * @param ps The compiled pathspec
+ */
+GIT_EXTERN(void) git_pathspec_free(git_pathspec *ps);
+
+/**
+ * Try to match a path against a pathspec
+ *
+ * Unlike most of the other pathspec matching functions, this will not
+ * fall back on the native case-sensitivity for your platform. You must
+ * explicitly pass flags to control case sensitivity or else this will
+ * fall back on being case sensitive.
+ *
+ * @param ps The compiled pathspec
+ * @param flags Combination of git_pathspec_flag_t options to control match
+ * @param path The pathname to attempt to match
+ * @return 1 is path matches spec, 0 if it does not
+ */
+GIT_EXTERN(int) git_pathspec_matches_path(
+ const git_pathspec *ps, uint32_t flags, const char *path);
+
+/**
+ * Match a pathspec against the working directory of a repository.
+ *
+ * This matches the pathspec against the current files in the working
+ * directory of the repository. It is an error to invoke this on a bare
+ * repo. This handles git ignores (i.e. ignored files will not be
+ * considered to match the `pathspec` unless the file is tracked in the
+ * index).
+ *
+ * If `out` is not NULL, this returns a `git_patchspec_match_list`. That
+ * contains the list of all matched filenames (unless you pass the
+ * `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of
+ * pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES`
+ * flag). You must call `git_pathspec_match_list_free()` on this object.
+ *
+ * @param out Output list of matches; pass NULL to just get return value
+ * @param repo The repository in which to match; bare repo is an error
+ * @param flags Combination of git_pathspec_flag_t options to control match
+ * @param ps Pathspec to be matched
+ * @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and
+ * the GIT_PATHSPEC_NO_MATCH_ERROR flag was given
+ */
+GIT_EXTERN(int) git_pathspec_match_workdir(
+ git_pathspec_match_list **out,
+ git_repository *repo,
+ uint32_t flags,
+ git_pathspec *ps);
+
+/**
+ * Match a pathspec against entries in an index.
+ *
+ * This matches the pathspec against the files in the repository index.
+ *
+ * NOTE: At the moment, the case sensitivity of this match is controlled
+ * by the current case-sensitivity of the index object itself and the
+ * USE_CASE and IGNORE_CASE flags will have no effect. This behavior will
+ * be corrected in a future release.
+ *
+ * If `out` is not NULL, this returns a `git_patchspec_match_list`. That
+ * contains the list of all matched filenames (unless you pass the
+ * `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of
+ * pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES`
+ * flag). You must call `git_pathspec_match_list_free()` on this object.
+ *
+ * @param out Output list of matches; pass NULL to just get return value
+ * @param index The index to match against
+ * @param flags Combination of git_pathspec_flag_t options to control match
+ * @param ps Pathspec to be matched
+ * @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and
+ * the GIT_PATHSPEC_NO_MATCH_ERROR flag is used
+ */
+GIT_EXTERN(int) git_pathspec_match_index(
+ git_pathspec_match_list **out,
+ git_index *index,
+ uint32_t flags,
+ git_pathspec *ps);
+
+/**
+ * Match a pathspec against files in a tree.
+ *
+ * This matches the pathspec against the files in the given tree.
+ *
+ * If `out` is not NULL, this returns a `git_patchspec_match_list`. That
+ * contains the list of all matched filenames (unless you pass the
+ * `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of
+ * pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES`
+ * flag). You must call `git_pathspec_match_list_free()` on this object.
+ *
+ * @param out Output list of matches; pass NULL to just get return value
+ * @param tree The root-level tree to match against
+ * @param flags Combination of git_pathspec_flag_t options to control match
+ * @param ps Pathspec to be matched
+ * @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and
+ * the GIT_PATHSPEC_NO_MATCH_ERROR flag is used
+ */
+GIT_EXTERN(int) git_pathspec_match_tree(
+ git_pathspec_match_list **out,
+ git_tree *tree,
+ uint32_t flags,
+ git_pathspec *ps);
+
+/**
+ * Match a pathspec against files in a diff list.
+ *
+ * This matches the pathspec against the files in the given diff list.
+ *
+ * If `out` is not NULL, this returns a `git_patchspec_match_list`. That
+ * contains the list of all matched filenames (unless you pass the
+ * `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of
+ * pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES`
+ * flag). You must call `git_pathspec_match_list_free()` on this object.
+ *
+ * @param out Output list of matches; pass NULL to just get return value
+ * @param diff A generated diff list
+ * @param flags Combination of git_pathspec_flag_t options to control match
+ * @param ps Pathspec to be matched
+ * @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and
+ * the GIT_PATHSPEC_NO_MATCH_ERROR flag is used
+ */
+GIT_EXTERN(int) git_pathspec_match_diff(
+ git_pathspec_match_list **out,
+ git_diff *diff,
+ uint32_t flags,
+ git_pathspec *ps);
+
+/**
+ * Free memory associates with a git_pathspec_match_list
+ *
+ * @param m The git_pathspec_match_list to be freed
+ */
+GIT_EXTERN(void) git_pathspec_match_list_free(git_pathspec_match_list *m);
+
+/**
+ * Get the number of items in a match list.
+ *
+ * @param m The git_pathspec_match_list object
+ * @return Number of items in match list
+ */
+GIT_EXTERN(size_t) git_pathspec_match_list_entrycount(
+ const git_pathspec_match_list *m);
+
+/**
+ * Get a matching filename by position.
+ *
+ * This routine cannot be used if the match list was generated by
+ * `git_pathspec_match_diff`. If so, it will always return NULL.
+ *
+ * @param m The git_pathspec_match_list object
+ * @param pos The index into the list
+ * @return The filename of the match
+ */
+GIT_EXTERN(const char *) git_pathspec_match_list_entry(
+ const git_pathspec_match_list *m, size_t pos);
+
+/**
+ * Get a matching diff delta by position.
+ *
+ * This routine can only be used if the match list was generated by
+ * `git_pathspec_match_diff`. Otherwise it will always return NULL.
+ *
+ * @param m The git_pathspec_match_list object
+ * @param pos The index into the list
+ * @return The filename of the match
+ */
+GIT_EXTERN(const git_diff_delta *) git_pathspec_match_list_diff_entry(
+ const git_pathspec_match_list *m, size_t pos);
+
+/**
+ * Get the number of pathspec items that did not match.
+ *
+ * This will be zero unless you passed GIT_PATHSPEC_FIND_FAILURES when
+ * generating the git_pathspec_match_list.
+ *
+ * @param m The git_pathspec_match_list object
+ * @return Number of items in original pathspec that had no matches
+ */
+GIT_EXTERN(size_t) git_pathspec_match_list_failed_entrycount(
+ const git_pathspec_match_list *m);
+
+/**
+ * Get an original pathspec string that had no matches.
+ *
+ * This will be return NULL for positions out of range.
+ *
+ * @param m The git_pathspec_match_list object
+ * @param pos The index into the failed items
+ * @return The pathspec pattern that didn't match anything
+ */
+GIT_EXTERN(const char *) git_pathspec_match_list_failed_entry(
+ const git_pathspec_match_list *m, size_t pos);
+
+GIT_END_DECL
+#endif
diff --git a/include/git2/push.h b/include/git2/push.h
new file mode 100644
index 000000000..cbf115661
--- /dev/null
+++ b/include/git2/push.h
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_push_h__
+#define INCLUDE_git_push_h__
+
+#include "common.h"
+#include "pack.h"
+
+/**
+ * @file git2/push.h
+ * @brief Git push management functions
+ * @defgroup git_push push management functions
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Controls the behavior of a git_push object.
+ */
+typedef struct {
+ unsigned int version;
+
+ /**
+ * If the transport being used to push to the remote requires the creation
+ * of a pack file, this controls the number of worker threads used by
+ * the packbuilder when creating that pack file to be sent to the remote.
+ *
+ * If set to 0, the packbuilder will auto-detect the number of threads
+ * to create. The default value is 1.
+ */
+ unsigned int pb_parallelism;
+} git_push_options;
+
+#define GIT_PUSH_OPTIONS_VERSION 1
+#define GIT_PUSH_OPTIONS_INIT { GIT_PUSH_OPTIONS_VERSION }
+
+/**
+ * Initializes a `git_push_options` with default values. Equivalent to
+ * creating an instance with GIT_PUSH_OPTIONS_INIT.
+ *
+ * @param opts the `git_push_options` instance to initialize.
+ * @param version the version of the struct; you should pass
+ * `GIT_PUSH_OPTIONS_VERSION` here.
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_push_init_options(
+ git_push_options *opts,
+ unsigned int version);
+
+/** Push network progress notification function */
+typedef int (*git_push_transfer_progress)(
+ unsigned int current,
+ unsigned int total,
+ size_t bytes,
+ void* payload);
+
+/**
+ * Create a new push object
+ *
+ * @param out New push object
+ * @param remote Remote instance
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_push_new(git_push **out, git_remote *remote);
+
+/**
+ * Set options on a push object
+ *
+ * @param push The push object
+ * @param opts The options to set on the push object
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_push_set_options(
+ git_push *push,
+ const git_push_options *opts);
+
+/**
+ * Set the callbacks for a push
+ *
+ * @param push The push object
+ * @param pack_progress_cb Function to call with progress information during
+ * pack building. Be aware that this is called inline with pack building
+ * operations, so performance may be affected.
+ * @param pack_progress_cb_payload Payload for the pack progress callback.
+ * @param transfer_progress_cb Function to call with progress information during
+ * the upload portion of a push. Be aware that this is called inline with
+ * pack building operations, so performance may be affected.
+ * @param transfer_progress_cb_payload Payload for the network progress callback.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_push_set_callbacks(
+ git_push *push,
+ git_packbuilder_progress pack_progress_cb,
+ void *pack_progress_cb_payload,
+ git_push_transfer_progress transfer_progress_cb,
+ void *transfer_progress_cb_payload);
+
+/**
+ * Add a refspec to be pushed
+ *
+ * @param push The push object
+ * @param refspec Refspec string
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_push_add_refspec(git_push *push, const char *refspec);
+
+/**
+ * Update remote tips after a push
+ *
+ * @param push The push object
+ * @param signature The identity to use when updating reflogs
+ * @param reflog_message The message to insert into the reflogs. If NULL, the
+ * default is "update by push".
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_push_update_tips(
+ git_push *push,
+ const git_signature *signature,
+ const char *reflog_message);
+
+/**
+ * Actually push all given refspecs
+ *
+ * Note: To check if the push was successful (i.e. all remote references
+ * have been updated as requested), you need to call both
+ * `git_push_unpack_ok` and `git_push_status_foreach`. The remote
+ * repository might have refused to update some or all of the references.
+ *
+ * @param push The push object
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_push_finish(git_push *push);
+
+/**
+ * Check if remote side successfully unpacked
+ *
+ * @param push The push object
+ *
+ * @return true if remote side successfully unpacked, false otherwise
+ */
+GIT_EXTERN(int) git_push_unpack_ok(const git_push *push);
+
+/**
+ * Invoke callback `cb' on each status entry
+ *
+ * For each of the updated references, we receive a status report in the
+ * form of `ok refs/heads/master` or `ng refs/heads/master <msg>`.
+ * `msg != NULL` means the reference has not been updated for the given
+ * reason.
+ *
+ * Return a non-zero value from the callback to stop the loop.
+ *
+ * @param push The push object
+ * @param cb The callback to call on each object
+ *
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_push_status_foreach(git_push *push,
+ int (*cb)(const char *ref, const char *msg, void *data),
+ void *data);
+
+/**
+ * Free the given push object
+ *
+ * @param push The push object
+ */
+GIT_EXTERN(void) git_push_free(git_push *push);
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/refdb.h b/include/git2/refdb.h
new file mode 100644
index 000000000..a315876ae
--- /dev/null
+++ b/include/git2/refdb.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_refdb_h__
+#define INCLUDE_git_refdb_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+#include "refs.h"
+
+/**
+ * @file git2/refdb.h
+ * @brief Git custom refs backend functions
+ * @defgroup git_refdb Git custom refs backend API
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Create a new reference database with no backends.
+ *
+ * Before the Ref DB can be used for read/writing, a custom database
+ * backend must be manually set using `git_refdb_set_backend()`
+ *
+ * @param out location to store the database pointer, if opened.
+ * Set to NULL if the open failed.
+ * @param repo the repository
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_refdb_new(git_refdb **out, git_repository *repo);
+
+/**
+ * Create a new reference database and automatically add
+ * the default backends:
+ *
+ * - git_refdb_dir: read and write loose and packed refs
+ * from disk, assuming the repository dir as the folder
+ *
+ * @param out location to store the database pointer, if opened.
+ * Set to NULL if the open failed.
+ * @param repo the repository
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_refdb_open(git_refdb **out, git_repository *repo);
+
+/**
+ * Suggests that the given refdb compress or optimize its references.
+ * This mechanism is implementation specific. For on-disk reference
+ * databases, for example, this may pack all loose references.
+ */
+GIT_EXTERN(int) git_refdb_compress(git_refdb *refdb);
+
+/**
+ * Close an open reference database.
+ *
+ * @param refdb reference database pointer or NULL
+ */
+GIT_EXTERN(void) git_refdb_free(git_refdb *refdb);
+
+/** @} */
+GIT_END_DECL
+
+#endif
diff --git a/include/git2/reflog.h b/include/git2/reflog.h
index 7f2781fc3..ac42a231c 100644
--- a/include/git2/reflog.h
+++ b/include/git2/reflog.h
@@ -1,26 +1,8 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_reflog_h__
#define INCLUDE_git_reflog_h__
@@ -41,32 +23,65 @@ GIT_BEGIN_DECL
/**
* Read the reflog for the given reference
*
+ * If there is no reflog file for the given
+ * reference yet, an empty reflog object will
+ * be returned.
+ *
* The reflog must be freed manually by using
* git_reflog_free().
*
- * @param reflog pointer to reflog
- * @param ref reference to read the reflog for
- * @return GIT_SUCCESS on success; error code otherwise
+ * @param out pointer to reflog
+ * @param repo the repostiory
+ * @param name reference to look up
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_reflog_read(git_reflog **reflog, git_reference *ref);
+GIT_EXTERN(int) git_reflog_read(git_reflog **out, git_repository *repo, const char *name);
/**
- * Write a new reflog for the given reference
+ * Write an existing in-memory reflog object back to disk
+ * using an atomic file lock.
*
- * If there is no reflog file for the given
- * reference yet, it will be created.
- *
- * `oid_old` may be NULL in case it's a new reference.
+ * @param reflog an existing reflog object
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_reflog_write(git_reflog *reflog);
+
+/**
+ * Add a new entry to the in-memory reflog.
*
* `msg` is optional and can be NULL.
*
- * @param ref the changed reference
- * @param oid_old the OID the reference was pointing to
+ * @param reflog an existing reflog object
+ * @param id the OID the reference is now pointing to
* @param committer the signature of the committer
* @param msg the reflog message
- * @return GIT_SUCCESS on success; error code otherwise
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_reflog_append(git_reflog *reflog, const git_oid *id, const git_signature *committer, const char *msg);
+
+/**
+ * Rename a reflog
+ *
+ * The reflog to be renamed is expected to already exist
+ *
+ * The new name will be checked for validity.
+ * See `git_reference_create_symbolic()` for rules about valid names.
+ *
+ * @param repo the repository
+ * @param old_name the old name of the reference
+ * @param name the new name of the reference
+ * @return 0 on success, GIT_EINVALIDSPEC or an error code
*/
-GIT_EXTERN(int) git_reflog_write(git_reference *ref, const git_oid *oid_old, const git_signature *committer, const char *msg);
+GIT_EXTERN(int) git_reflog_rename(git_repository *repo, const char *old_name, const char *name);
+
+/**
+ * Delete the reflog for the given reference
+ *
+ * @param repo the repository
+ * @param name the reflog to delete
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_reflog_delete(git_repository *repo, const char *name);
/**
* Get the number of log entries in a reflog
@@ -74,16 +89,42 @@ GIT_EXTERN(int) git_reflog_write(git_reference *ref, const git_oid *oid_old, con
* @param reflog the previously loaded reflog
* @return the number of log entries
*/
-GIT_EXTERN(unsigned int) git_reflog_entrycount(git_reflog *reflog);
+GIT_EXTERN(size_t) git_reflog_entrycount(git_reflog *reflog);
/**
* Lookup an entry by its index
*
+ * Requesting the reflog entry with an index of 0 (zero) will
+ * return the most recently created entry.
+ *
* @param reflog a previously loaded reflog
- * @param idx the position to lookup
+ * @param idx the position of the entry to lookup. Should be greater than or
+ * equal to 0 (zero) and less than `git_reflog_entrycount()`.
* @return the entry; NULL if not found
*/
-GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(git_reflog *reflog, unsigned int idx);
+GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(git_reflog *reflog, size_t idx);
+
+/**
+ * Remove an entry from the reflog by its index
+ *
+ * To ensure there's no gap in the log history, set `rewrite_previous_entry`
+ * param value to 1. When deleting entry `n`, member old_oid of entry `n-1`
+ * (if any) will be updated with the value of member new_oid of entry `n+1`.
+ *
+ * @param reflog a previously loaded reflog.
+ *
+ * @param idx the position of the entry to remove. Should be greater than or
+ * equal to 0 (zero) and less than `git_reflog_entrycount()`.
+ *
+ * @param rewrite_previous_entry 1 to rewrite the history; 0 otherwise.
+ *
+ * @return 0 on success, GIT_ENOTFOUND if the entry doesn't exist
+ * or an error code.
+ */
+GIT_EXTERN(int) git_reflog_drop(
+ git_reflog *reflog,
+ size_t idx,
+ int rewrite_previous_entry);
/**
* Get the old oid
@@ -91,7 +132,7 @@ GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(git_reflog *reflog
* @param entry a reflog entry
* @return the old oid
*/
-GIT_EXTERN(char *) git_reflog_entry_oidold(const git_reflog_entry *entry);
+GIT_EXTERN(const git_oid *) git_reflog_entry_id_old(const git_reflog_entry *entry);
/**
* Get the new oid
@@ -99,7 +140,7 @@ GIT_EXTERN(char *) git_reflog_entry_oidold(const git_reflog_entry *entry);
* @param entry a reflog entry
* @return the new oid at this time
*/
-GIT_EXTERN(char *) git_reflog_entry_oidnew(const git_reflog_entry *entry);
+GIT_EXTERN(const git_oid *) git_reflog_entry_id_new(const git_reflog_entry *entry);
/**
* Get the committer of this entry
@@ -107,15 +148,15 @@ GIT_EXTERN(char *) git_reflog_entry_oidnew(const git_reflog_entry *entry);
* @param entry a reflog entry
* @return the committer
*/
-GIT_EXTERN(git_signature *) git_reflog_entry_committer(const git_reflog_entry *entry);
+GIT_EXTERN(const git_signature *) git_reflog_entry_committer(const git_reflog_entry *entry);
/**
- * Get the log msg
+ * Get the log message
*
* @param entry a reflog entry
* @return the log msg
*/
-GIT_EXTERN(char *) git_reflog_entry_msg(const git_reflog_entry *entry);
+GIT_EXTERN(const char *) git_reflog_entry_message(const git_reflog_entry *entry);
/**
* Free the reflog
diff --git a/include/git2/refs.h b/include/git2/refs.h
index ff2bc9d87..e5bb15c7c 100644
--- a/include/git2/refs.h
+++ b/include/git2/refs.h
@@ -1,26 +1,8 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_refs_h__
#define INCLUDE_git_refs_h__
@@ -28,6 +10,7 @@
#include "common.h"
#include "types.h"
#include "oid.h"
+#include "strarray.h"
/**
* @file git2/refs.h
@@ -39,237 +22,717 @@
GIT_BEGIN_DECL
/**
- * Lookup a reference by its name in a repository.
+ * Lookup a reference by name in a repository.
+ *
+ * The returned reference must be freed by the user.
*
- * The generated reference is owned by the repository and
- * should not be freed by the user.
+ * The name will be checked for validity.
+ * See `git_reference_symbolic_create()` for rules about valid names.
*
- * @param reference_out pointer to the looked-up reference
+ * @param out pointer to the looked-up reference
* @param repo the repository to look up the reference
- * @param name the long name for the reference (e.g. HEAD, ref/heads/master, refs/tags/v0.1.0, ...)
- * @return 0 on success; error code otherwise
+ * @param name the long name for the reference (e.g. HEAD, refs/heads/master, refs/tags/v0.1.0, ...)
+ * @return 0 on success, GIT_ENOTFOUND, GIT_EINVALIDSPEC or an error code.
+ */
+GIT_EXTERN(int) git_reference_lookup(git_reference **out, git_repository *repo, const char *name);
+
+/**
+ * Lookup a reference by name and resolve immediately to OID.
+ *
+ * This function provides a quick way to resolve a reference name straight
+ * through to the object id that it refers to. This avoids having to
+ * allocate or free any `git_reference` objects for simple situations.
+ *
+ * The name will be checked for validity.
+ * See `git_reference_symbolic_create()` for rules about valid names.
+ *
+ * @param out Pointer to oid to be filled in
+ * @param repo The repository in which to look up the reference
+ * @param name The long name for the reference (e.g. HEAD, refs/heads/master, refs/tags/v0.1.0, ...)
+ * @return 0 on success, GIT_ENOTFOUND, GIT_EINVALIDSPEC or an error code.
+ */
+GIT_EXTERN(int) git_reference_name_to_id(
+ git_oid *out, git_repository *repo, const char *name);
+
+/**
+ * Lookup a reference by DWIMing its short name
+ *
+ * Apply the git precendence rules to the given shorthand to determine
+ * which reference the user is refering to.
+ *
+ * @param out pointer in which to store the reference
+ * @param repo the repository in which to look
+ * @param shorthand the short name for the reference
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_reference_lookup(git_reference **reference_out, git_repository *repo, const char *name);
+GIT_EXTERN(int) git_reference_dwim(git_reference **out, git_repository *repo, const char *shorthand);
+
+/**
+ * Conditionally create a new symbolic reference.
+ *
+ * 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:
+ *
+ * 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.
+ *
+ * The signature and message for the reflog will be ignored if the
+ * reference does not belong in the standard set (HEAD, branches and
+ * remote-tracking branches) and it does not have a reflog.
+ *
+ * It will return GIT_EMODIFIED if the reference's value at the time
+ * of updating does not match the one passed through `current_value`
+ * (i.e. if the ref has changed since the user read it).
+ *
+ * @param out Pointer to the newly created reference
+ * @param repo Repository where that reference will live
+ * @param name The name of the reference
+ * @param target The target of the reference
+ * @param force Overwrite existing references
+ * @param current_value The expected value of the reference when updating
+ * @param signature The identity that will used to populate the reflog entry
+ * @param log_message The one line long message to be appended to the reflog
+ * @return 0 on success, GIT_EEXISTS, GIT_EINVALIDSPEC, GIT_EMODIFIED or an error code
+ */
+GIT_EXTERN(int) git_reference_symbolic_create_matching(git_reference **out, git_repository *repo, const char *name, const char *target, int force, const char *current_value, const git_signature *signature, const char *log_message);
/**
* 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.
*
- * This reference is owned by the repository and shall not
- * be free'd by the user.
+ * The symbolic reference will be created in the repository and written to
+ * the disk. The generated reference object must be freed by the user.
*
- * If `force` is true and there already exists a reference
- * with the same name, it will be overwritten.
+ * Valid reference names must follow one of two patterns:
*
- * @param ref_out Pointer to the newly created reference
+ * 1. Top-level names must contain only capital letters and underscores,
+ * and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD").
+ * 2. Names prefixed with "refs/" can be almost anything. You must avoid
+ * the characters '~', '^', ':', '\\', '?', '[', and '*', and the
+ * sequences ".." and "@{" which have special meaning to revparse.
+ *
+ * This function will return an error if a reference already exists with the
+ * given name unless `force` is true, in which case it will be overwritten.
+ *
+ * The signature and message for the reflog will be ignored if the
+ * reference does not belong in the standard set (HEAD, branches and
+ * remote-tracking branches) and it does not have a reflog.
+ *
+ * @param out Pointer to the newly created reference
* @param repo Repository where that reference will live
* @param name The name of the reference
* @param target The target of the reference
* @param force Overwrite existing references
- * @return 0 on success; error code otherwise
+ * @param signature The identity that will used to populate the reflog entry
+ * @param log_message The one line long message to be appended to the reflog
+ * @return 0 on success, GIT_EEXISTS, GIT_EINVALIDSPEC or an error code
*/
-GIT_EXTERN(int) git_reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target, int force);
+GIT_EXTERN(int) git_reference_symbolic_create(git_reference **out, git_repository *repo, const char *name, const char *target, int force, const git_signature *signature, const char *log_message);
/**
- * Create a new object id reference.
+ * Create a new direct reference.
+ *
+ * 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 direct reference will be created in the repository and written to
+ * the disk. The generated reference object must be freed by the user.
*
- * The reference will be created in the repository and written
- * to the disk.
+ * Valid reference names must follow one of two patterns:
*
- * This reference is owned by the repository and shall not
- * be free'd 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
+ * The signature and message for the reflog will be ignored if the
+ * reference does not belong in the standard set (HEAD, branches and
+ * remote-tracking branches) and and it does not have a reflog.
+ *
+ * @param out Pointer to the newly created reference
* @param repo Repository where that reference will live
* @param name The name of the reference
* @param id The object id pointed to by the reference.
* @param force Overwrite existing references
- * @return 0 on success; error code otherwise
+ * @param signature The identity that will used to populate the reflog entry
+ * @param log_message The one line long message to be appended to the reflog
+ * @return 0 on success, GIT_EEXISTS, GIT_EINVALIDSPEC or an error code
*/
-GIT_EXTERN(int) git_reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id, int force);
+GIT_EXTERN(int) git_reference_create(git_reference **out, git_repository *repo, const char *name, const git_oid *id, int force, const git_signature *signature, const char *log_message);
/**
- * Get the OID pointed to by a reference.
+ * Conditionally create new direct reference
+ *
+ * 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 direct 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:
+ *
+ * 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.
*
- * Only available if the reference is direct (i.e. not symbolic)
+ * 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.
+ *
+ * The signature and message for the reflog will be ignored if the
+ * reference does not belong in the standard set (HEAD, branches and
+ * remote-tracking branches) and and it does not have a reflog.
+ *
+ * It will return GIT_EMODIFIED if the reference's value at the time
+ * of updating does not match the one passed through `current_id`
+ * (i.e. if the ref has changed since the user read it).
+ *
+ * @param out Pointer to the newly created reference
+ * @param repo Repository where that reference will live
+ * @param name The name of the reference
+ * @param id The object id pointed to by the reference.
+ * @param force Overwrite existing references
+ * @param current_id The expected value of the reference at the time of update
+ * @param signature The identity that will used to populate the reflog entry
+ * @param log_message The one line long message to be appended to the reflog
+ * @return 0 on success, GIT_EMODIFIED if the value of the reference
+ * has changed, GIT_EEXISTS, GIT_EINVALIDSPEC or an error code
+ */
+GIT_EXTERN(int) git_reference_create_matching(git_reference **out, git_repository *repo, const char *name, const git_oid *id, int force, const git_oid *current_id, const git_signature *signature, const char *log_message);
+
+/**
+ * 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).
+ *
+ * To find the OID of a symbolic ref, call `git_reference_resolve()` and
+ * then this function (or maybe use `git_reference_name_to_id()` to
+ * directly resolve a reference name all the way through to an OID).
*
* @param ref The reference
* @return a pointer to the oid if available, NULL otherwise
*/
-GIT_EXTERN(const git_oid *) git_reference_oid(git_reference *ref);
+GIT_EXTERN(const git_oid *) git_reference_target(const git_reference *ref);
/**
- * Get full name to the reference pointed by this reference
+ * Return the peeled OID target of this reference.
*
- * Only available if the reference is symbolic
+ * This peeled OID only applies to direct references that point to
+ * a hard Tag object: it is the result of peeling such Tag.
+ *
+ * @param ref The reference
+ * @return a pointer to the oid if available, NULL otherwise
+ */
+GIT_EXTERN(const git_oid *) git_reference_target_peel(const git_reference *ref);
+
+/**
+ * Get full name to the reference pointed to by a symbolic reference.
+ *
+ * Only available if the reference is symbolic.
*
* @param ref The reference
* @return a pointer to the name if available, NULL otherwise
*/
-GIT_EXTERN(const char *) git_reference_target(git_reference *ref);
+GIT_EXTERN(const char *) git_reference_symbolic_target(const git_reference *ref);
/**
- * Get the type of a reference
+ * Get the type of a reference.
*
* Either direct (GIT_REF_OID) or symbolic (GIT_REF_SYMBOLIC)
*
* @param ref The reference
* @return the type
*/
-GIT_EXTERN(git_rtype) git_reference_type(git_reference *ref);
+GIT_EXTERN(git_ref_t) git_reference_type(const git_reference *ref);
/**
- * Get the full name of a reference
+ * Get the full name of a reference.
+ *
+ * See `git_reference_symbolic_create()` for rules about valid names.
*
* @param ref The reference
* @return the full name for the ref
*/
-GIT_EXTERN(const char *) git_reference_name(git_reference *ref);
+GIT_EXTERN(const char *) git_reference_name(const git_reference *ref);
/**
- * Resolve a symbolic reference
+ * Resolve a symbolic reference to a direct reference.
+ *
+ * This method iteratively peels a symbolic reference until it resolves to
+ * a direct reference to an OID.
*
- * Thie 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.
*
- * If a direct reference is passed as an argument,
- * that reference is returned immediately
+ * 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 out Pointer to the peeled reference
* @param ref The reference
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_reference_resolve(git_reference **resolved_ref, git_reference *ref);
+GIT_EXTERN(int) git_reference_resolve(git_reference **out, const git_reference *ref);
/**
- * Get the repository where a reference resides
+ * Get the repository where a reference resides.
*
* @param ref The reference
* @return a pointer to the repo
*/
-GIT_EXTERN(git_repository *) git_reference_owner(git_reference *ref);
+GIT_EXTERN(git_repository *) git_reference_owner(const git_reference *ref);
/**
- * Set the symbolic target of a reference.
+ * Create a new reference with the same name as the given reference but a
+ * different symbolic target. The reference must be a symbolic reference,
+ * otherwise this will fail.
*
- * The reference must be a symbolic reference, otherwise
- * this method will fail.
+ * The new reference will be written to disk, overwriting the given reference.
*
- * The reference will be automatically updated in
- * memory and on disk.
+ * The target name will be checked for validity.
+ * See `git_reference_symbolic_create()` for rules about valid names.
*
+ * The signature and message for the reflog will be ignored if the
+ * reference does not belong in the standard set (HEAD, branches and
+ * remote-tracking branches) and and it does not have a reflog.
+ *
+ * @param out Pointer to the newly created reference
* @param ref The reference
* @param target The new target for the reference
- * @return 0 on success; error code otherwise
+ * @param signature The identity that will used to populate the reflog entry
+ * @param log_message The one line long message to be appended to the reflog
+ * @return 0 on success, GIT_EINVALIDSPEC or an error code
*/
-GIT_EXTERN(int) git_reference_set_target(git_reference *ref, const char *target);
+GIT_EXTERN(int) git_reference_symbolic_set_target(
+ git_reference **out,
+ git_reference *ref,
+ const char *target,
+ const git_signature *signature,
+ const char *log_message);
/**
- * Set the OID target of a reference.
- *
- * The reference must be a direct reference, otherwise
- * this method will fail.
+ * Conditionally create a new reference with the same name as the given reference but a
+ * different OID target. The reference must be a direct reference, otherwise
+ * this will fail.
*
- * The reference will be automatically updated in
- * memory and on disk.
+ * The new reference will be written to disk, overwriting the given reference.
*
+ * @param out Pointer to the newly created reference
* @param ref The reference
* @param id The new target OID for the reference
- * @return 0 on success; error code otherwise
+ * @param signature The identity that will used to populate the reflog entry
+ * @param log_message The one line long message to be appended to the reflog
+ * @return 0 on success, GIT_EMODIFIED if the value of the reference
+ * has changed since it was read, or an error code
*/
-GIT_EXTERN(int) git_reference_set_oid(git_reference *ref, const git_oid *id);
+GIT_EXTERN(int) git_reference_set_target(
+ git_reference **out,
+ git_reference *ref,
+ const git_oid *id,
+ const git_signature *signature,
+ const char *log_message);
/**
- * Rename an existing reference
+ * Rename an existing reference.
*
* This method works for both direct and symbolic references.
- * The new name will be checked for validity and may be
- * modified into a normalized form.
*
- * The refernece will be immediately renamed in-memory
- * and on disk.
+ * The new name will be checked for validity.
+ * See `git_reference_symbolic_create()` for rules about valid names.
+ *
+ * If the `force` flag is not enabled, and there's already
+ * a reference with the given name, the renaming will fail.
+ *
+ * IMPORTANT:
+ * The user needs to write a proper reflog entry if the
+ * reflog is enabled for the repository. We only rename
+ * the reflog if it exists.
+ *
+ * @param ref The reference to rename
+ * @param new_name The new name for the reference
+ * @param force Overwrite an existing reference
+ * @param signature The identity that will used to populate the reflog entry
+ * @param log_message The one line long message to be appended to the reflog
+ * @return 0 on success, GIT_EINVALIDSPEC, GIT_EEXISTS or an error code
*
*/
-GIT_EXTERN(int) git_reference_rename(git_reference *ref, const char *new_name, int force);
+GIT_EXTERN(int) git_reference_rename(
+ git_reference **new_ref,
+ git_reference *ref,
+ const char *new_name,
+ int force,
+ const git_signature *signature,
+ const char *log_message);
/**
- * Delete an existing reference
+ * Delete an existing reference.
*
- * This method works for both direct and symbolic references.
+ * This method works for both direct and symbolic references. The reference
+ * will be immediately removed on disk but the memory will not be freed.
+ * Callers must call `git_reference_free`.
*
- * The reference will be immediately removed on disk and from
- * memory. The given reference pointer will no longer be valid.
+ * This function will return an error if the reference has changed
+ * from the time it was looked up.
*
+ * @param ref The reference to remove
+ * @return 0, GIT_EMODIFIED or an error code
*/
GIT_EXTERN(int) git_reference_delete(git_reference *ref);
/**
- * Pack all the loose references in the repository
+ * Delete an existing reference by name
*
- * This method will load into the cache all the loose
- * references on the repository and update the
- * `packed-refs` file with them.
+ * This method removes the named reference from the repository without
+ * looking at its old value.
*
- * Once the `packed-refs` file has been written properly,
- * the loose references will be removed from disk.
+ * @param name The reference to remove
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_reference_remove(git_repository *repo, const char *name);
+
+/**
+ * Fill a list with all the references that can be found in a repository.
*
- * WARNING: calling this method may invalidate any existing
- * references previously loaded on the cache.
+ * The string array will be filled with the names of all references; these
+ * values are owned by the user and should be free'd manually when no
+ * longer needed, using `git_strarray_free()`.
*
- * @param repo Repository where the loose refs will be packed
- * @return 0 on success; error code otherwise
+ * @param array Pointer to a git_strarray structure where
+ * the reference names will be stored
+ * @param repo Repository where to find the refs
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_reference_packall(git_repository *repo);
+GIT_EXTERN(int) git_reference_list(git_strarray *array, git_repository *repo);
+
+typedef int (*git_reference_foreach_cb)(git_reference *reference, void *payload);
+typedef int (*git_reference_foreach_name_cb)(const char *name, void *payload);
/**
- * Fill a list with all the references that can be found
- * in a repository.
+ * Perform a callback on each reference in the repository.
*
- * The listed references may be filtered by type, or using
- * a bitwise OR of several types. Use the magic value
- * `GIT_REF_LISTALL` to obtain all references, including
- * packed ones.
+ * The `callback` function will be called for each reference in the
+ * repository, receiving the reference object and the `payload` value
+ * passed to this method. Returning a non-zero value from the callback
+ * will terminate the iteration.
*
- * The string array will be filled with the names of all
- * references; these values are owned by the user and
- * should be free'd manually when no longer needed, using
- * `git_strarray_free`.
+ * @param repo Repository where to find the refs
+ * @param callback Function which will be called for every listed ref
+ * @param payload Additional data to pass to the callback
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_reference_foreach(
+ git_repository *repo,
+ git_reference_foreach_cb callback,
+ void *payload);
+
+/**
+ * Perform a callback on the fully-qualified name of each reference.
+ *
+ * The `callback` function will be called for each reference in the
+ * repository, receiving the name of the reference and the `payload` value
+ * passed to this method. Returning a non-zero value from the callback
+ * will terminate the iteration.
*
- * @param array Pointer to a git_strarray structure where
- * the reference names will be stored
* @param repo Repository where to find the refs
- * @param list_flags Filtering flags for the reference
- * listing.
- * @return 0 on success; error code otherwise
+ * @param callback Function which will be called for every listed ref name
+ * @param payload Additional data to pass to the callback
+ * @return 0 on success, non-zero callback return value, or error code
*/
-GIT_EXTERN(int) git_reference_listall(git_strarray *array, git_repository *repo, unsigned int list_flags);
+GIT_EXTERN(int) git_reference_foreach_name(
+ git_repository *repo,
+ git_reference_foreach_name_cb callback,
+ void *payload);
+/**
+ * Free the given reference.
+ *
+ * @param ref git_reference
+ */
+GIT_EXTERN(void) git_reference_free(git_reference *ref);
/**
- * Perform an operation on each reference in the repository
+ * Compare two references.
*
- * The processed references may be filtered by type, or using
- * a bitwise OR of several types. Use the magic value
- * `GIT_REF_LISTALL` to obtain all references, including
- * packed ones.
+ * @param ref1 The first git_reference
+ * @param ref2 The second git_reference
+ * @return 0 if the same, else a stable but meaningless ordering.
+ */
+GIT_EXTERN(int) git_reference_cmp(
+ const git_reference *ref1,
+ const git_reference *ref2);
+
+/**
+ * Create an iterator for the repo's references
+ *
+ * @param out pointer in which to store the iterator
+ * @param repo the repository
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_reference_iterator_new(
+ git_reference_iterator **out,
+ git_repository *repo);
+
+/**
+ * Create an iterator for the repo's references that match the
+ * specified glob
+ *
+ * @param out pointer in which to store the iterator
+ * @param repo the repository
+ * @param glob the glob to match against the reference names
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_reference_iterator_glob_new(
+ git_reference_iterator **out,
+ git_repository *repo,
+ const char *glob);
+
+/**
+ * Get the next reference
+ *
+ * @param out pointer in which to store the reference
+ * @param iter the iterator
+ * @return 0, GIT_ITEROVER if there are no more; or an error code
+ */
+GIT_EXTERN(int) git_reference_next(git_reference **out, git_reference_iterator *iter);
+
+/**
+ * Get the next reference's name
+ *
+ * This function is provided for convenience in case only the names
+ * are interesting as it avoids the allocation of the `git_reference`
+ * object which `git_reference_next()` needs.
+ *
+ * @param out pointer in which to store the string
+ * @param iter the iterator
+ * @return 0, GIT_ITEROVER if there are no more; or an error code
+ */
+GIT_EXTERN(int) git_reference_next_name(const char **out, git_reference_iterator *iter);
+
+/**
+ * Free the iterator and its associated resources
+ *
+ * @param iter the iterator to free
+ */
+GIT_EXTERN(void) git_reference_iterator_free(git_reference_iterator *iter);
+
+/**
+ * Perform a callback on each reference in the repository whose name
+ * matches the given pattern.
*
- * The `callback` function will be called for each of the references
- * in the repository, and will receive the name of the reference and
- * the `payload` value passed to this method.
+ * This function acts like `git_reference_foreach()` with an additional
+ * pattern match being applied to the reference name before issuing the
+ * callback function. See that function for more information.
+ *
+ * The pattern is matched using fnmatch or "glob" style where a '*' matches
+ * any sequence of letters, a '?' matches any letter, and square brackets
+ * can be used to define character ranges (such as "[0-9]" for digits).
*
* @param repo Repository where to find the refs
- * @param list_flags Filtering flags for the reference
- * listing.
+ * @param glob Pattern to match (fnmatch-style) against reference name.
* @param callback Function which will be called for every listed ref
* @param payload Additional data to pass to the callback
- * @return 0 on success; error code otherwise
+ * @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,
+ git_reference_foreach_name_cb callback,
+ void *payload);
+
+/**
+ * Check if a reflog exists for the specified reference.
+ *
+ * @param repo the repository
+ * @param refname the reference's name
+ * @return 0 when no reflog can be found, 1 when it exists;
+ * otherwise an error code.
+ */
+GIT_EXTERN(int) git_reference_has_log(git_repository *repo, const char *refname);
+
+/**
+ * Ensure there is a reflog for a particular reference.
+ *
+ * Make sure that successive updates to the reference will append to
+ * its log.
+ *
+ * @param repo the repository
+ * @param refname the reference's name
+ * @return 0 or an error code.
+ */
+GIT_EXTERN(int) git_reference_ensure_log(git_repository *repo, const char *refname);
+
+/**
+ * Check if a reference is a local branch.
+ *
+ * @param ref A git reference
+ *
+ * @return 1 when the reference lives in the refs/heads
+ * namespace; 0 otherwise.
+ */
+GIT_EXTERN(int) git_reference_is_branch(const git_reference *ref);
+
+/**
+ * Check if a reference is a remote tracking branch
+ *
+ * @param ref A git reference
+ *
+ * @return 1 when the reference lives in the refs/remotes
+ * namespace; 0 otherwise.
+ */
+GIT_EXTERN(int) git_reference_is_remote(const git_reference *ref);
+
+/**
+ * Check if a reference is a tag
+ *
+ * @param ref A git reference
+ *
+ * @return 1 when the reference lives in the refs/tags
+ * namespace; 0 otherwise.
+ */
+GIT_EXTERN(int) git_reference_is_tag(const git_reference *ref);
+
+/**
+ * Check if a reference is a note
+ *
+ * @param ref A git reference
+ *
+ * @return 1 when the reference lives in the refs/notes
+ * namespace; 0 otherwise.
+ */
+GIT_EXTERN(int) git_reference_is_note(const git_reference *ref);
+
+typedef enum {
+ GIT_REF_FORMAT_NORMAL = 0u,
+
+ /**
+ * Control whether one-level refnames are accepted
+ * (i.e., refnames that do not contain multiple /-separated
+ * components). Those are expected to be written only using
+ * uppercase letters and underscore (FETCH_HEAD, ...)
+ */
+ GIT_REF_FORMAT_ALLOW_ONELEVEL = (1u << 0),
+
+ /**
+ * Interpret the provided name as a reference pattern for a
+ * refspec (as used with remote repositories). If this option
+ * is enabled, the name is allowed to contain a single * (<star>)
+ * in place of a one full pathname component
+ * (e.g., foo/<star>/bar but not foo/bar<star>).
+ */
+ GIT_REF_FORMAT_REFSPEC_PATTERN = (1u << 1),
+
+ /**
+ * Interpret the name as part of a refspec in shorthand form
+ * so the `ONELEVEL` naming rules aren't enforced and 'master'
+ * becomes a valid name.
+ */
+ GIT_REF_FORMAT_REFSPEC_SHORTHAND = (1u << 2),
+} git_reference_normalize_t;
+
+/**
+ * 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.
+ *
+ * See `git_reference_symbolic_create()` for rules about valid names.
+ *
+ * @param buffer_out User allocated buffer to store normalized name
+ * @param buffer_size Size of buffer_out
+ * @param name Reference name to be checked.
+ * @param flags Flags to constrain name validation rules - see the
+ * GIT_REF_FORMAT constants above.
+ * @return 0 on success, GIT_EBUFS if buffer is too small, GIT_EINVALIDSPEC
+ * or an error code.
*/
-GIT_EXTERN(int) git_reference_foreach(git_repository *repo, unsigned int list_flags, int (*callback)(const char *, void *), void *payload);
+GIT_EXTERN(int) git_reference_normalize_name(
+ char *buffer_out,
+ size_t buffer_size,
+ const char *name,
+ unsigned int flags);
+
+/**
+ * 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.
+ *
+ * If you pass `GIT_OBJ_ANY` as the target type, then the object
+ * will be peeled until a non-tag object is met.
+ *
+ * @param out Pointer to the peeled git_object
+ * @param ref The reference to be processed
+ * @param type The type of the requested object (GIT_OBJ_COMMIT,
+ * GIT_OBJ_TAG, GIT_OBJ_TREE, GIT_OBJ_BLOB or GIT_OBJ_ANY).
+ * @return 0 on success, GIT_EAMBIGUOUS, GIT_ENOTFOUND or an error code
+ */
+GIT_EXTERN(int) git_reference_peel(
+ git_object **out,
+ git_reference *ref,
+ git_otype type);
+
+/**
+ * Ensure the reference name is well-formed.
+ *
+ * Valid reference names must follow one of two patterns:
+ *
+ * 1. Top-level names must contain only capital letters and underscores,
+ * and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD").
+ * 2. Names prefixed with "refs/" can be almost anything. You must avoid
+ * the characters '~', '^', ':', '\\', '?', '[', and '*', and the
+ * sequences ".." and "@{" which have special meaning to revparse.
+ *
+ * @param refname name to be checked.
+ * @return 1 if the reference name is acceptable; 0 if it isn't
+ */
+GIT_EXTERN(int) git_reference_is_valid_name(const char *refname);
+
+/**
+ * Get the reference's short name
+ *
+ * This will transform the reference name into a name "human-readable"
+ * version. If no shortname is appropriate, it will return the full
+ * name.
+ *
+ * The memory is owned by the reference and must not be freed.
+ *
+ * @param ref a reference
+ * @return the human-readable version of the name
+ */
+GIT_EXTERN(const char *) git_reference_shorthand(const git_reference *ref);
+
/** @} */
GIT_END_DECL
diff --git a/include/git2/refspec.h b/include/git2/refspec.h
index dd0dc5873..9acdc72d5 100644
--- a/include/git2/refspec.h
+++ b/include/git2/refspec.h
@@ -1,31 +1,16 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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_refspec_h__
#define INCLUDE_git_refspec_h__
-#include "git2/types.h"
+#include "common.h"
+#include "types.h"
+#include "net.h"
+#include "buffer.h"
/**
* @file git2/refspec.h
@@ -42,7 +27,7 @@ GIT_BEGIN_DECL
* @param refspec the refspec
* @return the refspec's source specifier
*/
-const char *git_refspec_src(const git_refspec *refspec);
+GIT_EXTERN(const char *) git_refspec_src(const git_refspec *refspec);
/**
* Get the destination specifier
@@ -50,28 +35,69 @@ const char *git_refspec_src(const git_refspec *refspec);
* @param refspec the refspec
* @return the refspec's destination specifier
*/
-const char *git_refspec_dst(const git_refspec *refspec);
+GIT_EXTERN(const char *) git_refspec_dst(const git_refspec *refspec);
+
+/**
+ * Get the refspec's string
+ *
+ * @param refspec the refspec
+ * @returns the refspec's original string
+ */
+GIT_EXTERN(const char *) git_refspec_string(const git_refspec *refspec);
+
+/**
+ * Get the force update setting
+ *
+ * @param refspec the refspec
+ * @return 1 if force update has been set, 0 otherwise
+ */
+GIT_EXTERN(int) git_refspec_force(const git_refspec *refspec);
+
+/**
+ * Get the refspec's direction.
+ *
+ * @param spec refspec
+ * @return GIT_DIRECTION_FETCH or GIT_DIRECTION_PUSH
+ */
+GIT_EXTERN(git_direction) git_refspec_direction(const git_refspec *spec);
+
+/**
+ * Check if a refspec's source descriptor matches a reference
+ *
+ * @param refspec the refspec
+ * @param refname the name of the reference to check
+ * @return 1 if the refspec matches, 0 otherwise
+ */
+GIT_EXTERN(int) git_refspec_src_matches(const git_refspec *refspec, const char *refname);
/**
- * Match a refspec's source descriptor with a reference name
+ * Check if a refspec's destination descriptor matches a reference
*
* @param refspec the refspec
* @param refname the name of the reference to check
- * @return GIT_SUCCESS on successful match; GIT_ENOMACH on match
- * failure or an error code on other failure
+ * @return 1 if the refspec matches, 0 otherwise
*/
-int git_refspec_src_match(const git_refspec *refspec, const char *refname);
+GIT_EXTERN(int) git_refspec_dst_matches(const git_refspec *refspec, const char *refname);
/**
* Transform a reference to its target following the refspec's rules
*
* @param out where to store the target name
- * @param outlen the size ouf the `out` buffer
* @param spec the refspec
* @param name the name of the reference to transform
- * @preturn GIT_SUCCESS, GIT_ESHORTBUFFER or another error
+ * @return 0, GIT_EBUFS or another error
+ */
+GIT_EXTERN(int) git_refspec_transform(git_buf *out, const git_refspec *spec, const char *name);
+
+/**
+ * Transform a target reference to its source reference following the refspec's rules
+ *
+ * @param out where to store the source reference name
+ * @param spec the refspec
+ * @param name the name of the reference to transform
+ * @return 0, GIT_EBUFS or another error
*/
-int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, const char *name);
+GIT_EXTERN(int) git_refspec_rtransform(git_buf *out, const git_refspec *spec, const char *name);
GIT_END_DECL
diff --git a/include/git2/remote.h b/include/git2/remote.h
index 93bc83452..c72c9c8cc 100644
--- a/include/git2/remote.h
+++ b/include/git2/remote.h
@@ -1,33 +1,20 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_remote_h__
#define INCLUDE_git_remote_h__
-#include "git2/common.h"
-#include "git2/repository.h"
-#include "git2/refspec.h"
+#include "common.h"
+#include "repository.h"
+#include "refspec.h"
+#include "net.h"
+#include "indexer.h"
+#include "strarray.h"
+#include "transport.h"
+
/**
* @file git2/remote.h
* @brief Git remote management functions
@@ -37,31 +24,116 @@
*/
GIT_BEGIN_DECL
-/*
- * TODO: This functions still need to be implemented:
- * - _listcb/_foreach
- * - _add
- * - _rename
- * - _del (needs support from config)
+typedef int (*git_remote_rename_problem_cb)(const char *problematic_refspec, void *payload);
+
+/**
+ * Add a remote with the default fetch refspec to the repository's configuration. This
+ * calls git_remote_save before returning.
+ *
+ * @param out the resulting remote
+ * @param repo the repository in which to create the remote
+ * @param name the remote's name
+ * @param url the remote's url
+ * @return 0, GIT_EINVALIDSPEC, GIT_EEXISTS or an error code
*/
+GIT_EXTERN(int) git_remote_create(
+ git_remote **out,
+ git_repository *repo,
+ const char *name,
+ const char *url);
+
+/**
+ * Add a remote with the provided fetch refspec (or default if NULL) to the repository's
+ * configuration. This
+ * calls git_remote_save before returning.
+ *
+ * @param out the resulting remote
+ * @param repo the repository in which to create the remote
+ * @param name the remote's name
+ * @param url the remote's url
+ * @param fetch the remote fetch value
+ * @return 0, GIT_EINVALIDSPEC, GIT_EEXISTS or an error code
+ */
+GIT_EXTERN(int) git_remote_create_with_fetchspec(
+ git_remote **out,
+ git_repository *repo,
+ const char *name,
+ const char *url,
+ const char *fetch);
+
+/**
+ * Create an anonymous remote
+ *
+ * Create a remote with the given url and refspec in memory. You can use
+ * this when you have a URL instead of a remote's name. Note that anonymous
+ * remotes cannot be converted to persisted remotes.
+ *
+ * The name, when provided, will be checked for validity.
+ * See `git_tag_create()` for rules about valid names.
+ *
+ * @param out pointer to the new remote object
+ * @param repo the associated repository
+ * @param url the remote repository's URL
+ * @param fetch the fetch refspec to use for this remote.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_remote_create_anonymous(
+ git_remote **out,
+ git_repository *repo,
+ const char *url,
+ const char *fetch);
/**
* Get the information for a particular remote
*
+ * The name will be checked for validity.
+ * See `git_tag_create()` for rules about valid names.
+ *
* @param out pointer to the new remote object
- * @param cfg the repository's configuration
+ * @param repo the associated repository
* @param name the remote's name
- * @return 0 on success; error value otherwise
+ * @return 0, GIT_ENOTFOUND, GIT_EINVALIDSPEC or an error code
*/
-GIT_EXTERN(int) git_remote_get(struct git_remote **out, struct git_config *cfg, const char *name);
+GIT_EXTERN(int) git_remote_load(git_remote **out, git_repository *repo, const char *name);
+
+/**
+ * Save a remote to its repository's configuration
+ *
+ * One can't save a in-memory remote. Doing so will
+ * result in a GIT_EINVALIDSPEC being returned.
+ *
+ * @param remote the remote to save to config
+ * @return 0, GIT_EINVALIDSPEC or an error code
+ */
+GIT_EXTERN(int) git_remote_save(const git_remote *remote);
+
+/**
+ * Create a copy of an existing remote. All internal strings are also
+ * duplicated. Callbacks are not duplicated.
+ *
+ * Call `git_remote_free` to free the data.
+ *
+ * @param dest pointer where to store the copy
+ * @param source object to copy
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_remote_dup(git_remote **dest, git_remote *source);
+
+/**
+ * Get the remote's repository
+ *
+ * @param remote the remote
+ * @return a pointer to the repository
+ */
+GIT_EXTERN(git_repository *) git_remote_owner(const git_remote *remote);
/**
* Get the remote's name
*
* @param remote the remote
- * @return a pointer to the name
+ * @return a pointer to the name or NULL for in-memory remotes
*/
-GIT_EXTERN(const char *) git_remote_name(struct git_remote *remote);
+GIT_EXTERN(const char *) git_remote_name(const git_remote *remote);
/**
* Get the remote's url
@@ -69,52 +141,502 @@ GIT_EXTERN(const char *) git_remote_name(struct git_remote *remote);
* @param remote the remote
* @return a pointer to the url
*/
-GIT_EXTERN(const char *) git_remote_url(struct git_remote *remote);
+GIT_EXTERN(const char *) git_remote_url(const git_remote *remote);
+
+/**
+ * Get the remote's url for pushing
+ *
+ * @param remote the remote
+ * @return a pointer to the url or NULL if no special url for pushing is set
+ */
+GIT_EXTERN(const char *) git_remote_pushurl(const git_remote *remote);
/**
- * Get the fetch refspec
+ * Set the remote's url
+ *
+ * Existing connections will not be updated.
*
* @param remote the remote
- * @return a pointer to the fetch refspec or NULL if it doesn't exist
+ * @param url the url to set
+ * @return 0 or an error value
*/
-GIT_EXTERN(const git_refspec *) git_remote_fetchspec(struct git_remote *remote);
+GIT_EXTERN(int) git_remote_set_url(git_remote *remote, const char* url);
/**
- * Get the push refspec
+ * Set the remote's url for pushing
+ *
+ * Existing connections will not be updated.
*
* @param remote the remote
- * @return a pointer to the push refspec or NULL if it doesn't exist
+ * @param url the url to set or NULL to clear the pushurl
+ * @return 0 or an error value
*/
+GIT_EXTERN(int) git_remote_set_pushurl(git_remote *remote, const char* url);
-GIT_EXTERN(const git_refspec *) git_remote_pushspec(struct git_remote *remote);
+/**
+ * Add a fetch refspec to the remote
+ *
+ * Convenience function for adding a single fetch refspec to the
+ * current list in the remote.
+ *
+ * @param remote the remote
+ * @param refspec the new fetch refspec
+ * @return 0 or an error value
+ */
+GIT_EXTERN(int) git_remote_add_fetch(git_remote *remote, const char *refspec);
+
+/**
+ * Get the remote's list of fetch refspecs
+ *
+ * The memory is owned by the user and should be freed with
+ * `git_strarray_free`.
+ *
+ * @param array pointer to the array in which to store the strings
+ * @param remote the remote to query
+ */
+GIT_EXTERN(int) git_remote_get_fetch_refspecs(git_strarray *array, const git_remote *remote);
+
+/**
+ * Set the remote's list of fetch refspecs
+ *
+ * The contents of the string array are copied.
+ *
+ * @param remote the remote to modify
+ * @param array the new list of fetch resfpecs
+ */
+GIT_EXTERN(int) git_remote_set_fetch_refspecs(git_remote *remote, git_strarray *array);
+
+/**
+ * Add a push refspec to the remote
+ *
+ * Convenience function for adding a single push refspec to the
+ * current list in the remote.
+ *
+ * @param remote the remote
+ * @param refspec the new push refspec
+ * @return 0 or an error value
+ */
+GIT_EXTERN(int) git_remote_add_push(git_remote *remote, const char *refspec);
+
+/**
+ * Get the remote's list of push refspecs
+ *
+ * The memory is owned by the user and should be freed with
+ * `git_strarray_free`.
+ *
+ * @param array pointer to the array in which to store the strings
+ * @param remote the remote to query
+ */
+GIT_EXTERN(int) git_remote_get_push_refspecs(git_strarray *array, const git_remote *remote);
+
+/**
+ * Set the remote's list of push refspecs
+ *
+ * The contents of the string array are copied.
+ *
+ * @param remote the remote to modify
+ * @param array the new list of push resfpecs
+ */
+GIT_EXTERN(int) git_remote_set_push_refspecs(git_remote *remote, git_strarray *array);
+
+/**
+ * Clear the refspecs
+ *
+ * Remove all configured fetch and push refspecs from the remote.
+ *
+ * @param remote the remote
+ */
+GIT_EXTERN(void) git_remote_clear_refspecs(git_remote *remote);
+
+/**
+ * Get the number of refspecs for a remote
+ *
+ * @param remote the remote
+ * @return the amount of refspecs configured in this remote
+ */
+GIT_EXTERN(size_t) git_remote_refspec_count(const git_remote *remote);
+
+/**
+ * Get a refspec from the remote
+ *
+ * @param remote the remote to query
+ * @param n the refspec to get
+ * @return the nth refspec
+ */
+GIT_EXTERN(const git_refspec *)git_remote_get_refspec(const git_remote *remote, size_t n);
/**
* Open a connection to a remote
*
- * The transport is selected based on the URL
+ * The transport is selected based on the URL. The direction argument
+ * is due to a limitation of the git protocol (over TCP or SSH) which
+ * starts up a specific binary which can only do the one or the other.
*
* @param remote the remote to connect to
- * @return GIT_SUCCESS or an error code
+ * @param direction GIT_DIRECTION_FETCH if you want to fetch or
+ * GIT_DIRECTION_PUSH if you want to push
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_remote_connect(struct git_remote *remote, int direction);
+GIT_EXTERN(int) git_remote_connect(git_remote *remote, git_direction direction);
/**
* Get a list of refs at the remote
*
- * The remote (or more exactly its transport) must be connected.
+ * The remote (or more exactly its transport) must be connected. The
+ * memory belongs to the remote.
+ *
+ * The array will stay valid as long as the remote object exists and
+ * its transport isn't changed, but a copy is recommended for usage of
+ * the data.
+ *
+ * @param out pointer to the array
+ * @param size the number of remote heads
+ * @param remote the remote
+ * @return 0 on success, or an error code
+ */
+GIT_EXTERN(int) git_remote_ls(const git_remote_head ***out, size_t *size, git_remote *remote);
+
+/**
+ * Download and index the packfile
+ *
+ * Connect to the remote if it hasn't been done yet, negotiate with
+ * the remote git which objects are missing, download and index the
+ * packfile.
+ *
+ * The .idx file will be created and both it and the packfile with be
+ * renamed to their final name.
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_remote_download(git_remote *remote);
+
+/**
+ * Check whether the remote is connected
+ *
+ * Check whether the remote's underlying transport is connected to the
+ * remote host.
*
- * @param refs where to store the refs
* @param remote the remote
- * @return GIT_SUCCESS or an error code
+ * @return 1 if it's connected, 0 otherwise.
*/
-GIT_EXTERN(int) git_remote_ls(git_remote *remote, git_headarray *refs);
+GIT_EXTERN(int) git_remote_connected(const git_remote *remote);
+
+/**
+ * Cancel the operation
+ *
+ * At certain points in its operation, the network code checks whether
+ * the operation has been cancelled and if so stops the operation.
+ *
+ * @param remote the remote
+ */
+GIT_EXTERN(void) git_remote_stop(git_remote *remote);
+
+/**
+ * Disconnect from the remote
+ *
+ * Close the connection to the remote and free the underlying
+ * transport.
+ *
+ * @param remote the remote to disconnect from
+ */
+GIT_EXTERN(void) git_remote_disconnect(git_remote *remote);
/**
* Free the memory associated with a remote
*
+ * This also disconnects from the remote, if the connection
+ * has not been closed yet (using git_remote_disconnect).
+ *
* @param remote the remote to free
*/
-GIT_EXTERN(void) git_remote_free(struct git_remote *remote);
+GIT_EXTERN(void) git_remote_free(git_remote *remote);
+
+/**
+ * Update the tips to the new state
+ *
+ * @param remote the remote to update
+ * @param signature The identity to use when updating reflogs
+ * @param reflog_message The message to insert into the reflogs. If NULL, the
+ * default is "fetch <name>", where <name> is the name of
+ * the remote (or its url, for in-memory remotes).
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_remote_update_tips(
+ git_remote *remote,
+ const git_signature *signature,
+ const char *reflog_message);
+
+/**
+ * Download new data and update tips
+ *
+ * Convenience function to connect to a remote, download the data,
+ * disconnect and update the remote-tracking branches.
+ *
+ * @param remote the remote to fetch from
+ * @param signature The identity to use when updating reflogs
+ * @param reflog_message The message to insert into the reflogs. If NULL, the
+ * default is "fetch"
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_remote_fetch(
+ git_remote *remote,
+ const git_signature *signature,
+ const char *reflog_message);
+
+/**
+ * Return whether a string is a valid remote URL
+ *
+ * @param url the url to check
+ * @return 1 if the url is valid, 0 otherwise
+ */
+GIT_EXTERN(int) git_remote_valid_url(const char *url);
+
+/**
+ * Return whether the passed URL is supported by this version of the library.
+ *
+ * @param url the url to check
+ * @return 1 if the url is supported, 0 otherwise
+*/
+GIT_EXTERN(int) git_remote_supported_url(const char* url);
+
+/**
+ * Get a list of the configured remotes for a repo
+ *
+ * The string array must be freed by the user.
+ *
+ * @param out a string array which receives the names of the remotes
+ * @param repo the repository to query
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_remote_list(git_strarray *out, git_repository *repo);
+
+/**
+ * Choose whether to check the server's certificate (applies to HTTPS only)
+ *
+ * @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);
+
+/**
+ * Sets a custom transport for the remote. The caller can use this function
+ * to bypass the automatic discovery of a transport by URL scheme (i.e.
+ * http://, https://, git://) and supply their own transport to be used
+ * instead. After providing the transport to a remote using this function,
+ * the transport object belongs exclusively to that remote, and the remote will
+ * free it when it is freed with git_remote_free.
+ *
+ * @param remote the remote to configure
+ * @param transport the transport object for the remote to use
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_remote_set_transport(
+ git_remote *remote,
+ git_transport *transport);
+
+/**
+ * Argument to the completion callback which tells it which operation
+ * finished.
+ */
+typedef enum git_remote_completion_type {
+ GIT_REMOTE_COMPLETION_DOWNLOAD,
+ GIT_REMOTE_COMPLETION_INDEXING,
+ GIT_REMOTE_COMPLETION_ERROR,
+} git_remote_completion_type;
+
+/**
+ * The callback settings structure
+ *
+ * Set the callbacks to be called by the remote when informing the user
+ * about the progress of the network operations.
+ */
+struct git_remote_callbacks {
+ unsigned int version;
+ /**
+ * Textual progress from the remote. Text send over the
+ * progress side-band will be passed to this function (this is
+ * the 'counting objects' output.
+ */
+ git_transport_message_cb sideband_progress;
+
+ /**
+ * Completion is called when different parts of the download
+ * process are done (currently unused).
+ */
+ int (*completion)(git_remote_completion_type type, void *data);
+
+ /**
+ * This will be called if the remote host requires
+ * authentication in order to connect to it.
+ *
+ * Returning GIT_PASSTHROUGH will make libgit2 behave as
+ * though this field isn't set.
+ */
+ git_cred_acquire_cb credentials;
+
+ /**
+ * During the download of new data, this will be regularly
+ * called with the current count of progress done by the
+ * indexer.
+ */
+ git_transfer_progress_cb transfer_progress;
+
+ /**
+ * Each time a reference is updated locally, this function
+ * will be called with information about it.
+ */
+ int (*update_tips)(const char *refname, const git_oid *a, const git_oid *b, void *data);
+
+ /**
+ * This will be passed to each of the callbacks in this struct
+ * as the last parameter.
+ */
+ void *payload;
+};
+
+#define GIT_REMOTE_CALLBACKS_VERSION 1
+#define GIT_REMOTE_CALLBACKS_INIT {GIT_REMOTE_CALLBACKS_VERSION}
+
+/**
+ * Initializes a `git_remote_callbacks` with default values. Equivalent to
+ * creating an instance with GIT_REMOTE_CALLBACKS_INIT.
+ *
+ * @param opts the `git_remote_callbacks` struct to initialize
+ * @param version Version of struct; pass `GIT_REMOTE_CALLBACKS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_remote_init_callbacks(
+ git_remote_callbacks *opts,
+ unsigned int version);
+
+/**
+ * Set the callbacks for a remote
+ *
+ * Note that the remote keeps its own copy of the data and you need to
+ * call this function again if you want to change the callbacks.
+ *
+ * @param remote the remote to configure
+ * @param callbacks a pointer to the user's callback settings
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_remote_set_callbacks(git_remote *remote, const git_remote_callbacks *callbacks);
+
+/**
+ * Retrieve the current callback structure
+ *
+ * This provides read access to the callbacks structure as the remote
+ * sees it.
+ *
+ * @param remote the remote to query
+ * @return a pointer to the callbacks structure
+ */
+GIT_EXTERN(const git_remote_callbacks *) git_remote_get_callbacks(git_remote *remote);
+
+/**
+ * Get the statistics structure that is filled in by the fetch operation.
+ */
+GIT_EXTERN(const git_transfer_progress *) git_remote_stats(git_remote *remote);
+
+typedef enum {
+ GIT_REMOTE_DOWNLOAD_TAGS_AUTO = 0,
+ GIT_REMOTE_DOWNLOAD_TAGS_NONE = 1,
+ GIT_REMOTE_DOWNLOAD_TAGS_ALL = 2
+} git_remote_autotag_option_t;
+
+/**
+ * Retrieve the tag auto-follow setting
+ *
+ * @param remote the remote to query
+ * @return the auto-follow setting
+ */
+GIT_EXTERN(git_remote_autotag_option_t) git_remote_autotag(const git_remote *remote);
+
+/**
+ * Set the tag auto-follow setting
+ *
+ * @param remote the remote to configure
+ * @param value a GIT_REMOTE_DOWNLOAD_TAGS value
+ */
+GIT_EXTERN(void) git_remote_set_autotag(
+ git_remote *remote,
+ git_remote_autotag_option_t value);
+
+/**
+ * Give the remote a new name
+ *
+ * All remote-tracking branches and configuration settings
+ * for the remote are updated.
+ *
+ * The new name will be checked for validity.
+ * See `git_tag_create()` for rules about valid names.
+ *
+ * A temporary in-memory remote cannot be given a name with this method.
+ *
+ * @param problems non-default refspecs cannot be renamed and will be
+ * stored here for further processing by the caller. Always free this
+ * strarray on succesful return.
+ * @param remote the remote to rename
+ * @param new_name the new name the remote should bear
+ * @return 0, GIT_EINVALIDSPEC, GIT_EEXISTS or an error code
+ */
+GIT_EXTERN(int) git_remote_rename(
+ git_strarray *problems,
+ git_remote *remote,
+ const char *new_name);
+
+/**
+ * Retrieve the update FETCH_HEAD setting.
+ *
+ * @param remote the remote to query
+ * @return the update FETCH_HEAD setting
+ */
+GIT_EXTERN(int) git_remote_update_fetchhead(git_remote *remote);
+
+/**
+ * Sets the update FETCH_HEAD setting. By default, FETCH_HEAD will be
+ * updated on every fetch. Set to 0 to disable.
+ *
+ * @param remote the remote to configure
+ * @param value 0 to disable updating FETCH_HEAD
+ */
+GIT_EXTERN(void) git_remote_set_update_fetchhead(git_remote *remote, int value);
+
+/**
+ * Ensure the remote name is well-formed.
+ *
+ * @param remote_name name to be checked.
+ * @return 1 if the reference name is acceptable; 0 if it isn't
+ */
+GIT_EXTERN(int) git_remote_is_valid_name(const char *remote_name);
+
+/**
+* Delete an existing persisted remote.
+*
+* All remote-tracking branches and configuration settings
+* for the remote will be removed.
+*
+* @param remote A valid remote
+* @return 0 on success, or an error code.
+*/
+GIT_EXTERN(int) git_remote_delete(git_remote *remote);
+
+/**
+ * Retrieve the name of the remote's default branch
+ *
+ * The default branch of a repository is the branch which HEAD points
+ * to. If the remote does not support reporting this information
+ * directly, it performs the guess as git does; that is, if there are
+ * multiple branches which point to the same commit, the first one is
+ * chosen. If the master branch is a candidate, it wins.
+ *
+ * This function must only be called after connecting.
+ *
+ * @param out the buffern in which to store the reference name
+ * @param remote the remote
+ * @return 0, GIT_ENOTFOUND if the remote does not have any references
+ * or none of them point to HEAD's commit, or an error message.
+ */
+GIT_EXTERN(int) git_remote_default_branch(git_buf *out, git_remote *remote);
/** @} */
GIT_END_DECL
diff --git a/include/git2/repository.h b/include/git2/repository.h
index 4088ff7f9..6a8ff4545 100644
--- a/include/git2/repository.h
+++ b/include/git2/repository.h
@@ -1,26 +1,8 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_repository_h__
#define INCLUDE_git_repository_h__
@@ -28,6 +10,7 @@
#include "common.h"
#include "types.h"
#include "oid.h"
+#include "buffer.h"
/**
* @file git2/repository.h
@@ -41,159 +24,126 @@ GIT_BEGIN_DECL
/**
* Open a git repository.
*
- * The 'path' argument must point to an existing git repository
- * folder, e.g.
- *
- * /path/to/my_repo/.git/ (normal repository)
- * objects/
- * index
- * HEAD
- *
- * /path/to/bare_repo/ (bare repository)
- * objects/
- * index
- * HEAD
+ * The 'path' argument must point to either a git repository
+ * folder, or an existing work dir.
*
- * The method will automatically detect if 'path' is a normal
- * or bare repository or fail is 'path' is neither.
+ * The method will automatically detect if 'path' is a normal
+ * or bare repository or fail is 'path' is neither.
*
- * @param repository pointer to the repo which will be opened
+ * @param out pointer to the repo which will be opened
* @param path the path to the repository
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_repository_open(git_repository **repository, const char *path);
-
+GIT_EXTERN(int) git_repository_open(git_repository **out, const char *path);
/**
- * Open a git repository by manually specifying all its paths
- *
- * @param repository pointer to the repo which will be opened
+ * Create a "fake" repository to wrap an object database
*
- * @param git_dir The full path to the repository folder
- * e.g. a '.git' folder for live repos, any folder for bare
- * Equivalent to $GIT_DIR.
- * Cannot be NULL.
+ * Create a repository object to wrap an object database to be used
+ * with the API when all you have is an object database. This doesn't
+ * have any paths associated with it, so use with care.
*
- * @param git_object_directory The full path to the ODB folder.
- * the folder where all the loose and packed objects are stored
- * Equivalent to $GIT_OBJECT_DIRECTORY.
- * If NULL, "$GIT_DIR/objects/" is assumed.
- *
- * @param git_index_file The full path to the index (dircache) file
- * Equivalent to $GIT_INDEX_FILE.
- * If NULL, "$GIT_DIR/index" is assumed.
- *
- * @param git_work_tree The full path to the working tree of the repository,
- * if the repository is not bare.
- * Equivalent to $GIT_WORK_TREE.
- * If NULL, the repository is assumed to be bare.
- *
- * @return 0 on success; error code otherwise
+ * @param out pointer to the repo
+ * @param odb the object database to wrap
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_repository_open2(git_repository **repository,
- const char *git_dir,
- const char *git_object_directory,
- const char *git_index_file,
- const char *git_work_tree);
-
+GIT_EXTERN(int) git_repository_wrap_odb(git_repository **out, git_odb *odb);
/**
- * Open a git repository by manually specifying its paths and
- * the object database it will use.
+ * Look for a git repository and copy its path in the given buffer.
+ * The lookup start from base_path and walk across parent directories
+ * if nothing has been found. The lookup ends when the first repository
+ * is found, or when reaching a directory referenced in ceiling_dirs
+ * or when the filesystem changes (in case across_fs is true).
*
- * @param repository pointer to the repo which will be opened
+ * The method will automatically detect if the repository is bare
+ * (if there is a repository).
*
- * @param git_dir The full path to the repository folder
- * e.g. a '.git' folder for live repos, any folder for bare
- * Equivalent to $GIT_DIR.
- * Cannot be NULL.
+ * @param out A pointer to a user-allocated git_buf which will contain
+ * the found path.
*
- * @param object_database A pointer to a git_odb created & initialized
- * by the user (e.g. with custom backends). This object database
- * will be owned by the repository and will be automatically free'd.
- * It should not be manually free'd by the user, or this
- * git_repository object will become invalid.
+ * @param start_path The base path where the lookup starts.
*
- * @param git_index_file The full path to the index (dircache) file
- * Equivalent to $GIT_INDEX_FILE.
- * If NULL, "$GIT_DIR/index" is assumed.
+ * @param across_fs If true, then the lookup will not stop when a
+ * filesystem device change is detected while exploring parent directories.
*
- * @param git_work_tree The full path to the working tree of the repository,
- * if the repository is not bare.
- * Equivalent to $GIT_WORK_TREE.
- * If NULL, the repository is assumed to be bare.
+ * @param ceiling_dirs A GIT_PATH_LIST_SEPARATOR separated list of
+ * absolute symbolic link free paths. The lookup will stop when any
+ * of this paths is reached. Note that the lookup always performs on
+ * start_path no matter start_path appears in ceiling_dirs ceiling_dirs
+ * might be NULL (which is equivalent to an empty string)
*
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
-
-GIT_EXTERN(int) git_repository_open3(git_repository **repository,
- const char *git_dir,
- git_odb *object_database,
- const char *git_index_file,
- const char *git_work_tree);
+GIT_EXTERN(int) git_repository_discover(
+ git_buf *out,
+ const char *start_path,
+ int across_fs,
+ const char *ceiling_dirs);
/**
- * Look for a git repository and copy its path in the given buffer. The lookup start
- * from base_path and walk across parent directories if nothing has been found. The
- * lookup ends when the first repository is found, or when reaching a directory
- * referenced in ceiling_dirs or when the filesystem changes (in case across_fs
- * is true).
- *
- * The method will automatically detect if the repository is bare (if there is
- * a repository).
- *
- * @param repository_path The user allocated buffer which will contain the found path.
- *
- * @param size repository_path size
- *
- * @param start_path The base path where the lookup starts.
- *
- * @param across_fs If true, then the lookup will not stop when a filesystem device change
- * is detected while exploring parent directories.
- *
- * @param ceiling_dirs A GIT_PATH_LIST_SEPARATOR separated list of absolute symbolic link
- * free paths. The lookup will stop when any of this paths is reached. Note that the
- * lookup always performs on start_path no matter start_path appears in ceiling_dirs
- * ceiling_dirs might be NULL (which is equivalent to an empty string)
- *
- * @return 0 on success; error code otherwise
+ * Option flags for `git_repository_open_ext`.
+ *
+ * * GIT_REPOSITORY_OPEN_NO_SEARCH - Only open the repository if it can be
+ * immediately found in the start_path. Do not walk up from the
+ * start_path looking at parent directories.
+ * * GIT_REPOSITORY_OPEN_CROSS_FS - Unless this flag is set, open will not
+ * continue searching across filesystem boundaries (i.e. when `st_dev`
+ * changes from the `stat` system call). (E.g. Searching in a user's home
+ * directory "/home/user/source/" will not return "/.git/" as the found
+ * repo if "/" is a different filesystem than "/home".)
+ * * GIT_REPOSITORY_OPEN_BARE - Open repository as a bare repo regardless
+ * of core.bare config, and defer loading config file for faster setup.
+ * Unlike `git_repository_open_bare`, this can follow gitlinks.
*/
-GIT_EXTERN(int) git_repository_discover(char *repository_path, size_t size, const char *start_path, int across_fs, const char *ceiling_dirs);
+typedef enum {
+ GIT_REPOSITORY_OPEN_NO_SEARCH = (1 << 0),
+ GIT_REPOSITORY_OPEN_CROSS_FS = (1 << 1),
+ GIT_REPOSITORY_OPEN_BARE = (1 << 2),
+} git_repository_open_flag_t;
/**
- * Get the object database behind a Git repository
- *
- * @param repo a repository object
- * @return a pointer to the object db
+ * Find and open a repository with extended controls.
+ *
+ * @param out Pointer to the repo which will be opened. This can
+ * actually be NULL if you only want to use the error code to
+ * see if a repo at this path could be opened.
+ * @param path Path to open as git repository. If the flags
+ * permit "searching", then this can be a path to a subdirectory
+ * inside the working directory of the repository.
+ * @param flags A combination of the GIT_REPOSITORY_OPEN flags above.
+ * @param ceiling_dirs A GIT_PATH_LIST_SEPARATOR delimited list of path
+ * prefixes at which the search for a containing repository should
+ * terminate.
+ * @return 0 on success, GIT_ENOTFOUND if no repository could be found,
+ * or -1 if there was a repository but open failed for some reason
+ * (such as repo corruption or system errors).
*/
-GIT_EXTERN(git_odb *) git_repository_database(git_repository *repo);
+GIT_EXTERN(int) git_repository_open_ext(
+ git_repository **out,
+ const char *path,
+ unsigned int flags,
+ const char *ceiling_dirs);
/**
- * Open the Index file of a Git repository
- *
- * This returns a new and unique `git_index` object representing the
- * active index for the repository.
- *
- * This method may be called more than once (e.g. on different threads).
- *
- * Each returned `git_index` object is independent and suffers no race
- * conditions: synchronization is done at the FS level.
+ * Open a bare repository on the serverside.
*
- * Each returned `git_index` object must be manually freed by the user,
- * using `git_index_free`.
+ * This is a fast open for bare repositories that will come in handy
+ * if you're e.g. hosting git repositories and need to access them
+ * efficiently
*
- * @param index Pointer where to store the index
- * @param repo a repository object
- * @return 0 on success; error code if the index could not be opened
+ * @param out Pointer to the repo which will be opened.
+ * @param bare_path Direct path to the bare repository
+ * @return 0 on success, or an error code
*/
-GIT_EXTERN(int) git_repository_index(git_index **index, git_repository *repo);
+GIT_EXTERN(int) git_repository_open_bare(git_repository **out, const char *bare_path);
/**
* Free a previously allocated repository
*
* Note that after a repository is free'd, all the objects it has spawned
* will still exist until they are manually closed by the user
- * with `git_object_close`, but accessing any of the attributes of
+ * with `git_object_free`, but accessing any of the attributes of
* an object without a backing repository will result in undefined
* behavior
*
@@ -206,17 +156,161 @@ GIT_EXTERN(void) git_repository_free(git_repository *repo);
*
* TODO:
* - Reinit the repository
- * - Create config files
*
- * @param repo_out pointer to the repo which will be created or reinitialized
+ * @param out pointer to the repo which will be created or reinitialized
* @param path the path to the repository
- * @param is_bare if true, a Git repository without a working directory is created
- * at the pointed path. If false, provided path will be considered as the working
- * directory into which the .git directory will be created.
+ * @param is_bare if true, a Git repository without a working directory is
+ * created at the pointed path. If false, provided path will be
+ * considered as the working directory into which the .git directory
+ * will be created.
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_repository_init(
+ git_repository **out,
+ const char *path,
+ unsigned is_bare);
+
+/**
+ * Option flags for `git_repository_init_ext`.
+ *
+ * These flags configure extra behaviors to `git_repository_init_ext`.
+ * In every case, the default behavior is the zero value (i.e. flag is
+ * not set). Just OR the flag values together for the `flags` parameter
+ * when initializing a new repo. Details of individual values are:
+ *
+ * * BARE - Create a bare repository with no working directory.
+ * * NO_REINIT - Return an GIT_EEXISTS error if the repo_path appears to
+ * already be an git repository.
+ * * NO_DOTGIT_DIR - Normally a "/.git/" will be appended to the repo
+ * path for non-bare repos (if it is not already there), but
+ * passing this flag prevents that behavior.
+ * * MKDIR - Make the repo_path (and workdir_path) as needed. Init is
+ * always willing to create the ".git" directory even without this
+ * flag. This flag tells init to create the trailing component of
+ * the repo and workdir paths as needed.
+ * * MKPATH - Recursively make all components of the repo and workdir
+ * paths as necessary.
+ * * EXTERNAL_TEMPLATE - libgit2 normally uses internal templates to
+ * initialize a new repo. This flags enables external templates,
+ * looking the "template_path" from the options if set, or the
+ * `init.templatedir` global config if not, or falling back on
+ * "/usr/share/git-core/templates" if it exists.
+ */
+typedef enum {
+ GIT_REPOSITORY_INIT_BARE = (1u << 0),
+ GIT_REPOSITORY_INIT_NO_REINIT = (1u << 1),
+ GIT_REPOSITORY_INIT_NO_DOTGIT_DIR = (1u << 2),
+ GIT_REPOSITORY_INIT_MKDIR = (1u << 3),
+ GIT_REPOSITORY_INIT_MKPATH = (1u << 4),
+ GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE = (1u << 5),
+} git_repository_init_flag_t;
+
+/**
+ * Mode options for `git_repository_init_ext`.
+ *
+ * Set the mode field of the `git_repository_init_options` structure
+ * either to the custom mode that you would like, or to one of the
+ * following modes:
*
- * @return 0 on success; error code otherwise
+ * * SHARED_UMASK - Use permissions configured by umask - the default.
+ * * SHARED_GROUP - Use "--shared=group" behavior, chmod'ing the new repo
+ * to be group writable and "g+sx" for sticky group assignment.
+ * * SHARED_ALL - Use "--shared=all" behavior, adding world readability.
+ * * Anything else - Set to custom value.
*/
-GIT_EXTERN(int) git_repository_init(git_repository **repo_out, const char *path, unsigned is_bare);
+typedef enum {
+ GIT_REPOSITORY_INIT_SHARED_UMASK = 0,
+ GIT_REPOSITORY_INIT_SHARED_GROUP = 0002775,
+ GIT_REPOSITORY_INIT_SHARED_ALL = 0002777,
+} git_repository_init_mode_t;
+
+/**
+ * Extended options structure for `git_repository_init_ext`.
+ *
+ * This contains extra options for `git_repository_init_ext` that enable
+ * additional initialization features. The fields are:
+ *
+ * * flags - Combination of GIT_REPOSITORY_INIT flags above.
+ * * mode - Set to one of the standard GIT_REPOSITORY_INIT_SHARED_...
+ * constants above, or to a custom value that you would like.
+ * * workdir_path - The path to the working dir or NULL for default (i.e.
+ * repo_path parent on non-bare repos). IF THIS IS RELATIVE PATH,
+ * IT WILL BE EVALUATED RELATIVE TO THE REPO_PATH. If this is not
+ * the "natural" working directory, a .git gitlink file will be
+ * created here linking to the repo_path.
+ * * description - If set, this will be used to initialize the "description"
+ * file in the repository, instead of using the template content.
+ * * template_path - When GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE is set,
+ * this contains the path to use for the template directory. If
+ * this is NULL, the config or default directory options will be
+ * used instead.
+ * * initial_head - The name of the head to point HEAD at. If NULL, then
+ * this will be treated as "master" and the HEAD ref will be set
+ * to "refs/heads/master". If this begins with "refs/" it will be
+ * used verbatim; otherwise "refs/heads/" will be prefixed.
+ * * origin_url - If this is non-NULL, then after the rest of the
+ * repository initialization is completed, an "origin" remote
+ * will be added pointing to this URL.
+ */
+typedef struct {
+ unsigned int version;
+ uint32_t flags;
+ uint32_t mode;
+ const char *workdir_path;
+ const char *description;
+ const char *template_path;
+ const char *initial_head;
+ const char *origin_url;
+} git_repository_init_options;
+
+#define GIT_REPOSITORY_INIT_OPTIONS_VERSION 1
+#define GIT_REPOSITORY_INIT_OPTIONS_INIT {GIT_REPOSITORY_INIT_OPTIONS_VERSION}
+
+/**
+ * Initializes a `git_repository_init_options` with default values. Equivalent
+ * to creating an instance with GIT_REPOSITORY_INIT_OPTIONS_INIT.
+ *
+ * @param opts the `git_repository_init_options` struct to initialize
+ * @param version Version of struct; pass `GIT_REPOSITORY_INIT_OPTIONS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_repository_init_init_options(
+ git_repository_init_options *opts,
+ unsigned int version);
+
+/**
+ * Create a new Git repository in the given folder with extended controls.
+ *
+ * This will initialize a new git repository (creating the repo_path
+ * if requested by flags) and working directory as needed. It will
+ * auto-detect the case sensitivity of the file system and if the
+ * file system supports file mode bits correctly.
+ *
+ * @param out Pointer to the repo which will be created or reinitialized.
+ * @param repo_path The path to the repository.
+ * @param opts Pointer to git_repository_init_options struct.
+ * @return 0 or an error code on failure.
+ */
+GIT_EXTERN(int) git_repository_init_ext(
+ git_repository **out,
+ const char *repo_path,
+ git_repository_init_options *opts);
+
+/**
+ * Retrieve and resolve the reference pointed at by HEAD.
+ *
+ * The returned `git_reference` will be owned by caller and
+ * `git_reference_free()` must be called when done with it to release the
+ * allocated memory and prevent a leak.
+ *
+ * @param out pointer to the reference which will be retrieved
+ * @param repo a repository object
+ *
+ * @return 0 on success, GIT_EUNBORNBRANCH when HEAD points to a non existing
+ * branch, GIT_ENOTFOUND when HEAD is missing; an error code otherwise
+ */
+GIT_EXTERN(int) git_repository_head(git_reference **out, git_repository *repo);
/**
* Check if a repository's HEAD is detached
@@ -225,28 +319,28 @@ GIT_EXTERN(int) git_repository_init(git_repository **repo_out, const char *path,
* instead of a branch.
*
* @param repo Repo to test
- * @return 1 if HEAD is detached, 0 if i'ts not; error code if there
+ * @return 1 if HEAD is detached, 0 if it's not; error code if there
* was an error.
*/
GIT_EXTERN(int) git_repository_head_detached(git_repository *repo);
/**
- * Check if the current branch is an orphan
+ * Check if the current branch is unborn
*
- * An orphan branch is one named from HEAD but which doesn't exist in
+ * An unborn branch is one named from HEAD but which doesn't exist in
* the refs namespace, because it doesn't have any commit to point to.
*
* @param repo Repo to test
- * @return 1 if the current branch is an orphan, 0 if it's not; error
- * code if therewas an error
+ * @return 1 if the current branch is unborn, 0 if it's not; error
+ * code if there was an error
*/
-GIT_EXTERN(int) git_repository_head_orphan(git_repository *repo);
+GIT_EXTERN(int) git_repository_head_unborn(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
@@ -255,77 +349,367 @@ GIT_EXTERN(int) git_repository_head_orphan(git_repository *repo);
GIT_EXTERN(int) git_repository_is_empty(git_repository *repo);
/**
- * Internal path identifiers for a repository
+ * Get the path of this repository
+ *
+ * This is the path of the `.git` folder for normal repositories,
+ * or of the repository itself for bare repositories.
+ *
+ * @param repo A repository object
+ * @return the path to the repository
*/
-typedef enum {
- GIT_REPO_PATH,
- GIT_REPO_PATH_INDEX,
- GIT_REPO_PATH_ODB,
- GIT_REPO_PATH_WORKDIR
-} git_repository_pathid;
+GIT_EXTERN(const char *) git_repository_path(git_repository *repo);
/**
- * Get one of the paths to the repository
+ * Get the path of the working directory for this repository
*
- * Possible values for `id`:
+ * If the repository is bare, this function will always return
+ * NULL.
*
- * GIT_REPO_PATH: return the path to the repository
- * GIT_REPO_PATH_INDEX: return the path to the index
- * GIT_REPO_PATH_ODB: return the path to the ODB
- * GIT_REPO_PATH_WORKDIR: return the path to the working
- * directory
+ * @param repo A repository object
+ * @return the path to the working dir, if it exists
+ */
+GIT_EXTERN(const char *) git_repository_workdir(git_repository *repo);
+
+/**
+ * Set the path to the working directory for this repository
*
- * @param repo a repository object
- * @param id The ID of the path to return
- * @return absolute path of the requested id
+ * The working directory doesn't need to be the same one
+ * that contains the `.git` folder for this repository.
+ *
+ * If this repository is bare, setting its working directory
+ * will turn it into a normal repository, capable of performing
+ * all the common workdir operations (checkout, status, index
+ * manipulation, etc).
+ *
+ * @param repo A repository object
+ * @param workdir The path to a working directory
+ * @param update_gitlink Create/update gitlink in workdir and set config
+ * "core.worktree" (if workdir is not the parent of the .git directory)
+ * @return 0, or an error code
*/
-GIT_EXTERN(const char *) git_repository_path(git_repository *repo, git_repository_pathid id);
+GIT_EXTERN(int) git_repository_set_workdir(
+ git_repository *repo, const char *workdir, int update_gitlink);
/**
* Check if a repository is bare
*
* @param repo Repo to test
- * @return 1 if the repository is empty, 0 otherwise.
+ * @return 1 if the repository is bare, 0 otherwise.
*/
GIT_EXTERN(int) git_repository_is_bare(git_repository *repo);
/**
- * Retrieve the relevant configuration for a repository
+ * Get the configuration file for this repository.
*
- * By default he returned `git_config` instance contains a single
- * configuration file, the `.gitconfig' file that may be found
- * inside the repository.
+ * If a configuration file has not been set, the default
+ * config set for the repository will be returned, including
+ * global and system configurations (if they are available).
*
- * If the `user_config_path` variable is not NULL, the given config
- * file will be also included in the configuration set. On most UNIX
- * systems, this file may be found on `$HOME/.gitconfig`.
+ * The configuration file must be freed once it's no longer
+ * being used by the user.
*
- * If the `system_config_path` variable is not NULL, the given config
- * file will be also included in the configuration set. On most UNIX
- * systems, this file may be found on `$PREFIX/etc/gitconfig`.
+ * @param out Pointer to store the loaded configuration
+ * @param repo A repository object
+ * @return 0, or an error code
+ */
+GIT_EXTERN(int) git_repository_config(git_config **out, git_repository *repo);
+
+/**
+ * Get a snapshot of the repository's configuration
*
- * The resulting `git_config` instance will query the files in the following
- * order:
+ * Convenience function to take a snapshot from the repository's
+ * configuration. The contents of this snapshot will not change,
+ * even if the underlying config files are modified.
*
- * - Repository configuration file
- * - User configuration file
- * - System configuration file
+ * The configuration file must be freed once it's no longer
+ * being used by the user.
*
- * The method will fail if any of the passed config files cannot be
- * found or accessed.
+ * @param out Pointer to store the loaded configuration
+ * @param repo the repository
+ * @return 0, or an error code
+ */
+GIT_EXTERN(int) git_repository_config_snapshot(git_config **out, git_repository *repo);
+
+/**
+ * Get the Object Database for this repository.
+ *
+ * If a custom ODB has not been set, the default
+ * database for the repository will be returned (the one
+ * located in `.git/objects`).
*
- * The returned `git_config` instance is owned by the caller and must
- * be manually free'd once it's no longer on use.
+ * The ODB must be freed once it's no longer being used by
+ * the user.
*
- * @param out the repository's configuration
- * @param repo the repository for which to get the config
- * @param user_config_path Path to the user config file
- * @param system_config_path Path to the system-wide config file
+ * @param out Pointer to store the loaded ODB
+ * @param repo A repository object
+ * @return 0, or an error code
*/
-GIT_EXTERN(int) git_repository_config(git_config **out,
+GIT_EXTERN(int) git_repository_odb(git_odb **out, git_repository *repo);
+
+/**
+ * Get the Reference Database Backend for this repository.
+ *
+ * If a custom refsdb has not been set, the default database for
+ * the repository will be returned (the one that manipulates loose
+ * and packed references in the `.git` directory).
+ *
+ * The refdb must be freed once it's no longer being used by
+ * the user.
+ *
+ * @param out Pointer to store the loaded refdb
+ * @param repo A repository object
+ * @return 0, or an error code
+ */
+GIT_EXTERN(int) git_repository_refdb(git_refdb **out, git_repository *repo);
+
+/**
+ * Get the Index file for this repository.
+ *
+ * If a custom index has not been set, the default
+ * index for the repository will be returned (the one
+ * located in `.git/index`).
+ *
+ * The index must be freed once it's no longer being used by
+ * the user.
+ *
+ * @param out Pointer to store the loaded index
+ * @param repo A repository object
+ * @return 0, or an error code
+ */
+GIT_EXTERN(int) git_repository_index(git_index **out, git_repository *repo);
+
+/**
+ * Retrieve git's prepared message
+ *
+ * Operations such as git revert/cherry-pick/merge with the -n option
+ * stop just short of creating a commit with the changes and save
+ * their prepared message in .git/MERGE_MSG so the next git-commit
+ * execution can present it to the user for them to amend if they
+ * wish.
+ *
+ * Use this function to get the contents of this file. Don't forget to
+ * remove the file after you create the commit.
+ *
+ * @param out git_buf to write data into
+ * @param repo Repository to read prepared message from
+ * @return 0, GIT_ENOTFOUND if no message exists or an error code
+ */
+GIT_EXTERN(int) git_repository_message(git_buf *out, git_repository *repo);
+
+/**
+ * Remove git's prepared message.
+ *
+ * Remove the message that `git_repository_message` retrieves.
+ */
+GIT_EXTERN(int) git_repository_message_remove(git_repository *repo);
+
+/**
+ * Remove all the metadata associated with an ongoing command like merge,
+ * revert, cherry-pick, etc. For example: MERGE_HEAD, MERGE_MSG, etc.
+ *
+ * @param repo A repository object
+ * @return 0 on success, or error
+ */
+GIT_EXTERN(int) git_repository_state_cleanup(git_repository *repo);
+
+typedef int (*git_repository_fetchhead_foreach_cb)(const char *ref_name,
+ const char *remote_url,
+ const git_oid *oid,
+ unsigned int is_merge,
+ void *payload);
+
+/**
+ * Invoke 'callback' for each entry in the given FETCH_HEAD file.
+ *
+ * Return a non-zero value from the callback to stop the loop.
+ *
+ * @param repo A repository object
+ * @param callback Callback function
+ * @param payload Pointer to callback data (optional)
+ * @return 0 on success, non-zero callback return value, GIT_ENOTFOUND if
+ * there is no FETCH_HEAD file, or other error code.
+ */
+GIT_EXTERN(int) git_repository_fetchhead_foreach(
git_repository *repo,
- const char *user_config_path,
- const char *system_config_path);
+ git_repository_fetchhead_foreach_cb callback,
+ void *payload);
+
+typedef int (*git_repository_mergehead_foreach_cb)(const git_oid *oid,
+ void *payload);
+
+/**
+ * If a merge is in progress, invoke 'callback' for each commit ID in the
+ * MERGE_HEAD file.
+ *
+ * Return a non-zero value from the callback to stop the loop.
+ *
+ * @param repo A repository object
+ * @param callback Callback function
+ * @param payload Pointer to callback data (optional)
+ * @return 0 on success, non-zero callback return value, GIT_ENOTFOUND if
+ * there is no MERGE_HEAD file, or other error code.
+ */
+GIT_EXTERN(int) git_repository_mergehead_foreach(
+ git_repository *repo,
+ git_repository_mergehead_foreach_cb callback,
+ void *payload);
+
+/**
+ * Calculate hash of file using repository filtering rules.
+ *
+ * If you simply want to calculate the hash of a file on disk with no filters,
+ * you can just use the `git_odb_hashfile()` API. However, if you want to
+ * hash a file in the repository and you want to apply filtering rules (e.g.
+ * crlf filters) before generating the SHA, then use this function.
+ *
+ * Note: if the repository has `core.safecrlf` set to fail and the
+ * filtering triggers that failure, then this function will return an
+ * error and not calculate the hash of the file.
+ *
+ * @param out Output value of calculated SHA
+ * @param repo Repository pointer
+ * @param path Path to file on disk whose contents should be hashed. If the
+ * repository is not NULL, this can be a relative path.
+ * @param type The object type to hash as (e.g. GIT_OBJ_BLOB)
+ * @param as_path The path to use to look up filtering rules. If this is
+ * NULL, then the `path` parameter will be used instead. If
+ * this is passed as the empty string, then no filters will be
+ * applied when calculating the hash.
+ * @return 0 on success, or an error code
+ */
+GIT_EXTERN(int) git_repository_hashfile(
+ git_oid *out,
+ git_repository *repo,
+ const char *path,
+ git_otype type,
+ const char *as_path);
+
+/**
+ * Make the repository HEAD point to the specified reference.
+ *
+ * If the provided reference points to a Tree or a Blob, the HEAD is
+ * unaltered and -1 is returned.
+ *
+ * If the provided reference points to a branch, the HEAD will point
+ * to that branch, staying attached, or become attached if it isn't yet.
+ * If the branch doesn't exist yet, no error will be return. The HEAD
+ * will then be attached to an unborn branch.
+ *
+ * Otherwise, the HEAD will be detached and will directly point to
+ * the Commit.
+ *
+ * @param repo Repository pointer
+ * @param refname Canonical name of the reference the HEAD should point at
+ * @param signature The identity that will used to populate the reflog entry
+ * @param log_message The one line long message to be appended to the reflog
+ * @return 0 on success, or an error code
+ */
+GIT_EXTERN(int) git_repository_set_head(
+ git_repository* repo,
+ const char* refname,
+ const git_signature *signature,
+ const char *log_message);
+
+/**
+ * Make the repository HEAD directly point to the Commit.
+ *
+ * If the provided committish cannot be found in the repository, the HEAD
+ * is unaltered and GIT_ENOTFOUND is returned.
+ *
+ * If the provided commitish cannot be peeled into a commit, the HEAD
+ * is unaltered and -1 is returned.
+ *
+ * Otherwise, the HEAD will eventually be detached and will directly point to
+ * the peeled Commit.
+ *
+ * @param repo Repository pointer
+ * @param commitish Object id of the Commit the HEAD should point to
+ * @param signature The identity that will used to populate the reflog entry
+ * @param log_message The one line long message to be appended to the reflog
+ * @return 0 on success, or an error code
+ */
+GIT_EXTERN(int) git_repository_set_head_detached(
+ git_repository* repo,
+ const git_oid* commitish,
+ const git_signature *signature,
+ const char *log_message);
+
+/**
+ * Detach the HEAD.
+ *
+ * If the HEAD is already detached and points to a Commit, 0 is returned.
+ *
+ * If the HEAD is already detached and points to a Tag, the HEAD is
+ * updated into making it point to the peeled Commit, and 0 is returned.
+ *
+ * If the HEAD is already detached and points to a non commitish, the HEAD is
+ * unaltered, and -1 is returned.
+ *
+ * Otherwise, the HEAD will be detached and point to the peeled Commit.
+ *
+ * @param repo Repository pointer
+ * @param signature The identity that will used to populate the reflog entry
+ * @param reflog_message The one line long message to be appended to the reflog
+ * @return 0 on success, GIT_EUNBORNBRANCH when HEAD points to a non existing
+ * branch or an error code
+ */
+GIT_EXTERN(int) git_repository_detach_head(
+ git_repository* repo,
+ const git_signature *signature,
+ const char *reflog_message);
+
+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);
+
+/**
+ * Sets the active namespace for this Git Repository
+ *
+ * This namespace affects all reference operations for the repo.
+ * See `man gitnamespaces`
+ *
+ * @param repo The repo
+ * @param nmspace The namespace. This should not include the refs
+ * folder, e.g. to namespace all references under `refs/namespaces/foo/`,
+ * use `foo` as the namespace.
+ * @return 0 on success, -1 on error
+ */
+GIT_EXTERN(int) git_repository_set_namespace(git_repository *repo, const char *nmspace);
+
+/**
+ * Get the currently active namespace for this repository
+ *
+ * @param repo The repo
+ * @return the active namespace, or NULL if there isn't one
+ */
+GIT_EXTERN(const char *) git_repository_get_namespace(git_repository *repo);
+
+
+/**
+ * Determine if the repository was a shallow clone
+ *
+ * @param repo The repository
+ * @return 1 if shallow, zero if not
+ */
+GIT_EXTERN(int) git_repository_is_shallow(git_repository *repo);
/** @} */
GIT_END_DECL
diff --git a/include/git2/reset.h b/include/git2/reset.h
new file mode 100644
index 000000000..b8c580339
--- /dev/null
+++ b/include/git2/reset.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_reset_h__
+#define INCLUDE_git_reset_h__
+
+/**
+ * @file git2/reset.h
+ * @brief Git reset management routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Kinds of reset operation
+ */
+typedef enum {
+ GIT_RESET_SOFT = 1, /**< Move the head to the given commit */
+ GIT_RESET_MIXED = 2, /**< SOFT plus reset index to the commit */
+ GIT_RESET_HARD = 3, /**< MIXED plus changes in working tree discarded */
+} git_reset_t;
+
+/**
+ * Sets the current head to the specified commit oid and optionally
+ * resets the index and working tree to match.
+ *
+ * SOFT reset means the Head will be moved to the commit.
+ *
+ * MIXED reset will trigger a SOFT reset, plus the index will be replaced
+ * with the content of the commit tree.
+ *
+ * HARD reset will trigger a MIXED reset and the working directory will be
+ * replaced with the content of the index. (Untracked and ignored files
+ * will be left alone, however.)
+ *
+ * TODO: Implement remaining kinds of resets.
+ *
+ * @param repo Repository where to perform the reset operation.
+ *
+ * @param target Committish to which the Head should be moved to. This object
+ * must belong to the given `repo` and can either be a git_commit or a
+ * git_tag. When a git_tag is being passed, it should be dereferencable
+ * to a git_commit which oid will be used as the target of the branch.
+ *
+ * @param reset_type Kind of reset operation to perform.
+ *
+ * @param signature The identity that will used to populate the reflog entry
+ *
+ * @param log_message The one line long message to be appended to the reflog.
+ * The reflog is only updated if the affected direct reference is actually
+ * changing. If NULL, the default is "reset: moving"; if you want something more
+ * useful, provide a message.
+ *
+ * @return 0 on success or an error code
+ */
+GIT_EXTERN(int) git_reset(
+ git_repository *repo,
+ git_object *target,
+ git_reset_t reset_type,
+ git_signature *signature,
+ const char *log_message);
+
+/**
+ * Updates some entries in the index from the target commit tree.
+ *
+ * The scope of the updated entries is determined by the paths
+ * being passed in the `pathspec` parameters.
+ *
+ * Passing a NULL `target` will result in removing
+ * entries in the index matching the provided pathspecs.
+ *
+ * @param repo Repository where to perform the reset operation.
+ *
+ * @param target The committish which content will be used to reset the content
+ * of the index.
+ *
+ * @param pathspecs List of pathspecs to operate on.
+ *
+ * @return 0 on success or an error code < 0
+ */
+GIT_EXTERN(int) git_reset_default(
+ git_repository *repo,
+ git_object *target,
+ git_strarray* pathspecs);
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/revert.h b/include/git2/revert.h
new file mode 100644
index 000000000..fc1767c93
--- /dev/null
+++ b/include/git2/revert.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_revert_h__
+#define INCLUDE_git_revert_h__
+
+#include "common.h"
+#include "types.h"
+#include "merge.h"
+
+/**
+ * @file git2/revert.h
+ * @brief Git revert routines
+ * @defgroup git_revert Git revert routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+typedef struct {
+ unsigned int version;
+
+ /** For merge commits, the "mainline" is treated as the parent. */
+ unsigned int mainline;
+
+ git_merge_options merge_opts;
+ git_checkout_options checkout_opts;
+} git_revert_options;
+
+#define GIT_REVERT_OPTIONS_VERSION 1
+#define GIT_REVERT_OPTIONS_INIT {GIT_REVERT_OPTIONS_VERSION, 0, GIT_MERGE_OPTIONS_INIT, GIT_CHECKOUT_OPTIONS_INIT}
+
+/**
+ * Initializes a `git_revert_options` with default values. Equivalent to
+ * creating an instance with GIT_REVERT_OPTIONS_INIT.
+ *
+ * @param opts the `git_revert_options` struct to initialize
+ * @param version Version of struct; pass `GIT_REVERT_OPTIONS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_revert_init_options(
+ git_revert_options *opts,
+ unsigned int version);
+
+/**
+ * Reverts the given commit against the given "our" commit, producing an
+ * index that reflects the result of the revert.
+ *
+ * The returned index must be freed explicitly with `git_index_free`.
+ *
+ * @param out pointer to store the index result in
+ * @param repo the repository that contains the given commits
+ * @param revert_commit the commit to revert
+ * @param our_commit the commit to revert against (eg, HEAD)
+ * @param mainline the parent of the revert commit, if it is a merge
+ * @param merge_options the merge options (or null for defaults)
+ * @return zero on success, -1 on failure.
+ */
+int git_revert_commit(
+ git_index **out,
+ git_repository *repo,
+ git_commit *revert_commit,
+ git_commit *our_commit,
+ unsigned int mainline,
+ const git_merge_options *merge_options);
+
+/**
+ * Reverts the given commit, producing changes in the working directory.
+ *
+ * @param repo the repository to revert
+ * @param commit the commit to revert
+ * @param given_opts merge flags
+ * @return zero on success, -1 on failure.
+ */
+GIT_EXTERN(int) git_revert(
+ git_repository *repo,
+ git_commit *commit,
+ const git_revert_options *given_opts);
+
+/** @} */
+GIT_END_DECL
+#endif
+
diff --git a/include/git2/revparse.h b/include/git2/revparse.h
new file mode 100644
index 000000000..d170e1621
--- /dev/null
+++ b/include/git2/revparse.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_revparse_h__
+#define INCLUDE_git_revparse_h__
+
+#include "common.h"
+#include "types.h"
+
+/**
+ * @file git2/revparse.h
+ * @brief Git revision parsing routines
+ * @defgroup git_revparse Git revision parsing routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Find a single object, as specified by a revision string.
+ *
+ * See `man gitrevisions`, or
+ * http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for
+ * information on the syntax accepted.
+ *
+ * The returned object should be released with `git_object_free` when no
+ * longer needed.
+ *
+ * @param out pointer to output object
+ * @param repo the repository to search in
+ * @param spec the textual specification for an object
+ * @return 0 on success, GIT_ENOTFOUND, GIT_EAMBIGUOUS, GIT_EINVALIDSPEC or an error code
+ */
+GIT_EXTERN(int) git_revparse_single(
+ git_object **out, git_repository *repo, const char *spec);
+
+/**
+ * Find a single object and intermediate reference by a revision string.
+ *
+ * See `man gitrevisions`, or
+ * http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for
+ * information on the syntax accepted.
+ *
+ * In some cases (`@{<-n>}` or `<branchname>@{upstream}`), the expression may
+ * point to an intermediate reference. When such expressions are being passed
+ * in, `reference_out` will be valued as well.
+ *
+ * The returned object should be released with `git_object_free` and the
+ * returned reference with `git_reference_free` when no longer needed.
+ *
+ * @param object_out pointer to output object
+ * @param reference_out pointer to output reference or NULL
+ * @param repo the repository to search in
+ * @param spec the textual specification for an object
+ * @return 0 on success, GIT_ENOTFOUND, GIT_EAMBIGUOUS, GIT_EINVALIDSPEC
+ * or an error code
+ */
+GIT_EXTERN(int) git_revparse_ext(
+ git_object **object_out,
+ git_reference **reference_out,
+ git_repository *repo,
+ const char *spec);
+
+/**
+ * Revparse flags. These indicate the intended behavior of the spec passed to
+ * git_revparse.
+ */
+typedef enum {
+ /** The spec targeted a single object. */
+ GIT_REVPARSE_SINGLE = 1 << 0,
+ /** The spec targeted a range of commits. */
+ GIT_REVPARSE_RANGE = 1 << 1,
+ /** The spec used the '...' operator, which invokes special semantics. */
+ GIT_REVPARSE_MERGE_BASE = 1 << 2,
+} git_revparse_mode_t;
+
+/**
+ * Git Revision Spec: output of a `git_revparse` operation
+ */
+typedef struct {
+ /** The left element of the revspec; must be freed by the user */
+ git_object *from;
+ /** The right element of the revspec; must be freed by the user */
+ git_object *to;
+ /** The intent of the revspec (i.e. `git_revparse_mode_t` flags) */
+ unsigned int flags;
+} git_revspec;
+
+/**
+ * Parse a revision string for `from`, `to`, and intent.
+ *
+ * See `man gitrevisions` or
+ * http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for
+ * information on the syntax accepted.
+ *
+ * @param revspec Pointer to an user-allocated git_revspec struct where
+ * the result of the rev-parse will be stored
+ * @param repo the repository to search in
+ * @param spec the rev-parse spec to parse
+ * @return 0 on success, GIT_INVALIDSPEC, GIT_ENOTFOUND, GIT_EAMBIGUOUS or an error code
+ */
+GIT_EXTERN(int) git_revparse(
+ git_revspec *revspec,
+ git_repository *repo,
+ const char *spec);
+
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/revwalk.h b/include/git2/revwalk.h
index b37a16c83..1bd6186f3 100644
--- a/include/git2/revwalk.h
+++ b/include/git2/revwalk.h
@@ -1,26 +1,8 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_revwalk_h__
#define INCLUDE_git_revwalk_h__
@@ -44,28 +26,28 @@ GIT_BEGIN_DECL
* and subject to change at any time.
* This is the default sorting for new walkers.
*/
-#define GIT_SORT_NONE (0)
+#define GIT_SORT_NONE (0)
/**
* Sort the repository contents in topological order
* (parents before children); this sorting mode
* can be combined with time sorting.
*/
-#define GIT_SORT_TOPOLOGICAL (1 << 0)
+#define GIT_SORT_TOPOLOGICAL (1 << 0)
/**
* Sort the repository contents by commit time;
* this sorting mode can be combined with
* topological sorting.
*/
-#define GIT_SORT_TIME (1 << 1)
+#define GIT_SORT_TIME (1 << 1)
/**
* Iterate through the repository contents in reverse
* order; this sorting mode can be combined with
* any of the above.
*/
-#define GIT_SORT_REVERSE (1 << 2)
+#define GIT_SORT_REVERSE (1 << 2)
/**
* Allocate a new revision walker to iterate through a repo.
@@ -81,11 +63,11 @@ GIT_BEGIN_DECL
* it is possible to have several revision walkers in
* several different threads walking the same repository.
*
- * @param walker pointer to the new revision walker
+ * @param out pointer to the new revision walker
* @param repo the repo to walk through
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_revwalk_new(git_revwalk **walker, git_repository *repo);
+GIT_EXTERN(int) git_revwalk_new(git_revwalk **out, git_repository *repo);
/**
* Reset the revision walker for reuse.
@@ -105,35 +87,109 @@ GIT_EXTERN(void) git_revwalk_reset(git_revwalk *walker);
/**
* Mark a commit to start traversal from.
*
- * The given OID must belong to a commit on the walked
+ * The given OID must belong to a committish on the walked
* repository.
*
* The given commit will be used as one of the roots
* when starting the revision walk. At least one commit
- * must be pushed the repository before a walk can
+ * must be pushed onto the walker before a walk can
* be started.
*
* @param walk the walker being used for the traversal.
- * @param oid the oid of the commit to start from.
- * @return 0 on success; error code otherwise
+ * @param id the oid of the commit to start from.
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_revwalk_push(git_revwalk *walk, const git_oid *oid);
+GIT_EXTERN(int) git_revwalk_push(git_revwalk *walk, const git_oid *id);
+/**
+ * Push matching references
+ *
+ * The OIDs pointed to by the references that match the given glob
+ * pattern will be pushed to the revision walker.
+ *
+ * A leading 'refs/' is implied if not present as well as a trailing
+ * '/\*' if the glob lacks '?', '\*' or '['.
+ *
+ * Any references matching this glob which do not point to a
+ * committish will be ignored.
+ *
+ * @param walk the walker being used for the traversal
+ * @param glob the glob pattern references should match
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_revwalk_push_glob(git_revwalk *walk, const char *glob);
+
+/**
+ * Push the repository's HEAD
+ *
+ * @param walk the walker being used for the traversal
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_revwalk_push_head(git_revwalk *walk);
/**
* Mark a commit (and its ancestors) uninteresting for the output.
*
- * The given OID must belong to a commit on the walked
+ * The given OID must belong to a committish on the walked
* repository.
*
* The resolved commit and all its parents will be hidden from the
* output on the revision walk.
*
* @param walk the walker being used for the traversal.
- * @param oid the oid of commit that will be ignored during the traversal
- * @return 0 on success; error code otherwise
+ * @param commit_id the oid of commit that will be ignored during the traversal
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_revwalk_hide(git_revwalk *walk, const git_oid *commit_id);
+
+/**
+ * Hide matching references.
+ *
+ * The OIDs pointed to by the references that match the given glob
+ * pattern and their ancestors will be hidden from the output on the
+ * revision walk.
+ *
+ * A leading 'refs/' is implied if not present as well as a trailing
+ * '/\*' if the glob lacks '?', '\*' or '['.
+ *
+ * Any references matching this glob which do not point to a
+ * committish will be ignored.
+ *
+ * @param walk the walker being used for the traversal
+ * @param glob the glob pattern references should match
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_revwalk_hide_glob(git_revwalk *walk, const char *glob);
+
+/**
+ * Hide the repository's HEAD
+ *
+ * @param walk the walker being used for the traversal
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_revwalk_hide_head(git_revwalk *walk);
+
+/**
+ * Push the OID pointed to by a reference
+ *
+ * The reference must point to a committish.
+ *
+ * @param walk the walker being used for the traversal
+ * @param refname the reference to push
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_revwalk_hide(git_revwalk *walk, const git_oid *oid);
+GIT_EXTERN(int) git_revwalk_push_ref(git_revwalk *walk, const char *refname);
+
+/**
+ * Hide the OID pointed to by a reference
+ *
+ * The reference must point to a committish.
+ *
+ * @param walk the walker being used for the traversal
+ * @param refname the reference to hide
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_revwalk_hide_ref(git_revwalk *walk, const char *refname);
/**
* Get the next commit from the revision walk.
@@ -148,12 +204,12 @@ GIT_EXTERN(int) git_revwalk_hide(git_revwalk *walk, const git_oid *oid);
*
* The revision walker is reset when the walk is over.
*
- * @param oid Pointer where to store the oid of the next commit
+ * @param out Pointer where to store the oid of the next commit
* @param walk the walker to pop the commit from.
- * @return GIT_SUCCESS if the next commit was found;
- * GIT_EREVWALKOVER if there are no commits left to iterate
+ * @return 0 if the next commit was found;
+ * GIT_ITEROVER if there are no commits left to iterate
*/
-GIT_EXTERN(int) git_revwalk_next(git_oid *oid, git_revwalk *walk);
+GIT_EXTERN(int) git_revwalk_next(git_oid *out, git_revwalk *walk);
/**
* Change the sorting mode when iterating through the
@@ -167,9 +223,32 @@ GIT_EXTERN(int) git_revwalk_next(git_oid *oid, git_revwalk *walk);
GIT_EXTERN(void) git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode);
/**
+ * Push and hide the respective endpoints of the given range.
+ *
+ * The range should be of the form
+ * <commit>..<commit>
+ * where each <commit> is in the form accepted by 'git_revparse_single'.
+ * The left-hand commit will be hidden and the right-hand commit pushed.
+ *
+ * @param walk the walker being used for the traversal
+ * @param range the range
+ * @return 0 or an error code
+ *
+ */
+GIT_EXTERN(int) git_revwalk_push_range(git_revwalk *walk, const char *range);
+
+/**
+ * Simplify the history by first-parent
+ *
+ * No parents other than the first for each commit will be enqueued.
+ */
+GIT_EXTERN(void) git_revwalk_simplify_first_parent(git_revwalk *walk);
+
+
+/**
* Free a revision walker previously allocated.
*
- * @param walk traversal handle to close. If NULL nothing occurs.
+ * @param walk traversal handle to close. If NULL nothing occurs.
*/
GIT_EXTERN(void) git_revwalk_free(git_revwalk *walk);
@@ -182,6 +261,30 @@ GIT_EXTERN(void) git_revwalk_free(git_revwalk *walk);
*/
GIT_EXTERN(git_repository *) git_revwalk_repository(git_revwalk *walk);
+/**
+ * This is a callback function that user can provide to hide a
+ * commit and its parents. If the callback function returns non-zero value,
+ * then this commit and its parents will be hidden.
+ *
+ * @param commit_id oid of Commit
+ * @param payload User-specified pointer to data to be passed as data payload
+ */
+typedef int(*git_revwalk_hide_cb)(
+ const git_oid *commit_id,
+ void *payload);
+
+/**
+ * Adds a callback function to hide a commit and its parents
+ *
+ * @param walk the revision walker
+ * @param hide_cb callback function to hide a commit and its parents
+ * @param payload data payload to be passed to callback function
+ */
+GIT_EXTERN(int) git_revwalk_add_hide_cb(
+ git_revwalk *walk,
+ git_revwalk_hide_cb hide_cb,
+ void *payload);
+
/** @} */
GIT_END_DECL
#endif
diff --git a/include/git2/signature.h b/include/git2/signature.h
index f5d03ac77..feb1b4073 100644
--- a/include/git2/signature.h
+++ b/include/git2/signature.h
@@ -1,26 +1,8 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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_signature_h__
#define INCLUDE_git_signature_h__
@@ -38,41 +20,66 @@
GIT_BEGIN_DECL
/**
- * Create a new action signature. The signature must be freed
- * manually or using git_signature_free
+ * Create a new action signature.
+ *
+ * Call `git_signature_free()` to free the data.
*
- * @param sig_out new signature, in case of error NULL
+ * Note: angle brackets ('<' and '>') characters are not allowed
+ * to be used in either the `name` or the `email` parameter.
+ *
+ * @param out new signature, in case of error NULL
* @param name name of the person
* @param email email of the person
* @param time time when the action happened
* @param offset timezone offset in minutes for the time
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_signature_new(git_signature **sig_out, const char *name, const char *email, git_time_t time, int offset);
+GIT_EXTERN(int) git_signature_new(git_signature **out, const char *name, const char *email, git_time_t time, int offset);
/**
- * Create a new action signature with a timestamp of 'now'. The
- * signature must be freed manually or using git_signature_free
+ * Create a new action signature with a timestamp of 'now'.
+ *
+ * Call `git_signature_free()` to free the data.
*
- * @param sig_out new signature, in case of error NULL
+ * @param out new signature, in case of error NULL
* @param name name of the person
* @param email email of the person
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_signature_now(git_signature **sig_out, const char *name, const char *email);
+GIT_EXTERN(int) git_signature_now(git_signature **out, const char *name, const char *email);
+/**
+ * Create a new action signature with default user and now timestamp.
+ *
+ * This looks up the user.name and user.email from the configuration and
+ * uses the current time as the timestamp, and creates a new signature
+ * based on that information. It will return GIT_ENOTFOUND if either the
+ * user.name or user.email are not set.
+ *
+ * @param out new signature
+ * @param repo repository pointer
+ * @return 0 on success, GIT_ENOTFOUND if config is missing, or error code
+ */
+GIT_EXTERN(int) git_signature_default(git_signature **out, git_repository *repo);
/**
- * Create a copy of an existing signature.
+ * Create a copy of an existing signature. All internal strings are also
+ * duplicated.
*
- * All internal strings are also duplicated.
- * @param sig signature to duplicated
- * @return a copy of sig, NULL on out of memory
+ * Call `git_signature_free()` to free the data.
+ *
+ * @param dest pointer where to store the copy
+ * @param sig signature to duplicate
+ * @return 0 or an error code
*/
-GIT_EXTERN(git_signature *) git_signature_dup(const git_signature *sig);
+GIT_EXTERN(int) git_signature_dup(git_signature **dest, const git_signature *sig);
/**
- * Free an existing signature
+ * Free an existing signature.
+ *
+ * Because the signature is not an opaque structure, it is legal to free it
+ * manually, but be sure to free the "name" and "email" strings in addition
+ * to the structure itself.
*
* @param sig signature to free
*/
diff --git a/include/git2/stash.h b/include/git2/stash.h
new file mode 100644
index 000000000..e2fe2cf0b
--- /dev/null
+++ b/include/git2/stash.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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
+
+typedef enum {
+ GIT_STASH_DEFAULT = 0,
+
+ /* All changes already added to the index
+ * are left intact in the working directory
+ */
+ GIT_STASH_KEEP_INDEX = (1 << 0),
+
+ /* All untracked files are also stashed and then
+ * cleaned up from the working directory
+ */
+ GIT_STASH_INCLUDE_UNTRACKED = (1 << 1),
+
+ /* All ignored files are also stashed and then
+ * cleaned up from the working directory
+ */
+ GIT_STASH_INCLUDE_IGNORED = (1 << 2),
+} git_stash_flags;
+
+/**
+ * Save the local modifications to a new stash.
+ *
+ * @param out Object id of the commit containing the stashed state.
+ * This commit is also the target of the direct reference refs/stash.
+ *
+ * @param repo The owning repository.
+ *
+ * @param stasher The identity of the person performing the stashing.
+ *
+ * @param message Optional description along with the stashed state.
+ *
+ * @param flags Flags to control the stashing process. (see GIT_STASH_* above)
+ *
+ * @return 0 on success, GIT_ENOTFOUND where there's nothing to stash,
+ * or error code.
+ */
+GIT_EXTERN(int) git_stash_save(
+ git_oid *out,
+ git_repository *repo,
+ const git_signature *stasher,
+ const char *message,
+ unsigned int flags);
+
+/**
+ * This is a callback function you can provide to iterate over all the
+ * stashed states that will be invoked per entry.
+ *
+ * @param index The position within the stash list. 0 points to the
+ * most recent stashed state.
+ * @param message The stash message.
+ * @param stash_id The commit oid of the stashed state.
+ * @param payload Extra parameter to callback function.
+ * @return 0 to continue iterating or non-zero to stop
+ */
+typedef int (*git_stash_cb)(
+ size_t index,
+ const char* message,
+ const git_oid *stash_id,
+ void *payload);
+
+/**
+ * Loop over all the stashed states and issue a callback for each one.
+ *
+ * If the callback returns a non-zero value, this will stop looping.
+ *
+ * @param repo Repository where to find the stash.
+ *
+ * @param callback 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, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_stash_foreach(
+ git_repository *repo,
+ git_stash_cb callback,
+ void *payload);
+
+/**
+ * Remove a single stashed state from the stash list.
+ *
+ * @param repo The owning repository.
+ *
+ * @param index The position within the stash list. 0 points to the
+ * most recent stashed state.
+ *
+ * @return 0 on success, or error code
+ */
+
+GIT_EXTERN(int) git_stash_drop(
+ git_repository *repo,
+ size_t index);
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/status.h b/include/git2/status.h
index 7946cc1f3..effe5e1ea 100644
--- a/include/git2/status.h
+++ b/include/git2/status.h
@@ -1,26 +1,8 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_status_h__
#define INCLUDE_git_status_h__
@@ -37,42 +19,346 @@
*/
GIT_BEGIN_DECL
-#define GIT_STATUS_CURRENT 0
-/** Flags for index status */
-#define GIT_STATUS_INDEX_NEW (1 << 0)
-#define GIT_STATUS_INDEX_MODIFIED (1 << 1)
-#define GIT_STATUS_INDEX_DELETED (1 << 2)
+/**
+ * Status flags for a single file.
+ *
+ * A combination of these values will be returned to indicate the status of
+ * a file. Status compares the working directory, the index, and the
+ * current HEAD of the repository. The `GIT_STATUS_INDEX` set of flags
+ * represents the status of file in the index relative to the HEAD, and the
+ * `GIT_STATUS_WT` set of flags represent the status of the file in the
+ * working directory relative to the index.
+ */
+typedef enum {
+ GIT_STATUS_CURRENT = 0,
+
+ GIT_STATUS_INDEX_NEW = (1u << 0),
+ GIT_STATUS_INDEX_MODIFIED = (1u << 1),
+ GIT_STATUS_INDEX_DELETED = (1u << 2),
+ GIT_STATUS_INDEX_RENAMED = (1u << 3),
+ GIT_STATUS_INDEX_TYPECHANGE = (1u << 4),
+
+ GIT_STATUS_WT_NEW = (1u << 7),
+ GIT_STATUS_WT_MODIFIED = (1u << 8),
+ GIT_STATUS_WT_DELETED = (1u << 9),
+ GIT_STATUS_WT_TYPECHANGE = (1u << 10),
+ GIT_STATUS_WT_RENAMED = (1u << 11),
+
+ GIT_STATUS_IGNORED = (1u << 14),
+} git_status_t;
+
+/**
+ * Function pointer to receive status on individual files
+ *
+ * `path` is the relative path to the file from the root of the repository.
+ *
+ * `status_flags` is a combination of `git_status_t` values that apply.
+ *
+ * `payload` is the value you passed to the foreach function as payload.
+ */
+typedef int (*git_status_cb)(
+ const char *path, unsigned int status_flags, void *payload);
+
+/**
+ * Select the files on which to report status.
+ *
+ * With `git_status_foreach_ext`, this will control which changes get
+ * callbacks. With `git_status_list_new`, these will control which
+ * changes are included in the list.
+ *
+ * - GIT_STATUS_SHOW_INDEX_AND_WORKDIR is the default. This roughly
+ * matches `git status --porcelain` regarding which files are
+ * included and in what order.
+ * - GIT_STATUS_SHOW_INDEX_ONLY only gives status based on HEAD to index
+ * comparison, not looking at working directory changes.
+ * - GIT_STATUS_SHOW_WORKDIR_ONLY only gives status based on index to
+ * working directory comparison, not comparing the index to the HEAD.
+ */
+typedef enum {
+ GIT_STATUS_SHOW_INDEX_AND_WORKDIR = 0,
+ GIT_STATUS_SHOW_INDEX_ONLY = 1,
+ GIT_STATUS_SHOW_WORKDIR_ONLY = 2,
+} git_status_show_t;
+
+/**
+ * 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 get callbacks.
+ * Again, these callbacks will only be made if the workdir files are
+ * included in the status "show" option.
+ * - GIT_STATUS_OPT_INCLUDE_UNMODIFIED indicates that callback should be
+ * made even on unmodified files.
+ * - GIT_STATUS_OPT_EXCLUDE_SUBMODULES indicates that submodules should be
+ * skipped. This only applies if there are no pending typechanges to
+ * the submodule (either from or to another type).
+ * - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS indicates that all files in
+ * untracked directories should be included. Normally if an entire
+ * directory is new, then just the top-level directory is included (with
+ * a trailing slash on the entry name). This flag says to include all
+ * of the individual files in the directory instead.
+ * - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH indicates that the given path
+ * should be treated as a literal path, and not as a pathspec pattern.
+ * - GIT_STATUS_OPT_RECURSE_IGNORED_DIRS indicates that the contents of
+ * ignored directories should be included in the status. This is like
+ * doing `git ls-files -o -i --exclude-standard` with core git.
+ * - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX indicates that rename detection
+ * should be processed between the head and the index and enables
+ * the GIT_STATUS_INDEX_RENAMED as a possible status flag.
+ * - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR indicates that rename
+ * detection should be run between the index and the working directory
+ * and enabled GIT_STATUS_WT_RENAMED as a possible status flag.
+ * - GIT_STATUS_OPT_SORT_CASE_SENSITIVELY overrides the native case
+ * sensitivity for the file system and forces the output to be in
+ * case-sensitive order
+ * - GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY overrides the native case
+ * sensitivity for the file system and forces the output to be in
+ * case-insensitive order
+ * - GIT_STATUS_OPT_RENAMES_FROM_REWRITES indicates that rename detection
+ * should include rewritten files
+ * - GIT_STATUS_OPT_NO_REFRESH bypasses the default status behavior of
+ * doing a "soft" index reload (i.e. reloading the index data if the
+ * file on disk has been modified outside libgit2).
+ * - GIT_STATUS_OPT_UPDATE_INDEX tells libgit2 to refresh the stat cache
+ * in the index for files that are unchanged but have out of date stat
+ * information in the index. It will result in less work being done on
+ * subsequent calls to get status. This is mutually exclusive with the
+ * NO_REFRESH option.
+ *
+ * Calling `git_status_foreach()` is like calling the extended version
+ * with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED,
+ * and GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS. Those options are bundled
+ * together as `GIT_STATUS_OPT_DEFAULTS` if you want them as a baseline.
+ */
+typedef enum {
+ GIT_STATUS_OPT_INCLUDE_UNTRACKED = (1u << 0),
+ GIT_STATUS_OPT_INCLUDE_IGNORED = (1u << 1),
+ GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1u << 2),
+ GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1u << 3),
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1u << 4),
+ GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH = (1u << 5),
+ GIT_STATUS_OPT_RECURSE_IGNORED_DIRS = (1u << 6),
+ GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX = (1u << 7),
+ GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR = (1u << 8),
+ GIT_STATUS_OPT_SORT_CASE_SENSITIVELY = (1u << 9),
+ GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY = (1u << 10),
+ GIT_STATUS_OPT_RENAMES_FROM_REWRITES = (1u << 11),
+ GIT_STATUS_OPT_NO_REFRESH = (1u << 12),
+ GIT_STATUS_OPT_UPDATE_INDEX = (1u << 13),
+} git_status_opt_t;
+
+#define GIT_STATUS_OPT_DEFAULTS \
+ (GIT_STATUS_OPT_INCLUDE_IGNORED | \
+ GIT_STATUS_OPT_INCLUDE_UNTRACKED | \
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS)
-/** Flags for worktree status */
-#define GIT_STATUS_WT_NEW (1 << 3)
-#define GIT_STATUS_WT_MODIFIED (1 << 4)
-#define GIT_STATUS_WT_DELETED (1 << 5)
+/**
+ * Options to control how `git_status_foreach_ext()` will issue callbacks.
+ *
+ * This structure is set so that zeroing it out will give you relatively
+ * sane defaults.
+ *
+ * The `show` value is one of the `git_status_show_t` constants that
+ * control which files to scan and in what order.
+ *
+ * The `flags` value is an OR'ed combination of the `git_status_opt_t`
+ * values above.
+ *
+ * The `pathspec` is an array of path patterns to match (using
+ * fnmatch-style matching), or just an array of paths to match exactly if
+ * `GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH` is specified in the flags.
+ */
+typedef struct {
+ unsigned int version;
+ git_status_show_t show;
+ unsigned int flags;
+ git_strarray pathspec;
+} git_status_options;
+
+#define GIT_STATUS_OPTIONS_VERSION 1
+#define GIT_STATUS_OPTIONS_INIT {GIT_STATUS_OPTIONS_VERSION}
+
+/**
+ * Initializes a `git_status_options` with default values. Equivalent to
+ * creating an instance with GIT_STATUS_OPTIONS_INIT.
+ *
+ * @param opts The `git_status_options` instance to initialize.
+ * @param version Version of struct; pass `GIT_STATUS_OPTIONS_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_status_init_options(
+ git_status_options *opts,
+ unsigned int version);
+
+/**
+ * A status entry, providing the differences between the file as it exists
+ * in HEAD and the index, and providing the differences between the index
+ * and the working directory.
+ *
+ * The `status` value provides the status flags for this file.
+ *
+ * The `head_to_index` value provides detailed information about the
+ * differences between the file in HEAD and the file in the index.
+ *
+ * The `index_to_workdir` value provides detailed information about the
+ * differences between the file in the index and the file in the
+ * working directory.
+ */
+typedef struct {
+ git_status_t status;
+ git_diff_delta *head_to_index;
+ git_diff_delta *index_to_workdir;
+} git_status_entry;
-// TODO Ignored files not handled yet
-#define GIT_STATUS_IGNORED (1 << 6)
/**
* 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
- * GIT_SUCCESS, this function will return that value.
+ * The callback is passed the path of the file, the status (a combination of
+ * the `git_status_t` values above) and the `payload` data pointer passed
+ * into this function.
+ *
+ * If the callback returns a non-zero value, this function will stop looping
+ * and return that value to caller.
+ *
+ * @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, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_status_foreach(
+ git_repository *repo,
+ git_status_cb callback,
+ void *payload);
+
+/**
+ * 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.
+ *
+ * Note that if a `pathspec` is given in the `git_status_options` to filter
+ * the status, then the results from rename detection (if you enable it) may
+ * not be accurate. To do rename detection properly, this must be called
+ * with no `pathspec` so that all files can be considered.
+ *
+ * @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, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_status_foreach_ext(
+ git_repository *repo,
+ const git_status_options *opts,
+ git_status_cb callback,
+ void *payload);
+
+/**
+ * Get file status for a single file.
+ *
+ * This tries to get status for the filename that you give. If no files
+ * match that name (in either the HEAD, index, or working directory), this
+ * returns GIT_ENOTFOUND.
+ *
+ * If the name matches multiple files (for example, if the `path` names a
+ * directory or if running on a case- insensitive filesystem and yet the
+ * HEAD has two entries that both match the path), then this returns
+ * GIT_EAMBIGUOUS because it cannot give correct results.
+ *
+ * This does not do any sort of rename detection. Renames require a set of
+ * targets and because of the path filtering, there is not enough
+ * information to check renames correctly. To check file status with rename
+ * detection, there is no choice but to do a full `git_status_list_new` and
+ * scan through looking for the path that you are interested in.
+ *
+ * @param status_flags Output combination of git_status_t values for file
+ * @param repo A repository object
+ * @param path The file to retrieve status for relative to the repo workdir
+ * @return 0 on success, GIT_ENOTFOUND if the file is not found in the HEAD,
+ * index, and work tree, GIT_EAMBIGUOUS if `path` matches multiple files
+ * or if it refers to a folder, and -1 on other errors.
+ */
+GIT_EXTERN(int) git_status_file(
+ unsigned int *status_flags,
+ git_repository *repo,
+ const char *path);
+
+/**
+ * Gather file status information and populate the `git_status_list`.
+ *
+ * Note that if a `pathspec` is given in the `git_status_options` to filter
+ * the status, then the results from rename detection (if you enable it) may
+ * not be accurate. To do rename detection properly, this must be called
+ * with no `pathspec` so that all files can be considered.
+ *
+ * @param out Pointer to store the status results in
+ * @param repo Repository object
+ * @param opts Status options structure
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_status_list_new(
+ git_status_list **out,
+ git_repository *repo,
+ const git_status_options *opts);
+
+/**
+ * Gets the count of status entries in this list.
+ *
+ * If there are no changes in status (at least according the options given
+ * when the status list was created), this can return 0.
*
- * @param repo a repository object
- * @param callback the function to call on each file
- * @return GIT_SUCCESS or the return value of the callback which did not return 0;
+ * @param statuslist Existing status list object
+ * @return the number of status entries
*/
-GIT_EXTERN(int) git_status_foreach(git_repository *repo, int (*callback)(const char *, unsigned int, void *), void *payload);
+GIT_EXTERN(size_t) git_status_list_entrycount(
+ git_status_list *statuslist);
/**
- * Get file status for a single file
+ * Get a pointer to one of the entries in the status list.
+ *
+ * The entry is not modifiable and should not be freed.
+ *
+ * @param statuslist Existing status list object
+ * @param idx Position of the entry
+ * @return Pointer to the entry; NULL if out of bounds
+ */
+GIT_EXTERN(const git_status_entry *) git_status_byindex(
+ git_status_list *statuslist,
+ size_t idx);
+
+/**
+ * Free an existing status list
+ *
+ * @param statuslist Existing status list object
+ */
+GIT_EXTERN(void) git_status_list_free(
+ git_status_list *statuslist);
+
+/**
+ * Test if the ignore rules apply to a given file.
+ *
+ * This function checks the ignore rules to see if they would apply to the
+ * given file. This indicates if the file would be ignored regardless of
+ * whether the file is already in the index or committed to the repository.
+ *
+ * One way to think of this is if you were to do "git add ." on the
+ * directory containing the file, would it be added or not?
*
- * @param 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_SUCCESS
+ * @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.
*/
-GIT_EXTERN(int) git_status_file(unsigned int *status_flags, git_repository *repo, const char *path);
+GIT_EXTERN(int) git_status_should_ignore(
+ int *ignored,
+ git_repository *repo,
+ const char *path);
/** @} */
GIT_END_DECL
diff --git a/include/git2/stdint.h b/include/git2/stdint.h
new file mode 100644
index 000000000..c66fbb817
--- /dev/null
+++ b/include/git2/stdint.h
@@ -0,0 +1,247 @@
+// ISO C9x compliant stdint.h for Microsoft Visual Studio
+// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124
+//
+// Copyright (c) 2006-2008 Alexander Chemeris
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. The name of the author may be used to endorse or promote products
+// derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef _MSC_VER // [
+#error "Use this header only with Microsoft Visual C++ compilers!"
+#endif // _MSC_VER ]
+
+#ifndef _MSC_STDINT_H_ // [
+#define _MSC_STDINT_H_
+
+#if _MSC_VER > 1000
+#pragma once
+#endif
+
+#include <limits.h>
+
+// For Visual Studio 6 in C++ mode and for many Visual Studio versions when
+// compiling for ARM we should wrap <wchar.h> include with 'extern "C++" {}'
+// or compiler give many errors like this:
+// error C2733: second C linkage of overloaded function 'wmemchr' not allowed
+#ifdef __cplusplus
+extern "C" {
+#endif
+# include <wchar.h>
+#ifdef __cplusplus
+}
+#endif
+
+// Define _W64 macros to mark types changing their size, like intptr_t.
+#ifndef _W64
+# if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300
+# define _W64 __w64
+# else
+# define _W64
+# endif
+#endif
+
+
+// 7.18.1 Integer types
+
+// 7.18.1.1 Exact-width integer types
+
+// Visual Studio 6 and Embedded Visual C++ 4 doesn't
+// realize that, e.g. char has the same size as __int8
+// so we give up on __intX for them.
+#if (_MSC_VER < 1300)
+ typedef signed char int8_t;
+ typedef signed short int16_t;
+ typedef signed int int32_t;
+ typedef unsigned char uint8_t;
+ typedef unsigned short uint16_t;
+ typedef unsigned int uint32_t;
+#else
+ typedef signed __int8 int8_t;
+ typedef signed __int16 int16_t;
+ typedef signed __int32 int32_t;
+ typedef unsigned __int8 uint8_t;
+ typedef unsigned __int16 uint16_t;
+ typedef unsigned __int32 uint32_t;
+#endif
+typedef signed __int64 int64_t;
+typedef unsigned __int64 uint64_t;
+
+
+// 7.18.1.2 Minimum-width integer types
+typedef int8_t int_least8_t;
+typedef int16_t int_least16_t;
+typedef int32_t int_least32_t;
+typedef int64_t int_least64_t;
+typedef uint8_t uint_least8_t;
+typedef uint16_t uint_least16_t;
+typedef uint32_t uint_least32_t;
+typedef uint64_t uint_least64_t;
+
+// 7.18.1.3 Fastest minimum-width integer types
+typedef int8_t int_fast8_t;
+typedef int16_t int_fast16_t;
+typedef int32_t int_fast32_t;
+typedef int64_t int_fast64_t;
+typedef uint8_t uint_fast8_t;
+typedef uint16_t uint_fast16_t;
+typedef uint32_t uint_fast32_t;
+typedef uint64_t uint_fast64_t;
+
+// 7.18.1.4 Integer types capable of holding object pointers
+#ifdef _WIN64 // [
+ typedef signed __int64 intptr_t;
+ typedef unsigned __int64 uintptr_t;
+#else // _WIN64 ][
+ typedef _W64 signed int intptr_t;
+ typedef _W64 unsigned int uintptr_t;
+#endif // _WIN64 ]
+
+// 7.18.1.5 Greatest-width integer types
+typedef int64_t intmax_t;
+typedef uint64_t uintmax_t;
+
+
+// 7.18.2 Limits of specified-width integer types
+
+#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259
+
+// 7.18.2.1 Limits of exact-width integer types
+#define INT8_MIN ((int8_t)_I8_MIN)
+#define INT8_MAX _I8_MAX
+#define INT16_MIN ((int16_t)_I16_MIN)
+#define INT16_MAX _I16_MAX
+#define INT32_MIN ((int32_t)_I32_MIN)
+#define INT32_MAX _I32_MAX
+#define INT64_MIN ((int64_t)_I64_MIN)
+#define INT64_MAX _I64_MAX
+#define UINT8_MAX _UI8_MAX
+#define UINT16_MAX _UI16_MAX
+#define UINT32_MAX _UI32_MAX
+#define UINT64_MAX _UI64_MAX
+
+// 7.18.2.2 Limits of minimum-width integer types
+#define INT_LEAST8_MIN INT8_MIN
+#define INT_LEAST8_MAX INT8_MAX
+#define INT_LEAST16_MIN INT16_MIN
+#define INT_LEAST16_MAX INT16_MAX
+#define INT_LEAST32_MIN INT32_MIN
+#define INT_LEAST32_MAX INT32_MAX
+#define INT_LEAST64_MIN INT64_MIN
+#define INT_LEAST64_MAX INT64_MAX
+#define UINT_LEAST8_MAX UINT8_MAX
+#define UINT_LEAST16_MAX UINT16_MAX
+#define UINT_LEAST32_MAX UINT32_MAX
+#define UINT_LEAST64_MAX UINT64_MAX
+
+// 7.18.2.3 Limits of fastest minimum-width integer types
+#define INT_FAST8_MIN INT8_MIN
+#define INT_FAST8_MAX INT8_MAX
+#define INT_FAST16_MIN INT16_MIN
+#define INT_FAST16_MAX INT16_MAX
+#define INT_FAST32_MIN INT32_MIN
+#define INT_FAST32_MAX INT32_MAX
+#define INT_FAST64_MIN INT64_MIN
+#define INT_FAST64_MAX INT64_MAX
+#define UINT_FAST8_MAX UINT8_MAX
+#define UINT_FAST16_MAX UINT16_MAX
+#define UINT_FAST32_MAX UINT32_MAX
+#define UINT_FAST64_MAX UINT64_MAX
+
+// 7.18.2.4 Limits of integer types capable of holding object pointers
+#ifdef _WIN64 // [
+# define INTPTR_MIN INT64_MIN
+# define INTPTR_MAX INT64_MAX
+# define UINTPTR_MAX UINT64_MAX
+#else // _WIN64 ][
+# define INTPTR_MIN INT32_MIN
+# define INTPTR_MAX INT32_MAX
+# define UINTPTR_MAX UINT32_MAX
+#endif // _WIN64 ]
+
+// 7.18.2.5 Limits of greatest-width integer types
+#define INTMAX_MIN INT64_MIN
+#define INTMAX_MAX INT64_MAX
+#define UINTMAX_MAX UINT64_MAX
+
+// 7.18.3 Limits of other integer types
+
+#ifdef _WIN64 // [
+# define PTRDIFF_MIN _I64_MIN
+# define PTRDIFF_MAX _I64_MAX
+#else // _WIN64 ][
+# define PTRDIFF_MIN _I32_MIN
+# define PTRDIFF_MAX _I32_MAX
+#endif // _WIN64 ]
+
+#define SIG_ATOMIC_MIN INT_MIN
+#define SIG_ATOMIC_MAX INT_MAX
+
+#ifndef SIZE_MAX // [
+# ifdef _WIN64 // [
+# define SIZE_MAX _UI64_MAX
+# else // _WIN64 ][
+# define SIZE_MAX _UI32_MAX
+# endif // _WIN64 ]
+#endif // SIZE_MAX ]
+
+// WCHAR_MIN and WCHAR_MAX are also defined in <wchar.h>
+#ifndef WCHAR_MIN // [
+# define WCHAR_MIN 0
+#endif // WCHAR_MIN ]
+#ifndef WCHAR_MAX // [
+# define WCHAR_MAX _UI16_MAX
+#endif // WCHAR_MAX ]
+
+#define WINT_MIN 0
+#define WINT_MAX _UI16_MAX
+
+#endif // __STDC_LIMIT_MACROS ]
+
+
+// 7.18.4 Limits of other integer types
+
+#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260
+
+// 7.18.4.1 Macros for minimum-width integer constants
+
+#define INT8_C(val) val##i8
+#define INT16_C(val) val##i16
+#define INT32_C(val) val##i32
+#define INT64_C(val) val##i64
+
+#define UINT8_C(val) val##ui8
+#define UINT16_C(val) val##ui16
+#define UINT32_C(val) val##ui32
+#define UINT64_C(val) val##ui64
+
+// 7.18.4.2 Macros for greatest-width integer constants
+#define INTMAX_C INT64_C
+#define UINTMAX_C UINT64_C
+
+#endif // __STDC_CONSTANT_MACROS ]
+
+
+#endif // _MSC_STDINT_H_ ]
diff --git a/include/git2/strarray.h b/include/git2/strarray.h
new file mode 100644
index 000000000..86fa25f3f
--- /dev/null
+++ b/include/git2/strarray.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_strarray_h__
+#define INCLUDE_git_strarray_h__
+
+#include "common.h"
+
+/**
+ * @file git2/strarray.h
+ * @brief Git string array routines
+ * @defgroup git_strarray Git string array routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/** Array of strings */
+typedef struct git_strarray {
+ char **strings;
+ size_t count;
+} git_strarray;
+
+/**
+ * Close a string array object
+ *
+ * This method should be called on `git_strarray` objects where the strings
+ * array is allocated and contains allocated strings, such as what you
+ * would get from `git_strarray_copy()`. Not doing so, will result in a
+ * memory leak.
+ *
+ * This does not free the `git_strarray` itself, since the library will
+ * never allocate that object directly itself (it is more commonly embedded
+ * inside another struct or created on the stack).
+ *
+ * @param array git_strarray from which to free string data
+ */
+GIT_EXTERN(void) git_strarray_free(git_strarray *array);
+
+/**
+ * Copy a string array object from source to target.
+ *
+ * Note: target is overwritten and hence should be empty, otherwise its
+ * contents are leaked. Call git_strarray_free() if necessary.
+ *
+ * @param tgt target
+ * @param src source
+ * @return 0 on success, < 0 on allocation failure
+ */
+GIT_EXTERN(int) git_strarray_copy(git_strarray *tgt, const git_strarray *src);
+
+
+/** @} */
+GIT_END_DECL
+
+#endif
+
diff --git a/include/git2/submodule.h b/include/git2/submodule.h
new file mode 100644
index 000000000..864d1c58c
--- /dev/null
+++ b/include/git2/submodule.h
@@ -0,0 +1,559 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_submodule_h__
+#define INCLUDE_git_submodule_h__
+
+#include "common.h"
+#include "types.h"
+#include "oid.h"
+
+/**
+ * @file git2/submodule.h
+ * @brief Git submodule management utilities
+ *
+ * Submodule support in libgit2 builds a list of known submodules and keeps
+ * it in the repository. The list is built from the .gitmodules file, the
+ * .git/config file, the index, and the HEAD tree. Items in the working
+ * directory that look like submodules (i.e. a git repo) but are not
+ * mentioned in those places won't be tracked.
+ *
+ * @defgroup git_submodule Git submodule management routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Return codes for submodule status.
+ *
+ * A combination of these flags will be returned to describe the status of a
+ * submodule. Depending on the "ignore" property of the submodule, some of
+ * the flags may never be returned because they indicate changes that are
+ * supposed to be ignored.
+ *
+ * Submodule info is contained in 4 places: the HEAD tree, the index, config
+ * files (both .git/config and .gitmodules), and the working directory. Any
+ * or all of those places might be missing information about the submodule
+ * depending on what state the repo is in. We consider all four places to
+ * build the combination of status flags.
+ *
+ * There are four values that are not really status, but give basic info
+ * about what sources of submodule data are available. These will be
+ * returned even if ignore is set to "ALL".
+ *
+ * * IN_HEAD - superproject head contains submodule
+ * * IN_INDEX - superproject index contains submodule
+ * * IN_CONFIG - superproject gitmodules has submodule
+ * * IN_WD - superproject workdir has submodule
+ *
+ * The following values will be returned so long as ignore is not "ALL".
+ *
+ * * INDEX_ADDED - in index, not in head
+ * * INDEX_DELETED - in head, not in index
+ * * INDEX_MODIFIED - index and head don't match
+ * * WD_UNINITIALIZED - workdir contains empty directory
+ * * WD_ADDED - in workdir, not index
+ * * WD_DELETED - in index, not workdir
+ * * WD_MODIFIED - index and workdir head don't match
+ *
+ * The following can only be returned if ignore is "NONE" or "UNTRACKED".
+ *
+ * * WD_INDEX_MODIFIED - submodule workdir index is dirty
+ * * WD_WD_MODIFIED - submodule workdir has modified files
+ *
+ * Lastly, the following will only be returned for ignore "NONE".
+ *
+ * * WD_UNTRACKED - wd contains untracked files
+ */
+typedef enum {
+ GIT_SUBMODULE_STATUS_IN_HEAD = (1u << 0),
+ GIT_SUBMODULE_STATUS_IN_INDEX = (1u << 1),
+ GIT_SUBMODULE_STATUS_IN_CONFIG = (1u << 2),
+ GIT_SUBMODULE_STATUS_IN_WD = (1u << 3),
+ GIT_SUBMODULE_STATUS_INDEX_ADDED = (1u << 4),
+ GIT_SUBMODULE_STATUS_INDEX_DELETED = (1u << 5),
+ GIT_SUBMODULE_STATUS_INDEX_MODIFIED = (1u << 6),
+ GIT_SUBMODULE_STATUS_WD_UNINITIALIZED = (1u << 7),
+ GIT_SUBMODULE_STATUS_WD_ADDED = (1u << 8),
+ GIT_SUBMODULE_STATUS_WD_DELETED = (1u << 9),
+ GIT_SUBMODULE_STATUS_WD_MODIFIED = (1u << 10),
+ GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED = (1u << 11),
+ GIT_SUBMODULE_STATUS_WD_WD_MODIFIED = (1u << 12),
+ GIT_SUBMODULE_STATUS_WD_UNTRACKED = (1u << 13),
+} git_submodule_status_t;
+
+#define GIT_SUBMODULE_STATUS__IN_FLAGS 0x000Fu
+#define GIT_SUBMODULE_STATUS__INDEX_FLAGS 0x0070u
+#define GIT_SUBMODULE_STATUS__WD_FLAGS 0x3F80u
+
+#define GIT_SUBMODULE_STATUS_IS_UNMODIFIED(S) \
+ (((S) & ~GIT_SUBMODULE_STATUS__IN_FLAGS) == 0)
+
+#define GIT_SUBMODULE_STATUS_IS_INDEX_UNMODIFIED(S) \
+ (((S) & GIT_SUBMODULE_STATUS__INDEX_FLAGS) == 0)
+
+#define GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(S) \
+ (((S) & (GIT_SUBMODULE_STATUS__WD_FLAGS & \
+ ~GIT_SUBMODULE_STATUS_WD_UNINITIALIZED)) == 0)
+
+#define GIT_SUBMODULE_STATUS_IS_WD_DIRTY(S) \
+ (((S) & (GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED | \
+ GIT_SUBMODULE_STATUS_WD_WD_MODIFIED | \
+ GIT_SUBMODULE_STATUS_WD_UNTRACKED)) != 0)
+
+/**
+ * Lookup submodule information by name or path.
+ *
+ * Given either the submodule name or path (they are usually the same), this
+ * returns a structure describing the submodule.
+ *
+ * There are two expected error scenarios:
+ *
+ * - The submodule is not mentioned in the HEAD, the index, and the config,
+ * but does "exist" in the working directory (i.e. there is a subdirectory
+ * that appears to be a Git repository). In this case, this function
+ * returns GIT_EEXISTS to indicate a sub-repository exists but not in a
+ * state where a git_submodule can be instantiated.
+ * - The submodule is not mentioned in the HEAD, index, or config and the
+ * working directory doesn't contain a value git repo at that path.
+ * There may or may not be anything else at that path, but nothing that
+ * looks like a submodule. In this case, this returns GIT_ENOTFOUND.
+ *
+ * You must call `git_submodule_free` when done with the submodule.
+ *
+ * @param out Output ptr to submodule; pass NULL to just get return code
+ * @param repo The parent repository
+ * @param name The name of or path to the submodule; trailing slashes okay
+ * @return 0 on success, GIT_ENOTFOUND if submodule does not exist,
+ * GIT_EEXISTS if a repository is found in working directory only,
+ * -1 on other errors.
+ */
+GIT_EXTERN(int) git_submodule_lookup(
+ git_submodule **out,
+ git_repository *repo,
+ const char *name);
+
+/**
+ * Release a submodule
+ *
+ * @param submodule Submodule object
+ */
+GIT_EXTERN(void) git_submodule_free(git_submodule *submodule);
+
+/**
+ * Iterate over all tracked submodules of a repository.
+ *
+ * See the note on `git_submodule` above. This iterates over the tracked
+ * submodules as decribed therein.
+ *
+ * If you are concerned about items in the working directory that look like
+ * submodules but are not tracked, the diff API will generate a diff record
+ * for workdir items that look like submodules but are not tracked, showing
+ * them as added in the workdir. Also, the status API will treat the entire
+ * subdirectory of a contained git repo as a single GIT_STATUS_WT_NEW item.
+ *
+ * @param repo The repository
+ * @param callback Function to be called with the name of each submodule.
+ * Return a non-zero value to terminate the iteration.
+ * @param payload Extra data to pass to callback
+ * @return 0 on success, -1 on error, or non-zero return value of callback
+ */
+GIT_EXTERN(int) git_submodule_foreach(
+ git_repository *repo,
+ int (*callback)(git_submodule *sm, const char *name, void *payload),
+ void *payload);
+
+/**
+ * Set up a new git submodule for checkout.
+ *
+ * This does "git submodule add" up to the fetch and checkout of the
+ * submodule contents. It preps a new submodule, creates an entry in
+ * .gitmodules and creates an empty initialized repository either at the
+ * given path in the working directory or in .git/modules with a gitlink
+ * from the working directory to the new repo.
+ *
+ * To fully emulate "git submodule add" call this function, then open the
+ * submodule repo and perform the clone step as needed. Lastly, call
+ * `git_submodule_add_finalize()` to wrap up adding the new submodule and
+ * .gitmodules to the index to be ready to commit.
+ *
+ * You must call `git_submodule_free` on the submodule object when done.
+ *
+ * @param out The newly created submodule ready to open for clone
+ * @param repo The repository in which you want to create the submodule
+ * @param url URL for the submodule's remote
+ * @param path Path at which the submodule should be created
+ * @param use_gitlink Should workdir contain a gitlink to the repo in
+ * .git/modules vs. repo directly in workdir.
+ * @return 0 on success, GIT_EEXISTS if submodule already exists,
+ * -1 on other errors.
+ */
+GIT_EXTERN(int) git_submodule_add_setup(
+ git_submodule **out,
+ git_repository *repo,
+ const char *url,
+ const char *path,
+ int use_gitlink);
+
+/**
+ * Resolve the setup of a new git submodule.
+ *
+ * This should be called on a submodule once you have called add setup
+ * and done the clone of the submodule. This adds the .gitmodules file
+ * and the newly cloned submodule to the index to be ready to be committed
+ * (but doesn't actually do the commit).
+ *
+ * @param submodule The submodule to finish adding.
+ */
+GIT_EXTERN(int) git_submodule_add_finalize(git_submodule *submodule);
+
+/**
+ * Add current submodule HEAD commit to index of superproject.
+ *
+ * @param submodule The submodule to add to the index
+ * @param write_index Boolean if this should immediately write the index
+ * file. If you pass this as false, you will have to get the
+ * git_index and explicitly call `git_index_write()` on it to
+ * save the change.
+ * @return 0 on success, <0 on failure
+ */
+GIT_EXTERN(int) git_submodule_add_to_index(
+ git_submodule *submodule,
+ int write_index);
+
+/**
+ * Write submodule settings to .gitmodules file.
+ *
+ * This commits any in-memory changes to the submodule to the gitmodules
+ * file on disk. You may also be interested in `git_submodule_init()` which
+ * writes submodule info to ".git/config" (which is better for local changes
+ * to submodule settings) and/or `git_submodule_sync()` which writes
+ * settings about remotes to the actual submodule repository.
+ *
+ * @param submodule The submodule to write.
+ * @return 0 on success, <0 on failure.
+ */
+GIT_EXTERN(int) git_submodule_save(git_submodule *submodule);
+
+/**
+ * Get the containing repository for a submodule.
+ *
+ * This returns a pointer to the repository that contains the submodule.
+ * This is a just a reference to the repository that was passed to the
+ * original `git_submodule_lookup()` call, so if that repository has been
+ * freed, then this may be a dangling reference.
+ *
+ * @param submodule Pointer to submodule object
+ * @return Pointer to `git_repository`
+ */
+GIT_EXTERN(git_repository *) git_submodule_owner(git_submodule *submodule);
+
+/**
+ * Get the name of submodule.
+ *
+ * @param submodule Pointer to submodule object
+ * @return Pointer to the submodule name
+ */
+GIT_EXTERN(const char *) git_submodule_name(git_submodule *submodule);
+
+/**
+ * Get the path to the submodule.
+ *
+ * The path is almost always the same as the submodule name, but the
+ * two are actually not required to match.
+ *
+ * @param submodule Pointer to submodule object
+ * @return Pointer to the submodule path
+ */
+GIT_EXTERN(const char *) git_submodule_path(git_submodule *submodule);
+
+/**
+ * Get the URL for the submodule.
+ *
+ * @param submodule Pointer to submodule object
+ * @return Pointer to the submodule url
+ */
+GIT_EXTERN(const char *) git_submodule_url(git_submodule *submodule);
+
+/**
+ * Resolve a submodule url relative to the given repository.
+ *
+ * @param out buffer to store the absolute submodule url in
+ * @param repo Pointer to repository object
+ * @param url Relative url
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_submodule_resolve_url(git_buf *out, git_repository *repo, const char *url);
+
+/**
+* Get the branch for the submodule.
+*
+* @param submodule Pointer to submodule object
+* @return Pointer to the submodule branch
+*/
+GIT_EXTERN(const char *) git_submodule_branch(git_submodule *submodule);
+
+/**
+ * Set the URL for the submodule.
+ *
+ * This sets the URL in memory for the submodule. This will be used for
+ * any following submodule actions while this submodule data is in memory.
+ *
+ * After calling this, you may wish to call `git_submodule_save()` to write
+ * the changes back to the ".gitmodules" file and `git_submodule_sync()` to
+ * write the changes to the checked out submodule repository.
+ *
+ * @param submodule Pointer to the submodule object
+ * @param url URL that should be used for the submodule
+ * @return 0 on success, <0 on failure
+ */
+GIT_EXTERN(int) git_submodule_set_url(git_submodule *submodule, const char *url);
+
+/**
+ * Get the OID for the submodule in the index.
+ *
+ * @param submodule Pointer to submodule object
+ * @return Pointer to git_oid or NULL if submodule is not in index.
+ */
+GIT_EXTERN(const git_oid *) git_submodule_index_id(git_submodule *submodule);
+
+/**
+ * Get the OID for the submodule in the current HEAD tree.
+ *
+ * @param submodule Pointer to submodule object
+ * @return Pointer to git_oid or NULL if submodule is not in the HEAD.
+ */
+GIT_EXTERN(const git_oid *) git_submodule_head_id(git_submodule *submodule);
+
+/**
+ * Get the OID for the submodule in the current working directory.
+ *
+ * This returns the OID that corresponds to looking up 'HEAD' in the checked
+ * out submodule. If there are pending changes in the index or anything
+ * else, this won't notice that. You should call `git_submodule_status()`
+ * for a more complete picture about the state of the working directory.
+ *
+ * @param submodule Pointer to submodule object
+ * @return Pointer to git_oid or NULL if submodule is not checked out.
+ */
+GIT_EXTERN(const git_oid *) git_submodule_wd_id(git_submodule *submodule);
+
+/**
+ * Get the ignore rule that will be used for the submodule.
+ *
+ * These values control the behavior of `git_submodule_status()` for this
+ * submodule. There are four ignore values:
+ *
+ * - **GIT_SUBMODULE_IGNORE_NONE** will consider any change to the contents
+ * of the submodule from a clean checkout to be dirty, including the
+ * addition of untracked files. This is the default if unspecified.
+ * - **GIT_SUBMODULE_IGNORE_UNTRACKED** examines the contents of the
+ * working tree (i.e. call `git_status_foreach()` on the submodule) but
+ * UNTRACKED files will not count as making the submodule dirty.
+ * - **GIT_SUBMODULE_IGNORE_DIRTY** means to only check if the HEAD of the
+ * submodule has moved for status. This is fast since it does not need to
+ * scan the working tree of the submodule at all.
+ * - **GIT_SUBMODULE_IGNORE_ALL** means not to open the submodule repo.
+ * The working directory will be consider clean so long as there is a
+ * checked out version present.
+ *
+ * plus the special **GIT_SUBMODULE_IGNORE_RESET** which can be used with
+ * `git_submodule_set_ignore()` to revert to the on-disk setting.
+ *
+ * @param submodule The submodule to check
+ * @return The current git_submodule_ignore_t valyue what will be used for
+ * this submodule.
+ */
+GIT_EXTERN(git_submodule_ignore_t) git_submodule_ignore(
+ git_submodule *submodule);
+
+/**
+ * Set the ignore rule for the submodule.
+ *
+ * This sets the in-memory ignore rule for the submodule which will
+ * control the behavior of `git_submodule_status()`.
+ *
+ * To make changes persistent, call `git_submodule_save()` to write the
+ * value to disk (in the ".gitmodules" and ".git/config" files).
+ *
+ * Call with `GIT_SUBMODULE_IGNORE_RESET` or call `git_submodule_reload()`
+ * to revert the in-memory rule to the value that is on disk.
+ *
+ * @param submodule The submodule to update
+ * @param ignore The new value for the ignore rule
+ * @return old value for ignore
+ */
+GIT_EXTERN(git_submodule_ignore_t) git_submodule_set_ignore(
+ git_submodule *submodule,
+ git_submodule_ignore_t ignore);
+
+/**
+ * Get the update rule that will be used for the submodule.
+ *
+ * This value controls the behavior of the `git submodule update` command.
+ * There are four useful values documented with `git_submodule_update_t`
+ * plus the `GIT_SUBMODULE_UPDATE_RESET` which can be used to revert to
+ * the on-disk setting.
+ *
+ * @param submodule The submodule to check
+ * @return The current git_submodule_update_t value that will be used
+ * for this submodule.
+ */
+GIT_EXTERN(git_submodule_update_t) git_submodule_update(
+ git_submodule *submodule);
+
+/**
+ * Set the update rule for the submodule.
+ *
+ * The initial value comes from the ".git/config" setting of
+ * `submodule.$name.update` for this submodule (which is initialized from
+ * the ".gitmodules" file). Using this function sets the update rule in
+ * memory for the submodule. Call `git_submodule_save()` to write out the
+ * new update rule.
+ *
+ * Calling this again with GIT_SUBMODULE_UPDATE_RESET or calling
+ * `git_submodule_reload()` will revert the rule to the on disk value.
+ *
+ * @param submodule The submodule to update
+ * @param update The new value to use
+ * @return old value for update
+ */
+GIT_EXTERN(git_submodule_update_t) git_submodule_set_update(
+ git_submodule *submodule,
+ git_submodule_update_t update);
+
+/**
+ * Read the fetchRecurseSubmodules rule for a submodule.
+ *
+ * This accesses the submodule.<name>.fetchRecurseSubmodules value for
+ * the submodule that controls fetching behavior for the submodule.
+ *
+ * Note that at this time, libgit2 does not honor this setting and the
+ * fetch functionality current ignores submodules.
+ *
+ * @return 0 if fetchRecurseSubmodules is false, 1 if true
+ */
+GIT_EXTERN(git_submodule_recurse_t) git_submodule_fetch_recurse_submodules(
+ git_submodule *submodule);
+
+/**
+ * Set the fetchRecurseSubmodules rule for a submodule.
+ *
+ * This sets the submodule.<name>.fetchRecurseSubmodules value for
+ * the submodule. You should call `git_submodule_save()` if you want
+ * to persist the new value.
+ *
+ * @param submodule The submodule to modify
+ * @param fetch_recurse_submodules Boolean value
+ * @return old value for fetchRecurseSubmodules
+ */
+GIT_EXTERN(git_submodule_recurse_t) git_submodule_set_fetch_recurse_submodules(
+ git_submodule *submodule,
+ git_submodule_recurse_t fetch_recurse_submodules);
+
+/**
+ * Copy submodule info into ".git/config" file.
+ *
+ * Just like "git submodule init", this copies information about the
+ * submodule into ".git/config". You can use the accessor functions
+ * above to alter the in-memory git_submodule object and control what
+ * is written to the config, overriding what is in .gitmodules.
+ *
+ * @param submodule The submodule to write into the superproject config
+ * @param overwrite By default, existing entries will not be overwritten,
+ * but setting this to true forces them to be updated.
+ * @return 0 on success, <0 on failure.
+ */
+GIT_EXTERN(int) git_submodule_init(git_submodule *submodule, int overwrite);
+
+/**
+ * Copy submodule remote info into submodule repo.
+ *
+ * This copies the information about the submodules URL into the checked out
+ * submodule config, acting like "git submodule sync". This is useful if
+ * you have altered the URL for the submodule (or it has been altered by a
+ * fetch of upstream changes) and you need to update your local repo.
+ */
+GIT_EXTERN(int) git_submodule_sync(git_submodule *submodule);
+
+/**
+ * Open the repository for a submodule.
+ *
+ * This is a newly opened repository object. The caller is responsible for
+ * calling `git_repository_free()` on it when done. Multiple calls to this
+ * function will return distinct `git_repository` objects. This will only
+ * work if the submodule is checked out into the working directory.
+ *
+ * @param repo Pointer to the submodule repo which was opened
+ * @param submodule Submodule to be opened
+ * @return 0 on success, <0 if submodule repo could not be opened.
+ */
+GIT_EXTERN(int) git_submodule_open(
+ git_repository **repo,
+ git_submodule *submodule);
+
+/**
+ * Reread submodule info from config, index, and HEAD.
+ *
+ * Call this to reread cached submodule information for this submodule if
+ * you have reason to believe that it has changed.
+ *
+ * @param submodule The submodule to reload
+ * @param force Force reload even if the data doesn't seem out of date
+ * @return 0 on success, <0 on error
+ */
+GIT_EXTERN(int) git_submodule_reload(git_submodule *submodule, int force);
+
+/**
+ * Reread all submodule info.
+ *
+ * Call this to reload all cached submodule information for the repo.
+ *
+ * @param repo The repository to reload submodule data for
+ * @param force Force full reload even if the data doesn't seem out of date
+ * @return 0 on success, <0 on error
+ */
+GIT_EXTERN(int) git_submodule_reload_all(git_repository *repo, int force);
+
+/**
+ * Get the status for a submodule.
+ *
+ * This looks at a submodule and tries to determine the status. It
+ * will return a combination of the `GIT_SUBMODULE_STATUS` values above.
+ * How deeply it examines the working directory to do this will depend
+ * on the `git_submodule_ignore_t` value for the submodule - which can be
+ * set either temporarily or permanently with `git_submodule_set_ignore()`.
+ *
+ * @param status Combination of `GIT_SUBMODULE_STATUS` flags
+ * @param submodule Submodule for which to get status
+ * @return 0 on success, <0 on error
+ */
+GIT_EXTERN(int) git_submodule_status(
+ unsigned int *status,
+ git_submodule *submodule);
+
+/**
+ * Get the locations of submodule information.
+ *
+ * This is a bit like a very lightweight version of `git_submodule_status`.
+ * It just returns a made of the first four submodule status values (i.e.
+ * the ones like GIT_SUBMODULE_STATUS_IN_HEAD, etc) that tell you where the
+ * submodule data comes from (i.e. the HEAD commit, gitmodules file, etc.).
+ * This can be useful if you want to know if the submodule is present in the
+ * working directory at this point in time, etc.
+ *
+ * @param location_status Combination of first four `GIT_SUBMODULE_STATUS` flags
+ * @param submodule Submodule for which to get status
+ * @return 0 on success, <0 on error
+ */
+GIT_EXTERN(int) git_submodule_location(
+ unsigned int *location_status,
+ git_submodule *submodule);
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/sys/commit.h b/include/git2/sys/commit.h
new file mode 100644
index 000000000..627d3ae2e
--- /dev/null
+++ b/include/git2/sys/commit.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_sys_git_commit_h__
+#define INCLUDE_sys_git_commit_h__
+
+#include "git2/common.h"
+#include "git2/types.h"
+#include "git2/oid.h"
+
+/**
+ * @file git2/sys/commit.h
+ * @brief Low-level Git commit creation
+ * @defgroup git_backend Git custom backend APIs
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Create new commit in the repository from a list of `git_oid` values.
+ *
+ * See documentation for `git_commit_create()` for information about the
+ * parameters, as the meaning is identical excepting that `tree` and
+ * `parents` now take `git_oid`. This is a dangerous API in that nor
+ * the `tree`, neither the `parents` list of `git_oid`s are checked for
+ * validity.
+ *
+ * @see git_commit_create
+ */
+GIT_EXTERN(int) git_commit_create_from_ids(
+ git_oid *id,
+ git_repository *repo,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_oid *tree,
+ size_t parent_count,
+ const git_oid *parents[]);
+
+/**
+ * Callback function to return parents for commit.
+ *
+ * This is invoked with the count of the number of parents processed so far
+ * along with the user supplied payload. This should return a git_oid of
+ * the next parent or NULL if all parents have been provided.
+ */
+typedef const git_oid *(*git_commit_parent_callback)(size_t idx, void *payload);
+
+/**
+ * Create a new commit in the repository with an callback to supply parents.
+ *
+ * See documentation for `git_commit_create()` for information about the
+ * parameters, as the meaning is identical excepting that `tree` takes a
+ * `git_oid` and doesn't check for validity, and `parent_cb` is invoked
+ * with `parent_payload` and should return `git_oid` values or NULL to
+ * indicate that all parents are accounted for.
+ *
+ * @see git_commit_create
+ */
+GIT_EXTERN(int) git_commit_create_from_callback(
+ git_oid *id,
+ git_repository *repo,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_oid *tree,
+ git_commit_parent_callback parent_cb,
+ void *parent_payload);
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/sys/config.h b/include/git2/sys/config.h
new file mode 100644
index 000000000..85e0d6417
--- /dev/null
+++ b/include/git2/sys/config.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_sys_git_config_backend_h__
+#define INCLUDE_sys_git_config_backend_h__
+
+#include "git2/common.h"
+#include "git2/types.h"
+#include "git2/config.h"
+
+/**
+ * @file git2/sys/config.h
+ * @brief Git config backend routines
+ * @defgroup git_backend Git custom backend APIs
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Every iterator must have this struct as its first element, so the
+ * API can talk to it. You'd define your iterator as
+ *
+ * struct my_iterator {
+ * git_config_iterator parent;
+ * ...
+ * }
+ *
+ * and assign `iter->parent.backend` to your `git_config_backend`.
+ */
+struct git_config_iterator {
+ git_config_backend *backend;
+ unsigned int flags;
+
+ /**
+ * Return the current entry and advance the iterator. The
+ * memory belongs to the library.
+ */
+ int (*next)(git_config_entry **entry, git_config_iterator *iter);
+
+ /**
+ * Free the iterator
+ */
+ void (*free)(git_config_iterator *iter);
+};
+
+/**
+ * Generic backend that implements the interface to
+ * access a configuration file
+ */
+struct git_config_backend {
+ unsigned int version;
+ struct git_config *cfg;
+
+ /* Open means open the file/database and parse if necessary */
+ int (*open)(struct git_config_backend *, git_config_level_t level);
+ int (*get)(struct git_config_backend *, const char *key, const git_config_entry **entry);
+ int (*set)(struct git_config_backend *, const char *key, const char *value);
+ int (*set_multivar)(git_config_backend *cfg, const char *name, const char *regexp, const char *value);
+ int (*del)(struct git_config_backend *, const char *key);
+ int (*del_multivar)(struct git_config_backend *, const char *key, const char *regexp);
+ int (*iterator)(git_config_iterator **, struct git_config_backend *);
+ int (*refresh)(struct git_config_backend *);
+ /** Produce a read-only version of this backend */
+ int (*snapshot)(struct git_config_backend **, struct git_config_backend *);
+ void (*free)(struct git_config_backend *);
+};
+#define GIT_CONFIG_BACKEND_VERSION 1
+#define GIT_CONFIG_BACKEND_INIT {GIT_CONFIG_BACKEND_VERSION}
+
+/**
+ * Initializes a `git_config_backend` with default values. Equivalent to
+ * creating an instance with GIT_CONFIG_BACKEND_INIT.
+ *
+ * @param opts the `git_config_backend` struct to initialize.
+ * @param version Version of struct; pass `GIT_CONFIG_BACKEND_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_config_init_backend(
+ git_config_backend *backend,
+ unsigned int version);
+
+/**
+ * Add a generic config file instance to an existing config
+ *
+ * Note that the configuration object will free the file
+ * automatically.
+ *
+ * Further queries on this config object will access each
+ * of the config file instances in order (instances with
+ * a higher priority level will be accessed first).
+ *
+ * @param cfg the configuration to add the file to
+ * @param file the configuration file (backend) to add
+ * @param level the priority level of the backend
+ * @param force if a config file already exists for the given
+ * priority level, replace it
+ * @return 0 on success, GIT_EEXISTS when adding more than one file
+ * for a given priority level (and force_replace set to 0), or error code
+ */
+GIT_EXTERN(int) git_config_add_backend(
+ git_config *cfg,
+ git_config_backend *file,
+ git_config_level_t level,
+ int force);
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/sys/diff.h b/include/git2/sys/diff.h
new file mode 100644
index 000000000..48d72f4f9
--- /dev/null
+++ b/include/git2/sys/diff.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_sys_git_diff_h__
+#define INCLUDE_sys_git_diff_h__
+
+#include "git2/common.h"
+#include "git2/types.h"
+#include "git2/oid.h"
+#include "git2/diff.h"
+#include "git2/status.h"
+
+/**
+ * @file git2/sys/diff.h
+ * @brief Low-level Git diff utilities
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Diff print callback that writes to a git_buf.
+ *
+ * This function is provided not for you to call it directly, but instead
+ * so you can use it as a function pointer to the `git_diff_print` or
+ * `git_patch_print` APIs. When using those APIs, you specify a callback
+ * to actually handle the diff and/or patch data.
+ *
+ * Use this callback to easily write that data to a `git_buf` buffer. You
+ * must pass a `git_buf *` value as the payload to the `git_diff_print`
+ * and/or `git_patch_print` function. The data will be appended to the
+ * buffer (after any existing content).
+ */
+GIT_EXTERN(int) git_diff_print_callback__to_buf(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk,
+ const git_diff_line *line,
+ void *payload); /*< payload must be a `git_buf *` */
+
+/**
+ * Diff print callback that writes to stdio FILE handle.
+ *
+ * This function is provided not for you to call it directly, but instead
+ * so you can use it as a function pointer to the `git_diff_print` or
+ * `git_patch_print` APIs. When using those APIs, you specify a callback
+ * to actually handle the diff and/or patch data.
+ *
+ * Use this callback to easily write that data to a stdio FILE handle. You
+ * must pass a `FILE *` value (such as `stdout` or `stderr` or the return
+ * value from `fopen()`) as the payload to the `git_diff_print`
+ * and/or `git_patch_print` function. If you pass NULL, this will write
+ * data to `stdout`.
+ */
+GIT_EXTERN(int) git_diff_print_callback__to_file_handle(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk,
+ const git_diff_line *line,
+ void *payload); /*< payload must be a `FILE *` */
+
+
+typedef struct {
+ unsigned int version;
+ size_t stat_calls;
+ size_t oid_calculations;
+} git_diff_perfdata;
+
+#define GIT_DIFF_PERFDATA_VERSION 1
+#define GIT_DIFF_PERFDATA_INIT {GIT_DIFF_PERFDATA_VERSION,0,0}
+
+/**
+ * Get performance data for a diff object.
+ *
+ * @param out Structure to be filled with diff performance data
+ * @param diff Diff to read performance data from
+ * @return 0 for success, <0 for error
+ */
+GIT_EXTERN(int) git_diff_get_perfdata(
+ git_diff_perfdata *out, const git_diff *diff);
+
+/**
+ * Get performance data for diffs from a git_status_list
+ */
+GIT_EXTERN(int) git_status_list_get_perfdata(
+ git_diff_perfdata *out, const git_status_list *status);
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/sys/filter.h b/include/git2/sys/filter.h
new file mode 100644
index 000000000..60248271a
--- /dev/null
+++ b/include/git2/sys/filter.h
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_sys_git_filter_h__
+#define INCLUDE_sys_git_filter_h__
+
+#include "git2/filter.h"
+
+/**
+ * @file git2/sys/filter.h
+ * @brief Git filter backend and plugin routines
+ * @defgroup git_backend Git custom backend APIs
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Look up a filter by name
+ *
+ * @param name The name of the filter
+ * @return Pointer to the filter object or NULL if not found
+ */
+GIT_EXTERN(git_filter *) git_filter_lookup(const char *name);
+
+#define GIT_FILTER_CRLF "crlf"
+#define GIT_FILTER_IDENT "ident"
+
+/**
+ * This is priority that the internal CRLF filter will be registered with
+ */
+#define GIT_FILTER_CRLF_PRIORITY 0
+
+/**
+ * This is priority that the internal ident filter will be registered with
+ */
+#define GIT_FILTER_IDENT_PRIORITY 100
+
+/**
+ * This is priority to use with a custom filter to imitate a core Git
+ * filter driver, so that it will be run last on checkout and first on
+ * checkin. You do not have to use this, but it helps compatibility.
+ */
+#define GIT_FILTER_DRIVER_PRIORITY 200
+
+/**
+ * Create a new empty filter list
+ *
+ * Normally you won't use this because `git_filter_list_load` will create
+ * the filter list for you, but you can use this in combination with the
+ * `git_filter_lookup` and `git_filter_list_push` functions to assemble
+ * your own chains of filters.
+ */
+GIT_EXTERN(int) git_filter_list_new(
+ git_filter_list **out,
+ git_repository *repo,
+ git_filter_mode_t mode,
+ uint32_t options);
+
+/**
+ * Add a filter to a filter list with the given payload.
+ *
+ * Normally you won't have to do this because the filter list is created
+ * by calling the "check" function on registered filters when the filter
+ * attributes are set, but this does allow more direct manipulation of
+ * filter lists when desired.
+ *
+ * Note that normally the "check" function can set up a payload for the
+ * filter. Using this function, you can either pass in a payload if you
+ * know the expected payload format, or you can pass NULL. Some filters
+ * may fail with a NULL payload. Good luck!
+ */
+GIT_EXTERN(int) git_filter_list_push(
+ git_filter_list *fl, git_filter *filter, void *payload);
+
+/**
+ * Look up how many filters are in the list
+ *
+ * We will attempt to apply all of these filters to any data passed in,
+ * but note that the filter apply action still has the option of skipping
+ * data that is passed in (for example, the CRLF filter will skip data
+ * that appears to be binary).
+ *
+ * @param fl A filter list
+ * @return The number of filters in the list
+ */
+GIT_EXTERN(size_t) git_filter_list_length(const git_filter_list *fl);
+
+/**
+ * A filter source represents a file/blob to be processed
+ */
+typedef struct git_filter_source git_filter_source;
+
+/**
+ * Get the repository that the source data is coming from.
+ */
+GIT_EXTERN(git_repository *) git_filter_source_repo(const git_filter_source *src);
+
+/**
+ * Get the path that the source data is coming from.
+ */
+GIT_EXTERN(const char *) git_filter_source_path(const git_filter_source *src);
+
+/**
+ * Get the file mode of the source file
+ * If the mode is unknown, this will return 0
+ */
+GIT_EXTERN(uint16_t) git_filter_source_filemode(const git_filter_source *src);
+
+/**
+ * Get the OID of the source
+ * If the OID is unknown (often the case with GIT_FILTER_CLEAN) then
+ * this will return NULL.
+ */
+GIT_EXTERN(const git_oid *) git_filter_source_id(const git_filter_source *src);
+
+/**
+ * Get the git_filter_mode_t to be used
+ */
+GIT_EXTERN(git_filter_mode_t) git_filter_source_mode(const git_filter_source *src);
+
+/**
+ * Get the combination git_filter_opt_t options to be applied
+ */
+GIT_EXTERN(uint32_t) git_filter_source_options(const git_filter_source *src);
+
+/*
+ * struct git_filter
+ *
+ * The filter lifecycle:
+ * - initialize - first use of filter
+ * - shutdown - filter removed/unregistered from system
+ * - check - considering filter for file
+ * - apply - apply filter to file contents
+ * - cleanup - done with file
+ */
+
+/**
+ * Initialize callback on filter
+ *
+ * Specified as `filter.initialize`, this is an optional callback invoked
+ * before a filter is first used. It will be called once at most.
+ *
+ * If non-NULL, the filter's `initialize` callback will be invoked right
+ * before the first use of the filter, so you can defer expensive
+ * initialization operations (in case libgit2 is being used in a way that
+ * doesn't need the filter).
+ */
+typedef int (*git_filter_init_fn)(git_filter *self);
+
+/**
+ * Shutdown callback on filter
+ *
+ * Specified as `filter.shutdown`, this is an optional callback invoked
+ * when the filter is unregistered or when libgit2 is shutting down. It
+ * will be called once at most and should release resources as needed.
+ * This may be called even if the `initialize` callback was not made.
+ *
+ * Typically this function will free the `git_filter` object itself.
+ */
+typedef void (*git_filter_shutdown_fn)(git_filter *self);
+
+/**
+ * Callback to decide if a given source needs this filter
+ *
+ * Specified as `filter.check`, this is an optional callback that checks
+ * if filtering is needed for a given source.
+ *
+ * It should return 0 if the filter should be applied (i.e. success),
+ * GIT_PASSTHROUGH if the filter should not be applied, or an error code
+ * to fail out of the filter processing pipeline and return to the caller.
+ *
+ * The `attr_values` will be set to the values of any attributes given in
+ * the filter definition. See `git_filter` below for more detail.
+ *
+ * The `payload` will be a pointer to a reference payload for the filter.
+ * This will start as NULL, but `check` can assign to this pointer for
+ * later use by the `apply` callback. Note that the value should be heap
+ * allocated (not stack), so that it doesn't go away before the `apply`
+ * callback can use it. If a filter allocates and assigns a value to the
+ * `payload`, it will need a `cleanup` callback to free the payload.
+ */
+typedef int (*git_filter_check_fn)(
+ git_filter *self,
+ void **payload, /* points to NULL ptr on entry, may be set */
+ const git_filter_source *src,
+ const char **attr_values);
+
+/**
+ * Callback to actually perform the data filtering
+ *
+ * Specified as `filter.apply`, this is the callback that actually filters
+ * data. If it successfully writes the output, it should return 0. Like
+ * `check`, it can return GIT_PASSTHROUGH to indicate that the filter
+ * doesn't want to run. Other error codes will stop filter processing and
+ * return to the caller.
+ *
+ * The `payload` value will refer to any payload that was set by the
+ * `check` callback. It may be read from or written to as needed.
+ */
+typedef int (*git_filter_apply_fn)(
+ git_filter *self,
+ void **payload, /* may be read and/or set */
+ git_buf *to,
+ const git_buf *from,
+ const git_filter_source *src);
+
+/**
+ * Callback to clean up after filtering has been applied
+ *
+ * Specified as `filter.cleanup`, this is an optional callback invoked
+ * after the filter has been applied. If the `check` or `apply` callbacks
+ * allocated a `payload` to keep per-source filter state, use this
+ * callback to free that payload and release resources as required.
+ */
+typedef void (*git_filter_cleanup_fn)(
+ git_filter *self,
+ void *payload);
+
+/**
+ * Filter structure used to register custom filters.
+ *
+ * To associate extra data with a filter, allocate extra data and put the
+ * `git_filter` struct at the start of your data buffer, then cast the
+ * `self` pointer to your larger structure when your callback is invoked.
+ *
+ * `version` should be set to GIT_FILTER_VERSION
+ *
+ * `attributes` is a whitespace-separated list of attribute names to check
+ * for this filter (e.g. "eol crlf text"). If the attribute name is bare,
+ * it will be simply loaded and passed to the `check` callback. If it has
+ * a value (i.e. "name=value"), the attribute must match that value for
+ * the filter to be applied.
+ *
+ * The `initialize`, `shutdown`, `check`, `apply`, and `cleanup` callbacks
+ * are all documented above with the respective function pointer typedefs.
+ */
+struct git_filter {
+ unsigned int version;
+
+ const char *attributes;
+
+ git_filter_init_fn initialize;
+ git_filter_shutdown_fn shutdown;
+ git_filter_check_fn check;
+ git_filter_apply_fn apply;
+ git_filter_cleanup_fn cleanup;
+};
+
+#define GIT_FILTER_VERSION 1
+
+/**
+ * Register a filter under a given name with a given priority.
+ *
+ * As mentioned elsewhere, the initialize callback will not be invoked
+ * immediately. It is deferred until the filter is used in some way.
+ *
+ * A filter's attribute checks and `check` and `apply` callbacks will be
+ * issued in order of `priority` on smudge (to workdir), and in reverse
+ * order of `priority` on clean (to odb).
+ *
+ * Two filters are preregistered with libgit2:
+ * - GIT_FILTER_CRLF with priority 0
+ * - GIT_FILTER_IDENT with priority 100
+ *
+ * Currently the filter registry is not thread safe, so any registering or
+ * deregistering of filters must be done outside of any possible usage of
+ * the filters (i.e. during application setup or shutdown).
+ *
+ * @param name A name by which the filter can be referenced. Attempting
+ * to register with an in-use name will return GIT_EEXISTS.
+ * @param filter The filter definition. This pointer will be stored as is
+ * by libgit2 so it must be a durable allocation (either static
+ * or on the heap).
+ * @param priority The priority for filter application
+ * @return 0 on successful registry, error code <0 on failure
+ */
+GIT_EXTERN(int) git_filter_register(
+ const char *name, git_filter *filter, int priority);
+
+/**
+ * Remove the filter with the given name
+ *
+ * Attempting to remove the builtin libgit2 filters is not permitted and
+ * will return an error.
+ *
+ * Currently the filter registry is not thread safe, so any registering or
+ * deregistering of filters must be done outside of any possible usage of
+ * the filters (i.e. during application setup or shutdown).
+ *
+ * @param name The name under which the filter was registered
+ * @return 0 on success, error code <0 on failure
+ */
+GIT_EXTERN(int) git_filter_unregister(const char *name);
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/sys/index.h b/include/git2/sys/index.h
new file mode 100644
index 000000000..1a06a4df1
--- /dev/null
+++ b/include/git2/sys/index.h
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_sys_git_index_h__
+#define INCLUDE_sys_git_index_h__
+
+/**
+ * @file git2/sys/index.h
+ * @brief Low-level Git index manipulation routines
+ * @defgroup git_backend Git custom backend APIs
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/** Representation of a rename conflict entry in the index. */
+typedef struct git_index_name_entry {
+ char *ancestor;
+ char *ours;
+ char *theirs;
+} git_index_name_entry;
+
+/** 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_reuc_entry;
+
+/** @name Conflict Name entry functions
+ *
+ * These functions work on rename conflict entries.
+ */
+/**@{*/
+
+/**
+ * Get the count of filename conflict entries currently in the index.
+ *
+ * @param index an existing index object
+ * @return integer of count of current filename conflict entries
+ */
+GIT_EXTERN(unsigned int) git_index_name_entrycount(git_index *index);
+
+/**
+ * Get a filename conflict entry from the index.
+ *
+ * The returned entry is read-only and should not be modified
+ * or freed by the caller.
+ *
+ * @param index an existing index object
+ * @param n the position of the entry
+ * @return a pointer to the filename conflict entry; NULL if out of bounds
+ */
+GIT_EXTERN(const git_index_name_entry *) git_index_name_get_byindex(
+ git_index *index, size_t n);
+
+/**
+ * Record the filenames involved in a rename conflict.
+ *
+ * @param index an existing index object
+ * @param ancestor the path of the file as it existed in the ancestor
+ * @param ours the path of the file as it existed in our tree
+ * @param theirs the path of the file as it existed in their tree
+ */
+GIT_EXTERN(int) git_index_name_add(git_index *index,
+ const char *ancestor, const char *ours, const char *theirs);
+
+/**
+ * Remove all filename conflict entries.
+ *
+ * @param index an existing index object
+ */
+GIT_EXTERN(void) git_index_name_clear(git_index *index);
+
+/**@}*/
+
+/** @name Resolve Undo (REUC) index entry manipulation.
+ *
+ * These functions work on the Resolve Undo index extension and contains
+ * data about the original files that led to a merge conflict.
+ */
+/**@{*/
+
+/**
+ * Get the count of resolve undo entries currently in the index.
+ *
+ * @param index an existing index object
+ * @return integer of count of current resolve undo entries
+ */
+GIT_EXTERN(unsigned int) git_index_reuc_entrycount(git_index *index);
+
+/**
+ * Finds the resolve undo entry that points to the given path in the Git
+ * index.
+ *
+ * @param at_pos the address to which the position of the reuc entry is written (optional)
+ * @param index an existing index object
+ * @param path path to search
+ * @return 0 if found, < 0 otherwise (GIT_ENOTFOUND)
+ */
+GIT_EXTERN(int) git_index_reuc_find(size_t *at_pos, git_index *index, const char *path);
+
+/**
+ * Get a resolve undo entry from the index.
+ *
+ * The returned entry is read-only and should not be modified
+ * or freed by the caller.
+ *
+ * @param index an existing index object
+ * @param path path to search
+ * @return the resolve undo entry; NULL if not found
+ */
+GIT_EXTERN(const git_index_reuc_entry *) git_index_reuc_get_bypath(git_index *index, const char *path);
+
+/**
+ * Get a resolve undo entry from the index.
+ *
+ * The returned entry is read-only and should not be modified
+ * or freed by the caller.
+ *
+ * @param index an existing index object
+ * @param n the position of the entry
+ * @return a pointer to the resolve undo entry; NULL if out of bounds
+ */
+GIT_EXTERN(const git_index_reuc_entry *) git_index_reuc_get_byindex(git_index *index, size_t n);
+
+/**
+ * Adds a resolve undo entry for a file based on the given parameters.
+ *
+ * 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.
+ *
+ * If there exists a resolve undo entry for the given path in the index,
+ * it will be removed.
+ *
+ * This method will fail in bare index instances.
+ *
+ * @param index an existing index object
+ * @param path filename to add
+ * @param ancestor_mode mode of the ancestor file
+ * @param ancestor_id oid of the ancestor file
+ * @param our_mode mode of our file
+ * @param our_id oid of our file
+ * @param their_mode mode of their file
+ * @param their_id oid of their file
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_reuc_add(git_index *index, const char *path,
+ int ancestor_mode, const git_oid *ancestor_id,
+ int our_mode, const git_oid *our_id,
+ int their_mode, const git_oid *their_id);
+
+/**
+ * Remove an resolve undo entry from the index
+ *
+ * @param index an existing index object
+ * @param n position of the resolve undo entry to remove
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_index_reuc_remove(git_index *index, size_t n);
+
+/**
+ * Remove all resolve undo entries from the index
+ *
+ * @param index an existing index object
+ */
+GIT_EXTERN(void) git_index_reuc_clear(git_index *index);
+
+/**@}*/
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/sys/mempack.h b/include/git2/sys/mempack.h
new file mode 100644
index 000000000..d3bc87b4d
--- /dev/null
+++ b/include/git2/sys/mempack.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_sys_git_odb_mempack_h__
+#define INCLUDE_sys_git_odb_mempack_h__
+
+#include "git2/common.h"
+#include "git2/types.h"
+#include "git2/oid.h"
+#include "git2/odb.h"
+
+/**
+ * @file git2/sys/mempack.h
+ * @brief Custom ODB backend that permits packing objects in-memory
+ * @defgroup git_backend Git custom backend APIs
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Instantiate a new mempack backend.
+ *
+ * The backend must be added to an existing ODB with the highest
+ * priority.
+ *
+ * git_mempack_new(&mempacker);
+ * git_repository_odb(&odb, repository);
+ * git_odb_add_backend(odb, mempacker, 999);
+ *
+ * Once the backend has been loaded, all writes to the ODB will
+ * instead be queued in memory, and can be finalized with
+ * `git_mempack_dump`.
+ *
+ * Subsequent reads will also be served from the in-memory store
+ * to ensure consistency, until the memory store is dumped.
+ *
+ * @param out Poiter where to store the ODB backend
+ * @return 0 on success; error code otherwise
+ */
+int git_mempack_new(git_odb_backend **out);
+
+/**
+ * Dump all the queued in-memory writes to a packfile.
+ *
+ * The contents of the packfile will be stored in the given buffer.
+ * It is the caller's responsability to ensure that the generated
+ * packfile is available to the repository (e.g. by writing it
+ * to disk, or doing something crazy like distributing it across
+ * several copies of the repository over a network).
+ *
+ * Once the generated packfile is available to the repository,
+ * call `git_mempack_reset` to cleanup the memory store.
+ *
+ * Calling `git_mempack_reset` before the packfile has been
+ * written to disk will result in an inconsistent repository
+ * (the objects in the memory store won't be accessible).
+ *
+ * @param pack Buffer where to store the raw packfile
+ * @param repo The active repository where the backend is loaded
+ * @param backend The mempack backend
+ * @return 0 on success; error code otherwise
+ */
+int git_mempack_dump(git_buf *pack, git_repository *repo, git_odb_backend *backend);
+
+/**
+ * Reset the memory packer by clearing all the queued objects.
+ *
+ * This assumes that `git_mempack_dump` has been called before to
+ * store all the queued objects into a single packfile.
+ *
+ * Alternatively, call `reset` without a previous dump to "undo"
+ * all the recently written objects, giving transaction-like
+ * semantics to the Git repository.
+ *
+ * @param backend The mempack backend
+ */
+void git_mempack_reset(git_odb_backend *backend);
+
+GIT_END_DECL
+
+#endif
diff --git a/include/git2/sys/odb_backend.h b/include/git2/sys/odb_backend.h
new file mode 100644
index 000000000..1fc3c3159
--- /dev/null
+++ b/include/git2/sys/odb_backend.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_sys_git_odb_backend_h__
+#define INCLUDE_sys_git_odb_backend_h__
+
+#include "git2/common.h"
+#include "git2/types.h"
+#include "git2/oid.h"
+#include "git2/odb.h"
+
+/**
+ * @file git2/sys/backend.h
+ * @brief Git custom backend implementors functions
+ * @defgroup git_backend Git custom backend APIs
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * An instance for a custom backend
+ */
+struct git_odb_backend {
+ unsigned int version;
+ git_odb *odb;
+
+ /* read and read_prefix each return to libgit2 a buffer which
+ * will be freed later. The buffer should be allocated using
+ * the function git_odb_backend_malloc to ensure that it can
+ * be safely freed later. */
+ int (* read)(
+ void **, size_t *, git_otype *, git_odb_backend *, const git_oid *);
+
+ /* To find a unique object given a prefix of its oid. The oid given
+ * must be so that the remaining (GIT_OID_HEXSZ - len)*4 bits are 0s.
+ */
+ int (* read_prefix)(
+ git_oid *, void **, size_t *, git_otype *,
+ git_odb_backend *, const git_oid *, size_t);
+
+ int (* read_header)(
+ size_t *, git_otype *, git_odb_backend *, const git_oid *);
+
+ /**
+ * Write an object into the backend. The id of the object has
+ * already been calculated and is passed in.
+ */
+ int (* write)(
+ git_odb_backend *, const git_oid *, const void *, size_t, git_otype);
+
+ int (* writestream)(
+ git_odb_stream **, git_odb_backend *, size_t, git_otype);
+
+ int (* readstream)(
+ git_odb_stream **, git_odb_backend *, const git_oid *);
+
+ int (* exists)(
+ git_odb_backend *, const git_oid *);
+
+ int (* exists_prefix)(
+ git_oid *, git_odb_backend *, const git_oid *, size_t);
+
+ /**
+ * If the backend implements a refreshing mechanism, it should be exposed
+ * through this endpoint. Each call to `git_odb_refresh()` will invoke it.
+ *
+ * However, the backend implementation should try to stay up-to-date as much
+ * as possible by itself as libgit2 will not automatically invoke
+ * `git_odb_refresh()`. For instance, a potential strategy for the backend
+ * implementation to achieve this could be to internally invoke this
+ * endpoint on failed lookups (ie. `exists()`, `read()`, `read_header()`).
+ */
+ int (* refresh)(git_odb_backend *);
+
+ int (* foreach)(
+ git_odb_backend *, git_odb_foreach_cb cb, void *payload);
+
+ int (* writepack)(
+ git_odb_writepack **, git_odb_backend *, git_odb *odb,
+ git_transfer_progress_cb progress_cb, void *progress_payload);
+
+ void (* free)(git_odb_backend *);
+};
+
+#define GIT_ODB_BACKEND_VERSION 1
+#define GIT_ODB_BACKEND_INIT {GIT_ODB_BACKEND_VERSION}
+
+/**
+ * Initializes a `git_odb_backend` with default values. Equivalent to
+ * creating an instance with GIT_ODB_BACKEND_INIT.
+ *
+ * @param opts the `git_odb_backend` struct to initialize.
+ * @param version Version the struct; pass `GIT_ODB_BACKEND_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_odb_init_backend(
+ git_odb_backend *backend,
+ unsigned int version);
+
+GIT_EXTERN(void *) git_odb_backend_malloc(git_odb_backend *backend, size_t len);
+
+GIT_END_DECL
+
+#endif
diff --git a/include/git2/sys/refdb_backend.h b/include/git2/sys/refdb_backend.h
new file mode 100644
index 000000000..3b216a287
--- /dev/null
+++ b/include/git2/sys/refdb_backend.h
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_sys_git_refdb_backend_h__
+#define INCLUDE_sys_git_refdb_backend_h__
+
+#include "git2/common.h"
+#include "git2/types.h"
+#include "git2/oid.h"
+
+/**
+ * @file git2/refdb_backend.h
+ * @brief Git custom refs backend functions
+ * @defgroup git_refdb_backend Git custom refs backend API
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+
+/**
+ * Every backend's iterator must have a pointer to itself as the first
+ * element, so the API can talk to it. You'd define your iterator as
+ *
+ * struct my_iterator {
+ * git_reference_iterator parent;
+ * ...
+ * }
+ *
+ * and assign `iter->parent.backend` to your `git_refdb_backend`.
+ */
+struct git_reference_iterator {
+ git_refdb *db;
+
+ /**
+ * Return the current reference and advance the iterator.
+ */
+ int (*next)(
+ git_reference **ref,
+ git_reference_iterator *iter);
+
+ /**
+ * Return the name of the current reference and advance the iterator
+ */
+ int (*next_name)(
+ const char **ref_name,
+ git_reference_iterator *iter);
+
+ /**
+ * Free the iterator
+ */
+ void (*free)(
+ git_reference_iterator *iter);
+};
+
+/** An instance for a custom backend */
+struct git_refdb_backend {
+ unsigned int version;
+
+ /**
+ * Queries the refdb backend to determine if the given ref_name
+ * exists. A refdb implementation must provide this function.
+ */
+ int (*exists)(
+ int *exists,
+ git_refdb_backend *backend,
+ const char *ref_name);
+
+ /**
+ * Queries the refdb backend for a given reference. A refdb
+ * implementation must provide this function.
+ */
+ int (*lookup)(
+ git_reference **out,
+ git_refdb_backend *backend,
+ const char *ref_name);
+
+ /**
+ * Allocate an iterator object for the backend.
+ *
+ * A refdb implementation must provide this function.
+ */
+ int (*iterator)(
+ git_reference_iterator **iter,
+ struct git_refdb_backend *backend,
+ const char *glob);
+
+ /*
+ * Writes the given reference to the refdb. A refdb implementation
+ * must provide this function.
+ */
+ int (*write)(git_refdb_backend *backend,
+ const git_reference *ref, int force,
+ const git_signature *who, const char *message,
+ const git_oid *old, const char *old_target);
+
+ int (*rename)(
+ git_reference **out, git_refdb_backend *backend,
+ const char *old_name, const char *new_name, int force,
+ const git_signature *who, const char *message);
+
+ /**
+ * Deletes the given reference from the refdb. A refdb implementation
+ * must provide this function.
+ */
+ int (*del)(git_refdb_backend *backend, const char *ref_name, const git_oid *old_id, const char *old_target);
+
+ /**
+ * Suggests that the given refdb compress or optimize its references.
+ * This mechanism is implementation specific. (For on-disk reference
+ * databases, this may pack all loose references.) A refdb
+ * implementation may provide this function; if it is not provided,
+ * nothing will be done.
+ */
+ int (*compress)(git_refdb_backend *backend);
+
+ /**
+ * Query whether a particular reference has a log (may be empty)
+ */
+ int (*has_log)(git_refdb_backend *backend, const char *refname);
+
+ /**
+ * Make sure a particular reference will have a reflog which
+ * will be appended to on writes.
+ */
+ int (*ensure_log)(git_refdb_backend *backend, const char *refname);
+
+ /**
+ * Frees any resources held by the refdb. A refdb implementation may
+ * provide this function; if it is not provided, nothing will be done.
+ */
+ void (*free)(git_refdb_backend *backend);
+
+ /**
+ * Read the reflog for the given reference name.
+ */
+ int (*reflog_read)(git_reflog **out, git_refdb_backend *backend, const char *name);
+
+ /**
+ * Write a reflog to disk.
+ */
+ int (*reflog_write)(git_refdb_backend *backend, git_reflog *reflog);
+
+ /**
+ * Rename a reflog
+ */
+ int (*reflog_rename)(git_refdb_backend *_backend, const char *old_name, const char *new_name);
+
+ /**
+ * Remove a reflog.
+ */
+ int (*reflog_delete)(git_refdb_backend *backend, const char *name);
+};
+
+#define GIT_REFDB_BACKEND_VERSION 1
+#define GIT_REFDB_BACKEND_INIT {GIT_REFDB_BACKEND_VERSION}
+
+/**
+ * Initializes a `git_refdb_backend` with default values. Equivalent to
+ * creating an instance with GIT_REFDB_BACKEND_INIT.
+ *
+ * @param opts the `git_refdb_backend` struct to initialize
+ * @param version Version of struct; pass `GIT_REFDB_BACKEND_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_refdb_init_backend(
+ git_refdb_backend *backend,
+ unsigned int version);
+
+/**
+ * Constructors for default filesystem-based refdb backend
+ *
+ * Under normal usage, this is called for you when the repository is
+ * opened / created, but you can use this to explicitly construct a
+ * filesystem refdb backend for a repository.
+ *
+ * @param backend_out Output pointer to the git_refdb_backend object
+ * @param repo Git repository to access
+ * @return 0 on success, <0 error code on failure
+ */
+GIT_EXTERN(int) git_refdb_backend_fs(
+ git_refdb_backend **backend_out,
+ git_repository *repo);
+
+/**
+ * Sets the custom backend to an existing reference DB
+ *
+ * The `git_refdb` will take ownership of the `git_refdb_backend` so you
+ * should NOT free it after calling this function.
+ *
+ * @param refdb database to add the backend to
+ * @param backend pointer to a git_refdb_backend instance
+ * @return 0 on success; error code otherwise
+ */
+GIT_EXTERN(int) git_refdb_set_backend(
+ git_refdb *refdb,
+ git_refdb_backend *backend);
+
+GIT_END_DECL
+
+#endif
diff --git a/include/git2/sys/reflog.h b/include/git2/sys/reflog.h
new file mode 100644
index 000000000..c9d0041b9
--- /dev/null
+++ b/include/git2/sys/reflog.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_sys_git_reflog_h__
+#define INCLUDE_sys_git_reflog_h__
+
+#include "git2/common.h"
+#include "git2/types.h"
+#include "git2/oid.h"
+
+GIT_BEGIN_DECL
+
+GIT_EXTERN(git_reflog_entry *) git_reflog_entry__alloc(void);
+GIT_EXTERN(void) git_reflog_entry__free(git_reflog_entry *entry);
+
+GIT_END_DECL
+
+#endif
diff --git a/include/git2/sys/refs.h b/include/git2/sys/refs.h
new file mode 100644
index 000000000..dd95ca12c
--- /dev/null
+++ b/include/git2/sys/refs.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_sys_git_refdb_h__
+#define INCLUDE_sys_git_refdb_h__
+
+#include "git2/common.h"
+#include "git2/types.h"
+#include "git2/oid.h"
+
+/**
+ * Create a new direct reference from an OID.
+ *
+ * @param name the reference name
+ * @param oid the object id for a direct reference
+ * @param peel the first non-tag object's OID, or NULL
+ * @return the created git_reference or NULL on error
+ */
+GIT_EXTERN(git_reference *) git_reference__alloc(
+ const char *name,
+ const git_oid *oid,
+ const git_oid *peel);
+
+/**
+ * Create a new symbolic reference.
+ *
+ * @param name the reference name
+ * @param target the target for a symbolic reference
+ * @return the created git_reference or NULL on error
+ */
+GIT_EXTERN(git_reference *) git_reference__alloc_symbolic(
+ const char *name,
+ const char *target);
+
+#endif
diff --git a/include/git2/sys/repository.h b/include/git2/sys/repository.h
new file mode 100644
index 000000000..36f8b5836
--- /dev/null
+++ b/include/git2/sys/repository.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_sys_git_repository_h__
+#define INCLUDE_sys_git_repository_h__
+
+/**
+ * @file git2/sys/repository.h
+ * @brief Git repository custom implementation routines
+ * @defgroup git_backend Git custom backend APIs
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Create a new repository with neither backends nor config object
+ *
+ * Note that this is only useful if you wish to associate the repository
+ * with a non-filesystem-backed object database and config store.
+ *
+ * @param out The blank repository
+ * @return 0 on success, or an error code
+ */
+GIT_EXTERN(int) git_repository_new(git_repository **out);
+
+/**
+ * Reset all the internal state in a repository.
+ *
+ * This will free all the mapped memory and internal objects
+ * of the repository and leave it in a "blank" state.
+ *
+ * There's no need to call this function directly unless you're
+ * trying to aggressively cleanup the repo before its
+ * deallocation. `git_repository_free` already performs this operation
+ * before deallocation the repo.
+ */
+GIT_EXTERN(void) git_repository__cleanup(git_repository *repo);
+
+/**
+ * Update the filesystem config settings for an open repository
+ *
+ * When a repository is initialized, config values are set based on the
+ * properties of the filesystem that the repository is on, such as
+ * "core.ignorecase", "core.filemode", "core.symlinks", etc. If the
+ * repository is moved to a new filesystem, these properties may no
+ * longer be correct and API calls may not behave as expected. This
+ * call reruns the phase of repository initialization that sets those
+ * properties to compensate for the current filesystem of the repo.
+ *
+ * @param repo A repository object
+ * @param recurse_submodules Should submodules be updated recursively
+ * @returrn 0 on success, < 0 on error
+ */
+GIT_EXTERN(int) git_repository_reinit_filesystem(
+ git_repository *repo,
+ int recurse_submodules);
+
+/**
+ * Set the configuration file for this repository
+ *
+ * This configuration file will be used for all configuration
+ * queries involving this repository.
+ *
+ * The repository will keep a reference to the config file;
+ * the user must still free the config after setting it
+ * to the repository, or it will leak.
+ *
+ * @param repo A repository object
+ * @param config A Config object
+ */
+GIT_EXTERN(void) git_repository_set_config(git_repository *repo, git_config *config);
+
+/**
+ * Set the Object Database for this repository
+ *
+ * The ODB will be used for all object-related operations
+ * involving this repository.
+ *
+ * The repository will keep a reference to the ODB; the user
+ * must still free the ODB object after setting it to the
+ * repository, or it will leak.
+ *
+ * @param repo A repository object
+ * @param odb An ODB object
+ */
+GIT_EXTERN(void) git_repository_set_odb(git_repository *repo, git_odb *odb);
+
+/**
+ * Set the Reference Database Backend for this repository
+ *
+ * The refdb will be used for all reference related operations
+ * involving this repository.
+ *
+ * The repository will keep a reference to the refdb; the user
+ * must still free the refdb object after setting it to the
+ * repository, or it will leak.
+ *
+ * @param repo A repository object
+ * @param refdb An refdb object
+ */
+GIT_EXTERN(void) git_repository_set_refdb(git_repository *repo, git_refdb *refdb);
+
+/**
+ * Set the index file for this repository
+ *
+ * This index will be used for all index-related operations
+ * involving this repository.
+ *
+ * The repository will keep a reference to the index file;
+ * the user must still free the index after setting it
+ * to the repository, or it will leak.
+ *
+ * @param repo A repository object
+ * @param index An index object
+ */
+GIT_EXTERN(void) git_repository_set_index(git_repository *repo, git_index *index);
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/tag.h b/include/git2/tag.h
index 2b10d4525..c822cee7c 100644
--- a/include/git2/tag.h
+++ b/include/git2/tag.h
@@ -1,26 +1,8 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_tag_h__
#define INCLUDE_git_tag_h__
@@ -29,6 +11,7 @@
#include "types.h"
#include "oid.h"
#include "object.h"
+#include "strarray.h"
/**
* @file git2/tag.h
@@ -42,15 +25,13 @@ GIT_BEGIN_DECL
/**
* Lookup a tag object from the repository.
*
- * @param tag pointer to the looked up tag
+ * @param out pointer to the looked up tag
* @param repo the repo to use when locating the tag.
* @param id identity of the tag to locate.
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_INLINE(int) git_tag_lookup(git_tag **tag, git_repository *repo, const git_oid *id)
-{
- return git_object_lookup((git_object **)tag, repo, id, (git_otype)GIT_OBJ_TAG);
-}
+GIT_EXTERN(int) git_tag_lookup(
+ git_tag **out, git_repository *repo, const git_oid *id);
/**
* Lookup a tag object from the repository,
@@ -58,34 +39,26 @@ GIT_INLINE(int) git_tag_lookup(git_tag **tag, git_repository *repo, const git_oi
*
* @see git_object_lookup_prefix
*
- * @param tag pointer to the looked up tag
+ * @param out pointer to the looked up tag
* @param repo the repo to use when locating the tag.
* @param id identity of the tag to locate.
* @param len the length of the short identifier
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_INLINE(int) git_tag_lookup_prefix(git_tag **tag, git_repository *repo, const git_oid *id, unsigned int len)
-{
- return git_object_lookup_prefix((git_object **)tag, repo, id, len, (git_otype)GIT_OBJ_TAG);
-}
+GIT_EXTERN(int) git_tag_lookup_prefix(
+ git_tag **out, git_repository *repo, const git_oid *id, size_t len);
/**
* Close an open tag
*
- * This is a wrapper around git_object_close()
+ * You can no longer use the git_tag pointer after this call.
*
- * IMPORTANT:
- * It *is* necessary to call this method when you stop
- * using a tag. Failure to do so will cause a memory leak.
+ * IMPORTANT: You MUST call this method when you are through with a tag to
+ * release memory. Failure to do so will cause a memory leak.
*
* @param tag the tag to close
*/
-
-GIT_INLINE(void) git_tag_close(git_tag *tag)
-{
- git_object_close((git_object *) tag);
-}
-
+GIT_EXTERN(void) git_tag_free(git_tag *tag);
/**
* Get the id of a tag.
@@ -93,7 +66,15 @@ GIT_INLINE(void) git_tag_close(git_tag *tag)
* @param tag a previously loaded tag.
* @return object identity for the tag.
*/
-GIT_EXTERN(const git_oid *) git_tag_id(git_tag *tag);
+GIT_EXTERN(const git_oid *) git_tag_id(const git_tag *tag);
+
+/**
+ * Get the repository that contains the tag.
+ *
+ * @param tag A previously loaded tag.
+ * @return Repository that contains this tag.
+ */
+GIT_EXTERN(git_repository *) git_tag_owner(const git_tag *tag);
/**
* Get the tagged object of a tag
@@ -101,11 +82,11 @@ GIT_EXTERN(const git_oid *) git_tag_id(git_tag *tag);
* This method performs a repository lookup for the
* given object and returns it
*
- * @param target pointer where to store the target
+ * @param target_out pointer where to store the target
* @param tag a previously loaded tag.
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_tag_target(git_object **target, git_tag *tag);
+GIT_EXTERN(int) git_tag_target(git_object **target_out, const git_tag *tag);
/**
* Get the OID of the tagged object of a tag
@@ -113,7 +94,7 @@ GIT_EXTERN(int) git_tag_target(git_object **target, git_tag *tag);
* @param tag a previously loaded tag.
* @return pointer to the OID
*/
-GIT_EXTERN(const git_oid *) git_tag_target_oid(git_tag *tag);
+GIT_EXTERN(const git_oid *) git_tag_target_id(const git_tag *tag);
/**
* Get the type of a tag's tagged object
@@ -121,7 +102,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(const git_tag *tag);
/**
* Get the name of a tag
@@ -129,23 +110,23 @@ GIT_EXTERN(git_otype) git_tag_type(git_tag *tag);
* @param tag a previously loaded tag.
* @return name of the tag
*/
-GIT_EXTERN(const char *) git_tag_name(git_tag *tag);
+GIT_EXTERN(const char *) git_tag_name(const git_tag *tag);
/**
* Get the tagger (author) of a tag
*
* @param tag a previously loaded tag.
- * @return reference to the tag's author
+ * @return reference to the tag's author or NULL when unspecified
*/
-GIT_EXTERN(const git_signature *) git_tag_tagger(git_tag *tag);
+GIT_EXTERN(const git_signature *) git_tag_tagger(const git_tag *tag);
/**
* Get the message of a tag
*
* @param tag a previously loaded tag.
- * @return message of the tag
+ * @return message of the tag or NULL when unspecified
*/
-GIT_EXTERN(const char *) git_tag_message(git_tag *tag);
+GIT_EXTERN(const char *) git_tag_message(const git_tag *tag);
/**
@@ -155,6 +136,13 @@ GIT_EXTERN(const char *) git_tag_message(git_tag *tag);
* this tag object. If `force` is true and a reference
* already exists with the given name, it'll be replaced.
*
+ * The message will not be cleaned up. This can be achieved
+ * through `git_message_prettify()`.
+ *
+ * The tag name will be checked for validity. You must avoid
+ * the characters '~', '^', ':', '\\', '?', '[', and '*', and the
+ * sequences ".." and "@{" which have special meaning to revparse.
+ *
* @param oid Pointer where to store the OID of the
* newly created tag. If the tag already exists, this parameter
* will be the oid of the existing tag, and the function will
@@ -170,24 +158,55 @@ GIT_EXTERN(const char *) git_tag_message(git_tag *tag);
* must belong to the given `repo`.
*
* @param tagger Signature of the tagger for this tag, and
- * of the tagging time
+ * of the tagging time
*
* @param message Full message for this tag
*
* @param force Overwrite existing references
*
- * @return 0 on success; error code otherwise.
+ * @return 0 on success, GIT_EINVALIDSPEC or an error code
* A tag object is written to the ODB, and a proper reference
* is written in the /refs/tags folder, pointing to it
*/
GIT_EXTERN(int) git_tag_create(
- git_oid *oid,
- git_repository *repo,
- const char *tag_name,
- const git_object *target,
- const git_signature *tagger,
- const char *message,
- int force);
+ git_oid *oid,
+ git_repository *repo,
+ const char *tag_name,
+ const git_object *target,
+ const git_signature *tagger,
+ const char *message,
+ int force);
+
+/**
+ * Create a new tag in the object database pointing to a git_object
+ *
+ * The message will not be cleaned up. This can be achieved
+ * through `git_message_prettify()`.
+ *
+ * @param oid Pointer where to store the OID of the
+ * newly created tag
+ *
+ * @param repo Repository where to store the tag
+ *
+ * @param tag_name Name for the tag
+ *
+ * @param target Object to which this tag points. This object
+ * must belong to the given `repo`.
+ *
+ * @param tagger Signature of the tagger for this tag, and
+ * of the tagging time
+ *
+ * @param message Full message for this tag
+ *
+ * @return 0 on success or an error code
+ */
+GIT_EXTERN(int) git_tag_annotation_create(
+ git_oid *oid,
+ git_repository *repo,
+ const char *tag_name,
+ const git_object *target,
+ const git_signature *tagger,
+ const char *message);
/**
* Create a new tag in the repository from a buffer
@@ -196,13 +215,13 @@ GIT_EXTERN(int) git_tag_create(
* @param repo Repository where to store the tag
* @param buffer Raw tag data
* @param force Overwrite existing tags
- * @return 0 on sucess; error code otherwise
+ * @return 0 on success; error code otherwise
*/
GIT_EXTERN(int) git_tag_create_frombuffer(
- git_oid *oid,
- git_repository *repo,
- const char *buffer,
- int force);
+ git_oid *oid,
+ git_repository *repo,
+ const char *buffer,
+ int force);
/**
* Create a new lightweight tag pointing at a target object
@@ -211,6 +230,9 @@ GIT_EXTERN(int) git_tag_create_frombuffer(
* this target object. If `force` is true and a reference
* already exists with the given name, it'll be replaced.
*
+ * The tag name will be checked for validity.
+ * See `git_tag_create()` for rules about valid names.
+ *
* @param oid Pointer where to store the OID of the provided
* target object. If the tag already exists, this parameter
* will be filled with the oid of the existing pointed object
@@ -227,30 +249,33 @@ GIT_EXTERN(int) git_tag_create_frombuffer(
*
* @param force Overwrite existing references
*
- * @return 0 on success; error code otherwise.
+ * @return 0 on success, GIT_EINVALIDSPEC or an error code
* A proper reference is written in the /refs/tags folder,
- * pointing to the provided target object
+ * pointing to the provided target object
*/
GIT_EXTERN(int) git_tag_create_lightweight(
- git_oid *oid,
- git_repository *repo,
- const char *tag_name,
- const git_object *target,
- int force);
+ git_oid *oid,
+ git_repository *repo,
+ const char *tag_name,
+ const git_object *target,
+ int force);
/**
* Delete an existing tag reference.
*
+ * The tag name will be checked for validity.
+ * See `git_tag_create()` for rules about valid names.
+ *
* @param repo Repository where lives the tag
*
* @param tag_name Name of the tag to be deleted;
* this name is validated for consistency.
*
- * @return 0 on success; error code otherwise.
+ * @return 0 on success, GIT_EINVALIDSPEC or an error code
*/
GIT_EXTERN(int) git_tag_delete(
- git_repository *repo,
- const char *tag_name);
+ git_repository *repo,
+ const char *tag_name);
/**
* Fill a list with all the tags in the Repository
@@ -263,11 +288,11 @@ GIT_EXTERN(int) git_tag_delete(
* @param tag_names Pointer to a git_strarray structure where
* the tag names will be stored
* @param repo Repository where to find the tags
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
GIT_EXTERN(int) git_tag_list(
- git_strarray *tag_names,
- git_repository *repo);
+ git_strarray *tag_names,
+ git_repository *repo);
/**
* Fill a list with all the tags in the Repository
@@ -285,12 +310,42 @@ GIT_EXTERN(int) git_tag_list(
* the tag names will be stored
* @param pattern Standard fnmatch pattern
* @param repo Repository where to find the tags
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
GIT_EXTERN(int) git_tag_list_match(
- git_strarray *tag_names,
- const char *pattern,
- git_repository *repo);
+ git_strarray *tag_names,
+ const char *pattern,
+ git_repository *repo);
+
+
+typedef int (*git_tag_foreach_cb)(const char *name, git_oid *oid, void *payload);
+
+/**
+ * Call callback `cb' for each tag in the repository
+ *
+ * @param repo Repository
+ * @param callback Callback function
+ * @param payload Pointer to callback data (optional)
+ */
+GIT_EXTERN(int) git_tag_foreach(
+ git_repository *repo,
+ git_tag_foreach_cb callback,
+ void *payload);
+
+
+/**
+ * Recursively peel a tag until a non tag git_object is found
+ *
+ * The retrieved `tag_target` object is owned by the repository
+ * and should be closed with the `git_object_free` method.
+ *
+ * @param tag_target_out Pointer to the peeled git_object
+ * @param tag The tag to be processed
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_tag_peel(
+ git_object **tag_target_out,
+ const git_tag *tag);
/** @} */
GIT_END_DECL
diff --git a/include/git2/thread-utils.h b/include/git2/thread-utils.h
deleted file mode 100644
index 62e6199a4..000000000
--- a/include/git2/thread-utils.h
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#ifndef INCLUDE_git_thread_utils_h__
-#define INCLUDE_git_thread_utils_h__
-
-/*
- * How TLS works is compiler+platform dependant
- * Sources: http://en.wikipedia.org/wiki/Thread-Specific_Storage
- * http://predef.sourceforge.net/precomp.html
- */
-
-#ifdef GIT_THREADS
-# define GIT_HAS_TLS 1
-
-/* No TLS in Cygwin */
-# if defined(__CHECKER__) || defined(__CYGWIN__)
-# undef GIT_HAS_TLS
-# define GIT_TLS
-
-/* No TLS in Mach binaries for Mac OS X */
-# elif defined(__APPLE__) && defined(__MACH__)
-# undef GIT_TLS
-# define GIT_TLS
-
-/* Normal TLS for GCC */
-# elif defined(__GNUC__) || \
- defined(__SUNPRO_C) || \
- defined(__SUNPRO_CC) || \
- defined(__xlc__) || \
- defined(__xlC__)
-# define GIT_TLS __thread
-
-/* ICC may run on Windows or Linux */
-# elif defined(__INTEL_COMPILER)
-# if defined(_WIN32) || defined(_WIN32_CE)
-# define GIT_TLS __declspec(thread)
-# else
-# define GIT_TLS __thread
-# endif
-
-/* Declspec for MSVC in Win32 */
-# elif defined(_WIN32) || \
- defined(_WIN32_CE) || \
- defined(__BORLANDC__)
-# define GIT_TLS __declspec(thread)
-
-/* Other platform; no TLS */
-# else
-# undef GIT_HAS_TLS
-# define GIT_TLS /* nothing: tls vars are thread-global */
-# endif
-#else /* Disable TLS if libgit2 is not threadsafe */
-# define GIT_TLS
-#endif /* GIT_THREADS */
-
-#endif /* INCLUDE_git_thread_utils_h__ */
diff --git a/include/git2/threads.h b/include/git2/threads.h
new file mode 100644
index 000000000..11f89729a
--- /dev/null
+++ b/include/git2/threads.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_threads_h__
+#define INCLUDE_git_threads_h__
+
+#include "common.h"
+
+/**
+ * @file git2/threads.h
+ * @brief Library level thread functions
+ * @defgroup git_thread Threading functions
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Init the threading system.
+ *
+ * If libgit2 has been built with GIT_THREADS
+ * on, this function must be called once before
+ * any other library functions.
+ *
+ * If libgit2 has been built without GIT_THREADS
+ * support, this function is a no-op.
+ *
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_threads_init(void);
+
+/**
+ * Shutdown the threading system.
+ *
+ * If libgit2 has been built with GIT_THREADS
+ * on, this function must be called before shutting
+ * down the library.
+ *
+ * If libgit2 has been built without GIT_THREADS
+ * support, this function is a no-op.
+ */
+GIT_EXTERN(void) git_threads_shutdown(void);
+
+/** @} */
+GIT_END_DECL
+#endif
+
diff --git a/include/git2/trace.h b/include/git2/trace.h
new file mode 100644
index 000000000..f9b4d6ff6
--- /dev/null
+++ b/include/git2/trace.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_trace_h__
+#define INCLUDE_git_trace_h__
+
+#include "common.h"
+#include "types.h"
+
+/**
+ * @file git2/trace.h
+ * @brief Git tracing configuration routines
+ * @defgroup git_trace Git tracing configuration routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * Available tracing levels. When tracing is set to a particular level,
+ * callers will be provided tracing at the given level and all lower levels.
+ */
+typedef enum {
+ /** No tracing will be performed. */
+ GIT_TRACE_NONE = 0,
+
+ /** Severe errors that may impact the program's execution */
+ GIT_TRACE_FATAL = 1,
+
+ /** Errors that do not impact the program's execution */
+ GIT_TRACE_ERROR = 2,
+
+ /** Warnings that suggest abnormal data */
+ GIT_TRACE_WARN = 3,
+
+ /** Informational messages about program execution */
+ GIT_TRACE_INFO = 4,
+
+ /** Detailed data that allows for debugging */
+ GIT_TRACE_DEBUG = 5,
+
+ /** Exceptionally detailed debugging data */
+ GIT_TRACE_TRACE = 6
+} git_trace_level_t;
+
+/**
+ * An instance for a tracing function
+ */
+typedef void (*git_trace_callback)(git_trace_level_t level, const char *msg);
+
+/**
+ * Sets the system tracing configuration to the specified level with the
+ * specified callback. When system events occur at a level equal to, or
+ * lower than, the given level they will be reported to the given callback.
+ *
+ * @param level Level to set tracing to
+ * @param cb Function to call with trace data
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_trace_set(git_trace_level_t level, git_trace_callback cb);
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/transport.h b/include/git2/transport.h
index 982b081f8..af7812b5d 100644
--- a/include/git2/transport.h
+++ b/include/git2/transport.h
@@ -1,57 +1,542 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "common.h"
-#include "types.h"
+#include "indexer.h"
#include "net.h"
+#include "types.h"
+
+#ifdef GIT_SSH
+#include <libssh2.h>
+#endif
/**
* @file git2/transport.h
- * @brief Git protocol transport abstraction
- * @defgroup git_transport Git protocol transport abstraction
+ * @brief Git transport interfaces and functions
+ * @defgroup git_transport interfaces and functions
* @ingroup Git
* @{
*/
GIT_BEGIN_DECL
+/*
+ *** Begin interface for credentials acquisition ***
+ */
+
+/** Authentication type requested */
+typedef enum {
+ /* git_cred_userpass_plaintext */
+ GIT_CREDTYPE_USERPASS_PLAINTEXT = (1u << 0),
+
+ /* git_cred_ssh_key */
+ GIT_CREDTYPE_SSH_KEY = (1u << 1),
+
+ /* git_cred_ssh_custom */
+ GIT_CREDTYPE_SSH_CUSTOM = (1u << 2),
+
+ /* git_cred_default */
+ GIT_CREDTYPE_DEFAULT = (1u << 3),
+
+ /* git_cred_ssh_interactive */
+ GIT_CREDTYPE_SSH_INTERACTIVE = (1u << 4),
+} git_credtype_t;
+
+/* The base structure for all credential types */
+typedef struct git_cred git_cred;
+
+struct git_cred {
+ git_credtype_t credtype;
+ void (*free)(git_cred *cred);
+};
+
+/** A plaintext username and password */
+typedef struct {
+ git_cred parent;
+ char *username;
+ char *password;
+} git_cred_userpass_plaintext;
+
+#ifdef GIT_SSH
+typedef LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC((*git_cred_sign_callback));
+typedef LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC((*git_cred_ssh_interactive_callback));
+#else
+typedef int (*git_cred_sign_callback)(void *, ...);
+typedef int (*git_cred_ssh_interactive_callback)(void *, ...);
+#endif
+
+/**
+ * A ssh key from disk
+ */
+typedef struct git_cred_ssh_key {
+ git_cred parent;
+ char *username;
+ char *publickey;
+ char *privatekey;
+ char *passphrase;
+} git_cred_ssh_key;
+
+/**
+ * Keyboard-interactive based ssh authentication
+ */
+typedef struct git_cred_ssh_interactive {
+ git_cred parent;
+ char *username;
+ git_cred_ssh_interactive_callback prompt_callback;
+ void *payload;
+} git_cred_ssh_interactive;
+
+/**
+ * A key with a custom signature function
+ */
+typedef struct git_cred_ssh_custom {
+ git_cred parent;
+ char *username;
+ char *publickey;
+ size_t publickey_len;
+ git_cred_sign_callback sign_callback;
+ void *payload;
+} git_cred_ssh_custom;
+
+/** A key for NTLM/Kerberos "default" credentials */
+typedef struct git_cred git_cred_default;
+
+/**
+ * Check whether a credential object contains username information.
+ *
+ * @param cred object to check
+ * @return 1 if the credential object has non-NULL username, 0 otherwise
+ */
+GIT_EXTERN(int) git_cred_has_username(git_cred *cred);
+
+/**
+ * Create a new plain-text username and password credential object.
+ * The supplied credential parameter will be internally duplicated.
+ *
+ * @param out The newly created credential object.
+ * @param username The username of the credential.
+ * @param password The password of the credential.
+ * @return 0 for success or an error code for failure
+ */
+GIT_EXTERN(int) git_cred_userpass_plaintext_new(
+ git_cred **out,
+ const char *username,
+ const char *password);
+
+/**
+ * Create a new passphrase-protected ssh key credential object.
+ * The supplied credential parameter will be internally duplicated.
+ *
+ * @param out The newly created credential object.
+ * @param username username to use to authenticate
+ * @param publickey The path to the public key of the credential.
+ * @param privatekey The path to the private key of the credential.
+ * @param passphrase The passphrase of the credential.
+ * @return 0 for success or an error code for failure
+ */
+GIT_EXTERN(int) git_cred_ssh_key_new(
+ git_cred **out,
+ const char *username,
+ const char *publickey,
+ const char *privatekey,
+ const char *passphrase);
+
+/**
+ * Create a new ssh keyboard-interactive based credential object.
+ * The supplied credential parameter will be internally duplicated.
+ *
+ * @param username Username to use to authenticate.
+ * @param prompt_callback The callback method used for prompts.
+ * @param payload Additional data to pass to the callback.
+ * @return 0 for success or an error code for failure.
+ */
+GIT_EXTERN(int) git_cred_ssh_interactive_new(
+ git_cred **out,
+ const char *username,
+ git_cred_ssh_interactive_callback prompt_callback,
+ void *payload);
+
+/**
+ * Create a new ssh key credential object used for querying an ssh-agent.
+ * The supplied credential parameter will be internally duplicated.
+ *
+ * @param out The newly created credential object.
+ * @param username username to use to authenticate
+ * @return 0 for success or an error code for failure
+ */
+GIT_EXTERN(int) git_cred_ssh_key_from_agent(
+ git_cred **out,
+ const char *username);
+
+/**
+ * Create an ssh key credential with a custom signing function.
+ *
+ * This lets you use your own function to sign the challenge.
+ *
+ * This function and its credential type is provided for completeness
+ * and wraps `libssh2_userauth_publickey()`, which is undocumented.
+ *
+ * The supplied credential parameter will be internally duplicated.
+ *
+ * @param out The newly created credential object.
+ * @param username username to use to authenticate
+ * @param publickey The bytes of the public key.
+ * @param publickey_len The length of the public key in bytes.
+ * @param sign_callback The callback method to sign the data during the challenge.
+ * @param payload Additional data to pass to the callback.
+ * @return 0 for success or an error code for failure
+ */
+GIT_EXTERN(int) git_cred_ssh_custom_new(
+ git_cred **out,
+ const char *username,
+ const char *publickey,
+ size_t publickey_len,
+ git_cred_sign_callback sign_callback,
+ void *payload);
+
+/**
+ * Create a "default" credential usable for Negotiate mechanisms like NTLM
+ * or Kerberos authentication.
+ *
+ * @return 0 for success or an error code for failure
+ */
+GIT_EXTERN(int) git_cred_default_new(git_cred **out);
+
+/**
+ * Signature of a function which acquires a credential object.
+ *
+ * - cred: The newly created credential object.
+ * - url: The resource for which we are demanding a credential.
+ * - username_from_url: The username that was embedded in a "user@host"
+ * remote url, or NULL if not included.
+ * - allowed_types: A bitmask stating which cred types are OK to return.
+ * - payload: The payload provided when specifying this callback.
+ * - returns 0 for success, < 0 to indicate an error, > 0 to indicate
+ * no credential was acquired
+ */
+typedef int (*git_cred_acquire_cb)(
+ git_cred **cred,
+ const char *url,
+ const char *username_from_url,
+ unsigned int allowed_types,
+ void *payload);
+
+/*
+ *** End interface for credentials acquisition ***
+ *** Begin base transport interface ***
+ */
+
+typedef enum {
+ GIT_TRANSPORTFLAGS_NONE = 0,
+ /* If the connection is secured with SSL/TLS, the authenticity
+ * of the server certificate should not be verified. */
+ GIT_TRANSPORTFLAGS_NO_CHECK_CERT = 1
+} git_transport_flags_t;
+
+typedef int (*git_transport_message_cb)(const char *str, int len, void *data);
+
+typedef struct git_transport git_transport;
+
+struct git_transport {
+ unsigned int version;
+ /* Set progress and error callbacks */
+ int (*set_callbacks)(
+ 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)(
+ git_transport *transport,
+ const char *url,
+ git_cred_acquire_cb cred_acquire_cb,
+ void *cred_acquire_payload,
+ int direction,
+ int flags);
+
+ /* This function may be called after a successful call to
+ * connect(). The array returned is owned by the transport and
+ * is guranteed until the next call of a transport function. */
+ int (*ls)(
+ const git_remote_head ***out,
+ size_t *size,
+ git_transport *transport);
+
+ /* Executes the push whose context is in the git_push object. */
+ int (*push)(git_transport *transport, git_push *push);
+
+ /* This function may be called after a successful call to connect(), when
+ * the direction is FETCH. The function performs a negotiation to calculate
+ * the wants list for the fetch. */
+ int (*negotiate_fetch)(
+ 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)(
+ git_transport *transport,
+ git_repository *repo,
+ git_transfer_progress *stats,
+ git_transfer_progress_cb progress_cb,
+ void *progress_payload);
+
+ /* Checks to see if the transport is connected */
+ int (*is_connected)(git_transport *transport);
+
+ /* Reads the flags value previously passed into connect() */
+ int (*read_flags)(git_transport *transport, int *flags);
+
+ /* Cancels any outstanding transport operation */
+ void (*cancel)(git_transport *transport);
+
+ /* This function is the reverse of connect() -- it terminates the
+ * connection to the remote end. */
+ int (*close)(git_transport *transport);
+
+ /* Frees/destructs the git_transport object. */
+ void (*free)(git_transport *transport);
+};
+
+#define GIT_TRANSPORT_VERSION 1
+#define GIT_TRANSPORT_INIT {GIT_TRANSPORT_VERSION}
+
+/**
+ * Initializes a `git_transport` with default values. Equivalent to
+ * creating an instance with GIT_TRANSPORT_INIT.
+ *
+ * @param opts the `git_transport` struct to initialize
+ * @param version Version of struct; pass `GIT_TRANSPORT_VERSION`
+ * @return Zero on success; -1 on failure.
+ */
+GIT_EXTERN(int) git_transport_init(
+ git_transport *opts,
+ unsigned int version);
+
+/**
+ * Function to use to create a transport from a URL. The transport database
+ * is scanned to find a transport that implements the scheme of the URI (i.e.
+ * git:// or http://) and a transport object is returned to the caller.
+ *
+ * @param out The newly created transport (out)
+ * @param owner The git_remote which will own this transport
+ * @param url The URL to connect to
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_transport_new(git_transport **out, git_remote *owner, const char *url);
+
+/* Signature of a function which creates a transport */
+typedef int (*git_transport_cb)(git_transport **out, git_remote *owner, void *param);
+
+/**
+ * Add a custom transport definition, to be used in addition to the built-in
+ * set of transports that come with libgit2.
+ *
+ * The caller is responsible for synchronizing calls to git_transport_register
+ * and git_transport_unregister with other calls to the library that
+ * instantiate transports.
+ *
+ * @param prefix The scheme (ending in "://") to match, i.e. "git://"
+ * @param priority The priority of this transport relative to others with
+ * the same prefix. Built-in transports have a priority of 1.
+ * @param cb The callback used to create an instance of the transport
+ * @param param A fixed parameter to pass to cb at creation time
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_transport_register(
+ const char *prefix,
+ unsigned priority,
+ git_transport_cb cb,
+ void *param);
+
+/**
+ *
+ * Unregister a custom transport definition which was previously registered
+ * with git_transport_register.
+ *
+ * @param prefix From the previous call to git_transport_register
+ * @param priority From the previous call to git_transport_register
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_transport_unregister(
+ const char *prefix,
+ unsigned priority);
+
+/* Transports which come with libgit2 (match git_transport_cb). The expected
+ * value for "param" is listed in-line below. */
+
+/**
+ * Create an instance of the dummy transport.
+ *
+ * @param out The newly created transport (out)
+ * @param owner The git_remote which will own this transport
+ * @param payload You must pass NULL for this parameter.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_transport_dummy(
+ git_transport **out,
+ git_remote *owner,
+ /* NULL */ void *payload);
+
/**
- * Get the appropriate transport for an URL.
- * @param tranport the transport for the url
- * @param url the url of the repo
+ * Create an instance of the local transport.
+ *
+ * @param out The newly created transport (out)
+ * @param owner The git_remote which will own this transport
+ * @param payload You must pass NULL for this parameter.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_transport_local(
+ git_transport **out,
+ git_remote *owner,
+ /* NULL */ void *payload);
+
+/**
+ * Create an instance of the smart transport.
+ *
+ * @param out The newly created transport (out)
+ * @param owner The git_remote which will own this transport
+ * @param payload A pointer to a git_smart_subtransport_definition
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_transport_new(git_transport **transport, const char *url);
+GIT_EXTERN(int) git_transport_smart(
+ git_transport **out,
+ git_remote *owner,
+ /* (git_smart_subtransport_definition *) */ void *payload);
+
+/*
+ *** End of base transport interface ***
+ *** Begin interface for subtransports for the smart transport ***
+ */
+
+/* The smart transport knows how to speak the git protocol, but it has no
+ * knowledge of how to establish a connection between it and another endpoint,
+ * or how to move data back and forth. For this, a subtransport interface is
+ * declared, and the smart transport delegates this work to the subtransports.
+ * Three subtransports are implemented: git, http, and winhttp. (The http and
+ * winhttp transports each implement both http and https.) */
+
+/* Subtransports can either be RPC = 0 (persistent connection) or RPC = 1
+ * (request/response). The smart transport handles the differences in its own
+ * logic. The git subtransport is RPC = 0, while http and winhttp are both
+ * RPC = 1. */
+
+/* Actions that the smart transport can ask
+ * a subtransport to perform */
+typedef enum {
+ GIT_SERVICE_UPLOADPACK_LS = 1,
+ GIT_SERVICE_UPLOADPACK = 2,
+ GIT_SERVICE_RECEIVEPACK_LS = 3,
+ GIT_SERVICE_RECEIVEPACK = 4,
+} git_smart_service_t;
+
+typedef struct git_smart_subtransport git_smart_subtransport;
+typedef struct git_smart_subtransport_stream git_smart_subtransport_stream;
+
+/* A stream used by the smart transport to read and write data
+ * from a subtransport */
+struct git_smart_subtransport_stream {
+ /* The owning subtransport */
+ git_smart_subtransport *subtransport;
+
+ int (*read)(
+ git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read);
+
+ int (*write)(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len);
+
+ void (*free)(
+ git_smart_subtransport_stream *stream);
+};
+
+/* An implementation of a subtransport which carries data for the
+ * smart transport */
+struct git_smart_subtransport {
+ int (* action)(
+ git_smart_subtransport_stream **out,
+ git_smart_subtransport *transport,
+ const char *url,
+ git_smart_service_t action);
+
+ /* Subtransports are guaranteed a call to close() between
+ * calls to action(), except for the following two "natural" progressions
+ * of actions against a constant URL.
+ *
+ * 1. UPLOADPACK_LS -> UPLOADPACK
+ * 2. RECEIVEPACK_LS -> RECEIVEPACK */
+ int (*close)(git_smart_subtransport *transport);
-GIT_EXTERN(int) git_transport_connect(git_transport *transport, int direction);
+ void (*free)(git_smart_subtransport *transport);
+};
-GIT_EXTERN(int) git_transport_ls(git_transport *transport, git_headarray *array);
-GIT_EXTERN(int) git_transport_close(git_transport *transport);
-GIT_EXTERN(void) git_transport_free(git_transport *transport);
+/* A function which creates a new subtransport for the smart transport */
+typedef int (*git_smart_subtransport_cb)(
+ git_smart_subtransport **out,
+ git_transport* owner);
-GIT_EXTERN(int) git_transport_add(git_transport *transport, const char *prefix);
+typedef struct git_smart_subtransport_definition {
+ /* The function to use to create the git_smart_subtransport */
+ git_smart_subtransport_cb callback;
+
+ /* True if the protocol is stateless; false otherwise. For example,
+ * http:// is stateless, but git:// is not. */
+ unsigned rpc;
+} git_smart_subtransport_definition;
+
+/* Smart transport subtransports that come with libgit2 */
+
+/**
+ * Create an instance of the http subtransport. This subtransport
+ * also supports https. On Win32, this subtransport may be implemented
+ * using the WinHTTP library.
+ *
+ * @param out The newly created subtransport
+ * @param owner The smart transport to own this subtransport
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_smart_subtransport_http(
+ git_smart_subtransport **out,
+ git_transport* owner);
+
+/**
+ * Create an instance of the git subtransport.
+ *
+ * @param out The newly created subtransport
+ * @param owner The smart transport to own this subtransport
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_smart_subtransport_git(
+ git_smart_subtransport **out,
+ git_transport* owner);
+
+/**
+ * Create an instance of the ssh 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_ssh(
+ git_smart_subtransport **out,
+ git_transport* owner);
+
+/*
+ *** End interface for subtransports for the smart transport ***
+ */
/** @} */
GIT_END_DECL
diff --git a/include/git2/tree.h b/include/git2/tree.h
index 5656f48f3..56922d40b 100644
--- a/include/git2/tree.h
+++ b/include/git2/tree.h
@@ -1,26 +1,8 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_tree_h__
#define INCLUDE_git_tree_h__
@@ -42,15 +24,13 @@ GIT_BEGIN_DECL
/**
* Lookup a tree object from the repository.
*
- * @param tree pointer to the looked up tree
- * @param repo the repo to use when locating the tree.
- * @param id identity of the tree to locate.
- * @return 0 on success; error code otherwise
+ * @param out Pointer to the looked up tree
+ * @param repo The repo to use when locating the tree.
+ * @param id Identity of the tree to locate.
+ * @return 0 or an error code
*/
-GIT_INLINE(int) git_tree_lookup(git_tree **tree, git_repository *repo, const git_oid *id)
-{
- return git_object_lookup((git_object **)tree, repo, id, GIT_OBJ_TREE);
-}
+GIT_EXTERN(int) git_tree_lookup(
+ git_tree **out, git_repository *repo, const git_oid *id);
/**
* Lookup a tree object from the repository,
@@ -58,34 +38,29 @@ GIT_INLINE(int) git_tree_lookup(git_tree **tree, git_repository *repo, const git
*
* @see git_object_lookup_prefix
*
- * @param tree pointer to the looked up tree
+ * @param out pointer to the looked up tree
* @param repo the repo to use when locating the tree.
* @param id identity of the tree to locate.
* @param len the length of the short identifier
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_INLINE(int) git_tree_lookup_prefix(git_tree **tree, git_repository *repo, const git_oid *id, unsigned int len)
-{
- return git_object_lookup_prefix((git_object **)tree, repo, id, len, GIT_OBJ_TREE);
-}
+GIT_EXTERN(int) git_tree_lookup_prefix(
+ git_tree **out,
+ git_repository *repo,
+ const git_oid *id,
+ size_t len);
/**
* Close an open tree
*
- * This is a wrapper around git_object_close()
+ * You can no longer use the git_tree pointer after this call.
*
- * IMPORTANT:
- * It *is* necessary to call this method when you stop
- * using a tree. Failure to do so will cause a memory leak.
+ * IMPORTANT: You MUST call this method when you stop using a tree to
+ * release memory. Failure to do so will cause a memory leak.
*
- * @param tree the tree to close
+ * @param tree The tree to close
*/
-
-GIT_INLINE(void) git_tree_close(git_tree *tree)
-{
- git_object_close((git_object *) tree);
-}
-
+GIT_EXTERN(void) git_tree_free(git_tree *tree);
/**
* Get the id of a tree.
@@ -93,7 +68,15 @@ GIT_INLINE(void) git_tree_close(git_tree *tree)
* @param tree a previously loaded tree.
* @return object identity for the tree.
*/
-GIT_EXTERN(const git_oid *) git_tree_id(git_tree *tree);
+GIT_EXTERN(const git_oid *) git_tree_id(const git_tree *tree);
+
+/**
+ * Get the repository that contains the tree.
+ *
+ * @param tree A previously loaded tree.
+ * @return Repository that contains this tree.
+ */
+GIT_EXTERN(git_repository *) git_tree_owner(const git_tree *tree);
/**
* Get the number of entries listed in a tree
@@ -101,33 +84,88 @@ GIT_EXTERN(const git_oid *) git_tree_id(git_tree *tree);
* @param tree a previously loaded tree.
* @return the number of entries in the tree
*/
-GIT_EXTERN(unsigned int) git_tree_entrycount(git_tree *tree);
+GIT_EXTERN(size_t) git_tree_entrycount(const git_tree *tree);
/**
* Lookup a tree entry by its filename
*
+ * This returns a git_tree_entry that is owned by the git_tree. You don't
+ * have to free it, but you must not use it after the git_tree is released.
+ *
* @param tree a previously loaded tree.
* @param filename the filename of the desired entry
* @return the tree entry; NULL if not found
*/
-GIT_EXTERN(const git_tree_entry *) git_tree_entry_byname(git_tree *tree, const char *filename);
+GIT_EXTERN(const git_tree_entry *) git_tree_entry_byname(
+ const git_tree *tree, const char *filename);
/**
* Lookup a tree entry by its position in the tree
*
+ * This returns a git_tree_entry that is owned by the git_tree. You don't
+ * have to free it, but you must not use it after the git_tree is released.
+ *
* @param tree a previously loaded tree.
* @param idx the position in the entry list
* @return the tree entry; NULL if not found
*/
-GIT_EXTERN(const git_tree_entry *) git_tree_entry_byindex(git_tree *tree, unsigned int idx);
+GIT_EXTERN(const git_tree_entry *) git_tree_entry_byindex(
+ const git_tree *tree, size_t idx);
/**
- * Get the UNIX file attributes of a tree entry
+ * Lookup a tree entry by SHA value.
*
- * @param entry a tree entry
- * @return attributes as an integer
+ * This returns a git_tree_entry that is owned by the git_tree. You don't
+ * have to free it, but you must not use it after the git_tree is released.
+ *
+ * Warning: this must examine every entry in the tree, so it is not fast.
+ *
+ * @param tree a previously loaded tree.
+ * @param id the sha being looked for
+ * @return the tree entry; NULL if not found
+ */
+GIT_EXTERN(const git_tree_entry *) git_tree_entry_byid(
+ const git_tree *tree, const git_oid *id);
+
+/**
+ * Retrieve a tree entry contained in a tree or in any of its subtrees,
+ * given its relative path.
+ *
+ * Unlike the other lookup functions, the returned tree entry is owned by
+ * the user and must be freed explicitly with `git_tree_entry_free()`.
+ *
+ * @param out Pointer where to store the tree entry
+ * @param root Previously loaded tree which is the root of the relative path
+ * @param path Path to the contained entry
+ * @return 0 on success; GIT_ENOTFOUND if the path does not exist
+ */
+GIT_EXTERN(int) git_tree_entry_bypath(
+ git_tree_entry **out,
+ const git_tree *root,
+ const char *path);
+
+/**
+ * Duplicate a tree entry
+ *
+ * Create a copy of a tree entry. The returned copy is owned by the user,
+ * and must be freed explicitly with `git_tree_entry_free()`.
+ *
+ * @param dest pointer where to store the copy
+ * @param source tree entry to duplicate
+ * @return 0 or an error code
*/
-GIT_EXTERN(unsigned int) git_tree_entry_attributes(const git_tree_entry *entry);
+GIT_EXTERN(int) git_tree_entry_dup(git_tree_entry **dest, const git_tree_entry *source);
+
+/**
+ * Free a user-owned tree entry
+ *
+ * IMPORTANT: This function is only needed for tree entries owned by the
+ * user, such as the ones returned by `git_tree_entry_dup()` or
+ * `git_tree_entry_bypath()`.
+ *
+ * @param entry The entry to free
+ */
+GIT_EXTERN(void) git_tree_entry_free(git_tree_entry *entry);
/**
* Get the filename of a tree entry
@@ -154,51 +192,66 @@ GIT_EXTERN(const git_oid *) git_tree_entry_id(const git_tree_entry *entry);
GIT_EXTERN(git_otype) git_tree_entry_type(const git_tree_entry *entry);
/**
- * Convert a tree entry to the git_object it points too.
+ * Get the UNIX file attributes of a tree entry
*
- * @param object pointer to the converted object
- * @param repo repository where to lookup the pointed object
* @param entry a tree entry
- * @return 0 on success; error code otherwise
+ * @return filemode as an integer
*/
-GIT_EXTERN(int) git_tree_entry_2object(git_object **object_out, git_repository *repo, const git_tree_entry *entry);
+GIT_EXTERN(git_filemode_t) git_tree_entry_filemode(const git_tree_entry *entry);
/**
- * Write a tree to the ODB from the index file
+ * Get the raw UNIX file attributes of a tree entry
*
- * 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.
+ * This function does not perform any normalization and is only useful
+ * if you need to be able to recreate the original tree object.
*
- * The index instance cannot be bare, and needs to be associated
- * to an existing repository.
+ * @param entry a tree entry
+ * @return filemode as an integer
+ */
+
+GIT_EXTERN(git_filemode_t) git_tree_entry_filemode_raw(const git_tree_entry *entry);
+/**
+ * Compare two tree entries
*
- * @param oid Pointer where to store the written tree
- * @param index Index to write
- * @return 0 on success; error code otherwise
+ * @param e1 first tree entry
+ * @param e2 second tree entry
+ * @return <0 if e1 is before e2, 0 if e1 == e2, >0 if e1 is after e2
+ */
+GIT_EXTERN(int) git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2);
+
+/**
+ * Convert a tree entry to the git_object it points to.
+ *
+ * You must call `git_object_free()` on the object when you are done with it.
+ *
+ * @param object_out pointer to the converted object
+ * @param repo repository where to lookup the pointed object
+ * @param entry a tree entry
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_tree_create_fromindex(git_oid *oid, git_index *index);
+GIT_EXTERN(int) git_tree_entry_to_object(
+ git_object **object_out,
+ git_repository *repo,
+ const git_tree_entry *entry);
/**
* Create a new tree builder.
*
- * The tree builder can be used to create or modify
- * trees in memory and write them as tree objects to the
- * database.
+ * The tree builder can be used to create or modify trees in memory and
+ * write them as tree objects to the database.
*
- * If the `source` parameter is not NULL, the tree builder
- * will be initialized with the entries of the given tree.
+ * If the `source` parameter is not NULL, the tree builder will be
+ * initialized with the entries of the given tree.
*
- * If the `source` parameter is NULL, the tree builder will
- * have no entries and will have to be filled manually.
+ * If the `source` parameter is NULL, the tree builder will start with no
+ * entries and will have to be filled manually.
*
- * @param builder_p Pointer where to store the tree builder
+ * @param out Pointer where to store the tree builder
* @param source Source tree to initialize the builder (optional)
- * @return 0 on sucess; error code otherwise
+ * @return 0 on success; error code otherwise
*/
-GIT_EXTERN(int) git_treebuilder_create(git_treebuilder **builder_p, const git_tree *source);
+GIT_EXTERN(int) git_treebuilder_create(
+ git_treebuilder **out, const git_tree *source);
/**
* Clear all the entires in the builder
@@ -208,6 +261,14 @@ GIT_EXTERN(int) git_treebuilder_create(git_treebuilder **builder_p, const git_tr
GIT_EXTERN(void) git_treebuilder_clear(git_treebuilder *bld);
/**
+ * Get the number of entries listed in a treebuilder
+ *
+ * @param bld a previously loaded treebuilder.
+ * @return the number of entries in the treebuilder
+ */
+GIT_EXTERN(unsigned int) git_treebuilder_entrycount(git_treebuilder *bld);
+
+/**
* Free a tree builder
*
* This will clear all the entries and free to builder.
@@ -228,7 +289,8 @@ GIT_EXTERN(void) git_treebuilder_free(git_treebuilder *bld);
* @param filename Name of the entry
* @return pointer to the entry; NULL if not found
*/
-GIT_EXTERN(const git_tree_entry *) git_treebuilder_get(git_treebuilder *bld, const char *filename);
+GIT_EXTERN(const git_tree_entry *) git_treebuilder_get(
+ git_treebuilder *bld, const char *filename);
/**
* Add or update an entry to the builder
@@ -236,20 +298,31 @@ GIT_EXTERN(const git_tree_entry *) git_treebuilder_get(git_treebuilder *bld, con
* Insert a new entry for `filename` in the builder with the
* given attributes.
*
- * if an entry named `filename` already exists, its attributes
+ * If an entry named `filename` already exists, its attributes
* will be updated with the given ones.
*
- * The optional pointer `entry_out` can be used to retrieve a
- * pointer to the newly created/updated entry.
+ * The optional pointer `out` can be used to retrieve a pointer to
+ * the newly created/updated entry. Pass NULL if you do not need it.
*
- * @param entry_out Pointer to store the entry (optional)
+ * No attempt is being made to ensure that the provided oid points
+ * to an existing git object in the object database, nor that the
+ * attributes make sense regarding the type of the pointed at object.
+ *
+ * @param out Pointer to store the entry (optional)
* @param bld Tree builder
* @param filename Filename of the entry
* @param id SHA1 oid of the entry
- * @param attributes Folder attributes of the entry
- * @return 0 on success; error code otherwise
+ * @param filemode Folder attributes of the entry. This parameter must
+ * be valued with one of the following entries: 0040000, 0100644,
+ * 0100755, 0120000 or 0160000.
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_treebuilder_insert(git_tree_entry **entry_out, git_treebuilder *bld, const char *filename, const git_oid *id, unsigned int attributes);
+GIT_EXTERN(int) git_treebuilder_insert(
+ const git_tree_entry **out,
+ git_treebuilder *bld,
+ const char *filename,
+ const git_oid *id,
+ git_filemode_t filemode);
/**
* Remove an entry from the builder by its filename
@@ -257,35 +330,84 @@ GIT_EXTERN(int) git_treebuilder_insert(git_tree_entry **entry_out, git_treebuild
* @param bld Tree builder
* @param filename Filename of the entry to remove
*/
-GIT_EXTERN(int) git_treebuilder_remove(git_treebuilder *bld, const char *filename);
+GIT_EXTERN(int) git_treebuilder_remove(
+ git_treebuilder *bld, const char *filename);
/**
- * Filter the entries in the tree
+ * Callback for git_treebuilder_filter
*
- * The `filter` callback will be called for each entry
- * in the tree with a pointer to the entry and the
- * provided `payload`: if the callback returns 1, the
- * entry will be filtered (removed from the builder).
+ * The return value is treated as a boolean, with zero indicating that the
+ * entry should be left alone and any non-zero value meaning that the
+ * entry should be removed from the treebuilder list (i.e. filtered out).
+ */
+typedef int (*git_treebuilder_filter_cb)(
+ const git_tree_entry *entry, void *payload);
+
+/**
+ * Selectively remove entries in the tree
+ *
+ * The `filter` callback will be called for each entry in the tree with a
+ * pointer to the entry and the provided `payload`; if the callback returns
+ * non-zero, the entry will be filtered (removed from the builder).
*
* @param bld Tree builder
* @param filter Callback to filter entries
+ * @param payload Extra data to pass to filter callback
*/
-GIT_EXTERN(void) git_treebuilder_filter(git_treebuilder *bld, int (*filter)(const git_tree_entry *, void *), void *payload);
+GIT_EXTERN(void) git_treebuilder_filter(
+ git_treebuilder *bld,
+ git_treebuilder_filter_cb filter,
+ void *payload);
/**
* Write the contents of the tree builder as a tree object
*
- * The tree builder will be written to the given `repo`, and
- * it's identifying SHA1 hash will be stored in the `oid`
- * pointer.
+ * The tree builder will be written to the given `repo`, and its
+ * identifying SHA1 hash will be stored in the `id` pointer.
*
- * @param oid Pointer where to store the written OID
- * @param repo Repository where to store the object
+ * @param id Pointer to store the OID of the newly written tree
+ * @param repo Repository in which to store the object
* @param bld Tree builder to write
- * @return 0 on success; error code otherwise
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *bld);
+GIT_EXTERN(int) git_treebuilder_write(
+ git_oid *id, git_repository *repo, git_treebuilder *bld);
+
+
+/** Callback for the tree traversal method */
+typedef int (*git_treewalk_cb)(
+ const char *root, const git_tree_entry *entry, void *payload);
+
+/** Tree traversal modes */
+typedef enum {
+ GIT_TREEWALK_PRE = 0, /* Pre-order */
+ GIT_TREEWALK_POST = 1, /* Post-order */
+} git_treewalk_mode;
+
+/**
+ * Traverse the entries in a tree and its subtrees in post or pre order.
+ *
+ * The entries will be traversed in the specified order, children subtrees
+ * will be automatically loaded as required, and the `callback` will be
+ * called once per entry with the current (relative) root for the entry and
+ * the entry data itself.
+ *
+ * If the callback returns a positive value, the passed entry will be
+ * skipped on the traversal (in pre mode). A negative value stops the walk.
+ *
+ * @param tree The tree to walk
+ * @param mode Traversal mode (pre or post-order)
+ * @param callback Function to call on each tree entry
+ * @param payload Opaque pointer to be passed on each callback
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_tree_walk(
+ const git_tree *tree,
+ git_treewalk_mode mode,
+ git_treewalk_cb callback,
+ void *payload);
/** @} */
+
GIT_END_DECL
#endif
diff --git a/include/git2/types.h b/include/git2/types.h
index 9d14c3e2f..6295ebbfa 100644
--- a/include/git2/types.h
+++ b/include/git2/types.h
@@ -1,30 +1,14 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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_types_h__
#define INCLUDE_git_types_h__
+#include "common.h"
+
/**
* @file git2/types.h
* @brief libgit2 base & compatibility types
@@ -48,11 +32,14 @@ GIT_BEGIN_DECL
* stat() functions, for all platforms.
*/
#include <sys/types.h>
+#ifdef __amigaos4__
+#include <stdint.h>
+#endif
#if defined(_MSC_VER)
typedef __int64 git_off_t;
-typedef __time64_t git_time_t;
+typedef __time64_t git_time_t;
#elif defined(__MINGW32__)
@@ -64,7 +51,7 @@ typedef __time64_t git_time_t;
typedef __haiku_std_int64 git_off_t;
typedef __haiku_std_int64 git_time_t;
-#else /* POSIX */
+#else /* POSIX */
/*
* Note: Can't use off_t since if a client program includes <sys/types.h>
@@ -79,15 +66,15 @@ typedef int64_t git_time_t;
/** Basic type (loose or packed) of any Git object. */
typedef enum {
GIT_OBJ_ANY = -2, /**< Object can be any of the following */
- GIT_OBJ_BAD = -1, /**< Object is invalid. */
- GIT_OBJ__EXT1 = 0, /**< Reserved for future use. */
- GIT_OBJ_COMMIT = 1, /**< A commit object. */
- GIT_OBJ_TREE = 2, /**< A tree (directory listing) object. */
- GIT_OBJ_BLOB = 3, /**< A file revision object. */
- GIT_OBJ_TAG = 4, /**< An annotated tag object. */
- GIT_OBJ__EXT2 = 5, /**< Reserved for future use. */
- GIT_OBJ_OFS_DELTA = 6, /**< A delta, base is given by an offset. */
- GIT_OBJ_REF_DELTA = 7, /**< A delta, base is given by object id. */
+ GIT_OBJ_BAD = -1, /**< Object is invalid. */
+ GIT_OBJ__EXT1 = 0, /**< Reserved for future use. */
+ GIT_OBJ_COMMIT = 1, /**< A commit object. */
+ GIT_OBJ_TREE = 2, /**< A tree (directory listing) object. */
+ GIT_OBJ_BLOB = 3, /**< A file revision object. */
+ GIT_OBJ_TAG = 4, /**< An annotated tag object. */
+ GIT_OBJ__EXT2 = 5, /**< Reserved for future use. */
+ GIT_OBJ_OFS_DELTA = 6, /**< A delta, base is given by an offset. */
+ GIT_OBJ_REF_DELTA = 7, /**< A delta, base is given by object id. */
} git_otype;
/** An open object database handle. */
@@ -102,6 +89,15 @@ 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;
+
+/** An open refs database handle. */
+typedef struct git_refdb git_refdb;
+
+/** A custom backend for refs */
+typedef struct git_refdb_backend git_refdb_backend;
+
/**
* Representation of an existing git repository,
* including all its object contents
@@ -135,11 +131,14 @@ typedef struct git_treebuilder git_treebuilder;
/** Memory representation of an index file. */
typedef struct git_index git_index;
+/** An iterator for conflicts in the index. */
+typedef struct git_index_conflict_iterator git_index_conflict_iterator;
+
/** Memory representation of a set of config files */
typedef struct git_config git_config;
/** Interface to access a configuration file */
-typedef struct git_config_file git_config_file;
+typedef struct git_config_backend git_config_backend;
/** Representation of a reference log entry */
typedef struct git_reflog_entry git_reflog_entry;
@@ -147,43 +146,202 @@ typedef struct git_reflog_entry git_reflog_entry;
/** Representation of a reference log */
typedef struct git_reflog git_reflog;
+/** Representation of a git note */
+typedef struct git_note git_note;
+
+/** Representation of a git packbuilder */
+typedef struct git_packbuilder git_packbuilder;
+
/** Time in a signature */
typedef struct git_time {
- git_time_t time; /** time in seconds from epoch */
- int offset; /** timezone offset, in minutes */
+ git_time_t time; /**< time in seconds from epoch */
+ int offset; /**< timezone offset, in minutes */
} git_time;
/** An action signature (e.g. for committers, taggers, etc) */
typedef struct git_signature {
- char *name; /** full name of the author */
- char *email; /** email of the author */
- git_time when; /** time when the action happened */
+ char *name; /**< full name of the author */
+ char *email; /**< email of the author */
+ git_time when; /**< time when the action happened */
} git_signature;
/** In-memory representation of a reference. */
typedef struct git_reference git_reference;
+/** Iterator for references */
+typedef struct git_reference_iterator git_reference_iterator;
+
+/** Merge heads, the input to merge */
+typedef struct git_merge_head git_merge_head;
+
+/** Merge result */
+typedef struct git_merge_result git_merge_result;
+
+/** Representation of a status collection */
+typedef struct git_status_list git_status_list;
+
+
/** Basic type of any Git reference. */
typedef enum {
- GIT_REF_INVALID = 0, /** Invalid reference */
- GIT_REF_OID = 1, /** A reference which points at an object id */
- GIT_REF_SYMBOLIC = 2, /** A reference which points at another reference */
- GIT_REF_PACKED = 4,
- GIT_REF_HAS_PEEL = 8,
- GIT_REF_LISTALL = GIT_REF_OID|GIT_REF_SYMBOLIC|GIT_REF_PACKED,
-} git_rtype;
+ GIT_REF_INVALID = 0, /**< Invalid reference */
+ GIT_REF_OID = 1, /**< A reference which points at an object id */
+ GIT_REF_SYMBOLIC = 2, /**< A reference which points at another reference */
+ GIT_REF_LISTALL = GIT_REF_OID|GIT_REF_SYMBOLIC,
+} git_ref_t;
+
+/** Basic type of any Git branch. */
+typedef enum {
+ GIT_BRANCH_LOCAL = 1,
+ GIT_BRANCH_REMOTE = 2,
+ GIT_BRANCH_ALL = GIT_BRANCH_LOCAL|GIT_BRANCH_REMOTE,
+} git_branch_t;
+/** Valid modes for index and tree entries. */
+typedef enum {
+ GIT_FILEMODE_NEW = 0000000,
+ GIT_FILEMODE_TREE = 0040000,
+ GIT_FILEMODE_BLOB = 0100644,
+ GIT_FILEMODE_BLOB_EXECUTABLE = 0100755,
+ GIT_FILEMODE_LINK = 0120000,
+ GIT_FILEMODE_COMMIT = 0160000,
+} git_filemode_t;
typedef struct git_refspec git_refspec;
typedef struct git_remote git_remote;
+typedef struct git_push git_push;
-/** A transport to use */
-typedef struct git_transport git_transport;
+typedef struct git_remote_head git_remote_head;
+typedef struct git_remote_callbacks git_remote_callbacks;
+
+/**
+ * This is passed as the first argument to the callback to allow the
+ * user to see the progress.
+ *
+ * - total_objects: number of objects in the packfile being downloaded
+ * - indexed_objects: received objects that have been hashed
+ * - received_objects: objects which have been downloaded
+ * - local_objects: locally-available objects that have been injected
+ * in order to fix a thin pack.
+ * - received-bytes: size of the packfile received up to now
+ */
+typedef struct git_transfer_progress {
+ unsigned int total_objects;
+ unsigned int indexed_objects;
+ unsigned int received_objects;
+ unsigned int local_objects;
+ unsigned int total_deltas;
+ unsigned int indexed_deltas;
+ size_t received_bytes;
+} git_transfer_progress;
-typedef int (*git_transport_cb)(git_transport **transport);
+/**
+ * Type for progress callbacks during indexing. Return a value less than zero
+ * to cancel the transfer.
+ *
+ * @param stats Structure containing information about the state of the transfer
+ * @param payload Payload provided by caller
+ */
+typedef int (*git_transfer_progress_cb)(const git_transfer_progress *stats, void *payload);
-typedef struct git_remote_head git_remote_head;
-typedef struct git_headarray git_headarray;
+/**
+ * Opaque structure representing a submodule.
+ */
+typedef struct git_submodule git_submodule;
+
+/**
+ * Submodule update values
+ *
+ * These values represent settings for the `submodule.$name.update`
+ * configuration value which says how to handle `git submodule update` for
+ * this submodule. The value is usually set in the ".gitmodules" file and
+ * copied to ".git/config" when the submodule is initialized.
+ *
+ * You can override this setting on a per-submodule basis with
+ * `git_submodule_set_update()` and write the changed value to disk using
+ * `git_submodule_save()`. If you have overwritten the value, you can
+ * revert it by passing `GIT_SUBMODULE_UPDATE_RESET` to the set function.
+ *
+ * The values are:
+ *
+ * - GIT_SUBMODULE_UPDATE_RESET: reset to the on-disk value.
+ * - GIT_SUBMODULE_UPDATE_CHECKOUT: the default; when a submodule is
+ * updated, checkout the new detached HEAD to the submodule directory.
+ * - GIT_SUBMODULE_UPDATE_REBASE: update by rebasing the current checked
+ * out branch onto the commit from the superproject.
+ * - GIT_SUBMODULE_UPDATE_MERGE: update by merging the commit in the
+ * superproject into the current checkout out branch of the submodule.
+ * - GIT_SUBMODULE_UPDATE_NONE: do not update this submodule even when
+ * the commit in the superproject is updated.
+ * - GIT_SUBMODULE_UPDATE_DEFAULT: not used except as static initializer
+ * when we don't want any particular update rule to be specified.
+ */
+typedef enum {
+ GIT_SUBMODULE_UPDATE_RESET = -1,
+
+ GIT_SUBMODULE_UPDATE_CHECKOUT = 1,
+ GIT_SUBMODULE_UPDATE_REBASE = 2,
+ GIT_SUBMODULE_UPDATE_MERGE = 3,
+ GIT_SUBMODULE_UPDATE_NONE = 4,
+
+ GIT_SUBMODULE_UPDATE_DEFAULT = 0
+} git_submodule_update_t;
+
+/**
+ * Submodule ignore values
+ *
+ * These values represent settings for the `submodule.$name.ignore`
+ * configuration value which says how deeply to look at the working
+ * directory when getting submodule status.
+ *
+ * You can override this value in memory on a per-submodule basis with
+ * `git_submodule_set_ignore()` and can write the changed value to disk
+ * with `git_submodule_save()`. If you have overwritten the value, you
+ * can revert to the on disk value by using `GIT_SUBMODULE_IGNORE_RESET`.
+ *
+ * The values are:
+ *
+ * - GIT_SUBMODULE_IGNORE_RESET: reset to the on-disk value.
+ * - GIT_SUBMODULE_IGNORE_NONE: don't ignore any change - i.e. even an
+ * untracked file, will mark the submodule as dirty. Ignored files are
+ * still ignored, of course.
+ * - GIT_SUBMODULE_IGNORE_UNTRACKED: ignore untracked files; only changes
+ * to tracked files, or the index or the HEAD commit will matter.
+ * - GIT_SUBMODULE_IGNORE_DIRTY: ignore changes in the working directory,
+ * only considering changes if the HEAD of submodule has moved from the
+ * value in the superproject.
+ * - GIT_SUBMODULE_IGNORE_ALL: never check if the submodule is dirty
+ * - GIT_SUBMODULE_IGNORE_DEFAULT: not used except as static initializer
+ * when we don't want any particular ignore rule to be specified.
+ */
+typedef enum {
+ GIT_SUBMODULE_IGNORE_RESET = -1, /**< reset to on-disk value */
+
+ GIT_SUBMODULE_IGNORE_NONE = 1, /**< any change or untracked == dirty */
+ GIT_SUBMODULE_IGNORE_UNTRACKED = 2, /**< dirty if tracked files change */
+ GIT_SUBMODULE_IGNORE_DIRTY = 3, /**< only dirty if HEAD moved */
+ GIT_SUBMODULE_IGNORE_ALL = 4, /**< never dirty */
+
+ GIT_SUBMODULE_IGNORE_DEFAULT = 0
+} git_submodule_ignore_t;
+
+/**
+ * Options for submodule recurse.
+ *
+ * Represent the value of `submodule.$name.fetchRecurseSubmodules`
+ *
+ * * GIT_SUBMODULE_RECURSE_RESET - reset to the on-disk value
+ * * GIT_SUBMODULE_RECURSE_NO - do no recurse into submodules
+ * * GIT_SUBMODULE_RECURSE_YES - recurse into submodules
+ * * GIT_SUBMODULE_RECURSE_ONDEMAND - recurse into submodules only when
+ * commit not already in local clone
+ */
+typedef enum {
+ GIT_SUBMODULE_RECURSE_RESET = -1,
+
+ GIT_SUBMODULE_RECURSE_NO = 0,
+ GIT_SUBMODULE_RECURSE_YES = 1,
+ GIT_SUBMODULE_RECURSE_ONDEMAND = 2,
+} git_submodule_recurse_t;
/** @} */
GIT_END_DECL
diff --git a/include/git2/version.h b/include/git2/version.h
new file mode 100644
index 000000000..5bda42735
--- /dev/null
+++ b/include/git2/version.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_version_h__
+#define INCLUDE_git_version_h__
+
+#define LIBGIT2_VERSION "0.21.0"
+#define LIBGIT2_VER_MAJOR 0
+#define LIBGIT2_VER_MINOR 21
+#define LIBGIT2_VER_REVISION 0
+
+#define LIBGIT2_SOVERSION 21
+
+#endif
diff --git a/include/git2/zlib.h b/include/git2/zlib.h
deleted file mode 100644
index 493566340..000000000
--- a/include/git2/zlib.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#ifndef INCLUDE_git_zlib_h__
-#define INCLUDE_git_zlib_h__
-
-#include <zlib.h>
-
-/**
- * @file git2/zlib.h
- * @brief Git data compression routines
- * @defgroup git_zlib Git data compression routines
- * @ingroup Git
- * @{
- */
-GIT_BEGIN_DECL
-
-#if defined(NO_DEFLATE_BOUND) || ZLIB_VERNUM < 0x1200
-/**
- * deflateBound returns an upper bound on the compressed size.
- *
- * This is a stub function used when zlib does not supply the
- * deflateBound() implementation itself.
- *
- * @param stream the stream pointer.
- * @param s total length of the source data (in bytes).
- * @return maximum length of the compressed data.
- */
-GIT_INLINE(size_t) deflateBound(z_streamp stream, size_t s)
-{
- return (s + ((s + 7) >> 3) + ((s + 63) >> 6) + 11);
-}
-#endif
-
-/** @} */
-GIT_END_DECL
-#endif
diff --git a/libgit2.pc.in b/libgit2.pc.in
index 6165ad678..8f5279234 100644
--- a/libgit2.pc.in
+++ b/libgit2.pc.in
@@ -1,9 +1,10 @@
-libdir=@CMAKE_INSTALL_PREFIX@/@INSTALL_LIB@
-includedir=@CMAKE_INSTALL_PREFIX@/@INSTALL_INC@
+libdir=@CMAKE_INSTALL_PREFIX@/@LIB_INSTALL_DIR@
+includedir=@CMAKE_INSTALL_PREFIX@/@INCLUDE_INSTALL_DIR@
Name: libgit2
Description: The git library, take 2
Version: @LIBGIT2_VERSION_STRING@
-Requires: libcrypto
-Libs: -L${libdir} -lgit2 -lz -lcrypto
+Requires.private: @LIBGIT2_PC_REQUIRES@
+Libs.private: @LIBGIT2_PC_LIBS@
+Libs: -L${libdir} -lgit2
Cflags: -I${includedir}
diff --git a/libgit2_clar.supp b/libgit2_clar.supp
new file mode 100644
index 000000000..bd22ada46
--- /dev/null
+++ b/libgit2_clar.supp
@@ -0,0 +1,49 @@
+{
+ ignore-zlib-errors-cond
+ Memcheck:Cond
+ obj:*libz.so*
+}
+
+{
+ ignore-giterr-set-leak
+ Memcheck:Leak
+ ...
+ fun:giterr_set
+}
+
+{
+ ignore-git-global-state-leak
+ Memcheck:Leak
+ ...
+ fun:git__global_state
+}
+
+{
+ ignore-openssl-ssl-leak
+ Memcheck:Leak
+ ...
+ obj:*libssl.so*
+ ...
+}
+
+{
+ ignore-openssl-crypto-leak
+ Memcheck:Leak
+ ...
+ obj:*libcrypto.so*
+ ...
+}
+
+{
+ ignore-openssl-crypto-cond
+ Memcheck:Cond
+ obj:*libcrypto.so*
+ ...
+}
+
+{
+ ignore-glibc-getaddrinfo-cache
+ Memcheck:Leak
+ ...
+ fun:__check_pf
+}
diff --git a/script/cibuild.sh b/script/cibuild.sh
new file mode 100755
index 000000000..699404bd2
--- /dev/null
+++ b/script/cibuild.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+if [ -n "$COVERITY" ];
+then
+ ./script/coverity.sh;
+ exit $?;
+fi
+
+# Create a test repo which we can use for the online::push tests
+mkdir $HOME/_temp
+git init --bare $HOME/_temp/test.git
+git daemon --listen=localhost --export-all --enable=receive-pack --base-path=$HOME/_temp $HOME/_temp 2>/dev/null &
+export GITTEST_REMOTE_URL="git://localhost/test.git"
+
+mkdir _build
+cd _build
+cmake .. -DCMAKE_INSTALL_PREFIX=../_install $OPTIONS || exit $?
+cmake --build . --target install || exit $?
+ctest -V . || exit $?
+
+# Now that we've tested the raw git protocol, let's set up ssh to we
+# can do the push tests over it
+
+killall git-daemon
+sudo start ssh
+ssh-keygen -t rsa -f ~/.ssh/id_rsa -N "" -q
+cat ~/.ssh/id_rsa.pub >>~/.ssh/authorized_keys
+ssh-keyscan -t rsa localhost >>~/.ssh/known_hosts
+
+export GITTEST_REMOTE_URL="ssh://localhost/$HOME/_temp/test.git"
+export GITTEST_REMOTE_USER=$USER
+export GITTEST_REMOTE_SSH_KEY="$HOME/.ssh/id_rsa"
+export GITTEST_REMOTE_SSH_PUBKEY="$HOME/.ssh/id_rsa.pub"
+export GITTEST_REMOTE_SSH_PASSPHRASE=""
+
+if [ -e ./libgit2_clar ]; then
+ ./libgit2_clar -sonline::push -sonline::clone::cred_callback_failure
+fi
diff --git a/script/coverity.sh b/script/coverity.sh
new file mode 100755
index 000000000..e72008883
--- /dev/null
+++ b/script/coverity.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+set -e
+
+# Environment check
+[ -z "$COVERITY_TOKEN" ] && echo "Need to set a coverity token" && exit 1
+
+# Only run this on our branches
+echo "Pull request: $TRAVIS_PULL_REQUEST | Slug: $TRAVIS_REPO_SLUG"
+if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_REPO_SLUG" != "libgit2/libgit2" ];
+then
+ echo "Only analyzing 'development' on the main repo."
+ exit 0
+fi
+
+COV_VERSION=6.6.1
+case `uname -m` in
+ i?86) BITS=32 ;;
+ amd64|x86_64) BITS=64 ;;
+esac
+SCAN_TOOL=https://scan.coverity.com/download/linux-${BITS}
+TOOL_BASE=`pwd`/_coverity-scan
+
+# Install coverity tools
+if [ ! -d $TOOL_BASE ]; then
+ echo "Downloading coverity..."
+ mkdir -p $TOOL_BASE
+ cd $TOOL_BASE
+ wget -O coverity_tool.tgz $SCAN_TOOL \
+ --post-data "project=libgit2&token=$COVERITY_TOKEN"
+ tar xzf coverity_tool.tgz
+ cd ..
+ TOOL_DIR=`find $TOOL_BASE -type d -name 'cov-analysis*'`
+ ln -s $TOOL_DIR $TOOL_BASE/cov-analysis
+fi
+
+COV_BUILD="$TOOL_BASE/cov-analysis/bin/cov-build"
+
+# Configure and build
+rm -rf _build
+mkdir _build
+cd _build
+cmake .. -DTHREADSAFE=ON
+COVERITY_UNSUPPORTED=1 \
+ $COV_BUILD --dir cov-int \
+ cmake --build .
+
+# Upload results
+tar czf libgit2.tgz cov-int
+SHA=`git rev-parse --short HEAD`
+curl \
+ --form project=libgit2 \
+ --form token=$COVERITY_TOKEN \
+ --form email=bs@github.com \
+ --form file=@libgit2.tgz \
+ --form version=$SHA \
+ --form description="Travis build" \
+ http://scan5.coverity.com/cgi-bin/upload.py
diff --git a/script/install-deps-linux.sh b/script/install-deps-linux.sh
new file mode 100755
index 000000000..347922b89
--- /dev/null
+++ b/script/install-deps-linux.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+set -x
+
+sudo apt-get -qq update &&
+sudo apt-get -qq install cmake libssh2-1-dev openssh-client openssh-server
diff --git a/script/install-deps-osx.sh b/script/install-deps-osx.sh
new file mode 100755
index 000000000..c2e0162d8
--- /dev/null
+++ b/script/install-deps-osx.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+set -x
+
+brew install libssh2 cmake
diff --git a/src/array.h b/src/array.h
new file mode 100644
index 000000000..f8a48722a
--- /dev/null
+++ b/src/array.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_array_h__
+#define INCLUDE_array_h__
+
+#include "common.h"
+
+/*
+ * Use this to declare a typesafe resizable array of items, a la:
+ *
+ * git_array_t(int) my_ints = GIT_ARRAY_INIT;
+ * ...
+ * int *i = git_array_alloc(my_ints);
+ * GITERR_CHECK_ALLOC(i);
+ * ...
+ * git_array_clear(my_ints);
+ *
+ * You may also want to do things like:
+ *
+ * typedef git_array_t(my_struct) my_struct_array_t;
+ */
+#define git_array_t(type) struct { type *ptr; uint32_t size, asize; }
+
+#define GIT_ARRAY_INIT { NULL, 0, 0 }
+
+#define git_array_init(a) \
+ do { (a).size = (a).asize = 0; (a).ptr = NULL; } while (0)
+
+#define git_array_init_to_size(a, desired) \
+ do { (a).size = 0; (a).asize = desired; (a).ptr = git__calloc(desired, sizeof(*(a).ptr)); } while (0)
+
+#define git_array_clear(a) \
+ do { git__free((a).ptr); git_array_init(a); } while (0)
+
+#define GITERR_CHECK_ARRAY(a) GITERR_CHECK_ALLOC((a).ptr)
+
+
+typedef git_array_t(char) git_array_generic_t;
+
+/* use a generic array for growth so this can return the new item */
+GIT_INLINE(void *) git_array_grow(void *_a, size_t item_size)
+{
+ git_array_generic_t *a = _a;
+ uint32_t new_size = (a->size < 8) ? 8 : a->asize * 3 / 2;
+ char *new_array = git__realloc(a->ptr, new_size * item_size);
+ if (!new_array) {
+ git_array_clear(*a);
+ return NULL;
+ } else {
+ a->ptr = new_array; a->asize = new_size; a->size++;
+ return a->ptr + (a->size - 1) * item_size;
+ }
+}
+
+#define git_array_alloc(a) \
+ (((a).size >= (a).asize) ? \
+ git_array_grow(&(a), sizeof(*(a).ptr)) : \
+ ((a).ptr ? &(a).ptr[(a).size++] : NULL))
+
+#define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : NULL)
+
+#define git_array_pop(a) ((a).size ? &(a).ptr[--(a).size] : NULL)
+
+#define git_array_get(a, i) (((i) < (a).size) ? &(a).ptr[(i)] : NULL)
+
+#define git_array_size(a) (a).size
+
+#define git_array_valid_index(a, i) ((i) < (a).size)
+
+#endif
diff --git a/src/attr.c b/src/attr.c
new file mode 100644
index 000000000..05b0c1b3c
--- /dev/null
+++ b/src/attr.c
@@ -0,0 +1,479 @@
+#include "common.h"
+#include "repository.h"
+#include "sysdir.h"
+#include "config.h"
+#include "attr_file.h"
+#include "ignore.h"
+#include "git2/oid.h"
+#include <ctype.h>
+
+GIT__USE_STRMAP;
+
+const char *git_attr__true = "[internal]__TRUE__";
+const char *git_attr__false = "[internal]__FALSE__";
+const char *git_attr__unset = "[internal]__UNSET__";
+
+git_attr_t git_attr_value(const char *attr)
+{
+ if (attr == NULL || attr == git_attr__unset)
+ return GIT_ATTR_UNSPECIFIED_T;
+
+ if (attr == git_attr__true)
+ return GIT_ATTR_TRUE_T;
+
+ if (attr == git_attr__false)
+ return GIT_ATTR_FALSE_T;
+
+ return GIT_ATTR_VALUE_T;
+}
+
+static int collect_attr_files(
+ git_repository *repo,
+ uint32_t flags,
+ const char *path,
+ git_vector *files);
+
+static void release_attr_files(git_vector *files);
+
+int git_attr_get(
+ const char **value,
+ git_repository *repo,
+ uint32_t flags,
+ const char *pathname,
+ const char *name)
+{
+ int error;
+ git_attr_path path;
+ git_vector files = GIT_VECTOR_INIT;
+ size_t i, j;
+ git_attr_file *file;
+ git_attr_name attr;
+ git_attr_rule *rule;
+
+ assert(value && repo && name);
+
+ *value = NULL;
+
+ if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
+ return -1;
+
+ if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0)
+ goto cleanup;
+
+ memset(&attr, 0, sizeof(attr));
+ attr.name = name;
+ attr.name_hash = git_attr_file__name_hash(name);
+
+ git_vector_foreach(&files, i, file) {
+
+ git_attr_file__foreach_matching_rule(file, &path, j, rule) {
+ size_t pos;
+
+ if (!git_vector_bsearch(&pos, &rule->assigns, &attr)) {
+ *value = ((git_attr_assignment *)git_vector_get(
+ &rule->assigns, pos))->value;
+ goto cleanup;
+ }
+ }
+ }
+
+cleanup:
+ release_attr_files(&files);
+ git_attr_path__free(&path);
+
+ return error;
+}
+
+
+typedef struct {
+ git_attr_name name;
+ git_attr_assignment *found;
+} attr_get_many_info;
+
+int git_attr_get_many(
+ const char **values,
+ git_repository *repo,
+ uint32_t flags,
+ const char *pathname,
+ size_t num_attr,
+ const char **names)
+{
+ int error;
+ git_attr_path path;
+ git_vector files = GIT_VECTOR_INIT;
+ size_t i, j, k;
+ git_attr_file *file;
+ git_attr_rule *rule;
+ attr_get_many_info *info = NULL;
+ size_t num_found = 0;
+
+ if (!num_attr)
+ return 0;
+
+ assert(values && repo && names);
+
+ if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
+ return -1;
+
+ if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0)
+ goto cleanup;
+
+ info = git__calloc(num_attr, sizeof(attr_get_many_info));
+ GITERR_CHECK_ALLOC(info);
+
+ git_vector_foreach(&files, i, file) {
+
+ git_attr_file__foreach_matching_rule(file, &path, j, rule) {
+
+ for (k = 0; k < num_attr; k++) {
+ size_t pos;
+
+ if (info[k].found != NULL) /* already found assignment */
+ continue;
+
+ if (!info[k].name.name) {
+ info[k].name.name = names[k];
+ info[k].name.name_hash = git_attr_file__name_hash(names[k]);
+ }
+
+ if (!git_vector_bsearch(&pos, &rule->assigns, &info[k].name)) {
+ info[k].found = (git_attr_assignment *)
+ git_vector_get(&rule->assigns, pos);
+ values[k] = info[k].found->value;
+
+ if (++num_found == num_attr)
+ goto cleanup;
+ }
+ }
+ }
+ }
+
+ for (k = 0; k < num_attr; k++) {
+ if (!info[k].found)
+ values[k] = NULL;
+ }
+
+cleanup:
+ release_attr_files(&files);
+ git_attr_path__free(&path);
+ git__free(info);
+
+ return error;
+}
+
+
+int git_attr_foreach(
+ git_repository *repo,
+ uint32_t flags,
+ const char *pathname,
+ int (*callback)(const char *name, const char *value, void *payload),
+ void *payload)
+{
+ int error;
+ git_attr_path path;
+ git_vector files = GIT_VECTOR_INIT;
+ size_t i, j, k;
+ git_attr_file *file;
+ git_attr_rule *rule;
+ git_attr_assignment *assign;
+ git_strmap *seen = NULL;
+
+ assert(repo && callback);
+
+ if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
+ return -1;
+
+ if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0 ||
+ (error = git_strmap_alloc(&seen)) < 0)
+ goto cleanup;
+
+ git_vector_foreach(&files, i, file) {
+
+ git_attr_file__foreach_matching_rule(file, &path, j, rule) {
+
+ git_vector_foreach(&rule->assigns, k, assign) {
+ /* skip if higher priority assignment was already seen */
+ if (git_strmap_exists(seen, assign->name))
+ continue;
+
+ git_strmap_insert(seen, assign->name, assign, error);
+ if (error < 0)
+ goto cleanup;
+
+ error = callback(assign->name, assign->value, payload);
+ if (error) {
+ giterr_set_after_callback(error);
+ goto cleanup;
+ }
+ }
+ }
+ }
+
+cleanup:
+ git_strmap_free(seen);
+ release_attr_files(&files);
+ git_attr_path__free(&path);
+
+ return error;
+}
+
+static int preload_attr_file(
+ git_repository *repo,
+ git_attr_file_source source,
+ const char *base,
+ const char *file)
+{
+ int error;
+ git_attr_file *preload = NULL;
+
+ if (!file)
+ return 0;
+ if (!(error = git_attr_cache__get(
+ &preload, repo, source, base, file, git_attr_file__parse_buffer)))
+ git_attr_file__free(preload);
+
+ return error;
+}
+
+static int attr_setup(git_repository *repo)
+{
+ int error = 0;
+ const char *workdir = git_repository_workdir(repo);
+ git_index *idx = NULL;
+ git_buf sys = GIT_BUF_INIT;
+
+ if ((error = git_attr_cache__init(repo)) < 0)
+ return error;
+
+ /* preload attribute files that could contain macros so the
+ * definitions will be available for later file parsing
+ */
+
+ if (!(error = git_sysdir_find_system_file(&sys, GIT_ATTR_FILE_SYSTEM))) {
+ error = preload_attr_file(
+ repo, GIT_ATTR_FILE__FROM_FILE, NULL, sys.ptr);
+ git_buf_free(&sys);
+ }
+ if (error < 0) {
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
+ } else
+ return error;
+ }
+
+ if ((error = preload_attr_file(
+ repo, GIT_ATTR_FILE__FROM_FILE,
+ NULL, git_repository_attr_cache(repo)->cfg_attr_file)) < 0)
+ return error;
+
+ if ((error = preload_attr_file(
+ repo, GIT_ATTR_FILE__FROM_FILE,
+ git_repository_path(repo), GIT_ATTR_FILE_INREPO)) < 0)
+ return error;
+
+ if (workdir != NULL &&
+ (error = preload_attr_file(
+ repo, GIT_ATTR_FILE__FROM_FILE, workdir, GIT_ATTR_FILE)) < 0)
+ return error;
+
+ if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
+ (error = preload_attr_file(
+ repo, GIT_ATTR_FILE__FROM_INDEX, NULL, GIT_ATTR_FILE)) < 0)
+ return error;
+
+ return error;
+}
+
+int git_attr_add_macro(
+ git_repository *repo,
+ const char *name,
+ const char *values)
+{
+ int error;
+ git_attr_rule *macro = NULL;
+ git_pool *pool;
+
+ if ((error = git_attr_cache__init(repo)) < 0)
+ return error;
+
+ macro = git__calloc(1, sizeof(git_attr_rule));
+ GITERR_CHECK_ALLOC(macro);
+
+ pool = &git_repository_attr_cache(repo)->pool;
+
+ macro->match.pattern = git_pool_strdup(pool, name);
+ GITERR_CHECK_ALLOC(macro->match.pattern);
+
+ macro->match.length = strlen(macro->match.pattern);
+ macro->match.flags = GIT_ATTR_FNMATCH_MACRO;
+
+ error = git_attr_assignment__parse(repo, pool, &macro->assigns, &values);
+
+ if (!error)
+ error = git_attr_cache__insert_macro(repo, macro);
+
+ if (error < 0)
+ git_attr_rule__free(macro);
+
+ return error;
+}
+
+typedef struct {
+ git_repository *repo;
+ uint32_t flags;
+ const char *workdir;
+ git_index *index;
+ git_vector *files;
+} attr_walk_up_info;
+
+static int attr_decide_sources(
+ uint32_t flags, bool has_wd, bool has_index, git_attr_file_source *srcs)
+{
+ int count = 0;
+
+ switch (flags & 0x03) {
+ case GIT_ATTR_CHECK_FILE_THEN_INDEX:
+ if (has_wd)
+ srcs[count++] = GIT_ATTR_FILE__FROM_FILE;
+ if (has_index)
+ srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
+ break;
+ case GIT_ATTR_CHECK_INDEX_THEN_FILE:
+ if (has_index)
+ srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
+ if (has_wd)
+ srcs[count++] = GIT_ATTR_FILE__FROM_FILE;
+ break;
+ case GIT_ATTR_CHECK_INDEX_ONLY:
+ if (has_index)
+ srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
+ break;
+ }
+
+ return count;
+}
+
+static int push_attr_file(
+ git_repository *repo,
+ git_vector *list,
+ git_attr_file_source source,
+ const char *base,
+ const char *filename)
+{
+ int error = 0;
+ git_attr_file *file = NULL;
+
+ error = git_attr_cache__get(
+ &file, repo, source, base, filename, git_attr_file__parse_buffer);
+ if (error < 0)
+ return error;
+
+ if (file != NULL) {
+ if ((error = git_vector_insert(list, file)) < 0)
+ git_attr_file__free(file);
+ }
+
+ return error;
+}
+
+static int push_one_attr(void *ref, git_buf *path)
+{
+ int error = 0, n_src, i;
+ attr_walk_up_info *info = (attr_walk_up_info *)ref;
+ git_attr_file_source src[2];
+
+ n_src = attr_decide_sources(
+ info->flags, info->workdir != NULL, info->index != NULL, src);
+
+ for (i = 0; !error && i < n_src; ++i)
+ error = push_attr_file(
+ info->repo, info->files, src[i], path->ptr, GIT_ATTR_FILE);
+
+ return error;
+}
+
+static void release_attr_files(git_vector *files)
+{
+ size_t i;
+ git_attr_file *file;
+
+ git_vector_foreach(files, i, file) {
+ git_attr_file__free(file);
+ files->contents[i] = NULL;
+ }
+ git_vector_free(files);
+}
+
+static int collect_attr_files(
+ git_repository *repo,
+ uint32_t flags,
+ const char *path,
+ git_vector *files)
+{
+ int error;
+ git_buf dir = GIT_BUF_INIT;
+ const char *workdir = git_repository_workdir(repo);
+ attr_walk_up_info info = { NULL };
+
+ if ((error = attr_setup(repo)) < 0)
+ return error;
+
+ /* Resolve path in a non-bare repo */
+ if (workdir != NULL)
+ error = git_path_find_dir(&dir, path, workdir);
+ else
+ error = git_path_dirname_r(&dir, path);
+ if (error < 0)
+ goto cleanup;
+
+ /* in precendence order highest to lowest:
+ * - $GIT_DIR/info/attributes
+ * - path components with .gitattributes
+ * - config core.attributesfile
+ * - $GIT_PREFIX/etc/gitattributes
+ */
+
+ error = push_attr_file(
+ repo, files, GIT_ATTR_FILE__FROM_FILE,
+ git_repository_path(repo), GIT_ATTR_FILE_INREPO);
+ if (error < 0)
+ goto cleanup;
+
+ info.repo = repo;
+ info.flags = flags;
+ info.workdir = workdir;
+ if (git_repository_index__weakptr(&info.index, repo) < 0)
+ giterr_clear(); /* no error even if there is no index */
+ info.files = files;
+
+ error = git_path_walk_up(&dir, workdir, push_one_attr, &info);
+ if (error < 0)
+ goto cleanup;
+
+ if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) {
+ error = push_attr_file(
+ repo, files, GIT_ATTR_FILE__FROM_FILE,
+ NULL, git_repository_attr_cache(repo)->cfg_attr_file);
+ if (error < 0)
+ goto cleanup;
+ }
+
+ if ((flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) {
+ error = git_sysdir_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM);
+ if (!error)
+ error = push_attr_file(
+ repo, files, GIT_ATTR_FILE__FROM_FILE, NULL, dir.ptr);
+ else if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
+ }
+ }
+
+ cleanup:
+ if (error < 0)
+ release_attr_files(files);
+ git_buf_free(&dir);
+
+ return error;
+}
diff --git a/src/attr.h b/src/attr.h
new file mode 100644
index 000000000..f9f216d07
--- /dev/null
+++ b/src/attr.h
@@ -0,0 +1,13 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_attr_h__
+#define INCLUDE_attr_h__
+
+#include "attr_file.h"
+#include "attrcache.h"
+
+#endif
diff --git a/src/attr_file.c b/src/attr_file.c
new file mode 100644
index 000000000..3e95a2134
--- /dev/null
+++ b/src/attr_file.c
@@ -0,0 +1,783 @@
+#include "common.h"
+#include "repository.h"
+#include "filebuf.h"
+#include "attr_file.h"
+#include "attrcache.h"
+#include "git2/blob.h"
+#include "git2/tree.h"
+#include "index.h"
+#include <ctype.h>
+
+static void attr_file_free(git_attr_file *file)
+{
+ bool unlock = !git_mutex_lock(&file->lock);
+ git_attr_file__clear_rules(file, false);
+ git_pool_clear(&file->pool);
+ if (unlock)
+ git_mutex_unlock(&file->lock);
+ git_mutex_free(&file->lock);
+
+ git__memzero(file, sizeof(*file));
+ git__free(file);
+}
+
+int git_attr_file__new(
+ git_attr_file **out,
+ git_attr_file_entry *entry,
+ git_attr_file_source source)
+{
+ git_attr_file *attrs = git__calloc(1, sizeof(git_attr_file));
+ GITERR_CHECK_ALLOC(attrs);
+
+ if (git_mutex_init(&attrs->lock) < 0) {
+ giterr_set(GITERR_OS, "Failed to initialize lock");
+ git__free(attrs);
+ return -1;
+ }
+
+ if (git_pool_init(&attrs->pool, 1, 0) < 0) {
+ attr_file_free(attrs);
+ return -1;
+ }
+
+ GIT_REFCOUNT_INC(attrs);
+ attrs->entry = entry;
+ attrs->source = source;
+ *out = attrs;
+ return 0;
+}
+
+int git_attr_file__clear_rules(git_attr_file *file, bool need_lock)
+{
+ unsigned int i;
+ git_attr_rule *rule;
+
+ if (need_lock && git_mutex_lock(&file->lock) < 0) {
+ giterr_set(GITERR_OS, "Failed to lock attribute file");
+ return -1;
+ }
+
+ git_vector_foreach(&file->rules, i, rule)
+ git_attr_rule__free(rule);
+ git_vector_free(&file->rules);
+
+ if (need_lock)
+ git_mutex_unlock(&file->lock);
+
+ return 0;
+}
+
+void git_attr_file__free(git_attr_file *file)
+{
+ if (!file)
+ return;
+ GIT_REFCOUNT_DEC(file, attr_file_free);
+}
+
+static int attr_file_oid_from_index(
+ git_oid *oid, git_repository *repo, const char *path)
+{
+ int error;
+ git_index *idx;
+ size_t pos;
+ const git_index_entry *entry;
+
+ if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
+ (error = git_index__find_pos(&pos, idx, path, 0, 0)) < 0)
+ return error;
+
+ if (!(entry = git_index_get_byindex(idx, pos)))
+ return GIT_ENOTFOUND;
+
+ *oid = entry->id;
+ return 0;
+}
+
+int git_attr_file__load(
+ git_attr_file **out,
+ git_repository *repo,
+ git_attr_file_entry *entry,
+ git_attr_file_source source,
+ git_attr_file_parser parser)
+{
+ int error = 0;
+ git_blob *blob = NULL;
+ git_buf content = GIT_BUF_INIT;
+ const char *data = NULL;
+ git_attr_file *file;
+ struct stat st;
+
+ *out = NULL;
+
+ switch (source) {
+ case GIT_ATTR_FILE__IN_MEMORY:
+ /* in-memory attribute file doesn't need data */
+ break;
+ case GIT_ATTR_FILE__FROM_INDEX: {
+ git_oid id;
+
+ if ((error = attr_file_oid_from_index(&id, repo, entry->path)) < 0 ||
+ (error = git_blob_lookup(&blob, repo, &id)) < 0)
+ return error;
+
+ data = git_blob_rawcontent(blob);
+ break;
+ }
+ case GIT_ATTR_FILE__FROM_FILE: {
+ int fd;
+
+ if (p_stat(entry->fullpath, &st) < 0)
+ return git_path_set_error(errno, entry->fullpath, "stat");
+ if (S_ISDIR(st.st_mode))
+ return GIT_ENOTFOUND;
+
+ /* For open or read errors, return ENOTFOUND to skip item */
+ /* TODO: issue warning when warning API is available */
+
+ if ((fd = git_futils_open_ro(entry->fullpath)) < 0)
+ return GIT_ENOTFOUND;
+
+ error = git_futils_readbuffer_fd(&content, fd, (size_t)st.st_size);
+ p_close(fd);
+
+ if (error < 0)
+ return GIT_ENOTFOUND;
+
+ data = content.ptr;
+ break;
+ }
+ default:
+ giterr_set(GITERR_INVALID, "Unknown file source %d", source);
+ return -1;
+ }
+
+ if ((error = git_attr_file__new(&file, entry, source)) < 0)
+ goto cleanup;
+
+ if (parser && (error = parser(repo, file, data)) < 0) {
+ git_attr_file__free(file);
+ goto cleanup;
+ }
+
+ /* write cache breaker */
+ if (source == GIT_ATTR_FILE__FROM_INDEX)
+ git_oid_cpy(&file->cache_data.oid, git_blob_id(blob));
+ else if (source == GIT_ATTR_FILE__FROM_FILE)
+ git_futils_filestamp_set_from_stat(&file->cache_data.stamp, &st);
+ /* else always cacheable */
+
+ *out = file;
+
+cleanup:
+ git_blob_free(blob);
+ git_buf_free(&content);
+
+ return error;
+}
+
+int git_attr_file__out_of_date(git_repository *repo, git_attr_file *file)
+{
+ if (!file)
+ return 1;
+
+ switch (file->source) {
+ case GIT_ATTR_FILE__IN_MEMORY:
+ return 0;
+
+ case GIT_ATTR_FILE__FROM_FILE:
+ return git_futils_filestamp_check(
+ &file->cache_data.stamp, file->entry->fullpath);
+
+ case GIT_ATTR_FILE__FROM_INDEX: {
+ int error;
+ git_oid id;
+
+ if ((error = attr_file_oid_from_index(
+ &id, repo, file->entry->path)) < 0)
+ return error;
+
+ return (git_oid__cmp(&file->cache_data.oid, &id) != 0);
+ }
+
+ default:
+ giterr_set(GITERR_INVALID, "Invalid file type %d", file->source);
+ return -1;
+ }
+}
+
+static int sort_by_hash_and_name(const void *a_raw, const void *b_raw);
+static void git_attr_rule__clear(git_attr_rule *rule);
+static bool parse_optimized_patterns(
+ git_attr_fnmatch *spec,
+ git_pool *pool,
+ const char *pattern);
+
+int git_attr_file__parse_buffer(
+ git_repository *repo, git_attr_file *attrs, const char *data)
+{
+ int error = 0;
+ const char *scan = data, *context = NULL;
+ git_attr_rule *rule = NULL;
+
+ /* if subdir file path, convert context for file paths */
+ if (attrs->entry &&
+ git_path_root(attrs->entry->path) < 0 &&
+ !git__suffixcmp(attrs->entry->path, "/" GIT_ATTR_FILE))
+ context = attrs->entry->path;
+
+ if (git_mutex_lock(&attrs->lock) < 0) {
+ giterr_set(GITERR_OS, "Failed to lock attribute file");
+ return -1;
+ }
+
+ while (!error && *scan) {
+ /* allocate rule if needed */
+ if (!rule && !(rule = git__calloc(1, sizeof(*rule)))) {
+ error = -1;
+ break;
+ }
+
+ rule->match.flags =
+ GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO;
+
+ /* parse the next "pattern attr attr attr" line */
+ if (!(error = git_attr_fnmatch__parse(
+ &rule->match, &attrs->pool, context, &scan)) &&
+ !(error = git_attr_assignment__parse(
+ repo, &attrs->pool, &rule->assigns, &scan)))
+ {
+ if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO)
+ /* TODO: warning if macro found in file below repo root */
+ error = git_attr_cache__insert_macro(repo, rule);
+ else
+ error = git_vector_insert(&attrs->rules, rule);
+ }
+
+ /* if the rule wasn't a pattern, on to the next */
+ if (error < 0) {
+ git_attr_rule__clear(rule); /* reset rule contents */
+ if (error == GIT_ENOTFOUND)
+ error = 0;
+ } else {
+ rule = NULL; /* vector now "owns" the rule */
+ }
+ }
+
+ git_mutex_unlock(&attrs->lock);
+ git_attr_rule__free(rule);
+
+ return error;
+}
+
+uint32_t git_attr_file__name_hash(const char *name)
+{
+ uint32_t h = 5381;
+ int c;
+ assert(name);
+ while ((c = (int)*name++) != 0)
+ h = ((h << 5) + h) + c;
+ return h;
+}
+
+int git_attr_file__lookup_one(
+ git_attr_file *file,
+ git_attr_path *path,
+ const char *attr,
+ const char **value)
+{
+ size_t i;
+ git_attr_name name;
+ git_attr_rule *rule;
+
+ *value = NULL;
+
+ name.name = attr;
+ name.name_hash = git_attr_file__name_hash(attr);
+
+ git_attr_file__foreach_matching_rule(file, path, i, rule) {
+ size_t pos;
+
+ if (!git_vector_bsearch(&pos, &rule->assigns, &name)) {
+ *value = ((git_attr_assignment *)
+ git_vector_get(&rule->assigns, pos))->value;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int git_attr_file__load_standalone(git_attr_file **out, const char *path)
+{
+ int error;
+ git_attr_file *file;
+ git_buf content = GIT_BUF_INIT;
+
+ error = git_attr_file__new(&file, NULL, GIT_ATTR_FILE__FROM_FILE);
+ if (error < 0)
+ return error;
+
+ error = git_attr_cache__alloc_file_entry(
+ &file->entry, NULL, path, &file->pool);
+ if (error < 0) {
+ git_attr_file__free(file);
+ return error;
+ }
+ /* because the cache entry is allocated from the file's own pool, we
+ * don't have to free it - freeing file+pool will free cache entry, too.
+ */
+
+ if (!(error = git_futils_readbuffer(&content, path))) {
+ error = git_attr_file__parse_buffer(NULL, file, content.ptr);
+ git_buf_free(&content);
+ }
+
+ if (error < 0)
+ git_attr_file__free(file);
+ else
+ *out = file;
+
+ return error;
+}
+
+bool git_attr_fnmatch__match(
+ git_attr_fnmatch *match,
+ git_attr_path *path)
+{
+ const char *filename;
+ int flags = 0;
+
+ if (match->flags & GIT_ATTR_FNMATCH_ICASE)
+ flags |= FNM_CASEFOLD;
+ if (match->flags & GIT_ATTR_FNMATCH_LEADINGDIR)
+ flags |= FNM_LEADING_DIR;
+
+ if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) {
+ filename = path->path;
+ flags |= FNM_PATHNAME;
+ } else {
+ filename = path->basename;
+
+ if (path->is_dir)
+ flags |= FNM_LEADING_DIR;
+ }
+
+ if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) {
+ int matchval;
+
+ /* for attribute checks or root ignore checks, fail match */
+ if (!(match->flags & GIT_ATTR_FNMATCH_IGNORE) ||
+ path->basename == path->path)
+ return false;
+
+ /* for ignore checks, use container of current item for check */
+ path->basename[-1] = '\0';
+ flags |= FNM_LEADING_DIR;
+ matchval = p_fnmatch(match->pattern, path->path, flags);
+ path->basename[-1] = '/';
+ return (matchval != FNM_NOMATCH);
+ }
+
+ return (p_fnmatch(match->pattern, filename, flags) != FNM_NOMATCH);
+}
+
+bool git_attr_rule__match(
+ git_attr_rule *rule,
+ git_attr_path *path)
+{
+ bool matched = git_attr_fnmatch__match(&rule->match, path);
+
+ if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE)
+ matched = !matched;
+
+ return matched;
+}
+
+git_attr_assignment *git_attr_rule__lookup_assignment(
+ git_attr_rule *rule, const char *name)
+{
+ size_t pos;
+ git_attr_name key;
+ key.name = name;
+ key.name_hash = git_attr_file__name_hash(name);
+
+ if (git_vector_bsearch(&pos, &rule->assigns, &key))
+ return NULL;
+
+ return git_vector_get(&rule->assigns, pos);
+}
+
+int git_attr_path__init(
+ git_attr_path *info, const char *path, const char *base)
+{
+ ssize_t root;
+
+ /* build full path as best we can */
+ git_buf_init(&info->full, 0);
+
+ if (git_path_join_unrooted(&info->full, path, base, &root) < 0)
+ return -1;
+
+ info->path = info->full.ptr + root;
+
+ /* remove trailing slashes */
+ while (info->full.size > 0) {
+ if (info->full.ptr[info->full.size - 1] != '/')
+ break;
+ info->full.size--;
+ }
+ info->full.ptr[info->full.size] = '\0';
+
+ /* skip leading slashes in path */
+ while (*info->path == '/')
+ info->path++;
+
+ /* find trailing basename component */
+ info->basename = strrchr(info->path, '/');
+ if (info->basename)
+ info->basename++;
+ if (!info->basename || !*info->basename)
+ info->basename = info->path;
+
+ info->is_dir = (int)git_path_isdir(info->full.ptr);
+
+ return 0;
+}
+
+void git_attr_path__free(git_attr_path *info)
+{
+ git_buf_free(&info->full);
+ info->path = NULL;
+ info->basename = NULL;
+}
+
+/*
+ * From gitattributes(5):
+ *
+ * Patterns have the following format:
+ *
+ * - A blank line matches no files, so it can serve as a separator for
+ * readability.
+ *
+ * - A line starting with # serves as a comment.
+ *
+ * - An optional prefix ! which negates the pattern; any matching file
+ * excluded by a previous pattern will become included again. If a negated
+ * pattern matches, this will override lower precedence patterns sources.
+ *
+ * - If the pattern ends with a slash, it is removed for the purpose of the
+ * following description, but it would only find a match with a directory. In
+ * other words, foo/ will match a directory foo and paths underneath it, but
+ * will not match a regular file or a symbolic link foo (this is consistent
+ * with the way how pathspec works in general in git).
+ *
+ * - If the pattern does not contain a slash /, git treats it as a shell glob
+ * pattern and checks for a match against the pathname without leading
+ * directories.
+ *
+ * - Otherwise, git treats the pattern as a shell glob suitable for consumption
+ * by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will
+ * not match a / in the pathname. For example, "Documentation/\*.html" matches
+ * "Documentation/git.html" but not "Documentation/ppc/ppc.html". A leading
+ * slash matches the beginning of the pathname; for example, "/\*.c" matches
+ * "cat-file.c" but not "mozilla-sha1/sha1.c".
+ */
+
+/*
+ * This will return 0 if the spec was filled out,
+ * GIT_ENOTFOUND if the fnmatch does not require matching, or
+ * another error code there was an actual problem.
+ */
+int git_attr_fnmatch__parse(
+ git_attr_fnmatch *spec,
+ git_pool *pool,
+ const char *context,
+ const char **base)
+{
+ const char *pattern, *scan;
+ int slash_count, allow_space;
+
+ assert(spec && base && *base);
+
+ if (parse_optimized_patterns(spec, pool, *base))
+ return 0;
+
+ spec->flags = (spec->flags & GIT_ATTR_FNMATCH__INCOMING);
+ allow_space = ((spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE) != 0);
+
+ pattern = *base;
+
+ while (git__isspace(*pattern)) pattern++;
+ if (!*pattern || *pattern == '#') {
+ *base = git__next_line(pattern);
+ return GIT_ENOTFOUND;
+ }
+
+ if (*pattern == '[' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWMACRO) != 0) {
+ if (strncmp(pattern, "[attr]", 6) == 0) {
+ spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO;
+ pattern += 6;
+ }
+ /* else a character range like [a-e]* which is accepted */
+ }
+
+ if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) {
+ spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE;
+ pattern++;
+ }
+
+ slash_count = 0;
+ for (scan = pattern; *scan != '\0'; ++scan) {
+ /* scan until (non-escaped) white space */
+ if (git__isspace(*scan) && *(scan - 1) != '\\') {
+ if (!allow_space || (*scan != ' ' && *scan != '\t'))
+ break;
+ }
+
+ if (*scan == '/') {
+ spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH;
+ slash_count++;
+ if (pattern == scan)
+ pattern++;
+ }
+ /* remember if we see an unescaped wildcard in pattern */
+ else if (git__iswildcard(*scan) &&
+ (scan == pattern || (*(scan - 1) != '\\')))
+ spec->flags = spec->flags | GIT_ATTR_FNMATCH_HASWILD;
+ }
+
+ *base = scan;
+
+ if ((spec->length = scan - pattern) == 0)
+ return GIT_ENOTFOUND;
+
+ if (pattern[spec->length - 1] == '/') {
+ spec->length--;
+ spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY;
+ if (--slash_count <= 0)
+ spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH;
+ }
+ if ((spec->flags & GIT_ATTR_FNMATCH_NOLEADINGDIR) == 0 &&
+ spec->length >= 2 &&
+ pattern[spec->length - 1] == '*' &&
+ pattern[spec->length - 2] == '/') {
+ spec->length -= 2;
+ spec->flags = spec->flags | GIT_ATTR_FNMATCH_LEADINGDIR;
+ /* leave FULLPATH match on, however */
+ }
+
+ if ((spec->flags & GIT_ATTR_FNMATCH_FULLPATH) != 0 &&
+ context != NULL && git_path_root(pattern) < 0)
+ {
+ /* use context path minus the trailing filename */
+ char *slash = strrchr(context, '/');
+ size_t contextlen = slash ? slash - context + 1 : 0;
+
+ /* given an unrooted fullpath match from a file inside a repo,
+ * prefix the pattern with the relative directory of the source file
+ */
+ spec->pattern = git_pool_malloc(
+ pool, (uint32_t)(contextlen + spec->length + 1));
+ if (spec->pattern) {
+ memcpy(spec->pattern, context, contextlen);
+ memcpy(spec->pattern + contextlen, pattern, spec->length);
+ spec->length += contextlen;
+ spec->pattern[spec->length] = '\0';
+ }
+ } else {
+ spec->pattern = git_pool_strndup(pool, pattern, spec->length);
+ }
+
+ if (!spec->pattern) {
+ *base = git__next_line(pattern);
+ return -1;
+ } else {
+ /* strip '\' that might have be used for internal whitespace */
+ spec->length = git__unescape(spec->pattern);
+ /* TODO: convert remaining '\' into '/' for POSIX ??? */
+ }
+
+ return 0;
+}
+
+static bool parse_optimized_patterns(
+ git_attr_fnmatch *spec,
+ git_pool *pool,
+ const char *pattern)
+{
+ if (!pattern[1] && (pattern[0] == '*' || pattern[0] == '.')) {
+ spec->flags = GIT_ATTR_FNMATCH_MATCH_ALL;
+ spec->pattern = git_pool_strndup(pool, pattern, 1);
+ spec->length = 1;
+
+ return true;
+ }
+
+ return false;
+}
+
+static int sort_by_hash_and_name(const void *a_raw, const void *b_raw)
+{
+ const git_attr_name *a = a_raw;
+ const git_attr_name *b = b_raw;
+
+ if (b->name_hash < a->name_hash)
+ return 1;
+ else if (b->name_hash > a->name_hash)
+ return -1;
+ else
+ return strcmp(b->name, a->name);
+}
+
+static void git_attr_assignment__free(git_attr_assignment *assign)
+{
+ /* name and value are stored in a git_pool associated with the
+ * git_attr_file, so they do not need to be freed here
+ */
+ assign->name = NULL;
+ assign->value = NULL;
+ git__free(assign);
+}
+
+static int merge_assignments(void **old_raw, void *new_raw)
+{
+ git_attr_assignment **old = (git_attr_assignment **)old_raw;
+ git_attr_assignment *new = (git_attr_assignment *)new_raw;
+
+ GIT_REFCOUNT_DEC(*old, git_attr_assignment__free);
+ *old = new;
+ return GIT_EEXISTS;
+}
+
+int git_attr_assignment__parse(
+ git_repository *repo,
+ git_pool *pool,
+ git_vector *assigns,
+ const char **base)
+{
+ int error;
+ const char *scan = *base;
+ git_attr_assignment *assign = NULL;
+
+ assert(assigns && !assigns->length);
+
+ git_vector_set_cmp(assigns, sort_by_hash_and_name);
+
+ while (*scan && *scan != '\n') {
+ const char *name_start, *value_start;
+
+ /* skip leading blanks */
+ while (git__isspace(*scan) && *scan != '\n') scan++;
+
+ /* allocate assign if needed */
+ if (!assign) {
+ assign = git__calloc(1, sizeof(git_attr_assignment));
+ GITERR_CHECK_ALLOC(assign);
+ GIT_REFCOUNT_INC(assign);
+ }
+
+ assign->name_hash = 5381;
+ assign->value = git_attr__true;
+
+ /* look for magic name prefixes */
+ if (*scan == '-') {
+ assign->value = git_attr__false;
+ scan++;
+ } else if (*scan == '!') {
+ assign->value = git_attr__unset; /* explicit unspecified state */
+ scan++;
+ } else if (*scan == '#') /* comment rest of line */
+ break;
+
+ /* find the name */
+ name_start = scan;
+ while (*scan && !git__isspace(*scan) && *scan != '=') {
+ assign->name_hash =
+ ((assign->name_hash << 5) + assign->name_hash) + *scan;
+ scan++;
+ }
+ if (scan == name_start) {
+ /* must have found lone prefix (" - ") or leading = ("=foo")
+ * or end of buffer -- advance until whitespace and continue
+ */
+ while (*scan && !git__isspace(*scan)) scan++;
+ continue;
+ }
+
+ /* allocate permanent storage for name */
+ assign->name = git_pool_strndup(pool, name_start, scan - name_start);
+ GITERR_CHECK_ALLOC(assign->name);
+
+ /* if there is an equals sign, find the value */
+ if (*scan == '=') {
+ for (value_start = ++scan; *scan && !git__isspace(*scan); ++scan);
+
+ /* if we found a value, allocate permanent storage for it */
+ if (scan > value_start) {
+ assign->value = git_pool_strndup(pool, value_start, scan - value_start);
+ GITERR_CHECK_ALLOC(assign->value);
+ }
+ }
+
+ /* expand macros (if given a repo with a macro cache) */
+ if (repo != NULL && assign->value == git_attr__true) {
+ git_attr_rule *macro =
+ git_attr_cache__lookup_macro(repo, assign->name);
+
+ if (macro != NULL) {
+ unsigned int i;
+ git_attr_assignment *massign;
+
+ git_vector_foreach(&macro->assigns, i, massign) {
+ GIT_REFCOUNT_INC(massign);
+
+ error = git_vector_insert_sorted(
+ assigns, massign, &merge_assignments);
+ if (error < 0 && error != GIT_EEXISTS)
+ return error;
+ }
+ }
+ }
+
+ /* insert allocated assign into vector */
+ error = git_vector_insert_sorted(assigns, assign, &merge_assignments);
+ if (error < 0 && error != GIT_EEXISTS)
+ return error;
+
+ /* clear assign since it is now "owned" by the vector */
+ assign = NULL;
+ }
+
+ if (assign != NULL)
+ git_attr_assignment__free(assign);
+
+ *base = git__next_line(scan);
+
+ return (assigns->length == 0) ? GIT_ENOTFOUND : 0;
+}
+
+static void git_attr_rule__clear(git_attr_rule *rule)
+{
+ unsigned int i;
+ git_attr_assignment *assign;
+
+ if (!rule)
+ return;
+
+ if (!(rule->match.flags & GIT_ATTR_FNMATCH_IGNORE)) {
+ git_vector_foreach(&rule->assigns, i, assign)
+ GIT_REFCOUNT_DEC(assign, git_attr_assignment__free);
+ git_vector_free(&rule->assigns);
+ }
+
+ /* match.pattern is stored in a git_pool, so no need to free */
+ rule->match.pattern = NULL;
+ rule->match.length = 0;
+}
+
+void git_attr_rule__free(git_attr_rule *rule)
+{
+ git_attr_rule__clear(rule);
+ git__free(rule);
+}
+
diff --git a/src/attr_file.h b/src/attr_file.h
new file mode 100644
index 000000000..87cde7e35
--- /dev/null
+++ b/src/attr_file.h
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_attr_file_h__
+#define INCLUDE_attr_file_h__
+
+#include "git2/oid.h"
+#include "git2/attr.h"
+#include "vector.h"
+#include "pool.h"
+#include "buffer.h"
+#include "fileops.h"
+
+#define GIT_ATTR_FILE ".gitattributes"
+#define GIT_ATTR_FILE_INREPO "info/attributes"
+#define GIT_ATTR_FILE_SYSTEM "gitattributes"
+#define GIT_ATTR_FILE_XDG "attributes"
+
+#define GIT_ATTR_FNMATCH_NEGATIVE (1U << 0)
+#define GIT_ATTR_FNMATCH_DIRECTORY (1U << 1)
+#define GIT_ATTR_FNMATCH_FULLPATH (1U << 2)
+#define GIT_ATTR_FNMATCH_MACRO (1U << 3)
+#define GIT_ATTR_FNMATCH_IGNORE (1U << 4)
+#define GIT_ATTR_FNMATCH_HASWILD (1U << 5)
+#define GIT_ATTR_FNMATCH_ALLOWSPACE (1U << 6)
+#define GIT_ATTR_FNMATCH_ICASE (1U << 7)
+#define GIT_ATTR_FNMATCH_MATCH_ALL (1U << 8)
+#define GIT_ATTR_FNMATCH_ALLOWNEG (1U << 9)
+#define GIT_ATTR_FNMATCH_ALLOWMACRO (1U << 10)
+#define GIT_ATTR_FNMATCH_LEADINGDIR (1U << 11)
+#define GIT_ATTR_FNMATCH_NOLEADINGDIR (1U << 12)
+
+#define GIT_ATTR_FNMATCH__INCOMING \
+ (GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG | \
+ GIT_ATTR_FNMATCH_ALLOWMACRO | GIT_ATTR_FNMATCH_NOLEADINGDIR)
+
+typedef enum {
+ GIT_ATTR_FILE__IN_MEMORY = 0,
+ GIT_ATTR_FILE__FROM_FILE = 1,
+ GIT_ATTR_FILE__FROM_INDEX = 2,
+
+ GIT_ATTR_FILE_NUM_SOURCES = 3
+} git_attr_file_source;
+
+extern const char *git_attr__true;
+extern const char *git_attr__false;
+extern const char *git_attr__unset;
+
+typedef struct {
+ char *pattern;
+ size_t length;
+ unsigned int flags;
+} git_attr_fnmatch;
+
+typedef struct {
+ git_attr_fnmatch match;
+ git_vector assigns; /* vector of <git_attr_assignment*> */
+} git_attr_rule;
+
+typedef struct {
+ git_refcount unused;
+ const char *name;
+ uint32_t name_hash;
+} git_attr_name;
+
+typedef struct {
+ git_refcount rc; /* for macros */
+ char *name;
+ uint32_t name_hash;
+ const char *value;
+} git_attr_assignment;
+
+typedef struct git_attr_file_entry git_attr_file_entry;
+
+typedef struct {
+ git_refcount rc;
+ git_mutex lock;
+ git_attr_file_entry *entry;
+ git_attr_file_source source;
+ git_vector rules; /* vector of <rule*> or <fnmatch*> */
+ git_pool pool;
+ union {
+ git_oid oid;
+ git_futils_filestamp stamp;
+ } cache_data;
+} git_attr_file;
+
+struct git_attr_file_entry {
+ git_attr_file *file[GIT_ATTR_FILE_NUM_SOURCES];
+ const char *path; /* points into fullpath */
+ char fullpath[GIT_FLEX_ARRAY];
+};
+
+typedef int (*git_attr_file_parser)(
+ git_repository *repo,
+ git_attr_file *file,
+ const char *data);
+
+typedef struct {
+ git_buf full;
+ char *path;
+ char *basename;
+ int is_dir;
+} git_attr_path;
+
+/*
+ * git_attr_file API
+ */
+
+int git_attr_file__new(
+ git_attr_file **out,
+ git_attr_file_entry *entry,
+ git_attr_file_source source);
+
+void git_attr_file__free(git_attr_file *file);
+
+int git_attr_file__load(
+ git_attr_file **out,
+ git_repository *repo,
+ git_attr_file_entry *ce,
+ git_attr_file_source source,
+ git_attr_file_parser parser);
+
+int git_attr_file__load_standalone(
+ git_attr_file **out, const char *path);
+
+int git_attr_file__out_of_date(
+ git_repository *repo, git_attr_file *file);
+
+int git_attr_file__parse_buffer(
+ git_repository *repo, git_attr_file *attrs, const char *data);
+
+int git_attr_file__clear_rules(
+ git_attr_file *file, bool need_lock);
+
+int git_attr_file__lookup_one(
+ git_attr_file *file,
+ git_attr_path *path,
+ const char *attr,
+ const char **value);
+
+/* loop over rules in file from bottom to top */
+#define git_attr_file__foreach_matching_rule(file, path, iter, rule) \
+ git_vector_rforeach(&(file)->rules, (iter), (rule)) \
+ if (git_attr_rule__match((rule), (path)))
+
+uint32_t git_attr_file__name_hash(const char *name);
+
+
+/*
+ * other utilities
+ */
+
+extern int git_attr_fnmatch__parse(
+ git_attr_fnmatch *spec,
+ git_pool *pool,
+ const char *source,
+ const char **base);
+
+extern bool git_attr_fnmatch__match(
+ git_attr_fnmatch *rule,
+ git_attr_path *path);
+
+extern void git_attr_rule__free(git_attr_rule *rule);
+
+extern bool git_attr_rule__match(
+ git_attr_rule *rule,
+ git_attr_path *path);
+
+extern git_attr_assignment *git_attr_rule__lookup_assignment(
+ git_attr_rule *rule, const char *name);
+
+extern int git_attr_path__init(
+ git_attr_path *info, const char *path, const char *base);
+
+extern void git_attr_path__free(git_attr_path *info);
+
+extern int git_attr_assignment__parse(
+ git_repository *repo, /* needed to expand macros */
+ git_pool *pool,
+ git_vector *assigns,
+ const char **scan);
+
+#endif
diff --git a/src/attrcache.c b/src/attrcache.c
new file mode 100644
index 000000000..b4579bfc0
--- /dev/null
+++ b/src/attrcache.c
@@ -0,0 +1,449 @@
+#include "common.h"
+#include "repository.h"
+#include "attr_file.h"
+#include "config.h"
+#include "sysdir.h"
+#include "ignore.h"
+
+GIT__USE_STRMAP;
+
+GIT_INLINE(int) attr_cache_lock(git_attr_cache *cache)
+{
+ GIT_UNUSED(cache); /* avoid warning if threading is off */
+
+ if (git_mutex_lock(&cache->lock) < 0) {
+ giterr_set(GITERR_OS, "Unable to get attr cache lock");
+ return -1;
+ }
+ return 0;
+}
+
+GIT_INLINE(void) attr_cache_unlock(git_attr_cache *cache)
+{
+ GIT_UNUSED(cache); /* avoid warning if threading is off */
+ git_mutex_unlock(&cache->lock);
+}
+
+GIT_INLINE(git_attr_file_entry *) attr_cache_lookup_entry(
+ git_attr_cache *cache, const char *path)
+{
+ khiter_t pos = git_strmap_lookup_index(cache->files, path);
+
+ if (git_strmap_valid_index(cache->files, pos))
+ return git_strmap_value_at(cache->files, pos);
+ else
+ return NULL;
+}
+
+int git_attr_cache__alloc_file_entry(
+ git_attr_file_entry **out,
+ const char *base,
+ const char *path,
+ git_pool *pool)
+{
+ size_t baselen = 0, pathlen = strlen(path);
+ size_t cachesize = sizeof(git_attr_file_entry) + pathlen + 1;
+ git_attr_file_entry *ce;
+
+ if (base != NULL && git_path_root(path) < 0) {
+ baselen = strlen(base);
+ cachesize += baselen;
+
+ if (baselen && base[baselen - 1] != '/')
+ cachesize++;
+ }
+
+ ce = git_pool_mallocz(pool, (uint32_t)cachesize);
+ GITERR_CHECK_ALLOC(ce);
+
+ if (baselen) {
+ memcpy(ce->fullpath, base, baselen);
+
+ if (base[baselen - 1] != '/')
+ ce->fullpath[baselen++] = '/';
+ }
+ memcpy(&ce->fullpath[baselen], path, pathlen);
+
+ ce->path = &ce->fullpath[baselen];
+ *out = ce;
+
+ return 0;
+}
+
+/* call with attrcache locked */
+static int attr_cache_make_entry(
+ git_attr_file_entry **out, git_repository *repo, const char *path)
+{
+ int error = 0;
+ git_attr_cache *cache = git_repository_attr_cache(repo);
+ git_attr_file_entry *entry = NULL;
+
+ error = git_attr_cache__alloc_file_entry(
+ &entry, git_repository_workdir(repo), path, &cache->pool);
+
+ if (!error) {
+ git_strmap_insert(cache->files, entry->path, entry, error);
+ if (error > 0)
+ error = 0;
+ }
+
+ *out = entry;
+ return error;
+}
+
+/* insert entry or replace existing if we raced with another thread */
+static int attr_cache_upsert(git_attr_cache *cache, git_attr_file *file)
+{
+ git_attr_file_entry *entry;
+ git_attr_file *old;
+
+ if (attr_cache_lock(cache) < 0)
+ return -1;
+
+ entry = attr_cache_lookup_entry(cache, file->entry->path);
+
+ GIT_REFCOUNT_OWN(file, entry);
+ GIT_REFCOUNT_INC(file);
+
+ old = git__compare_and_swap(
+ &entry->file[file->source], entry->file[file->source], file);
+
+ if (old) {
+ GIT_REFCOUNT_OWN(old, NULL);
+ git_attr_file__free(old);
+ }
+
+ attr_cache_unlock(cache);
+ return 0;
+}
+
+static int attr_cache_remove(git_attr_cache *cache, git_attr_file *file)
+{
+ int error = 0;
+ git_attr_file_entry *entry;
+
+ if (!file)
+ return 0;
+ if ((error = attr_cache_lock(cache)) < 0)
+ return error;
+
+ if ((entry = attr_cache_lookup_entry(cache, file->entry->path)) != NULL)
+ file = git__compare_and_swap(&entry->file[file->source], file, NULL);
+
+ attr_cache_unlock(cache);
+
+ if (file) {
+ GIT_REFCOUNT_OWN(file, NULL);
+ git_attr_file__free(file);
+ }
+
+ return error;
+}
+
+/* Look up cache entry and file.
+ * - If entry is not present, create it while the cache is locked.
+ * - If file is present, increment refcount before returning it, so the
+ * cache can be unlocked and it won't go away.
+ */
+static int attr_cache_lookup(
+ git_attr_file **out_file,
+ git_attr_file_entry **out_entry,
+ git_repository *repo,
+ git_attr_file_source source,
+ const char *base,
+ const char *filename)
+{
+ int error = 0;
+ git_buf path = GIT_BUF_INIT;
+ const char *wd = git_repository_workdir(repo), *relfile;
+ git_attr_cache *cache = git_repository_attr_cache(repo);
+ git_attr_file_entry *entry = NULL;
+ git_attr_file *file = NULL;
+
+ /* join base and path as needed */
+ if (base != NULL && git_path_root(filename) < 0) {
+ if (git_buf_joinpath(&path, base, filename) < 0)
+ return -1;
+ filename = path.ptr;
+ }
+
+ relfile = filename;
+ if (wd && !git__prefixcmp(relfile, wd))
+ relfile += strlen(wd);
+
+ /* check cache for existing entry */
+ if ((error = attr_cache_lock(cache)) < 0)
+ goto cleanup;
+
+ entry = attr_cache_lookup_entry(cache, relfile);
+ if (!entry)
+ error = attr_cache_make_entry(&entry, repo, relfile);
+ else if (entry->file[source] != NULL) {
+ file = entry->file[source];
+ GIT_REFCOUNT_INC(file);
+ }
+
+ attr_cache_unlock(cache);
+
+cleanup:
+ *out_file = file;
+ *out_entry = entry;
+
+ git_buf_free(&path);
+ return error;
+}
+
+int git_attr_cache__get(
+ git_attr_file **out,
+ git_repository *repo,
+ git_attr_file_source source,
+ const char *base,
+ const char *filename,
+ git_attr_file_parser parser)
+{
+ int error = 0;
+ git_attr_cache *cache = git_repository_attr_cache(repo);
+ git_attr_file_entry *entry = NULL;
+ git_attr_file *file = NULL, *updated = NULL;
+
+ if ((error = attr_cache_lookup(
+ &file, &entry, repo, source, base, filename)) < 0)
+ return error;
+
+ /* load file if we don't have one or if existing one is out of date */
+ if (!file || (error = git_attr_file__out_of_date(repo, file)) > 0)
+ error = git_attr_file__load(&updated, repo, entry, source, parser);
+
+ /* if we loaded the file, insert into and/or update cache */
+ if (updated) {
+ if ((error = attr_cache_upsert(cache, updated)) < 0)
+ git_attr_file__free(updated);
+ else {
+ git_attr_file__free(file); /* offset incref from lookup */
+ file = updated;
+ }
+ }
+
+ /* if file could not be loaded */
+ if (error < 0) {
+ /* remove existing entry */
+ if (file) {
+ attr_cache_remove(cache, file);
+ git_attr_file__free(file); /* offset incref from lookup */
+ file = NULL;
+ }
+ /* no error if file simply doesn't exist */
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
+ }
+ }
+
+ *out = file;
+ return error;
+}
+
+bool git_attr_cache__is_cached(
+ git_repository *repo,
+ git_attr_file_source source,
+ const char *filename)
+{
+ git_attr_cache *cache = git_repository_attr_cache(repo);
+ git_strmap *files;
+ khiter_t pos;
+ git_attr_file_entry *entry;
+
+ if (!cache || !(files = cache->files))
+ return false;
+
+ pos = git_strmap_lookup_index(files, filename);
+ if (!git_strmap_valid_index(files, pos))
+ return false;
+
+ entry = git_strmap_value_at(files, pos);
+
+ return entry && (entry->file[source] != NULL);
+}
+
+
+static int attr_cache__lookup_path(
+ char **out, git_config *cfg, const char *key, const char *fallback)
+{
+ git_buf buf = GIT_BUF_INIT;
+ int error;
+ const git_config_entry *entry = NULL;
+
+ *out = NULL;
+
+ if ((error = git_config__lookup_entry(&entry, cfg, key, false)) < 0)
+ return error;
+
+ if (entry) {
+ const char *cfgval = entry->value;
+
+ /* expand leading ~/ as needed */
+ if (cfgval && cfgval[0] == '~' && cfgval[1] == '/' &&
+ !git_sysdir_find_global_file(&buf, &cfgval[2]))
+ *out = git_buf_detach(&buf);
+ else if (cfgval)
+ *out = git__strdup(cfgval);
+ }
+ else if (!git_sysdir_find_xdg_file(&buf, fallback))
+ *out = git_buf_detach(&buf);
+
+ git_buf_free(&buf);
+
+ return error;
+}
+
+static void attr_cache__free(git_attr_cache *cache)
+{
+ bool unlock;
+
+ if (!cache)
+ return;
+
+ unlock = (git_mutex_lock(&cache->lock) == 0);
+
+ if (cache->files != NULL) {
+ git_attr_file_entry *entry;
+ git_attr_file *file;
+ int i;
+
+ git_strmap_foreach_value(cache->files, entry, {
+ for (i = 0; i < GIT_ATTR_FILE_NUM_SOURCES; ++i) {
+ if ((file = git__swap(entry->file[i], NULL)) != NULL) {
+ GIT_REFCOUNT_OWN(file, NULL);
+ git_attr_file__free(file);
+ }
+ }
+ });
+ git_strmap_free(cache->files);
+ }
+
+ if (cache->macros != NULL) {
+ git_attr_rule *rule;
+
+ git_strmap_foreach_value(cache->macros, rule, {
+ git_attr_rule__free(rule);
+ });
+ git_strmap_free(cache->macros);
+ }
+
+ git_pool_clear(&cache->pool);
+
+ git__free(cache->cfg_attr_file);
+ cache->cfg_attr_file = NULL;
+
+ git__free(cache->cfg_excl_file);
+ cache->cfg_excl_file = NULL;
+
+ if (unlock)
+ git_mutex_unlock(&cache->lock);
+ git_mutex_free(&cache->lock);
+
+ git__free(cache);
+}
+
+int git_attr_cache__do_init(git_repository *repo)
+{
+ int ret = 0;
+ git_attr_cache *cache = git_repository_attr_cache(repo);
+ git_config *cfg = NULL;
+
+ if (cache)
+ return 0;
+
+ cache = git__calloc(1, sizeof(git_attr_cache));
+ GITERR_CHECK_ALLOC(cache);
+
+ /* set up lock */
+ if (git_mutex_init(&cache->lock) < 0) {
+ giterr_set(GITERR_OS, "Unable to initialize lock for attr cache");
+ git__free(cache);
+ return -1;
+ }
+
+ if ((ret = git_repository_config_snapshot(&cfg, repo)) < 0)
+ goto cancel;
+
+ /* cache config settings for attributes and ignores */
+ ret = attr_cache__lookup_path(
+ &cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG);
+ if (ret < 0)
+ goto cancel;
+
+ ret = attr_cache__lookup_path(
+ &cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG);
+ if (ret < 0)
+ goto cancel;
+
+ /* allocate hashtable for attribute and ignore file contents,
+ * hashtable for attribute macros, and string pool
+ */
+ if ((ret = git_strmap_alloc(&cache->files)) < 0 ||
+ (ret = git_strmap_alloc(&cache->macros)) < 0 ||
+ (ret = git_pool_init(&cache->pool, 1, 0)) < 0)
+ goto cancel;
+
+ cache = git__compare_and_swap(&repo->attrcache, NULL, cache);
+ if (cache)
+ goto cancel; /* raced with another thread, free this but no error */
+
+ git_config_free(cfg);
+
+ /* insert default macros */
+ return git_attr_add_macro(repo, "binary", "-diff -crlf -text");
+
+cancel:
+ attr_cache__free(cache);
+ git_config_free(cfg);
+ return ret;
+}
+
+void git_attr_cache_flush(git_repository *repo)
+{
+ git_attr_cache *cache;
+
+ /* this could be done less expensively, but for now, we'll just free
+ * the entire attrcache and let the next use reinitialize it...
+ */
+ if (repo && (cache = git__swap(repo->attrcache, NULL)) != NULL)
+ attr_cache__free(cache);
+}
+
+int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
+{
+ git_attr_cache *cache = git_repository_attr_cache(repo);
+ git_strmap *macros = cache->macros;
+ int error;
+
+ /* TODO: generate warning log if (macro->assigns.length == 0) */
+ if (macro->assigns.length == 0)
+ return 0;
+
+ if (git_mutex_lock(&cache->lock) < 0) {
+ giterr_set(GITERR_OS, "Unable to get attr cache lock");
+ error = -1;
+ } else {
+ git_strmap_insert(macros, macro->match.pattern, macro, error);
+ git_mutex_unlock(&cache->lock);
+ }
+
+ return (error < 0) ? -1 : 0;
+}
+
+git_attr_rule *git_attr_cache__lookup_macro(
+ git_repository *repo, const char *name)
+{
+ git_strmap *macros = git_repository_attr_cache(repo)->macros;
+ khiter_t pos;
+
+ pos = git_strmap_lookup_index(macros, name);
+
+ if (!git_strmap_valid_index(macros, pos))
+ return NULL;
+
+ return (git_attr_rule *)git_strmap_value_at(macros, pos);
+}
+
diff --git a/src/attrcache.h b/src/attrcache.h
new file mode 100644
index 000000000..be0a22f5c
--- /dev/null
+++ b/src/attrcache.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_attrcache_h__
+#define INCLUDE_attrcache_h__
+
+#include "attr_file.h"
+#include "strmap.h"
+
+#define GIT_ATTR_CONFIG "core.attributesfile"
+#define GIT_IGNORE_CONFIG "core.excludesfile"
+
+typedef struct {
+ char *cfg_attr_file; /* cached value of core.attributesfile */
+ char *cfg_excl_file; /* cached value of core.excludesfile */
+ git_strmap *files; /* hash path to git_attr_cache_entry records */
+ git_strmap *macros; /* hash name to vector<git_attr_assignment> */
+ git_mutex lock;
+ git_pool pool;
+} git_attr_cache;
+
+extern int git_attr_cache__do_init(git_repository *repo);
+
+#define git_attr_cache__init(REPO) \
+ (git_repository_attr_cache(REPO) ? 0 : git_attr_cache__do_init(REPO))
+
+/* get file - loading and reload as needed */
+extern int git_attr_cache__get(
+ git_attr_file **file,
+ git_repository *repo,
+ git_attr_file_source source,
+ const char *base,
+ const char *filename,
+ git_attr_file_parser parser);
+
+extern bool git_attr_cache__is_cached(
+ git_repository *repo,
+ git_attr_file_source source,
+ const char *path);
+
+extern int git_attr_cache__alloc_file_entry(
+ git_attr_file_entry **out,
+ const char *base,
+ const char *path,
+ git_pool *pool);
+
+extern int git_attr_cache__insert_macro(
+ git_repository *repo, git_attr_rule *macro);
+
+extern git_attr_rule *git_attr_cache__lookup_macro(
+ git_repository *repo, const char *name);
+
+#endif
diff --git a/src/bitvec.h b/src/bitvec.h
new file mode 100644
index 000000000..544832d95
--- /dev/null
+++ b/src/bitvec.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_bitvec_h__
+#define INCLUDE_bitvec_h__
+
+#include "common.h"
+
+/*
+ * This is a silly little fixed length bit vector type that will store
+ * vectors of 64 bits or less directly in the structure and allocate
+ * memory for vectors longer than 64 bits. You can use the two versions
+ * transparently through the API and avoid heap allocation completely when
+ * using a short bit vector as a result.
+ */
+typedef struct {
+ size_t length;
+ union {
+ uint64_t *words;
+ uint64_t bits;
+ } u;
+} git_bitvec;
+
+GIT_INLINE(int) git_bitvec_init(git_bitvec *bv, size_t capacity)
+{
+ memset(bv, 0x0, sizeof(*bv));
+
+ if (capacity >= 64) {
+ bv->length = (capacity / 64) + 1;
+ bv->u.words = git__calloc(bv->length, sizeof(uint64_t));
+ if (!bv->u.words)
+ return -1;
+ }
+
+ return 0;
+}
+
+#define GIT_BITVEC_MASK(BIT) ((uint64_t)1 << (BIT % 64))
+#define GIT_BITVEC_WORD(BV, BIT) (BV->length ? &BV->u.words[BIT / 64] : &BV->u.bits)
+
+GIT_INLINE(void) git_bitvec_set(git_bitvec *bv, size_t bit, bool on)
+{
+ uint64_t *word = GIT_BITVEC_WORD(bv, bit);
+ uint64_t mask = GIT_BITVEC_MASK(bit);
+
+ if (on)
+ *word |= mask;
+ else
+ *word &= ~mask;
+}
+
+GIT_INLINE(bool) git_bitvec_get(git_bitvec *bv, size_t bit)
+{
+ uint64_t *word = GIT_BITVEC_WORD(bv, bit);
+ return (*word & GIT_BITVEC_MASK(bit)) != 0;
+}
+
+GIT_INLINE(void) git_bitvec_clear(git_bitvec *bv)
+{
+ if (!bv->length)
+ bv->u.bits = 0;
+ else
+ memset(bv->u.words, 0x0, bv->length * sizeof(uint64_t));
+}
+
+GIT_INLINE(void) git_bitvec_free(git_bitvec *bv)
+{
+ if (bv->length)
+ git__free(bv->u.words);
+}
+
+#endif
diff --git a/src/blame.c b/src/blame.c
new file mode 100644
index 000000000..eb977c287
--- /dev/null
+++ b/src/blame.c
@@ -0,0 +1,488 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "blame.h"
+#include "git2/commit.h"
+#include "git2/revparse.h"
+#include "git2/revwalk.h"
+#include "git2/tree.h"
+#include "git2/diff.h"
+#include "git2/blob.h"
+#include "git2/signature.h"
+#include "util.h"
+#include "repository.h"
+#include "blame_git.h"
+
+
+static int hunk_byfinalline_search_cmp(const void *key, const void *entry)
+{
+ git_blame_hunk *hunk = (git_blame_hunk*)entry;
+
+ size_t lineno = *(size_t*)key;
+ size_t lines_in_hunk = (size_t)hunk->lines_in_hunk;
+ size_t final_start_line_number = (size_t)hunk->final_start_line_number;
+
+ if (lineno < final_start_line_number)
+ return -1;
+ if (lineno >= final_start_line_number + lines_in_hunk)
+ return 1;
+ return 0;
+}
+
+static int paths_cmp(const void *a, const void *b) { return git__strcmp((char*)a, (char*)b); }
+static int hunk_cmp(const void *_a, const void *_b)
+{
+ git_blame_hunk *a = (git_blame_hunk*)_a,
+ *b = (git_blame_hunk*)_b;
+
+ return a->final_start_line_number - b->final_start_line_number;
+}
+
+static bool hunk_ends_at_or_before_line(git_blame_hunk *hunk, size_t line)
+{
+ return line >= (size_t)(hunk->final_start_line_number + hunk->lines_in_hunk - 1);
+}
+
+static bool hunk_starts_at_or_after_line(git_blame_hunk *hunk, size_t line)
+{
+ return line <= hunk->final_start_line_number;
+}
+
+static git_blame_hunk* new_hunk(
+ uint16_t start,
+ uint16_t lines,
+ uint16_t orig_start,
+ const char *path)
+{
+ git_blame_hunk *hunk = git__calloc(1, sizeof(git_blame_hunk));
+ if (!hunk) return NULL;
+
+ hunk->lines_in_hunk = lines;
+ hunk->final_start_line_number = start;
+ hunk->orig_start_line_number = orig_start;
+ hunk->orig_path = path ? git__strdup(path) : NULL;
+
+ return hunk;
+}
+
+static git_blame_hunk* dup_hunk(git_blame_hunk *hunk)
+{
+ git_blame_hunk *newhunk = new_hunk(
+ hunk->final_start_line_number,
+ hunk->lines_in_hunk,
+ hunk->orig_start_line_number,
+ hunk->orig_path);
+ git_oid_cpy(&newhunk->orig_commit_id, &hunk->orig_commit_id);
+ git_oid_cpy(&newhunk->final_commit_id, &hunk->final_commit_id);
+ newhunk->boundary = hunk->boundary;
+ git_signature_dup(&newhunk->final_signature, hunk->final_signature);
+ git_signature_dup(&newhunk->orig_signature, hunk->orig_signature);
+ return newhunk;
+}
+
+static void free_hunk(git_blame_hunk *hunk)
+{
+ git__free((void*)hunk->orig_path);
+ git_signature_free(hunk->final_signature);
+ git_signature_free(hunk->orig_signature);
+ git__free(hunk);
+}
+
+/* Starting with the hunk that includes start_line, shift all following hunks'
+ * final_start_line by shift_by lines */
+static void shift_hunks_by(git_vector *v, size_t start_line, int shift_by)
+{
+ size_t i;
+
+ if (!git_vector_bsearch2(&i, v, hunk_byfinalline_search_cmp, &start_line)) {
+ for (; i < v->length; i++) {
+ git_blame_hunk *hunk = (git_blame_hunk*)v->contents[i];
+ hunk->final_start_line_number += shift_by;
+ }
+ }
+}
+
+git_blame* git_blame__alloc(
+ git_repository *repo,
+ git_blame_options opts,
+ const char *path)
+{
+ git_blame *gbr = git__calloc(1, sizeof(git_blame));
+ if (!gbr)
+ return NULL;
+
+ gbr->repository = repo;
+ gbr->options = opts;
+
+ if (git_vector_init(&gbr->hunks, 8, hunk_cmp) < 0 ||
+ git_vector_init(&gbr->paths, 8, paths_cmp) < 0 ||
+ (gbr->path = git__strdup(path)) == NULL ||
+ git_vector_insert(&gbr->paths, git__strdup(path)) < 0)
+ {
+ git_blame_free(gbr);
+ return NULL;
+ }
+
+ return gbr;
+}
+
+void git_blame_free(git_blame *blame)
+{
+ size_t i;
+ git_blame_hunk *hunk;
+
+ if (!blame) return;
+
+ git_vector_foreach(&blame->hunks, i, hunk)
+ free_hunk(hunk);
+ git_vector_free(&blame->hunks);
+
+ git_vector_free_deep(&blame->paths);
+
+ git_array_clear(blame->line_index);
+
+ git__free(blame->path);
+ git_blob_free(blame->final_blob);
+ git__free(blame);
+}
+
+uint32_t git_blame_get_hunk_count(git_blame *blame)
+{
+ assert(blame);
+ return (uint32_t)blame->hunks.length;
+}
+
+const git_blame_hunk *git_blame_get_hunk_byindex(git_blame *blame, uint32_t index)
+{
+ assert(blame);
+ return (git_blame_hunk*)git_vector_get(&blame->hunks, index);
+}
+
+const git_blame_hunk *git_blame_get_hunk_byline(git_blame *blame, uint32_t lineno)
+{
+ size_t i, new_lineno = (size_t)lineno;
+ assert(blame);
+
+ if (!git_vector_bsearch2(&i, &blame->hunks, hunk_byfinalline_search_cmp, &new_lineno)) {
+ return git_blame_get_hunk_byindex(blame, (uint32_t)i);
+ }
+
+ return NULL;
+}
+
+static void normalize_options(
+ git_blame_options *out,
+ const git_blame_options *in,
+ git_repository *repo)
+{
+ git_blame_options dummy = GIT_BLAME_OPTIONS_INIT;
+ if (!in) in = &dummy;
+
+ memcpy(out, in, sizeof(git_blame_options));
+
+ /* No newest_commit => HEAD */
+ if (git_oid_iszero(&out->newest_commit)) {
+ git_reference_name_to_id(&out->newest_commit, repo, "HEAD");
+ }
+
+ /* min_line 0 really means 1 */
+ if (!out->min_line) out->min_line = 1;
+ /* max_line 0 really means N, but we don't know N yet */
+
+ /* Fix up option implications */
+ if (out->flags & GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES)
+ out->flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES;
+ if (out->flags & GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES)
+ out->flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES;
+ if (out->flags & GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES)
+ out->flags |= GIT_BLAME_TRACK_COPIES_SAME_FILE;
+}
+
+static git_blame_hunk *split_hunk_in_vector(
+ git_vector *vec,
+ git_blame_hunk *hunk,
+ size_t rel_line,
+ bool return_new)
+{
+ size_t new_line_count;
+ git_blame_hunk *nh;
+
+ /* Don't split if already at a boundary */
+ if (rel_line <= 0 ||
+ rel_line >= hunk->lines_in_hunk)
+ {
+ return hunk;
+ }
+
+ new_line_count = hunk->lines_in_hunk - rel_line;
+ nh = new_hunk((uint16_t)(hunk->final_start_line_number+rel_line), (uint16_t)new_line_count,
+ (uint16_t)(hunk->orig_start_line_number+rel_line), hunk->orig_path);
+ git_oid_cpy(&nh->final_commit_id, &hunk->final_commit_id);
+ git_oid_cpy(&nh->orig_commit_id, &hunk->orig_commit_id);
+
+ /* Adjust hunk that was split */
+ hunk->lines_in_hunk -= (uint16_t)new_line_count;
+ git_vector_insert_sorted(vec, nh, NULL);
+ {
+ git_blame_hunk *ret = return_new ? nh : hunk;
+ return ret;
+ }
+}
+
+/*
+ * Construct a list of char indices for where lines begin
+ * Adapted from core git:
+ * https://github.com/gitster/git/blob/be5c9fb9049ed470e7005f159bb923a5f4de1309/builtin/blame.c#L1760-L1789
+ */
+static int index_blob_lines(git_blame *blame)
+{
+ const char *buf = blame->final_buf;
+ git_off_t len = blame->final_buf_size;
+ int num = 0, incomplete = 0, bol = 1;
+ size_t *i;
+
+ if (len && buf[len-1] != '\n')
+ incomplete++; /* incomplete line at the end */
+ while (len--) {
+ if (bol) {
+ i = git_array_alloc(blame->line_index);
+ GITERR_CHECK_ALLOC(i);
+ *i = buf - blame->final_buf;
+ bol = 0;
+ }
+ if (*buf++ == '\n') {
+ num++;
+ bol = 1;
+ }
+ }
+ i = git_array_alloc(blame->line_index);
+ GITERR_CHECK_ALLOC(i);
+ *i = buf - blame->final_buf;
+ blame->num_lines = num + incomplete;
+ return blame->num_lines;
+}
+
+static git_blame_hunk* hunk_from_entry(git_blame__entry *e)
+{
+ git_blame_hunk *h = new_hunk(
+ e->lno+1, e->num_lines, e->s_lno+1, e->suspect->path);
+ git_oid_cpy(&h->final_commit_id, git_commit_id(e->suspect->commit));
+ git_oid_cpy(&h->orig_commit_id, git_commit_id(e->suspect->commit));
+ git_signature_dup(&h->final_signature, git_commit_author(e->suspect->commit));
+ git_signature_dup(&h->orig_signature, git_commit_author(e->suspect->commit));
+ h->boundary = e->is_boundary ? 1 : 0;
+ return h;
+}
+
+static int load_blob(git_blame *blame)
+{
+ int error;
+
+ if (blame->final_blob) return 0;
+
+ error = git_commit_lookup(&blame->final, blame->repository, &blame->options.newest_commit);
+ if (error < 0)
+ goto cleanup;
+ error = git_object_lookup_bypath((git_object**)&blame->final_blob,
+ (git_object*)blame->final, blame->path, GIT_OBJ_BLOB);
+
+cleanup:
+ return error;
+}
+
+static int blame_internal(git_blame *blame)
+{
+ int error;
+ git_blame__entry *ent = NULL;
+ git_blame__origin *o;
+
+ if ((error = load_blob(blame)) < 0 ||
+ (error = git_blame__get_origin(&o, blame, blame->final, blame->path)) < 0)
+ goto cleanup;
+ blame->final_buf = git_blob_rawcontent(blame->final_blob);
+ blame->final_buf_size = git_blob_rawsize(blame->final_blob);
+
+ ent = git__calloc(1, sizeof(git_blame__entry));
+ ent->num_lines = index_blob_lines(blame);
+ ent->lno = blame->options.min_line - 1;
+ ent->num_lines = ent->num_lines - blame->options.min_line + 1;
+ if (blame->options.max_line > 0)
+ ent->num_lines = blame->options.max_line - blame->options.min_line + 1;
+ ent->s_lno = ent->lno;
+ ent->suspect = o;
+
+ blame->ent = ent;
+ blame->path = blame->path;
+
+ git_blame__like_git(blame, blame->options.flags);
+
+cleanup:
+ for (ent = blame->ent; ent; ) {
+ git_blame__entry *e = ent->next;
+
+ git_vector_insert(&blame->hunks, hunk_from_entry(ent));
+
+ git_blame__free_entry(ent);
+ ent = e;
+ }
+
+ return error;
+}
+
+/*******************************************************************************
+ * File blaming
+ ******************************************************************************/
+
+int git_blame_file(
+ git_blame **out,
+ git_repository *repo,
+ const char *path,
+ git_blame_options *options)
+{
+ int error = -1;
+ git_blame_options normOptions = GIT_BLAME_OPTIONS_INIT;
+ git_blame *blame = NULL;
+
+ assert(out && repo && path);
+ normalize_options(&normOptions, options, repo);
+
+ blame = git_blame__alloc(repo, normOptions, path);
+ GITERR_CHECK_ALLOC(blame);
+
+ if ((error = load_blob(blame)) < 0)
+ goto on_error;
+
+ if ((error = blame_internal(blame)) < 0)
+ goto on_error;
+
+ *out = blame;
+ return 0;
+
+on_error:
+ git_blame_free(blame);
+ return error;
+}
+
+/*******************************************************************************
+ * Buffer blaming
+ *******************************************************************************/
+
+static bool hunk_is_bufferblame(git_blame_hunk *hunk)
+{
+ return git_oid_iszero(&hunk->final_commit_id);
+}
+
+static int buffer_hunk_cb(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk,
+ void *payload)
+{
+ git_blame *blame = (git_blame*)payload;
+ uint32_t wedge_line;
+
+ GIT_UNUSED(delta);
+
+ wedge_line = (hunk->old_lines == 0) ? hunk->new_start : hunk->old_start;
+ blame->current_diff_line = wedge_line;
+
+ blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byline(blame, wedge_line);
+ if (!blame->current_hunk) {
+ /* Line added at the end of the file */
+ blame->current_hunk = new_hunk(wedge_line, 0, wedge_line, blame->path);
+ git_vector_insert(&blame->hunks, blame->current_hunk);
+ } else if (!hunk_starts_at_or_after_line(blame->current_hunk, wedge_line)){
+ /* If this hunk doesn't start between existing hunks, split a hunk up so it does */
+ blame->current_hunk = split_hunk_in_vector(&blame->hunks, blame->current_hunk,
+ wedge_line - blame->current_hunk->orig_start_line_number, true);
+ }
+
+ return 0;
+}
+
+static int ptrs_equal_cmp(const void *a, const void *b) { return a<b ? -1 : a>b ? 1 : 0; }
+static int buffer_line_cb(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk,
+ const git_diff_line *line,
+ void *payload)
+{
+ git_blame *blame = (git_blame*)payload;
+
+ GIT_UNUSED(delta);
+ GIT_UNUSED(hunk);
+ GIT_UNUSED(line);
+
+ if (line->origin == GIT_DIFF_LINE_ADDITION) {
+ if (hunk_is_bufferblame(blame->current_hunk) &&
+ hunk_ends_at_or_before_line(blame->current_hunk, blame->current_diff_line)) {
+ /* Append to the current buffer-blame hunk */
+ blame->current_hunk->lines_in_hunk++;
+ shift_hunks_by(&blame->hunks, blame->current_diff_line+1, 1);
+ } else {
+ /* Create a new buffer-blame hunk with this line */
+ shift_hunks_by(&blame->hunks, blame->current_diff_line, 1);
+ blame->current_hunk = new_hunk((uint16_t)blame->current_diff_line, 1, 0, blame->path);
+ git_vector_insert_sorted(&blame->hunks, blame->current_hunk, NULL);
+ }
+ blame->current_diff_line++;
+ }
+
+ if (line->origin == GIT_DIFF_LINE_DELETION) {
+ /* Trim the line from the current hunk; remove it if it's now empty */
+ size_t shift_base = blame->current_diff_line + blame->current_hunk->lines_in_hunk+1;
+
+ if (--(blame->current_hunk->lines_in_hunk) == 0) {
+ size_t i;
+ shift_base--;
+ if (!git_vector_search2(&i, &blame->hunks, ptrs_equal_cmp, blame->current_hunk)) {
+ git_vector_remove(&blame->hunks, i);
+ free_hunk(blame->current_hunk);
+ blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byindex(blame, (uint32_t)i);
+ }
+ }
+ shift_hunks_by(&blame->hunks, shift_base, -1);
+ }
+ return 0;
+}
+
+int git_blame_buffer(
+ git_blame **out,
+ git_blame *reference,
+ const char *buffer,
+ size_t buffer_len)
+{
+ git_blame *blame;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ size_t i;
+ git_blame_hunk *hunk;
+
+ diffopts.context_lines = 0;
+
+ assert(out && reference && buffer && buffer_len);
+
+ blame = git_blame__alloc(reference->repository, reference->options, reference->path);
+
+ /* Duplicate all of the hunk structures in the reference blame */
+ git_vector_foreach(&reference->hunks, i, hunk) {
+ git_vector_insert(&blame->hunks, dup_hunk(hunk));
+ }
+
+ /* Diff to the reference blob */
+ git_diff_blob_to_buffer(reference->final_blob, blame->path,
+ buffer, buffer_len, blame->path,
+ &diffopts, NULL, buffer_hunk_cb, buffer_line_cb, blame);
+
+ *out = blame;
+ return 0;
+}
+
+int git_blame_init_options(git_blame_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_blame_options, GIT_BLAME_OPTIONS_INIT);
+ return 0;
+}
diff --git a/src/blame.h b/src/blame.h
new file mode 100644
index 000000000..7e23de808
--- /dev/null
+++ b/src/blame.h
@@ -0,0 +1,93 @@
+#ifndef INCLUDE_blame_h__
+#define INCLUDE_blame_h__
+
+#include "git2/blame.h"
+#include "common.h"
+#include "vector.h"
+#include "diff.h"
+#include "array.h"
+#include "git2/oid.h"
+
+/*
+ * One blob in a commit that is being suspected
+ */
+typedef struct git_blame__origin {
+ int refcnt;
+ struct git_blame__origin *previous;
+ git_commit *commit;
+ git_blob *blob;
+ char path[GIT_FLEX_ARRAY];
+} git_blame__origin;
+
+/*
+ * Each group of lines is described by a git_blame__entry; it can be split
+ * as we pass blame to the parents. They form a linked list in the
+ * scoreboard structure, sorted by the target line number.
+ */
+typedef struct git_blame__entry {
+ struct git_blame__entry *prev;
+ struct git_blame__entry *next;
+
+ /* the first line of this group in the final image;
+ * internally all line numbers are 0 based.
+ */
+ int lno;
+
+ /* how many lines this group has */
+ int num_lines;
+
+ /* the commit that introduced this group into the final image */
+ git_blame__origin *suspect;
+
+ /* true if the suspect is truly guilty; false while we have not
+ * checked if the group came from one of its parents.
+ */
+ bool guilty;
+
+ /* true if the entry has been scanned for copies in the current parent
+ */
+ bool scanned;
+
+ /* the line number of the first line of this group in the
+ * suspect's file; internally all line numbers are 0 based.
+ */
+ int s_lno;
+
+ /* how significant this entry is -- cached to avoid
+ * scanning the lines over and over.
+ */
+ unsigned score;
+
+ /* Whether this entry has been tracked to a boundary commit.
+ */
+ bool is_boundary;
+} git_blame__entry;
+
+struct git_blame {
+ char *path;
+ git_repository *repository;
+ git_blame_options options;
+
+ git_vector hunks;
+ git_vector paths;
+
+ git_blob *final_blob;
+ git_array_t(size_t) line_index;
+
+ size_t current_diff_line;
+ git_blame_hunk *current_hunk;
+
+ /* Scoreboard fields */
+ git_commit *final;
+ git_blame__entry *ent;
+ int num_lines;
+ const char *final_buf;
+ git_off_t final_buf_size;
+};
+
+git_blame *git_blame__alloc(
+ git_repository *repo,
+ git_blame_options opts,
+ const char *path);
+
+#endif
diff --git a/src/blame_git.c b/src/blame_git.c
new file mode 100644
index 000000000..72afb852b
--- /dev/null
+++ b/src/blame_git.c
@@ -0,0 +1,624 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "blame_git.h"
+#include "commit.h"
+#include "blob.h"
+#include "xdiff/xinclude.h"
+
+/*
+ * Origin is refcounted and usually we keep the blob contents to be
+ * reused.
+ */
+static git_blame__origin *origin_incref(git_blame__origin *o)
+{
+ if (o)
+ o->refcnt++;
+ return o;
+}
+
+static void origin_decref(git_blame__origin *o)
+{
+ if (o && --o->refcnt <= 0) {
+ if (o->previous)
+ origin_decref(o->previous);
+ git_blob_free(o->blob);
+ git_commit_free(o->commit);
+ git__free(o);
+ }
+}
+
+/* Given a commit and a path in it, create a new origin structure. */
+static int make_origin(git_blame__origin **out, git_commit *commit, const char *path)
+{
+ int error = 0;
+ git_blame__origin *o;
+
+ o = git__calloc(1, sizeof(*o) + strlen(path) + 1);
+ GITERR_CHECK_ALLOC(o);
+ o->commit = commit;
+ o->refcnt = 1;
+ strcpy(o->path, path);
+
+ if (!(error = git_object_lookup_bypath((git_object**)&o->blob, (git_object*)commit,
+ path, GIT_OBJ_BLOB))) {
+ *out = o;
+ } else {
+ origin_decref(o);
+ }
+ return error;
+}
+
+/* Locate an existing origin or create a new one. */
+int git_blame__get_origin(
+ git_blame__origin **out,
+ git_blame *blame,
+ git_commit *commit,
+ const char *path)
+{
+ git_blame__entry *e;
+
+ for (e = blame->ent; e; e = e->next) {
+ if (e->suspect->commit == commit && !strcmp(e->suspect->path, path)) {
+ *out = origin_incref(e->suspect);
+ }
+ }
+ return make_origin(out, commit, path);
+}
+
+typedef struct blame_chunk_cb_data {
+ git_blame *blame;
+ git_blame__origin *target;
+ git_blame__origin *parent;
+ long tlno;
+ long plno;
+}blame_chunk_cb_data;
+
+static bool same_suspect(git_blame__origin *a, git_blame__origin *b)
+{
+ if (a == b)
+ return true;
+ if (git_oid_cmp(git_commit_id(a->commit), git_commit_id(b->commit)))
+ return false;
+ return 0 == strcmp(a->path, b->path);
+}
+
+/* find the line number of the last line the target is suspected for */
+static int find_last_in_target(git_blame *blame, git_blame__origin *target)
+{
+ git_blame__entry *e;
+ int last_in_target = -1;
+
+ for (e=blame->ent; e; e=e->next) {
+ if (e->guilty || !same_suspect(e->suspect, target))
+ continue;
+ if (last_in_target < e->s_lno + e->num_lines)
+ last_in_target = e->s_lno + e->num_lines;
+ }
+ return last_in_target;
+}
+
+/*
+ * It is known that lines between tlno to same came from parent, and e
+ * has an overlap with that range. it also is known that parent's
+ * line plno corresponds to e's line tlno.
+ *
+ * <---- e ----->
+ * <------> (entirely within)
+ * <------------> (extends past)
+ * <------------> (starts before)
+ * <------------------> (entirely encloses)
+ *
+ * Split e into potentially three parts; before this chunk, the chunk
+ * to be blamed for the parent, and after that portion.
+ */
+static void split_overlap(git_blame__entry *split, git_blame__entry *e,
+ int tlno, int plno, int same, git_blame__origin *parent)
+{
+ int chunk_end_lno;
+
+ if (e->s_lno < tlno) {
+ /* there is a pre-chunk part not blamed on the parent */
+ split[0].suspect = origin_incref(e->suspect);
+ split[0].lno = e->lno;
+ split[0].s_lno = e->s_lno;
+ split[0].num_lines = tlno - e->s_lno;
+ split[1].lno = e->lno + tlno - e->s_lno;
+ split[1].s_lno = plno;
+ } else {
+ split[1].lno = e->lno;
+ split[1].s_lno = plno + (e->s_lno - tlno);
+ }
+
+ if (same < e->s_lno + e->num_lines) {
+ /* there is a post-chunk part not blamed on parent */
+ split[2].suspect = origin_incref(e->suspect);
+ split[2].lno = e->lno + (same - e->s_lno);
+ split[2].s_lno = e->s_lno + (same - e->s_lno);
+ split[2].num_lines = e->s_lno + e->num_lines - same;
+ chunk_end_lno = split[2].lno;
+ } else {
+ chunk_end_lno = e->lno + e->num_lines;
+ }
+ split[1].num_lines = chunk_end_lno - split[1].lno;
+
+ /*
+ * if it turns out there is nothing to blame the parent for, forget about
+ * the splitting. !split[1].suspect signals this.
+ */
+ if (split[1].num_lines < 1)
+ return;
+ split[1].suspect = origin_incref(parent);
+}
+
+/*
+ * Link in a new blame entry to the scoreboard. Entries that cover the same
+ * line range have been removed from the scoreboard previously.
+ */
+static void add_blame_entry(git_blame *blame, git_blame__entry *e)
+{
+ git_blame__entry *ent, *prev = NULL;
+
+ origin_incref(e->suspect);
+
+ for (ent = blame->ent; ent && ent->lno < e->lno; ent = ent->next)
+ prev = ent;
+
+ /* prev, if not NULL, is the last one that is below e */
+ e->prev = prev;
+ if (prev) {
+ e->next = prev->next;
+ prev->next = e;
+ } else {
+ e->next = blame->ent;
+ blame->ent = e;
+ }
+ if (e->next)
+ e->next->prev = e;
+}
+
+/*
+ * src typically is on-stack; we want to copy the information in it to
+ * a malloced blame_entry that is already on the linked list of the scoreboard.
+ * The origin of dst loses a refcnt while the origin of src gains one.
+ */
+static void dup_entry(git_blame__entry *dst, git_blame__entry *src)
+{
+ git_blame__entry *p, *n;
+
+ p = dst->prev;
+ n = dst->next;
+ origin_incref(src->suspect);
+ origin_decref(dst->suspect);
+ memcpy(dst, src, sizeof(*src));
+ dst->prev = p;
+ dst->next = n;
+ dst->score = 0;
+}
+
+/*
+ * split_overlap() divided an existing blame e into up to three parts in split.
+ * Adjust the linked list of blames in the scoreboard to reflect the split.
+ */
+static void split_blame(git_blame *blame, git_blame__entry *split, git_blame__entry *e)
+{
+ git_blame__entry *new_entry;
+
+ if (split[0].suspect && split[2].suspect) {
+ /* The first part (reuse storage for the existing entry e */
+ dup_entry(e, &split[0]);
+
+ /* The last part -- me */
+ new_entry = git__malloc(sizeof(*new_entry));
+ memcpy(new_entry, &(split[2]), sizeof(git_blame__entry));
+ add_blame_entry(blame, new_entry);
+
+ /* ... and the middle part -- parent */
+ new_entry = git__malloc(sizeof(*new_entry));
+ memcpy(new_entry, &(split[1]), sizeof(git_blame__entry));
+ add_blame_entry(blame, new_entry);
+ } else if (!split[0].suspect && !split[2].suspect) {
+ /*
+ * The parent covers the entire area; reuse storage for e and replace it
+ * with the parent
+ */
+ dup_entry(e, &split[1]);
+ } else if (split[0].suspect) {
+ /* me and then parent */
+ dup_entry(e, &split[0]);
+ new_entry = git__malloc(sizeof(*new_entry));
+ memcpy(new_entry, &(split[1]), sizeof(git_blame__entry));
+ add_blame_entry(blame, new_entry);
+ } else {
+ /* parent and then me */
+ dup_entry(e, &split[1]);
+ new_entry = git__malloc(sizeof(*new_entry));
+ memcpy(new_entry, &(split[2]), sizeof(git_blame__entry));
+ add_blame_entry(blame, new_entry);
+ }
+}
+
+/*
+ * After splitting the blame, the origins used by the on-stack blame_entry
+ * should lose one refcnt each.
+ */
+static void decref_split(git_blame__entry *split)
+{
+ int i;
+ for (i=0; i<3; i++)
+ origin_decref(split[i].suspect);
+}
+
+/*
+ * Helper for blame_chunk(). blame_entry e is known to overlap with the patch
+ * hunk; split it and pass blame to the parent.
+ */
+static void blame_overlap(
+ git_blame *blame,
+ git_blame__entry *e,
+ int tlno,
+ int plno,
+ int same,
+ git_blame__origin *parent)
+{
+ git_blame__entry split[3] = {{0}};
+
+ split_overlap(split, e, tlno, plno, same, parent);
+ if (split[1].suspect)
+ split_blame(blame, split, e);
+ decref_split(split);
+}
+
+/*
+ * Process one hunk from the patch between the current suspect for blame_entry
+ * e and its parent. Find and split the overlap, and pass blame to the
+ * overlapping part to the parent.
+ */
+static void blame_chunk(
+ git_blame *blame,
+ int tlno,
+ int plno,
+ int same,
+ git_blame__origin *target,
+ git_blame__origin *parent)
+{
+ git_blame__entry *e;
+
+ for (e = blame->ent; e; e = e->next) {
+ if (e->guilty || !same_suspect(e->suspect, target))
+ continue;
+ if (same <= e->s_lno)
+ continue;
+ if (tlno < e->s_lno + e->num_lines) {
+ blame_overlap(blame, e, tlno, plno, same, parent);
+ }
+ }
+}
+
+static int my_emit(
+ xdfenv_t *xe,
+ xdchange_t *xscr,
+ xdemitcb_t *ecb,
+ xdemitconf_t const *xecfg)
+{
+ xdchange_t *xch = xscr;
+ GIT_UNUSED(xe);
+ GIT_UNUSED(xecfg);
+ while (xch) {
+ blame_chunk_cb_data *d = ecb->priv;
+ blame_chunk(d->blame, d->tlno, d->plno, xch->i2, d->target, d->parent);
+ d->plno = xch->i1 + xch->chg1;
+ d->tlno = xch->i2 + xch->chg2;
+ xch = xch->next;
+ }
+ return 0;
+}
+
+static void trim_common_tail(mmfile_t *a, mmfile_t *b, long ctx)
+{
+ const int blk = 1024;
+ long trimmed = 0, recovered = 0;
+ char *ap = a->ptr + a->size;
+ char *bp = b->ptr + b->size;
+ long smaller = (long)((a->size < b->size) ? a->size : b->size);
+
+ if (ctx)
+ return;
+
+ while (blk + trimmed <= smaller && !memcmp(ap - blk, bp - blk, blk)) {
+ trimmed += blk;
+ ap -= blk;
+ bp -= blk;
+ }
+
+ while (recovered < trimmed)
+ if (ap[recovered++] == '\n')
+ break;
+ a->size -= trimmed - recovered;
+ b->size -= trimmed - recovered;
+}
+
+static int diff_hunks(mmfile_t file_a, mmfile_t file_b, void *cb_data)
+{
+ xpparam_t xpp = {0};
+ xdemitconf_t xecfg = {0};
+ xdemitcb_t ecb = {0};
+
+ xecfg.emit_func = (void(*)(void))my_emit;
+ ecb.priv = cb_data;
+
+ trim_common_tail(&file_a, &file_b, 0);
+ return xdl_diff(&file_a, &file_b, &xpp, &xecfg, &ecb);
+}
+
+static void fill_origin_blob(git_blame__origin *o, mmfile_t *file)
+{
+ memset(file, 0, sizeof(*file));
+ if (o->blob) {
+ file->ptr = (char*)git_blob_rawcontent(o->blob);
+ file->size = (size_t)git_blob_rawsize(o->blob);
+ }
+}
+
+static int pass_blame_to_parent(
+ git_blame *blame,
+ git_blame__origin *target,
+ git_blame__origin *parent)
+{
+ int last_in_target;
+ mmfile_t file_p, file_o;
+ blame_chunk_cb_data d = { blame, target, parent, 0, 0 };
+
+ last_in_target = find_last_in_target(blame, target);
+ if (last_in_target < 0)
+ return 1; /* nothing remains for this target */
+
+ fill_origin_blob(parent, &file_p);
+ fill_origin_blob(target, &file_o);
+
+ diff_hunks(file_p, file_o, &d);
+ /* The reset (i.e. anything after tlno) are the same as the parent */
+ blame_chunk(blame, d.tlno, d.plno, last_in_target, target, parent);
+
+ return 0;
+}
+
+static int paths_on_dup(void **old, void *new)
+{
+ GIT_UNUSED(old);
+ git__free(new);
+ return -1;
+}
+
+static git_blame__origin* find_origin(
+ git_blame *blame,
+ git_commit *parent,
+ git_blame__origin *origin)
+{
+ git_blame__origin *porigin = NULL;
+ git_diff *difflist = NULL;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_tree *otree=NULL, *ptree=NULL;
+
+ /* Get the trees from this commit and its parent */
+ if (0 != git_commit_tree(&otree, origin->commit) ||
+ 0 != git_commit_tree(&ptree, parent))
+ goto cleanup;
+
+ /* Configure the diff */
+ diffopts.context_lines = 0;
+ diffopts.flags = GIT_DIFF_SKIP_BINARY_CHECK;
+
+ /* Check to see if files we're interested have changed */
+ diffopts.pathspec.count = blame->paths.length;
+ diffopts.pathspec.strings = (char**)blame->paths.contents;
+ if (0 != git_diff_tree_to_tree(&difflist, blame->repository, ptree, otree, &diffopts))
+ goto cleanup;
+
+ if (!git_diff_num_deltas(difflist)) {
+ /* No changes; copy data */
+ git_blame__get_origin(&porigin, blame, parent, origin->path);
+ } else {
+ git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
+ int i;
+
+ /* Generate a full diff between the two trees */
+ git_diff_free(difflist);
+ diffopts.pathspec.count = 0;
+ if (0 != git_diff_tree_to_tree(&difflist, blame->repository, ptree, otree, &diffopts))
+ goto cleanup;
+
+ /* Let diff find renames */
+ findopts.flags = GIT_DIFF_FIND_RENAMES;
+ if (0 != git_diff_find_similar(difflist, &findopts))
+ goto cleanup;
+
+ /* Find one that matches */
+ for (i=0; i<(int)git_diff_num_deltas(difflist); i++) {
+ const git_diff_delta *delta = git_diff_get_delta(difflist, i);
+
+ if (!git_vector_bsearch(NULL, &blame->paths, delta->new_file.path))
+ {
+ git_vector_insert_sorted(&blame->paths, (void*)git__strdup(delta->old_file.path),
+ paths_on_dup);
+ make_origin(&porigin, parent, delta->old_file.path);
+ }
+ }
+ }
+
+cleanup:
+ git_diff_free(difflist);
+ git_tree_free(otree);
+ git_tree_free(ptree);
+ return porigin;
+}
+
+/*
+ * The blobs of origin and porigin exactly match, so everything origin is
+ * suspected for can be blamed on the parent.
+ */
+static void pass_whole_blame(git_blame *blame,
+ git_blame__origin *origin, git_blame__origin *porigin)
+{
+ git_blame__entry *e;
+
+ if (!porigin->blob)
+ git_object_lookup((git_object**)&porigin->blob, blame->repository,
+ git_blob_id(origin->blob), GIT_OBJ_BLOB);
+ for (e=blame->ent; e; e=e->next) {
+ if (!same_suspect(e->suspect, origin))
+ continue;
+ origin_incref(porigin);
+ origin_decref(e->suspect);
+ e->suspect = porigin;
+ }
+}
+
+static void pass_blame(git_blame *blame, git_blame__origin *origin, uint32_t opt)
+{
+ git_commit *commit = origin->commit;
+ int i, num_parents;
+ git_blame__origin *sg_buf[16];
+ git_blame__origin *porigin, **sg_origin = sg_buf;
+
+ num_parents = git_commit_parentcount(commit);
+ if (!git_oid_cmp(git_commit_id(commit), &blame->options.oldest_commit))
+ /* Stop at oldest specified commit */
+ num_parents = 0;
+ else if (opt & GIT_BLAME_FIRST_PARENT && num_parents > 1)
+ /* Limit search to the first parent */
+ num_parents = 1;
+
+ if (!num_parents) {
+ git_oid_cpy(&blame->options.oldest_commit, git_commit_id(commit));
+ goto finish;
+ }
+ else if (num_parents < (int)ARRAY_SIZE(sg_buf))
+ memset(sg_buf, 0, sizeof(sg_buf));
+ else
+ sg_origin = git__calloc(num_parents, sizeof(*sg_origin));
+
+ for (i=0; i<num_parents; i++) {
+ git_commit *p;
+ int j, same;
+
+ if (sg_origin[i])
+ continue;
+
+ git_commit_parent(&p, origin->commit, i);
+ porigin = find_origin(blame, p, origin);
+
+ if (!porigin)
+ continue;
+ if (porigin->blob && origin->blob &&
+ !git_oid_cmp(git_blob_id(porigin->blob), git_blob_id(origin->blob))) {
+ pass_whole_blame(blame, origin, porigin);
+ origin_decref(porigin);
+ goto finish;
+ }
+ for (j = same = 0; j<i; j++)
+ if (sg_origin[j] &&
+ !git_oid_cmp(git_blob_id(sg_origin[j]->blob), git_blob_id(porigin->blob))) {
+ same = 1;
+ break;
+ }
+ if (!same)
+ sg_origin[i] = porigin;
+ else
+ origin_decref(porigin);
+ }
+
+ /* Standard blame */
+ for (i=0; i<num_parents; i++) {
+ git_blame__origin *porigin = sg_origin[i];
+ if (!porigin)
+ continue;
+ if (!origin->previous) {
+ origin_incref(porigin);
+ origin->previous = porigin;
+ }
+ if (pass_blame_to_parent(blame, origin, porigin))
+ goto finish;
+ }
+
+ /* TODO: optionally find moves in parents' files */
+
+ /* TODO: optionally find copies in parents' files */
+
+finish:
+ for (i=0; i<num_parents; i++)
+ if (sg_origin[i])
+ origin_decref(sg_origin[i]);
+ if (sg_origin != sg_buf)
+ git__free(sg_origin);
+ return;
+}
+
+/*
+ * If two blame entries that are next to each other came from
+ * contiguous lines in the same origin (i.e. <commit, path> pair),
+ * merge them together.
+ */
+static void coalesce(git_blame *blame)
+{
+ git_blame__entry *ent, *next;
+
+ for (ent=blame->ent; ent && (next = ent->next); ent = next) {
+ if (same_suspect(ent->suspect, next->suspect) &&
+ ent->guilty == next->guilty &&
+ ent->s_lno + ent->num_lines == next->s_lno)
+ {
+ ent->num_lines += next->num_lines;
+ ent->next = next->next;
+ if (ent->next)
+ ent->next->prev = ent;
+ origin_decref(next->suspect);
+ git__free(next);
+ ent->score = 0;
+ next = ent; /* again */
+ }
+ }
+}
+
+void git_blame__like_git(git_blame *blame, uint32_t opt)
+{
+ while (true) {
+ git_blame__entry *ent;
+ git_blame__origin *suspect = NULL;
+
+ /* Find a suspect to break down */
+ for (ent = blame->ent; !suspect && ent; ent = ent->next)
+ if (!ent->guilty)
+ suspect = ent->suspect;
+ if (!suspect)
+ return; /* all done */
+
+ /* We'll use this suspect later in the loop, so hold on to it for now. */
+ origin_incref(suspect);
+ pass_blame(blame, suspect, opt);
+
+ /* Take responsibility for the remaining entries */
+ for (ent = blame->ent; ent; ent = ent->next) {
+ if (same_suspect(ent->suspect, suspect)) {
+ ent->guilty = true;
+ ent->is_boundary = !git_oid_cmp(
+ git_commit_id(suspect->commit),
+ &blame->options.oldest_commit);
+ }
+ }
+ origin_decref(suspect);
+ }
+
+ coalesce(blame);
+}
+
+void git_blame__free_entry(git_blame__entry *ent)
+{
+ if (!ent) return;
+ origin_decref(ent->suspect);
+ git__free(ent);
+}
diff --git a/src/blame_git.h b/src/blame_git.h
new file mode 100644
index 000000000..3ec2710b8
--- /dev/null
+++ b/src/blame_git.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_blame_git__
+#define INCLUDE_blame_git__
+
+#include "blame.h"
+
+int git_blame__get_origin(
+ git_blame__origin **out,
+ git_blame *sb,
+ git_commit *commit,
+ const char *path);
+void git_blame__free_entry(git_blame__entry *ent);
+void git_blame__like_git(git_blame *sb, uint32_t flags);
+
+#endif
diff --git a/src/blob.c b/src/blob.c
index b8282e505..30d5b705b 100644
--- a/src/blob.c
+++ b/src/blob.c
@@ -1,140 +1,370 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
*/
#include "git2/common.h"
#include "git2/object.h"
#include "git2/repository.h"
+#include "git2/odb_backend.h"
#include "common.h"
+#include "filebuf.h"
#include "blob.h"
+#include "filter.h"
+#include "buf_text.h"
-const void *git_blob_rawcontent(git_blob *blob)
+const void *git_blob_rawcontent(const git_blob *blob)
{
assert(blob);
- return blob->odb_object->raw.data;
+ return git_odb_object_data(blob->odb_object);
}
-int git_blob_rawsize(git_blob *blob)
+git_off_t git_blob_rawsize(const git_blob *blob)
{
assert(blob);
- return blob->odb_object->raw.len;
+ return (git_off_t)git_odb_object_size(blob->odb_object);
+}
+
+int git_blob__getbuf(git_buf *buffer, git_blob *blob)
+{
+ return git_buf_set(
+ buffer,
+ git_odb_object_data(blob->odb_object),
+ git_odb_object_size(blob->odb_object));
}
-void git_blob__free(git_blob *blob)
+void git_blob__free(void *blob)
{
- git_odb_object_close(blob->odb_object);
- free(blob);
+ git_odb_object_free(((git_blob *)blob)->odb_object);
+ git__free(blob);
}
-int git_blob__parse(git_blob *blob, git_odb_object *odb_obj)
+int git_blob__parse(void *blob, git_odb_object *odb_obj)
{
assert(blob);
git_cached_obj_incref((git_cached_obj *)odb_obj);
- blob->odb_object = odb_obj;
- return GIT_SUCCESS;
+ ((git_blob *)blob)->odb_object = odb_obj;
+ return 0;
}
-int git_blob_create_frombuffer(git_oid *oid, git_repository *repo, const void *buffer, size_t len)
+int git_blob_create_frombuffer(
+ git_oid *id, git_repository *repo, const void *buffer, size_t len)
{
int error;
+ git_odb *odb;
git_odb_stream *stream;
- if ((error = git_odb_open_wstream(&stream, repo->db, len, GIT_OBJ_BLOB)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to create blob");
+ assert(id && repo);
+
+ if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 ||
+ (error = git_odb_open_wstream(&stream, odb, len, GIT_OBJ_BLOB)) < 0)
+ return error;
+
+ if ((error = git_odb_stream_write(stream, buffer, len)) == 0)
+ error = git_odb_stream_finalize_write(id, stream);
+
+ git_odb_stream_free(stream);
+ return error;
+}
+
+static int write_file_stream(
+ git_oid *id, git_odb *odb, const char *path, git_off_t file_size)
+{
+ int fd, error;
+ char buffer[4096];
+ git_odb_stream *stream = NULL;
+ ssize_t read_len = -1, written = 0;
- if ((error = stream->write(stream, buffer, len)) < GIT_SUCCESS) {
- stream->free(stream);
+ if ((error = git_odb_open_wstream(
+ &stream, odb, (size_t)file_size, GIT_OBJ_BLOB)) < 0)
return error;
+
+ if ((fd = git_futils_open_ro(path)) < 0) {
+ git_odb_stream_free(stream);
+ return -1;
+ }
+
+ while (!error && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) {
+ error = git_odb_stream_write(stream, buffer, read_len);
+ written += read_len;
}
- error = stream->finalize_write(oid, stream);
- stream->free(stream);
+ p_close(fd);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to create blob");
+ if (written != file_size || read_len < 0) {
+ giterr_set(GITERR_OS, "Failed to read file into stream");
+ error = -1;
+ }
+
+ if (!error)
+ error = git_odb_stream_finalize_write(id, stream);
- return GIT_SUCCESS;
+ git_odb_stream_free(stream);
+ return error;
}
-int git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *path)
+static int write_file_filtered(
+ git_oid *id,
+ git_off_t *size,
+ git_odb *odb,
+ const char *full_path,
+ git_filter_list *fl)
{
- int error, islnk;
- int fd = 0;
- char full_path[GIT_PATH_MAX];
- char buffer[2048];
- git_off_t size;
- git_odb_stream *stream;
+ int error;
+ git_buf tgt = GIT_BUF_INIT;
+
+ error = git_filter_list_apply_to_file(&tgt, fl, NULL, full_path);
+
+ /* Write the file to disk if it was properly filtered */
+ if (!error) {
+ *size = tgt.size;
+
+ error = git_odb_write(id, odb, tgt.ptr, tgt.size, GIT_OBJ_BLOB);
+ }
+
+ git_buf_free(&tgt);
+ return error;
+}
+
+static int write_symlink(
+ git_oid *id, git_odb *odb, const char *path, size_t link_size)
+{
+ char *link_data;
+ ssize_t read_len;
+ int error;
+
+ link_data = git__malloc(link_size);
+ GITERR_CHECK_ALLOC(link_data);
+
+ read_len = p_readlink(path, link_data, link_size);
+ if (read_len != (ssize_t)link_size) {
+ giterr_set(GITERR_OS, "Failed to create blob. Can't read symlink '%s'", path);
+ git__free(link_data);
+ return -1;
+ }
+
+ error = git_odb_write(id, odb, (void *)link_data, link_size, GIT_OBJ_BLOB);
+ git__free(link_data);
+ return error;
+}
+
+int git_blob__create_from_paths(
+ git_oid *id,
+ struct stat *out_st,
+ git_repository *repo,
+ const char *content_path,
+ const char *hint_path,
+ mode_t hint_mode,
+ bool try_load_filters)
+{
+ int error;
struct stat st;
+ git_odb *odb = NULL;
+ git_off_t size;
+ mode_t mode;
+ git_buf path = GIT_BUF_INIT;
+
+ assert(hint_path || !try_load_filters);
- if (repo->path_workdir == NULL)
- return git__throw(GIT_ENOTFOUND, "Failed to create blob. (No working directory found)");
+ if (!content_path) {
+ if (git_repository__ensure_not_bare(repo, "create blob from file") < 0)
+ return GIT_EBAREREPO;
- git_path_join(full_path, repo->path_workdir, path);
+ if (git_buf_joinpath(
+ &path, git_repository_workdir(repo), hint_path) < 0)
+ return -1;
- error = p_lstat(full_path, &st);
- if (error < 0) {
- return git__throw(GIT_EOSERR, "Failed to stat blob. %s", strerror(errno));
+ content_path = path.ptr;
}
- islnk = S_ISLNK(st.st_mode);
+ if ((error = git_path_lstat(content_path, &st)) < 0 ||
+ (error = git_repository_odb(&odb, repo)) < 0)
+ goto done;
+
+ if (out_st)
+ memcpy(out_st, &st, sizeof(st));
+
size = st.st_size;
+ mode = hint_mode ? hint_mode : st.st_mode;
+
+ if (S_ISLNK(mode)) {
+ error = write_symlink(id, odb, content_path, (size_t)size);
+ } else {
+ git_filter_list *fl = NULL;
+
+ if (try_load_filters)
+ /* Load the filters for writing this file to the ODB */
+ error = git_filter_list_load(
+ &fl, repo, NULL, hint_path,
+ GIT_FILTER_TO_ODB, GIT_FILTER_OPT_DEFAULT);
- if (!islnk)
- if ((fd = p_open(full_path, O_RDONLY)) < 0)
- return git__throw(GIT_ENOTFOUND, "Failed to create blob. Could not open '%s'", full_path);
+ if (error < 0)
+ /* well, that didn't work */;
+ else if (fl == NULL)
+ /* No filters need to be applied to the document: we can stream
+ * directly from disk */
+ error = write_file_stream(id, odb, content_path, size);
+ else {
+ /* We need to apply one or more filters */
+ error = write_file_filtered(id, &size, odb, content_path, fl);
+
+ git_filter_list_free(fl);
+ }
- if ((error = git_odb_open_wstream(&stream, repo->db, (size_t)size, GIT_OBJ_BLOB)) < GIT_SUCCESS) {
- if (!islnk)
- p_close(fd);
- return git__rethrow(error, "Failed to create blob");
+ /*
+ * TODO: eventually support streaming filtered files, for files
+ * which are bigger than a given threshold. This is not a priority
+ * because applying a filter in streaming mode changes the final
+ * size of the blob, and without knowing its final size, the blob
+ * cannot be written in stream mode to the ODB.
+ *
+ * The plan is to do streaming writes to a tempfile on disk and then
+ * opening streaming that file to the ODB, using
+ * `write_file_stream`.
+ *
+ * CAREFULLY DESIGNED APIS YO
+ */
}
- while (size > 0) {
- ssize_t read_len;
+done:
+ git_odb_free(odb);
+ git_buf_free(&path);
- if (!islnk)
- read_len = p_read(fd, buffer, sizeof(buffer));
- else
- read_len = p_readlink(full_path, buffer, sizeof(buffer));
+ return error;
+}
+
+int git_blob_create_fromworkdir(
+ git_oid *id, git_repository *repo, const char *path)
+{
+ return git_blob__create_from_paths(id, NULL, repo, NULL, path, 0, true);
+}
+
+int git_blob_create_fromdisk(
+ git_oid *id, git_repository *repo, const char *path)
+{
+ int error;
+ git_buf full_path = GIT_BUF_INIT;
+ const char *workdir, *hintpath;
+
+ if ((error = git_path_prettify(&full_path, path, NULL)) < 0) {
+ git_buf_free(&full_path);
+ return error;
+ }
+
+ hintpath = git_buf_cstr(&full_path);
+ workdir = git_repository_workdir(repo);
+
+ if (workdir && !git__prefixcmp(hintpath, workdir))
+ hintpath += strlen(workdir);
+
+ error = git_blob__create_from_paths(
+ id, NULL, repo, git_buf_cstr(&full_path), hintpath, 0, true);
+
+ git_buf_free(&full_path);
+ return error;
+}
+
+#define BUFFER_SIZE 4096
+
+int git_blob_create_fromchunks(
+ git_oid *id,
+ git_repository *repo,
+ const char *hintpath,
+ int (*source_cb)(char *content, size_t max_length, void *payload),
+ void *payload)
+{
+ int error;
+ char *content = NULL;
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf path = GIT_BUF_INIT;
+
+ assert(id && repo && source_cb);
+
+ if ((error = git_buf_joinpath(
+ &path, git_repository_path(repo), GIT_OBJECTS_DIR "streamed")) < 0)
+ goto cleanup;
+
+ content = git__malloc(BUFFER_SIZE);
+ GITERR_CHECK_ALLOC(content);
- if (read_len < 0) {
- if (!islnk)
- p_close(fd);
- stream->free(stream);
- return git__throw(GIT_EOSERR, "Failed to create blob. Can't read full file");
+ if ((error = git_filebuf_open(
+ &file, git_buf_cstr(&path), GIT_FILEBUF_TEMPORARY, 0666)) < 0)
+ goto cleanup;
+
+ while (1) {
+ int read_bytes = source_cb(content, BUFFER_SIZE, payload);
+
+ if (!read_bytes)
+ break;
+
+ if (read_bytes > BUFFER_SIZE) {
+ giterr_set(GITERR_OBJECT, "Invalid chunk size while creating blob");
+ error = GIT_EBUFS;
+ } else if (read_bytes < 0) {
+ error = giterr_set_after_callback(read_bytes);
+ } else {
+ error = git_filebuf_write(&file, content, read_bytes);
}
- stream->write(stream, buffer, read_len);
- size -= read_len;
+ if (error < 0)
+ goto cleanup;
}
- error = stream->finalize_write(oid, stream);
- stream->free(stream);
- if (!islnk)
- p_close(fd);
+ if ((error = git_filebuf_flush(&file)) < 0)
+ goto cleanup;
+
+ error = git_blob__create_from_paths(
+ id, NULL, repo, file.path_lock, hintpath, 0, hintpath != NULL);
+
+cleanup:
+ git_buf_free(&path);
+ git_filebuf_cleanup(&file);
+ git__free(content);
+
+ return error;
+}
+
+int git_blob_is_binary(const git_blob *blob)
+{
+ git_buf content;
+
+ assert(blob);
+
+ content.ptr = blob->odb_object->buffer;
+ content.size =
+ min(blob->odb_object->cached.size, GIT_FILTER_BYTES_TO_CHECK_NUL);
+ content.asize = 0;
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create blob");
+ return git_buf_text_is_binary(&content);
}
+int git_blob_filtered_content(
+ git_buf *out,
+ git_blob *blob,
+ const char *path,
+ int check_for_binary_data)
+{
+ int error = 0;
+ git_filter_list *fl = NULL;
+
+ assert(blob && path && out);
+
+ git_buf_sanitize(out);
+
+ if (check_for_binary_data && git_blob_is_binary(blob))
+ return 0;
+
+ if (!(error = git_filter_list_load(
+ &fl, git_blob_owner(blob), blob, path,
+ GIT_FILTER_TO_WORKTREE, GIT_FILTER_OPT_DEFAULT))) {
+
+ error = git_filter_list_apply_to_blob(out, fl, blob);
+
+ git_filter_list_free(fl);
+ }
+
+ return error;
+}
diff --git a/src/blob.h b/src/blob.h
index 4300d7e54..4cd9f1e0c 100644
--- a/src/blob.h
+++ b/src/blob.h
@@ -1,3 +1,9 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_blob_h__
#define INCLUDE_blob_h__
@@ -11,7 +17,17 @@ struct git_blob {
git_odb_object *odb_object;
};
-void git_blob__free(git_blob *blob);
-int git_blob__parse(git_blob *blob, git_odb_object *obj);
+void git_blob__free(void *blob);
+int git_blob__parse(void *blob, git_odb_object *obj);
+int git_blob__getbuf(git_buf *buffer, git_blob *blob);
+
+extern int git_blob__create_from_paths(
+ git_oid *out_oid,
+ struct stat *out_st,
+ git_repository *repo,
+ const char *full_path,
+ const char *hint_path,
+ mode_t hint_mode,
+ bool apply_filters);
#endif
diff --git a/src/branch.c b/src/branch.c
new file mode 100644
index 000000000..52760853b
--- /dev/null
+++ b/src/branch.c
@@ -0,0 +1,620 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "commit.h"
+#include "tag.h"
+#include "config.h"
+#include "refspec.h"
+#include "refs.h"
+#include "remote.h"
+
+#include "git2/branch.h"
+
+static int retrieve_branch_reference(
+ git_reference **branch_reference_out,
+ git_repository *repo,
+ const char *branch_name,
+ int is_remote)
+{
+ git_reference *branch = NULL;
+ int error = 0;
+ char *prefix;
+ git_buf ref_name = GIT_BUF_INIT;
+
+ prefix = is_remote ? GIT_REFS_REMOTES_DIR : GIT_REFS_HEADS_DIR;
+
+ if ((error = git_buf_joinpath(&ref_name, prefix, branch_name)) < 0)
+ /* OOM */;
+ else if ((error = git_reference_lookup(&branch, repo, ref_name.ptr)) < 0)
+ giterr_set(
+ GITERR_REFERENCE, "Cannot locate %s branch '%s'",
+ is_remote ? "remote-tracking" : "local", branch_name);
+
+ *branch_reference_out = branch; /* will be NULL on error */
+
+ git_buf_free(&ref_name);
+ return error;
+}
+
+static int not_a_local_branch(const char *reference_name)
+{
+ giterr_set(
+ GITERR_INVALID,
+ "Reference '%s' is not a local branch.", reference_name);
+ return -1;
+}
+
+int git_branch_create(
+ git_reference **ref_out,
+ git_repository *repository,
+ const char *branch_name,
+ const git_commit *commit,
+ int force,
+ const git_signature *signature,
+ const char *log_message)
+{
+ int is_head = 0;
+ git_reference *branch = NULL;
+ git_buf canonical_branch_name = GIT_BUF_INIT,
+ log_message_buf = GIT_BUF_INIT;
+ int error = -1;
+
+ assert(branch_name && commit && ref_out);
+ assert(git_object_owner((const git_object *)commit) == repository);
+
+ if (force && git_branch_lookup(&branch, repository, branch_name, GIT_BRANCH_LOCAL) == 0) {
+ error = git_branch_is_head(branch);
+ git_reference_free(branch);
+ branch = NULL;
+
+ if (error < 0)
+ goto cleanup;
+
+ is_head = error;
+ }
+
+ if (is_head && force) {
+ giterr_set(GITERR_REFERENCE, "Cannot force update branch '%s' as it is "
+ "the current HEAD of the repository.", branch_name);
+ error = -1;
+ goto cleanup;
+ }
+
+ if (git_buf_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0)
+ goto cleanup;
+
+ if (git_buf_sets(&log_message_buf, log_message ? log_message : "Branch: created") < 0)
+ goto cleanup;
+
+ error = git_reference_create(&branch, repository,
+ git_buf_cstr(&canonical_branch_name), git_commit_id(commit), force, signature,
+ git_buf_cstr(&log_message_buf));
+
+ if (!error)
+ *ref_out = branch;
+
+cleanup:
+ git_buf_free(&canonical_branch_name);
+ git_buf_free(&log_message_buf);
+ return error;
+}
+
+int git_branch_delete(git_reference *branch)
+{
+ int is_head;
+ git_buf config_section = GIT_BUF_INIT;
+ int error = -1;
+
+ assert(branch);
+
+ if (!git_reference_is_branch(branch) && !git_reference_is_remote(branch)) {
+ giterr_set(GITERR_INVALID, "Reference '%s' is not a valid branch.",
+ git_reference_name(branch));
+ return GIT_ENOTFOUND;
+ }
+
+ if ((is_head = git_branch_is_head(branch)) < 0)
+ return is_head;
+
+ if (is_head) {
+ giterr_set(GITERR_REFERENCE, "Cannot delete branch '%s' as it is "
+ "the current HEAD of the repository.", git_reference_name(branch));
+ return -1;
+ }
+
+ if (git_buf_join(&config_section, '.', "branch",
+ 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;
+
+ if (git_reflog_delete(git_reference_owner(branch), git_reference_name(branch)) < 0)
+ goto on_error;
+
+ error = 0;
+
+on_error:
+ git_buf_free(&config_section);
+ return error;
+}
+
+typedef struct {
+ git_reference_iterator *iter;
+ unsigned int flags;
+} branch_iter;
+
+int git_branch_next(git_reference **out, git_branch_t *out_type, git_branch_iterator *_iter)
+{
+ branch_iter *iter = (branch_iter *) _iter;
+ git_reference *ref;
+ int error;
+
+ while ((error = git_reference_next(&ref, iter->iter)) == 0) {
+ if ((iter->flags & GIT_BRANCH_LOCAL) &&
+ !git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR)) {
+ *out = ref;
+ *out_type = GIT_BRANCH_LOCAL;
+
+ return 0;
+ } else if ((iter->flags & GIT_BRANCH_REMOTE) &&
+ !git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR)) {
+ *out = ref;
+ *out_type = GIT_BRANCH_REMOTE;
+
+ return 0;
+ } else {
+ git_reference_free(ref);
+ }
+ }
+
+ return error;
+}
+
+int git_branch_iterator_new(
+ git_branch_iterator **out,
+ git_repository *repo,
+ git_branch_t list_flags)
+{
+ branch_iter *iter;
+
+ iter = git__calloc(1, sizeof(branch_iter));
+ GITERR_CHECK_ALLOC(iter);
+
+ iter->flags = list_flags;
+
+ if (git_reference_iterator_new(&iter->iter, repo) < 0) {
+ git__free(iter);
+ return -1;
+ }
+
+ *out = (git_branch_iterator *) iter;
+
+ return 0;
+}
+
+void git_branch_iterator_free(git_branch_iterator *_iter)
+{
+ branch_iter *iter = (branch_iter *) _iter;
+
+ if (iter == NULL)
+ return;
+
+ git_reference_iterator_free(iter->iter);
+ git__free(iter);
+}
+
+int git_branch_move(
+ git_reference **out,
+ git_reference *branch,
+ const char *new_branch_name,
+ int force,
+ const git_signature *signature,
+ const char *log_message)
+{
+ git_buf new_reference_name = GIT_BUF_INIT,
+ old_config_section = GIT_BUF_INIT,
+ new_config_section = GIT_BUF_INIT,
+ log_message_buf = GIT_BUF_INIT;
+ int error;
+
+ assert(branch && new_branch_name);
+
+ if (!git_reference_is_branch(branch))
+ return not_a_local_branch(git_reference_name(branch));
+
+ if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0)
+ goto done;
+
+ if (log_message) {
+ if ((error = git_buf_sets(&log_message_buf, log_message)) < 0)
+ goto done;
+ } else {
+ if ((error = git_buf_printf(&log_message_buf, "Branch: renamed %s to %s",
+ git_reference_name(branch), git_buf_cstr(&new_reference_name))) < 0)
+ goto done;
+ }
+
+ /* first update ref then config so failure won't trash config */
+
+ error = git_reference_rename(
+ out, branch, git_buf_cstr(&new_reference_name), force,
+ signature, git_buf_cstr(&log_message_buf));
+ if (error < 0)
+ goto done;
+
+ git_buf_join(&old_config_section, '.', "branch",
+ git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR));
+ git_buf_join(&new_config_section, '.', "branch", new_branch_name);
+
+ error = git_config_rename_section(
+ git_reference_owner(branch),
+ git_buf_cstr(&old_config_section),
+ git_buf_cstr(&new_config_section));
+
+done:
+ git_buf_free(&new_reference_name);
+ git_buf_free(&old_config_section);
+ git_buf_free(&new_config_section);
+ git_buf_free(&log_message_buf);
+
+ return error;
+}
+
+int git_branch_lookup(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *branch_name,
+ git_branch_t branch_type)
+{
+ assert(ref_out && repo && branch_name);
+
+ return retrieve_branch_reference(ref_out, repo, branch_name, branch_type == GIT_BRANCH_REMOTE);
+}
+
+int git_branch_name(
+ const char **out,
+ const git_reference *ref)
+{
+ const char *branch_name;
+
+ assert(out && ref);
+
+ branch_name = ref->name;
+
+ if (git_reference_is_branch(ref)) {
+ branch_name += strlen(GIT_REFS_HEADS_DIR);
+ } else if (git_reference_is_remote(ref)) {
+ branch_name += strlen(GIT_REFS_REMOTES_DIR);
+ } else {
+ giterr_set(GITERR_INVALID,
+ "Reference '%s' is neither a local nor a remote branch.", ref->name);
+ return -1;
+ }
+ *out = branch_name;
+ return 0;
+}
+
+static int retrieve_upstream_configuration(
+ const char **out,
+ const git_config *config,
+ const char *canonical_branch_name,
+ const char *format)
+{
+ git_buf buf = GIT_BUF_INIT;
+ int error;
+
+ if (git_buf_printf(&buf, format,
+ canonical_branch_name + strlen(GIT_REFS_HEADS_DIR)) < 0)
+ return -1;
+
+ error = git_config_get_string(out, config, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+ return error;
+}
+
+int git_branch_upstream_name(
+ git_buf *out,
+ git_repository *repo,
+ const char *refname)
+{
+ const char *remote_name, *merge_name;
+ git_buf buf = GIT_BUF_INIT;
+ int error = -1;
+ git_remote *remote = NULL;
+ const git_refspec *refspec;
+ git_config *config;
+
+ assert(out && refname);
+
+ git_buf_sanitize(out);
+
+ if (!git_reference__is_branch(refname))
+ return not_a_local_branch(refname);
+
+ if ((error = git_repository_config_snapshot(&config, repo)) < 0)
+ return error;
+
+ if ((error = retrieve_upstream_configuration(
+ &remote_name, config, refname, "branch.%s.remote")) < 0)
+ goto cleanup;
+
+ if ((error = retrieve_upstream_configuration(
+ &merge_name, config, refname, "branch.%s.merge")) < 0)
+ goto cleanup;
+
+ if (!*remote_name || !*merge_name) {
+ giterr_set(GITERR_REFERENCE,
+ "branch '%s' does not have an upstream", refname);
+ error = GIT_ENOTFOUND;
+ goto cleanup;
+ }
+
+ if (strcmp(".", remote_name) != 0) {
+ if ((error = git_remote_load(&remote, repo, remote_name)) < 0)
+ goto cleanup;
+
+ refspec = git_remote__matching_refspec(remote, merge_name);
+ if (!refspec) {
+ error = GIT_ENOTFOUND;
+ goto cleanup;
+ }
+
+ if (git_refspec_transform(&buf, refspec, merge_name) < 0)
+ goto cleanup;
+ } else
+ if (git_buf_sets(&buf, merge_name) < 0)
+ goto cleanup;
+
+ error = git_buf_set(out, git_buf_cstr(&buf), git_buf_len(&buf));
+
+cleanup:
+ git_config_free(config);
+ git_remote_free(remote);
+ git_buf_free(&buf);
+ return error;
+}
+
+int git_branch_remote_name(git_buf *buf, git_repository *repo, const char *refname)
+{
+ git_strarray remote_list = {0};
+ size_t i;
+ git_remote *remote;
+ const git_refspec *fetchspec;
+ int error = 0;
+ char *remote_name = NULL;
+
+ assert(buf && repo && refname);
+
+ git_buf_sanitize(buf);
+
+ /* Verify that this is a remote branch */
+ if (!git_reference__is_remote(refname)) {
+ giterr_set(GITERR_INVALID, "Reference '%s' is not a remote branch.",
+ refname);
+ error = GIT_ERROR;
+ goto cleanup;
+ }
+
+ /* Get the remotes */
+ if ((error = git_remote_list(&remote_list, repo)) < 0)
+ goto cleanup;
+
+ /* Find matching remotes */
+ for (i = 0; i < remote_list.count; i++) {
+ if ((error = git_remote_load(&remote, repo, remote_list.strings[i])) < 0)
+ continue;
+
+ fetchspec = git_remote__matching_dst_refspec(remote, refname);
+ if (fetchspec) {
+ /* If we have not already set out yet, then set
+ * it to the matching remote name. Otherwise
+ * multiple remotes match this reference, and it
+ * is ambiguous. */
+ if (!remote_name) {
+ remote_name = remote_list.strings[i];
+ } else {
+ git_remote_free(remote);
+
+ giterr_set(GITERR_REFERENCE,
+ "Reference '%s' is ambiguous", refname);
+ error = GIT_EAMBIGUOUS;
+ goto cleanup;
+ }
+ }
+
+ git_remote_free(remote);
+ }
+
+ if (remote_name) {
+ git_buf_clear(buf);
+ error = git_buf_puts(buf, remote_name);
+ } else {
+ giterr_set(GITERR_REFERENCE,
+ "Could not determine remote for '%s'", refname);
+ error = GIT_ENOTFOUND;
+ }
+
+cleanup:
+ if (error < 0)
+ git_buf_free(buf);
+
+ git_strarray_free(&remote_list);
+ return error;
+}
+
+int git_branch_upstream(
+ git_reference **tracking_out,
+ const git_reference *branch)
+{
+ int error;
+ git_buf tracking_name = GIT_BUF_INIT;
+
+ if ((error = git_branch_upstream_name(&tracking_name,
+ git_reference_owner(branch), git_reference_name(branch))) < 0)
+ return error;
+
+ error = git_reference_lookup(
+ tracking_out,
+ git_reference_owner(branch),
+ git_buf_cstr(&tracking_name));
+
+ git_buf_free(&tracking_name);
+ return error;
+}
+
+static int unset_upstream(git_config *config, const char *shortname)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ if (git_buf_printf(&buf, "branch.%s.remote", shortname) < 0)
+ return -1;
+
+ if (git_config_delete_entry(config, git_buf_cstr(&buf)) < 0)
+ goto on_error;
+
+ git_buf_clear(&buf);
+ if (git_buf_printf(&buf, "branch.%s.merge", shortname) < 0)
+ goto on_error;
+
+ if (git_config_delete_entry(config, git_buf_cstr(&buf)) < 0)
+ goto on_error;
+
+ git_buf_free(&buf);
+ return 0;
+
+on_error:
+ git_buf_free(&buf);
+ return -1;
+}
+
+int git_branch_set_upstream(git_reference *branch, const char *upstream_name)
+{
+ git_buf key = GIT_BUF_INIT, value = GIT_BUF_INIT;
+ git_reference *upstream;
+ git_repository *repo;
+ git_remote *remote = NULL;
+ git_config *config;
+ const char *name, *shortname;
+ int local;
+ const git_refspec *fetchspec;
+
+ name = git_reference_name(branch);
+ if (!git_reference__is_branch(name))
+ return not_a_local_branch(name);
+
+ if (git_repository_config__weakptr(&config, git_reference_owner(branch)) < 0)
+ return -1;
+
+ shortname = name + strlen(GIT_REFS_HEADS_DIR);
+
+ if (upstream_name == NULL)
+ return unset_upstream(config, shortname);
+
+ repo = git_reference_owner(branch);
+
+ /* First we need to figure out whether it's a branch or remote-tracking */
+ if (git_branch_lookup(&upstream, repo, upstream_name, GIT_BRANCH_LOCAL) == 0)
+ local = 1;
+ else if (git_branch_lookup(&upstream, repo, upstream_name, GIT_BRANCH_REMOTE) == 0)
+ local = 0;
+ else {
+ giterr_set(GITERR_REFERENCE,
+ "Cannot set upstream for branch '%s'", shortname);
+ return GIT_ENOTFOUND;
+ }
+
+ /*
+ * If it's local, the remote is "." and the branch name is
+ * simply the refname. Otherwise we need to figure out what
+ * the remote-tracking branch's name on the remote is and use
+ * that.
+ */
+ if (local)
+ git_buf_puts(&value, ".");
+ else
+ git_branch_remote_name(&value, repo, git_reference_name(upstream));
+
+ if (git_buf_printf(&key, "branch.%s.remote", shortname) < 0)
+ goto on_error;
+
+ if (git_config_set_string(config, git_buf_cstr(&key), git_buf_cstr(&value)) < 0)
+ goto on_error;
+
+ if (local) {
+ git_buf_clear(&value);
+ if (git_buf_puts(&value, git_reference_name(upstream)) < 0)
+ goto on_error;
+ } else {
+ /* Get the remoe-tracking branch's refname in its repo */
+ if (git_remote_load(&remote, repo, git_buf_cstr(&value)) < 0)
+ goto on_error;
+
+ fetchspec = git_remote__matching_dst_refspec(remote, git_reference_name(upstream));
+ git_buf_clear(&value);
+ if (!fetchspec || git_refspec_rtransform(&value, fetchspec, git_reference_name(upstream)) < 0)
+ goto on_error;
+
+ git_remote_free(remote);
+ remote = NULL;
+ }
+
+ git_buf_clear(&key);
+ if (git_buf_printf(&key, "branch.%s.merge", shortname) < 0)
+ goto on_error;
+
+ if (git_config_set_string(config, git_buf_cstr(&key), git_buf_cstr(&value)) < 0)
+ goto on_error;
+
+ git_reference_free(upstream);
+ git_buf_free(&key);
+ git_buf_free(&value);
+
+ return 0;
+
+on_error:
+ git_reference_free(upstream);
+ git_buf_free(&key);
+ git_buf_free(&value);
+ git_remote_free(remote);
+
+ return -1;
+}
+
+int git_branch_is_head(
+ const git_reference *branch)
+{
+ git_reference *head;
+ bool is_same = false;
+ int error;
+
+ assert(branch);
+
+ if (!git_reference_is_branch(branch))
+ return false;
+
+ error = git_repository_head(&head, git_reference_owner(branch));
+
+ if (error == GIT_EUNBORNBRANCH || error == GIT_ENOTFOUND)
+ return false;
+
+ if (error < 0)
+ return -1;
+
+ is_same = strcmp(
+ git_reference_name(branch),
+ git_reference_name(head)) == 0;
+
+ git_reference_free(head);
+
+ return is_same;
+}
diff --git a/src/branch.h b/src/branch.h
new file mode 100644
index 000000000..d02f2af0d
--- /dev/null
+++ b/src/branch.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_branch_h__
+#define INCLUDE_branch_h__
+
+#include "buffer.h"
+
+int git_branch_upstream__name(
+ git_buf *tracking_name,
+ git_repository *repo,
+ const char *canonical_branch_name);
+
+#endif
diff --git a/src/bswap.h b/src/bswap.h
index b9211c3c8..486df82f4 100644
--- a/src/bswap.h
+++ b/src/bswap.h
@@ -1,8 +1,8 @@
/*
- * Let's make sure we always have a sane definition for ntohl()/htonl().
- * Some libraries define those as a function call, just to perform byte
- * shifting, bringing significant overhead to what should be a simple
- * operation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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"
@@ -14,8 +14,8 @@
GIT_INLINE(uint32_t) default_swab32(uint32_t val)
{
return (((val & 0xff000000) >> 24) |
- ((val & 0x00ff0000) >> 8) |
- ((val & 0x0000ff00) << 8) |
+ ((val & 0x00ff0000) >> 8) |
+ ((val & 0x0000ff00) << 8) |
((val & 0x000000ff) << 24));
}
diff --git a/src/buf_text.c b/src/buf_text.c
new file mode 100644
index 000000000..631feb3f8
--- /dev/null
+++ b/src/buf_text.c
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "buf_text.h"
+
+int git_buf_text_puts_escaped(
+ git_buf *buf,
+ const char *string,
+ const char *esc_chars,
+ const char *esc_with)
+{
+ const char *scan;
+ size_t total = 0, esc_len = strlen(esc_with), count;
+
+ if (!string)
+ return 0;
+
+ for (scan = string; *scan; ) {
+ /* count run of non-escaped characters */
+ count = strcspn(scan, esc_chars);
+ total += count;
+ scan += count;
+ /* count run of escaped characters */
+ count = strspn(scan, esc_chars);
+ total += count * (esc_len + 1);
+ scan += count;
+ }
+
+ if (git_buf_grow(buf, buf->size + total + 1) < 0)
+ return -1;
+
+ for (scan = string; *scan; ) {
+ count = strcspn(scan, esc_chars);
+
+ memmove(buf->ptr + buf->size, scan, count);
+ scan += count;
+ buf->size += count;
+
+ for (count = strspn(scan, esc_chars); count > 0; --count) {
+ /* copy escape sequence */
+ memmove(buf->ptr + buf->size, esc_with, esc_len);
+ buf->size += esc_len;
+ /* copy character to be escaped */
+ buf->ptr[buf->size] = *scan;
+ buf->size++;
+ scan++;
+ }
+ }
+
+ buf->ptr[buf->size] = '\0';
+
+ return 0;
+}
+
+void git_buf_text_unescape(git_buf *buf)
+{
+ buf->size = git__unescape(buf->ptr);
+}
+
+int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src)
+{
+ const char *scan = src->ptr;
+ const char *scan_end = src->ptr + src->size;
+ const char *next = memchr(scan, '\r', src->size);
+ char *out;
+
+ assert(tgt != src);
+
+ if (!next)
+ return git_buf_set(tgt, src->ptr, src->size);
+
+ /* reduce reallocs while in the loop */
+ if (git_buf_grow(tgt, src->size + 1) < 0)
+ return -1;
+ out = tgt->ptr;
+ tgt->size = 0;
+
+ /* Find the next \r and copy whole chunk up to there to tgt */
+ for (; next; scan = next + 1, next = memchr(scan, '\r', scan_end - scan)) {
+ if (next > scan) {
+ size_t copylen = (size_t)(next - scan);
+ memcpy(out, scan, copylen);
+ out += copylen;
+ }
+
+ /* Do not drop \r unless it is followed by \n */
+ if (next + 1 == scan_end || next[1] != '\n')
+ *out++ = '\r';
+ }
+
+ /* Copy remaining input into dest */
+ if (scan < scan_end) {
+ size_t remaining = (size_t)(scan_end - scan);
+ memcpy(out, scan, remaining);
+ out += remaining;
+ }
+
+ tgt->size = (size_t)(out - tgt->ptr);
+ tgt->ptr[tgt->size] = '\0';
+
+ return 0;
+}
+
+int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src)
+{
+ const char *start = src->ptr;
+ const char *end = start + src->size;
+ const char *scan = start;
+ const char *next = memchr(scan, '\n', src->size);
+
+ assert(tgt != src);
+
+ if (!next)
+ return git_buf_set(tgt, src->ptr, src->size);
+
+ /* attempt to reduce reallocs while in the loop */
+ if (git_buf_grow(tgt, src->size + (src->size >> 4) + 1) < 0)
+ return -1;
+ tgt->size = 0;
+
+ for (; next; scan = next + 1, next = memchr(scan, '\n', end - scan)) {
+ size_t copylen = next - scan;
+ /* don't convert existing \r\n to \r\r\n */
+ size_t extralen = (next > start && next[-1] == '\r') ? 1 : 2;
+ size_t needsize = tgt->size + copylen + extralen + 1;
+
+ if (tgt->asize < needsize && git_buf_grow(tgt, needsize) < 0)
+ return -1;
+
+ if (next > scan) {
+ memcpy(tgt->ptr + tgt->size, scan, copylen);
+ tgt->size += copylen;
+ }
+ if (extralen == 2)
+ tgt->ptr[tgt->size++] = '\r';
+ tgt->ptr[tgt->size++] = '\n';
+ }
+
+ return git_buf_put(tgt, scan, end - scan);
+}
+
+int git_buf_text_common_prefix(git_buf *buf, const git_strarray *strings)
+{
+ size_t i;
+ const char *str, *pfx;
+
+ git_buf_clear(buf);
+
+ if (!strings || !strings->count)
+ return 0;
+
+ /* initialize common prefix to first string */
+ if (git_buf_sets(buf, strings->strings[0]) < 0)
+ return -1;
+
+ /* go through the rest of the strings, truncating to shared prefix */
+ for (i = 1; i < strings->count; ++i) {
+
+ for (str = strings->strings[i], pfx = buf->ptr;
+ *str && *str == *pfx; str++, pfx++)
+ /* scanning */;
+
+ git_buf_truncate(buf, pfx - buf->ptr);
+
+ if (!buf->size)
+ break;
+ }
+
+ return 0;
+}
+
+bool git_buf_text_is_binary(const git_buf *buf)
+{
+ const char *scan = buf->ptr, *end = buf->ptr + buf->size;
+ git_bom_t bom;
+ int printable = 0, nonprintable = 0;
+
+ scan += git_buf_text_detect_bom(&bom, buf, 0);
+
+ if (bom > GIT_BOM_UTF8)
+ return 1;
+
+ while (scan < end) {
+ unsigned char c = *scan++;
+
+ if (c > 0x1F && c < 0x7F)
+ printable++;
+ else if (c == '\0')
+ return true;
+ else if (!git__isspace(c))
+ nonprintable++;
+ }
+
+ return ((printable >> 7) < nonprintable);
+}
+
+bool git_buf_text_contains_nul(const git_buf *buf)
+{
+ return (memchr(buf->ptr, '\0', buf->size) != NULL);
+}
+
+int git_buf_text_detect_bom(git_bom_t *bom, const git_buf *buf, size_t offset)
+{
+ const char *ptr;
+ size_t len;
+
+ *bom = GIT_BOM_NONE;
+ /* need at least 2 bytes after offset to look for any BOM */
+ if (buf->size < offset + 2)
+ return 0;
+
+ ptr = buf->ptr + offset;
+ len = buf->size - offset;
+
+ switch (*ptr++) {
+ case 0:
+ if (len >= 4 && ptr[0] == 0 && ptr[1] == '\xFE' && ptr[2] == '\xFF') {
+ *bom = GIT_BOM_UTF32_BE;
+ return 4;
+ }
+ break;
+ case '\xEF':
+ if (len >= 3 && ptr[0] == '\xBB' && ptr[1] == '\xBF') {
+ *bom = GIT_BOM_UTF8;
+ return 3;
+ }
+ break;
+ case '\xFE':
+ if (*ptr == '\xFF') {
+ *bom = GIT_BOM_UTF16_BE;
+ return 2;
+ }
+ break;
+ case '\xFF':
+ if (*ptr != '\xFE')
+ break;
+ if (len >= 4 && ptr[1] == 0 && ptr[2] == 0) {
+ *bom = GIT_BOM_UTF32_LE;
+ return 4;
+ } else {
+ *bom = GIT_BOM_UTF16_LE;
+ return 2;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+bool git_buf_text_gather_stats(
+ git_buf_text_stats *stats, const git_buf *buf, bool skip_bom)
+{
+ const char *scan = buf->ptr, *end = buf->ptr + buf->size;
+ int skip;
+
+ memset(stats, 0, sizeof(*stats));
+
+ /* BOM detection */
+ skip = git_buf_text_detect_bom(&stats->bom, buf, 0);
+ if (skip_bom)
+ scan += skip;
+
+ /* Ignore EOF character */
+ if (buf->size > 0 && end[-1] == '\032')
+ end--;
+
+ /* Counting loop */
+ while (scan < end) {
+ unsigned char c = *scan++;
+
+ if (c > 0x1F && c != 0x7F)
+ stats->printable++;
+ else switch (c) {
+ case '\0':
+ stats->nul++;
+ stats->nonprintable++;
+ break;
+ case '\n':
+ stats->lf++;
+ break;
+ case '\r':
+ stats->cr++;
+ if (scan < end && *scan == '\n')
+ stats->crlf++;
+ break;
+ case '\t': case '\f': case '\v': case '\b': case 0x1b: /*ESC*/
+ stats->printable++;
+ break;
+ default:
+ stats->nonprintable++;
+ break;
+ }
+ }
+
+ return (stats->nul > 0 ||
+ ((stats->printable >> 7) < stats->nonprintable));
+}
diff --git a/src/buf_text.h b/src/buf_text.h
new file mode 100644
index 000000000..3ac9d1443
--- /dev/null
+++ b/src/buf_text.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_buf_text_h__
+#define INCLUDE_buf_text_h__
+
+#include "buffer.h"
+
+typedef enum {
+ GIT_BOM_NONE = 0,
+ GIT_BOM_UTF8 = 1,
+ GIT_BOM_UTF16_LE = 2,
+ GIT_BOM_UTF16_BE = 3,
+ GIT_BOM_UTF32_LE = 4,
+ GIT_BOM_UTF32_BE = 5
+} git_bom_t;
+
+typedef struct {
+ git_bom_t bom; /* BOM found at head of text */
+ unsigned int nul, cr, lf, crlf; /* NUL, CR, LF and CRLF counts */
+ unsigned int printable, nonprintable; /* These are just approximations! */
+} git_buf_text_stats;
+
+/**
+ * Append string to buffer, prefixing each character from `esc_chars` with
+ * `esc_with` string.
+ *
+ * @param buf Buffer to append data to
+ * @param string String to escape and append
+ * @param esc_chars Characters to be escaped
+ * @param esc_with String to insert in from of each found character
+ * @return 0 on success, <0 on failure (probably allocation problem)
+ */
+extern int git_buf_text_puts_escaped(
+ git_buf *buf,
+ const char *string,
+ const char *esc_chars,
+ const char *esc_with);
+
+/**
+ * Append string escaping characters that are regex special
+ */
+GIT_INLINE(int) git_buf_text_puts_escape_regex(git_buf *buf, const char *string)
+{
+ return git_buf_text_puts_escaped(buf, string, "^.[]$()|*+?{}\\", "\\");
+}
+
+/**
+ * Unescape all characters in a buffer in place
+ *
+ * I.e. remove backslashes
+ */
+extern void git_buf_text_unescape(git_buf *buf);
+
+/**
+ * Replace all \r\n with \n. Does not modify \r without trailing \n.
+ *
+ * @return 0 on success, -1 on memory error
+ */
+extern int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src);
+
+/**
+ * Replace all \n with \r\n. Does not modify existing \r\n.
+ *
+ * @return 0 on success, -1 on memory error
+ */
+extern int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src);
+
+/**
+ * Fill buffer with the common prefix of a array of strings
+ *
+ * Buffer will be set to empty if there is no common prefix
+ */
+extern int git_buf_text_common_prefix(git_buf *buf, const git_strarray *strs);
+
+/**
+ * Check quickly if buffer looks like it contains binary data
+ *
+ * @param buf Buffer to check
+ * @return true if buffer looks like non-text data
+ */
+extern bool git_buf_text_is_binary(const git_buf *buf);
+
+/**
+ * Check quickly if buffer contains a NUL byte
+ *
+ * @param buf Buffer to check
+ * @return true if buffer contains a NUL byte
+ */
+extern bool git_buf_text_contains_nul(const git_buf *buf);
+
+/**
+ * Check if a buffer begins with a UTF BOM
+ *
+ * @param bom Set to the type of BOM detected or GIT_BOM_NONE
+ * @param buf Buffer in which to check the first bytes for a BOM
+ * @param offset Offset into buffer to look for BOM
+ * @return Number of bytes of BOM data (or 0 if no BOM found)
+ */
+extern int git_buf_text_detect_bom(
+ git_bom_t *bom, const git_buf *buf, size_t offset);
+
+/**
+ * Gather stats for a piece of text
+ *
+ * Fill the `stats` structure with counts of unreadable characters, carriage
+ * returns, etc, so it can be used in heuristics. This automatically skips
+ * a trailing EOF (\032 character). Also it will look for a BOM at the
+ * start of the text and can be told to skip that as well.
+ *
+ * @param stats Structure to be filled in
+ * @param buf Text to process
+ * @param skip_bom Exclude leading BOM from stats if true
+ * @return Does the buffer heuristically look like binary data
+ */
+extern bool git_buf_text_gather_stats(
+ git_buf_text_stats *stats, const git_buf *buf, bool skip_bom);
+
+#endif
diff --git a/src/buffer.c b/src/buffer.c
index 6af4c9195..b8f8660ed 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -1,95 +1,620 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
#include "buffer.h"
#include "posix.h"
-#include <stdarg.h>
+#include "git2/buffer.h"
+#include <ctype.h>
+
+/* Used as default value for git_buf->ptr so that people can always
+ * assume ptr is non-NULL and zero terminated even for new git_bufs.
+ */
+char git_buf__initbuf[1];
+
+char git_buf__oom[1];
#define ENSURE_SIZE(b, d) \
- if ((ssize_t)(d) >= buf->asize && git_buf_grow(b, (d)) < GIT_SUCCESS)\
- return;
+ if ((d) > buf->asize && git_buf_grow(b, (d)) < 0)\
+ return -1;
+
+
+void git_buf_init(git_buf *buf, size_t initial_size)
+{
+ buf->asize = 0;
+ buf->size = 0;
+ buf->ptr = git_buf__initbuf;
-int git_buf_grow(git_buf *buf, size_t target_size)
+ if (initial_size)
+ git_buf_grow(buf, initial_size);
+}
+
+int git_buf_try_grow(
+ git_buf *buf, size_t target_size, bool mark_oom, bool preserve_external)
{
char *new_ptr;
+ size_t new_size;
+
+ if (buf->ptr == git_buf__oom)
+ return -1;
- if (buf->asize < 0)
- return GIT_ENOMEM;
+ if (!target_size)
+ target_size = buf->size;
- if (buf->asize == 0)
- buf->asize = target_size;
+ if (target_size <= buf->asize)
+ return 0;
+
+ if (buf->asize == 0) {
+ new_size = target_size;
+ new_ptr = NULL;
+ } else {
+ new_size = buf->asize;
+ new_ptr = buf->ptr;
+ }
/* grow the buffer size by 1.5, until it's big enough
* to fit our target size */
- while (buf->asize < (int)target_size)
- buf->asize = (buf->asize << 1) - (buf->asize >> 1);
+ while (new_size < target_size)
+ new_size = (new_size << 1) - (new_size >> 1);
+
+ /* round allocation up to multiple of 8 */
+ new_size = (new_size + 7) & ~7;
+
+ new_ptr = git__realloc(new_ptr, new_size);
- new_ptr = git__realloc(buf->ptr, buf->asize);
if (!new_ptr) {
- buf->asize = -1;
- return GIT_ENOMEM;
+ if (mark_oom) {
+ if (buf->ptr) git__free(buf->ptr);
+ buf->ptr = git_buf__oom;
+ }
+ return -1;
}
- buf->ptr = new_ptr;
- return GIT_SUCCESS;
+ if (preserve_external && !buf->asize && buf->ptr != NULL && buf->size > 0)
+ memcpy(new_ptr, buf->ptr, min(buf->size, new_size));
+
+ buf->asize = new_size;
+ buf->ptr = new_ptr;
+
+ /* truncate the existing buffer size if necessary */
+ if (buf->size >= buf->asize)
+ buf->size = buf->asize - 1;
+ buf->ptr[buf->size] = '\0';
+
+ return 0;
}
-int git_buf_oom(const git_buf *buf)
+int git_buf_grow(git_buf *buffer, size_t target_size)
{
- return (buf->asize < 0);
+ return git_buf_try_grow(buffer, target_size, true, true);
+}
+
+void git_buf_free(git_buf *buf)
+{
+ if (!buf) return;
+
+ if (buf->asize > 0 && buf->ptr != NULL && buf->ptr != git_buf__oom)
+ git__free(buf->ptr);
+
+ git_buf_init(buf, 0);
}
-void git_buf_putc(git_buf *buf, char c)
+void git_buf_sanitize(git_buf *buf)
{
- ENSURE_SIZE(buf, buf->size + 1);
+ if (buf->ptr == NULL) {
+ assert(buf->size == 0 && buf->asize == 0);
+ buf->ptr = git_buf__initbuf;
+ } else if (buf->asize > buf->size)
+ buf->ptr[buf->size] = '\0';
+}
+
+void git_buf_clear(git_buf *buf)
+{
+ buf->size = 0;
+
+ if (!buf->ptr) {
+ buf->ptr = git_buf__initbuf;
+ buf->asize = 0;
+ }
+
+ if (buf->asize > 0)
+ buf->ptr[0] = '\0';
+}
+
+int git_buf_set(git_buf *buf, const void *data, size_t len)
+{
+ if (len == 0 || data == NULL) {
+ git_buf_clear(buf);
+ } else {
+ if (data != buf->ptr) {
+ ENSURE_SIZE(buf, len + 1);
+ memmove(buf->ptr, data, len);
+ }
+
+ buf->size = len;
+ if (buf->asize > buf->size)
+ buf->ptr[buf->size] = '\0';
+
+ }
+ return 0;
+}
+
+int git_buf_sets(git_buf *buf, const char *string)
+{
+ return git_buf_set(buf, string, string ? strlen(string) : 0);
+}
+
+int git_buf_putc(git_buf *buf, char c)
+{
+ ENSURE_SIZE(buf, buf->size + 2);
buf->ptr[buf->size++] = c;
+ buf->ptr[buf->size] = '\0';
+ return 0;
+}
+
+int git_buf_putcn(git_buf *buf, char c, size_t len)
+{
+ ENSURE_SIZE(buf, buf->size + len + 1);
+ memset(buf->ptr + buf->size, c, len);
+ buf->size += len;
+ buf->ptr[buf->size] = '\0';
+ return 0;
}
-void git_buf_put(git_buf *buf, const char *data, size_t len)
+int git_buf_put(git_buf *buf, const char *data, size_t len)
{
- ENSURE_SIZE(buf, buf->size + len);
- memcpy(buf->ptr + buf->size, data, len);
+ ENSURE_SIZE(buf, buf->size + len + 1);
+ memmove(buf->ptr + buf->size, data, len);
buf->size += len;
+ buf->ptr[buf->size] = '\0';
+ return 0;
+}
+
+int git_buf_puts(git_buf *buf, const char *string)
+{
+ assert(string);
+ return git_buf_put(buf, string, strlen(string));
}
-void git_buf_puts(git_buf *buf, const char *string)
+static const char b64str[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+int git_buf_put_base64(git_buf *buf, const char *data, size_t len)
{
- git_buf_put(buf, string, strlen(string));
+ size_t extra = len % 3;
+ uint8_t *write, a, b, c;
+ const uint8_t *read = (const uint8_t *)data;
+
+ ENSURE_SIZE(buf, buf->size + 4 * ((len / 3) + !!extra) + 1);
+ write = (uint8_t *)&buf->ptr[buf->size];
+
+ /* convert each run of 3 bytes into 4 output bytes */
+ for (len -= extra; len > 0; len -= 3) {
+ a = *read++;
+ b = *read++;
+ c = *read++;
+
+ *write++ = b64str[a >> 2];
+ *write++ = b64str[(a & 0x03) << 4 | b >> 4];
+ *write++ = b64str[(b & 0x0f) << 2 | c >> 6];
+ *write++ = b64str[c & 0x3f];
+ }
+
+ if (extra > 0) {
+ a = *read++;
+ b = (extra > 1) ? *read++ : 0;
+
+ *write++ = b64str[a >> 2];
+ *write++ = b64str[(a & 0x03) << 4 | b >> 4];
+ *write++ = (extra > 1) ? b64str[(b & 0x0f) << 2] : '=';
+ *write++ = '=';
+ }
+
+ buf->size = ((char *)write) - buf->ptr;
+ buf->ptr[buf->size] = '\0';
+
+ return 0;
}
-void git_buf_printf(git_buf *buf, const char *format, ...)
+static const char b85str[] =
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~";
+
+int git_buf_put_base85(git_buf *buf, const char *data, size_t len)
+{
+ ENSURE_SIZE(buf, buf->size + (5 * ((len / 4) + !!(len % 4))) + 1);
+
+ while (len) {
+ uint32_t acc = 0;
+ char b85[5];
+ int i;
+
+ for (i = 24; i >= 0; i -= 8) {
+ uint8_t ch = *data++;
+ acc |= ch << i;
+
+ if (--len == 0)
+ break;
+ }
+
+ for (i = 4; i >= 0; i--) {
+ int val = acc % 85;
+ acc /= 85;
+
+ b85[i] = b85str[val];
+ }
+
+ for (i = 0; i < 5; i++)
+ buf->ptr[buf->size++] = b85[i];
+ }
+
+ buf->ptr[buf->size] = '\0';
+
+ return 0;
+}
+
+int git_buf_vprintf(git_buf *buf, const char *format, va_list ap)
{
int len;
- va_list arglist;
+ const size_t expected_size = buf->size + (strlen(format) * 2);
- ENSURE_SIZE(buf, buf->size + 1);
+ ENSURE_SIZE(buf, expected_size);
while (1) {
- va_start(arglist, format);
- len = p_vsnprintf(buf->ptr + buf->size, buf->asize - buf->size, format, arglist);
- va_end(arglist);
+ va_list args;
+ va_copy(args, ap);
+
+ len = p_vsnprintf(
+ buf->ptr + buf->size,
+ buf->asize - buf->size,
+ format, args
+ );
+
+ va_end(args);
if (len < 0) {
- buf->asize = -1;
- return;
+ git__free(buf->ptr);
+ buf->ptr = git_buf__oom;
+ return -1;
}
- if (len + 1 <= buf->asize - buf->size) {
+ if ((size_t)len + 1 <= buf->asize - buf->size) {
buf->size += len;
- return;
+ break;
}
ENSURE_SIZE(buf, buf->size + len + 1);
}
+
+ return 0;
}
-const char *git_buf_cstr(git_buf *buf)
+int git_buf_printf(git_buf *buf, const char *format, ...)
{
- if (buf->size + 1 >= buf->asize && git_buf_grow(buf, buf->size + 1) < GIT_SUCCESS)
+ int r;
+ va_list ap;
+
+ va_start(ap, format);
+ r = git_buf_vprintf(buf, format, ap);
+ va_end(ap);
+
+ return r;
+}
+
+void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf)
+{
+ size_t copylen;
+
+ assert(data && datasize && buf);
+
+ data[0] = '\0';
+
+ if (buf->size == 0 || buf->asize <= 0)
+ return;
+
+ copylen = buf->size;
+ if (copylen > datasize - 1)
+ copylen = datasize - 1;
+ memmove(data, buf->ptr, copylen);
+ data[copylen] = '\0';
+}
+
+void git_buf_consume(git_buf *buf, const char *end)
+{
+ if (end > buf->ptr && end <= buf->ptr + buf->size) {
+ size_t consumed = end - buf->ptr;
+ memmove(buf->ptr, end, buf->size - consumed);
+ buf->size -= consumed;
+ buf->ptr[buf->size] = '\0';
+ }
+}
+
+void git_buf_truncate(git_buf *buf, size_t len)
+{
+ if (len >= buf->size)
+ return;
+
+ buf->size = len;
+ if (buf->size < buf->asize)
+ buf->ptr[buf->size] = '\0';
+}
+
+void git_buf_shorten(git_buf *buf, size_t amount)
+{
+ if (buf->size > amount)
+ git_buf_truncate(buf, buf->size - amount);
+ else
+ git_buf_clear(buf);
+}
+
+void git_buf_rtruncate_at_char(git_buf *buf, char separator)
+{
+ ssize_t idx = git_buf_rfind_next(buf, separator);
+ git_buf_truncate(buf, idx < 0 ? 0 : (size_t)idx);
+}
+
+void git_buf_swap(git_buf *buf_a, git_buf *buf_b)
+{
+ git_buf t = *buf_a;
+ *buf_a = *buf_b;
+ *buf_b = t;
+}
+
+char *git_buf_detach(git_buf *buf)
+{
+ char *data = buf->ptr;
+
+ if (buf->asize == 0 || buf->ptr == git_buf__oom)
return NULL;
+ git_buf_init(buf, 0);
+
+ return data;
+}
+
+void git_buf_attach(git_buf *buf, char *ptr, size_t asize)
+{
+ git_buf_free(buf);
+
+ if (ptr) {
+ buf->ptr = ptr;
+ buf->size = strlen(ptr);
+ if (asize)
+ buf->asize = (asize < buf->size) ? buf->size + 1 : asize;
+ else /* pass 0 to fall back on strlen + 1 */
+ buf->asize = buf->size + 1;
+ } else {
+ git_buf_grow(buf, asize);
+ }
+}
+
+int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...)
+{
+ va_list ap;
+ int i;
+ size_t total_size = 0, original_size = buf->size;
+ char *out, *original = buf->ptr;
+
+ if (buf->size > 0 && buf->ptr[buf->size - 1] != separator)
+ ++total_size; /* space for initial separator */
+
+ /* Make two passes to avoid multiple reallocation */
+
+ va_start(ap, nbuf);
+ for (i = 0; i < nbuf; ++i) {
+ const char* segment;
+ size_t segment_len;
+
+ segment = va_arg(ap, const char *);
+ if (!segment)
+ continue;
+
+ segment_len = strlen(segment);
+ total_size += segment_len;
+ if (segment_len == 0 || segment[segment_len - 1] != separator)
+ ++total_size; /* space for separator */
+ }
+ va_end(ap);
+
+ /* expand buffer if needed */
+ if (total_size == 0)
+ return 0;
+ if (git_buf_grow(buf, buf->size + total_size + 1) < 0)
+ return -1;
+
+ out = buf->ptr + buf->size;
+
+ /* append separator to existing buf if needed */
+ if (buf->size > 0 && out[-1] != separator)
+ *out++ = separator;
+
+ va_start(ap, nbuf);
+ for (i = 0; i < nbuf; ++i) {
+ const char* segment;
+ size_t segment_len;
+
+ segment = va_arg(ap, const char *);
+ if (!segment)
+ continue;
+
+ /* deal with join that references buffer's original content */
+ if (segment >= original && segment < original + original_size) {
+ size_t offset = (segment - original);
+ segment = buf->ptr + offset;
+ segment_len = original_size - offset;
+ } else {
+ segment_len = strlen(segment);
+ }
+
+ /* skip leading separators */
+ if (out > buf->ptr && out[-1] == separator)
+ while (segment_len > 0 && *segment == separator) {
+ segment++;
+ segment_len--;
+ }
+
+ /* copy over next buffer */
+ if (segment_len > 0) {
+ memmove(out, segment, segment_len);
+ out += segment_len;
+ }
+
+ /* append trailing separator (except for last item) */
+ if (i < nbuf - 1 && out > buf->ptr && out[-1] != separator)
+ *out++ = separator;
+ }
+ va_end(ap);
+
+ /* set size based on num characters actually written */
+ buf->size = out - buf->ptr;
buf->ptr[buf->size] = '\0';
- return buf->ptr;
+
+ return 0;
}
-void git_buf_free(git_buf *buf)
+int git_buf_join(
+ git_buf *buf,
+ char separator,
+ const char *str_a,
+ const char *str_b)
+{
+ size_t strlen_a = str_a ? strlen(str_a) : 0;
+ size_t strlen_b = strlen(str_b);
+ int need_sep = 0;
+ ssize_t offset_a = -1;
+
+ /* not safe to have str_b point internally to the buffer */
+ assert(str_b < buf->ptr || str_b >= buf->ptr + buf->size);
+
+ /* figure out if we need to insert a separator */
+ if (separator && strlen_a) {
+ while (*str_b == separator) { str_b++; strlen_b--; }
+ if (str_a[strlen_a - 1] != separator)
+ need_sep = 1;
+ }
+
+ /* str_a could be part of the buffer */
+ if (str_a >= buf->ptr && str_a < buf->ptr + buf->size)
+ offset_a = str_a - buf->ptr;
+
+ if (git_buf_grow(buf, strlen_a + strlen_b + need_sep + 1) < 0)
+ return -1;
+ assert(buf->ptr);
+
+ /* fix up internal pointers */
+ if (offset_a >= 0)
+ str_a = buf->ptr + offset_a;
+
+ /* do the actual copying */
+ if (offset_a != 0 && str_a)
+ memmove(buf->ptr, str_a, strlen_a);
+ if (need_sep)
+ buf->ptr[strlen_a] = separator;
+ memcpy(buf->ptr + strlen_a + need_sep, str_b, strlen_b);
+
+ buf->size = strlen_a + strlen_b + need_sep;
+ buf->ptr[buf->size] = '\0';
+
+ return 0;
+}
+
+int git_buf_join3(
+ git_buf *buf,
+ char separator,
+ const char *str_a,
+ const char *str_b,
+ const char *str_c)
+{
+ size_t len_a = strlen(str_a), len_b = strlen(str_b), len_c = strlen(str_c);
+ int sep_a = 0, sep_b = 0;
+ char *tgt;
+
+ /* for this function, disallow pointers into the existing buffer */
+ assert(str_a < buf->ptr || str_a >= buf->ptr + buf->size);
+ assert(str_b < buf->ptr || str_b >= buf->ptr + buf->size);
+ assert(str_c < buf->ptr || str_c >= buf->ptr + buf->size);
+
+ if (separator) {
+ if (len_a > 0) {
+ while (*str_b == separator) { str_b++; len_b--; }
+ sep_a = (str_a[len_a - 1] != separator);
+ }
+ if (len_a > 0 || len_b > 0)
+ while (*str_c == separator) { str_c++; len_c--; }
+ if (len_b > 0)
+ sep_b = (str_b[len_b - 1] != separator);
+ }
+
+ if (git_buf_grow(buf, len_a + sep_a + len_b + sep_b + len_c + 1) < 0)
+ return -1;
+
+ tgt = buf->ptr;
+
+ if (len_a) {
+ memcpy(tgt, str_a, len_a);
+ tgt += len_a;
+ }
+ if (sep_a)
+ *tgt++ = separator;
+ if (len_b) {
+ memcpy(tgt, str_b, len_b);
+ tgt += len_b;
+ }
+ if (sep_b)
+ *tgt++ = separator;
+ if (len_c)
+ memcpy(tgt, str_c, len_c);
+
+ buf->size = len_a + sep_a + len_b + sep_b + len_c;
+ buf->ptr[buf->size] = '\0';
+
+ return 0;
+}
+
+void git_buf_rtrim(git_buf *buf)
{
- free(buf->ptr);
+ while (buf->size > 0) {
+ if (!git__isspace(buf->ptr[buf->size - 1]))
+ break;
+
+ buf->size--;
+ }
+
+ if (buf->asize > buf->size)
+ buf->ptr[buf->size] = '\0';
+}
+
+int git_buf_cmp(const git_buf *a, const git_buf *b)
+{
+ int result = memcmp(a->ptr, b->ptr, min(a->size, b->size));
+ return (result != 0) ? result :
+ (a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0;
+}
+
+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
+ */
+ ENSURE_SIZE(buf, buf->size + nb_to_insert - nb_to_insert + 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 1209340a1..70d6d73b3 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -1,24 +1,189 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_buffer_h__
#define INCLUDE_buffer_h__
#include "common.h"
+#include "git2/strarray.h"
+#include "git2/buffer.h"
-typedef struct {
- char *ptr;
- ssize_t asize, size;
-} git_buf;
+/* typedef struct {
+ * char *ptr;
+ * size_t asize, size;
+ * } git_buf;
+ */
-#define GIT_BUF_INIT {NULL, 0, 0}
+extern char git_buf__initbuf[];
+extern char git_buf__oom[];
-int git_buf_grow(git_buf *buf, size_t target_size);
-int git_buf_oom(const git_buf *buf);
-void git_buf_putc(git_buf *buf, char c);
-void git_buf_put(git_buf *buf, const char *data, size_t len);
-void git_buf_puts(git_buf *buf, const char *string);
-void git_buf_printf(git_buf *buf, const char *format, ...) GIT_FORMAT_PRINTF(2, 3);
-const char *git_buf_cstr(git_buf *buf);
-void git_buf_free(git_buf *buf);
+/* Use to initialize buffer structure when git_buf is on stack */
+#define GIT_BUF_INIT { git_buf__initbuf, 0, 0 }
+
+GIT_INLINE(bool) git_buf_is_allocated(const git_buf *buf)
+{
+ return (buf->ptr != NULL && buf->asize > 0);
+}
+
+/**
+ * Initialize a git_buf structure.
+ *
+ * For the cases where GIT_BUF_INIT cannot be used to do static
+ * initialization.
+ */
+extern void git_buf_init(git_buf *buf, size_t initial_size);
+
+/**
+ * Attempt to grow the buffer to hold at least `target_size` bytes.
+ *
+ * If the allocation fails, this will return an error. If `mark_oom` is true,
+ * this will mark the buffer as invalid for future operations; if false,
+ * existing buffer content will be preserved, but calling code must handle
+ * that buffer was not expanded. If `preserve_external` is true, then any
+ * existing data pointed to be `ptr` even if `asize` is zero will be copied
+ * into the newly allocated buffer.
+ */
+extern int git_buf_try_grow(
+ git_buf *buf, size_t target_size, bool mark_oom, bool preserve_external);
+
+/**
+ * Sanitizes git_buf structures provided from user input. Users of the
+ * library, when providing git_buf's, may wish to provide a NULL ptr for
+ * ease of handling. The buffer routines, however, expect a non-NULL ptr
+ * always. This helper method simply handles NULL input, converting to a
+ * git_buf__initbuf.
+ */
+extern void git_buf_sanitize(git_buf *buf);
+
+extern void git_buf_swap(git_buf *buf_a, git_buf *buf_b);
+extern char *git_buf_detach(git_buf *buf);
+extern void git_buf_attach(git_buf *buf, char *ptr, size_t asize);
+
+/**
+ * Test if there have been any reallocation failures with this git_buf.
+ *
+ * Any function that writes to a git_buf can fail due to memory allocation
+ * issues. If one fails, the git_buf will be marked with an OOM error and
+ * further calls to modify the buffer will fail. Check git_buf_oom() at the
+ * end of your sequence and it will be true if you ran out of memory at any
+ * point with that buffer.
+ *
+ * @return false if no error, true if allocation error
+ */
+GIT_INLINE(bool) git_buf_oom(const git_buf *buf)
+{
+ return (buf->ptr == git_buf__oom);
+}
+
+/*
+ * Functions below that return int value error codes will return 0 on
+ * success or -1 on failure (which generally means an allocation failed).
+ * Using a git_buf where the allocation has failed with result in -1 from
+ * all further calls using that buffer. As a result, you can ignore the
+ * return code of these functions and call them in a series then just call
+ * git_buf_oom at the end.
+ */
+int git_buf_sets(git_buf *buf, const char *string);
+int git_buf_putc(git_buf *buf, char c);
+int git_buf_putcn(git_buf *buf, char c, size_t len);
+int git_buf_put(git_buf *buf, const char *data, size_t len);
+int git_buf_puts(git_buf *buf, const char *string);
+int git_buf_printf(git_buf *buf, const char *format, ...) GIT_FORMAT_PRINTF(2, 3);
+int git_buf_vprintf(git_buf *buf, const char *format, va_list ap);
+void git_buf_clear(git_buf *buf);
+void git_buf_consume(git_buf *buf, const char *end);
+void git_buf_truncate(git_buf *buf, size_t len);
+void git_buf_shorten(git_buf *buf, size_t amount);
+void git_buf_rtruncate_at_char(git_buf *path, char separator);
+
+/** General join with separator */
+int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...);
+/** Fast join of two strings - first may legally point into `buf` data */
+int git_buf_join(git_buf *buf, char separator, const char *str_a, const char *str_b);
+/** Fast join of three strings - cannot reference `buf` data */
+int git_buf_join3(git_buf *buf, char separator, const char *str_a, const char *str_b, const char *str_c);
+
+/**
+ * Join two strings as paths, inserting a slash between as needed.
+ * @return 0 on success, -1 on failure
+ */
+GIT_INLINE(int) git_buf_joinpath(git_buf *buf, const char *a, const char *b)
+{
+ return git_buf_join(buf, '/', a, b);
+}
+
+GIT_INLINE(const char *) git_buf_cstr(const git_buf *buf)
+{
+ return buf->ptr;
+}
+
+GIT_INLINE(size_t) git_buf_len(const git_buf *buf)
+{
+ return buf->size;
+}
+
+void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf);
#define git_buf_PUTS(buf, str) git_buf_put(buf, str, sizeof(str) - 1)
+GIT_INLINE(ssize_t) git_buf_rfind_next(const git_buf *buf, char ch)
+{
+ ssize_t idx = (ssize_t)buf->size - 1;
+ while (idx >= 0 && buf->ptr[idx] == ch) idx--;
+ while (idx >= 0 && buf->ptr[idx] != ch) idx--;
+ return idx;
+}
+
+GIT_INLINE(ssize_t) git_buf_rfind(const git_buf *buf, char ch)
+{
+ ssize_t idx = (ssize_t)buf->size - 1;
+ while (idx >= 0 && buf->ptr[idx] != ch) idx--;
+ return idx;
+}
+
+GIT_INLINE(ssize_t) git_buf_find(const git_buf *buf, char ch)
+{
+ void *found = memchr(buf->ptr, ch, buf->size);
+ return found ? (ssize_t)((const char *)found - buf->ptr) : -1;
+}
+
+/* Remove whitespace from the end of the buffer */
+void git_buf_rtrim(git_buf *buf);
+
+int git_buf_cmp(const git_buf *a, const git_buf *b);
+
+/* Write data as base64 encoded in buffer */
+int git_buf_put_base64(git_buf *buf, const char *data, size_t len);
+
+/* Write data as "base85" encoded in buffer */
+int git_buf_put_base85(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 433fc3d9c..36ce66570 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -1,129 +1,280 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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 "thread-utils.h"
+#include "util.h"
#include "cache.h"
+#include "odb.h"
+#include "object.h"
+#include "git2/oid.h"
+
+GIT__USE_OIDMAP
+
+bool git_cache__enabled = true;
+ssize_t git_cache__max_storage = (256 * 1024 * 1024);
+git_atomic_ssize git_cache__current_storage = {0};
+
+static size_t git_cache__max_object_size[8] = {
+ 0, /* GIT_OBJ__EXT1 */
+ 4096, /* GIT_OBJ_COMMIT */
+ 4096, /* GIT_OBJ_TREE */
+ 0, /* GIT_OBJ_BLOB */
+ 4096, /* GIT_OBJ_TAG */
+ 0, /* GIT_OBJ__EXT2 */
+ 0, /* GIT_OBJ_OFS_DELTA */
+ 0 /* GIT_OBJ_REF_DELTA */
+};
-int git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr)
+int git_cache_set_max_object_size(git_otype type, size_t size)
{
- size_t i;
+ if (type < 0 || (size_t)type >= ARRAY_SIZE(git_cache__max_object_size)) {
+ giterr_set(GITERR_INVALID, "type out of range");
+ return -1;
+ }
+
+ git_cache__max_object_size[type] = size;
+ return 0;
+}
- if (size < 8)
- size = 8;
+void git_cache_dump_stats(git_cache *cache)
+{
+ git_cached_obj *object;
- /* round up size to closest power of 2 */
- size--;
- size |= size >> 1;
- size |= size >> 2;
- size |= size >> 4;
- size |= size >> 8;
- size |= size >> 16;
+ if (kh_size(cache->map) == 0)
+ return;
- cache->size_mask = size;
- cache->lru_count = 0;
- cache->free_obj = free_ptr;
+ printf("Cache %p: %d items cached, %d bytes\n",
+ cache, kh_size(cache->map), (int)cache->used_memory);
- cache->nodes = git__malloc((size + 1) * sizeof(cache_node));
- if (cache->nodes == NULL)
- return GIT_ENOMEM;
+ kh_foreach_value(cache->map, object, {
+ char oid_str[9];
+ printf(" %s%c %s (%d)\n",
+ git_object_type2string(object->type),
+ object->flags == GIT_CACHE_STORE_PARSED ? '*' : ' ',
+ git_oid_tostr(oid_str, sizeof(oid_str), &object->oid),
+ (int)object->size
+ );
+ });
+}
- for (i = 0; i < (size + 1); ++i) {
- git_mutex_init(&cache->nodes[i].lock);
- cache->nodes[i].ptr = NULL;
+int git_cache_init(git_cache *cache)
+{
+ memset(cache, 0, sizeof(*cache));
+ cache->map = git_oidmap_alloc();
+ if (git_mutex_init(&cache->lock)) {
+ giterr_set(GITERR_OS, "Failed to initialize cache mutex");
+ return -1;
}
+ return 0;
+}
+
+/* called with lock */
+static void clear_cache(git_cache *cache)
+{
+ git_cached_obj *evict = NULL;
+
+ if (kh_size(cache->map) == 0)
+ return;
+
+ kh_foreach_value(cache->map, evict, {
+ git_cached_obj_decref(evict);
+ });
+
+ kh_clear(oid, cache->map);
+ git_atomic_ssize_add(&git_cache__current_storage, -cache->used_memory);
+ cache->used_memory = 0;
+}
- return GIT_SUCCESS;
+void git_cache_clear(git_cache *cache)
+{
+ if (git_mutex_lock(&cache->lock) < 0)
+ return;
+
+ clear_cache(cache);
+
+ git_mutex_unlock(&cache->lock);
}
void git_cache_free(git_cache *cache)
{
- size_t i;
+ git_cache_clear(cache);
+ git_oidmap_free(cache->map);
+ git_mutex_free(&cache->lock);
+ git__memzero(cache, sizeof(*cache));
+}
+
+/* Called with lock */
+static void cache_evict_entries(git_cache *cache)
+{
+ uint32_t seed = rand();
+ size_t evict_count = 8;
+ ssize_t evicted_memory = 0;
+
+ /* do not infinite loop if there's not enough entries to evict */
+ if (evict_count > kh_size(cache->map)) {
+ clear_cache(cache);
+ return;
+ }
+
+ while (evict_count > 0) {
+ khiter_t pos = seed++ % kh_end(cache->map);
- for (i = 0; i < (cache->size_mask + 1); ++i) {
- if (cache->nodes[i].ptr)
- git_cached_obj_decref(cache->nodes[i].ptr, cache->free_obj);
+ if (kh_exist(cache->map, pos)) {
+ git_cached_obj *evict = kh_val(cache->map, pos);
- git_mutex_free(&cache->nodes[i].lock);
+ evict_count--;
+ evicted_memory += evict->size;
+ git_cached_obj_decref(evict);
+
+ kh_del(oid, cache->map, pos);
+ }
}
- free(cache->nodes);
+ cache->used_memory -= evicted_memory;
+ git_atomic_ssize_add(&git_cache__current_storage, -evicted_memory);
+}
+
+static bool cache_should_store(git_otype object_type, size_t object_size)
+{
+ size_t max_size = git_cache__max_object_size[object_type];
+ return git_cache__enabled && object_size < max_size;
}
-void *git_cache_get(git_cache *cache, const git_oid *oid)
+static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags)
{
- const uint32_t *hash;
- cache_node *node = NULL;
- void *result = NULL;
+ khiter_t pos;
+ git_cached_obj *entry = NULL;
- hash = (const uint32_t *)oid->id;
- node = &cache->nodes[hash[0] & cache->size_mask];
+ if (!git_cache__enabled || git_mutex_lock(&cache->lock) < 0)
+ return NULL;
- git_mutex_lock(&node->lock);
- {
- if (node->ptr && git_cached_obj_compare(node->ptr, oid) == 0) {
- git_cached_obj_incref(node->ptr);
- result = node->ptr;
+ pos = kh_get(oid, cache->map, oid);
+ if (pos != kh_end(cache->map)) {
+ entry = kh_val(cache->map, pos);
+
+ if (flags && entry->flags != flags) {
+ entry = NULL;
+ } else {
+ git_cached_obj_incref(entry);
}
}
- git_mutex_unlock(&node->lock);
- return result;
+ git_mutex_unlock(&cache->lock);
+
+ return entry;
}
-void *git_cache_try_store(git_cache *cache, void *entry)
+static void *cache_store(git_cache *cache, git_cached_obj *entry)
{
- const uint32_t *hash;
- const git_oid *oid;
- cache_node *node = NULL;
-
- oid = &((git_cached_obj*)entry)->oid;
- hash = (const uint32_t *)oid->id;
- node = &cache->nodes[hash[0] & cache->size_mask];
+ khiter_t pos;
- /* increase the refcount on this object, because
- * the cache now owns it */
git_cached_obj_incref(entry);
- git_mutex_lock(&node->lock);
-
- if (node->ptr == NULL) {
- node->ptr = entry;
- } else if (git_cached_obj_compare(node->ptr, oid) == 0) {
- git_cached_obj_decref(entry, cache->free_obj);
- entry = node->ptr;
- } else {
- git_cached_obj_decref(node->ptr, cache->free_obj);
- node->ptr = entry;
+
+ if (!git_cache__enabled && cache->used_memory > 0) {
+ git_cache_clear(cache);
+ return entry;
}
- /* increase the refcount again, because we are
- * returning it to the user */
- git_cached_obj_incref(entry);
- git_mutex_unlock(&node->lock);
+ if (!cache_should_store(entry->type, entry->size))
+ return entry;
+
+ if (git_mutex_lock(&cache->lock) < 0)
+ return entry;
+
+ /* soften the load on the cache */
+ if (git_cache__current_storage.val > git_cache__max_storage)
+ cache_evict_entries(cache);
+
+ pos = kh_get(oid, cache->map, &entry->oid);
+
+ /* not found */
+ if (pos == kh_end(cache->map)) {
+ int rval;
+
+ pos = kh_put(oid, cache->map, &entry->oid, &rval);
+ if (rval >= 0) {
+ kh_key(cache->map, pos) = &entry->oid;
+ kh_val(cache->map, pos) = entry;
+ git_cached_obj_incref(entry);
+ cache->used_memory += entry->size;
+ git_atomic_ssize_add(&git_cache__current_storage, (ssize_t)entry->size);
+ }
+ }
+ /* found */
+ else {
+ git_cached_obj *stored_entry = kh_val(cache->map, pos);
+ if (stored_entry->flags == entry->flags) {
+ git_cached_obj_decref(entry);
+ git_cached_obj_incref(stored_entry);
+ entry = stored_entry;
+ } else if (stored_entry->flags == GIT_CACHE_STORE_RAW &&
+ entry->flags == GIT_CACHE_STORE_PARSED) {
+ git_cached_obj_decref(stored_entry);
+ git_cached_obj_incref(entry);
+
+ kh_key(cache->map, pos) = &entry->oid;
+ kh_val(cache->map, pos) = entry;
+ } else {
+ /* NO OP */
+ }
+ }
+
+ git_mutex_unlock(&cache->lock);
return entry;
}
+
+void *git_cache_store_raw(git_cache *cache, git_odb_object *entry)
+{
+ entry->cached.flags = GIT_CACHE_STORE_RAW;
+ return cache_store(cache, (git_cached_obj *)entry);
+}
+
+void *git_cache_store_parsed(git_cache *cache, git_object *entry)
+{
+ entry->cached.flags = GIT_CACHE_STORE_PARSED;
+ return cache_store(cache, (git_cached_obj *)entry);
+}
+
+git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid)
+{
+ return cache_get(cache, oid, GIT_CACHE_STORE_RAW);
+}
+
+git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid)
+{
+ return cache_get(cache, oid, GIT_CACHE_STORE_PARSED);
+}
+
+void *git_cache_get_any(git_cache *cache, const git_oid *oid)
+{
+ return cache_get(cache, oid, GIT_CACHE_STORE_ANY);
+}
+
+void git_cached_obj_decref(void *_obj)
+{
+ git_cached_obj *obj = _obj;
+
+ if (git_atomic_dec(&obj->refcount) == 0) {
+ switch (obj->flags) {
+ case GIT_CACHE_STORE_RAW:
+ git_odb_object__free(_obj);
+ break;
+
+ case GIT_CACHE_STORE_PARSED:
+ git_object__free(_obj);
+ break;
+
+ default:
+ git__free(_obj);
+ break;
+ }
+ }
+}
diff --git a/src/cache.h b/src/cache.h
index 4794dea3a..53fbcf4e9 100644
--- a/src/cache.h
+++ b/src/cache.h
@@ -1,3 +1,9 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_cache_h__
#define INCLUDE_cache_h__
@@ -6,53 +12,56 @@
#include "git2/odb.h"
#include "thread-utils.h"
+#include "oidmap.h"
-#define GIT_DEFAULT_CACHE_SIZE 128
-
-typedef void (*git_cached_obj_freeptr)(void *);
+enum {
+ GIT_CACHE_STORE_ANY = 0,
+ GIT_CACHE_STORE_RAW = 1,
+ GIT_CACHE_STORE_PARSED = 2
+};
typedef struct {
- git_oid oid;
+ git_oid oid;
+ int16_t type; /* git_otype value */
+ uint16_t flags; /* GIT_CACHE_STORE value */
+ size_t size;
git_atomic refcount;
} git_cached_obj;
typedef struct {
- git_cached_obj *ptr;
- git_mutex lock;
-} cache_node;
-
-typedef struct {
- cache_node *nodes;
-
- unsigned int lru_count;
- size_t size_mask;
- git_cached_obj_freeptr free_obj;
+ git_oidmap *map;
+ git_mutex lock;
+ ssize_t used_memory;
} git_cache;
+extern bool git_cache__enabled;
+extern ssize_t git_cache__max_storage;
+extern git_atomic_ssize git_cache__current_storage;
+
+int git_cache_set_max_object_size(git_otype type, size_t size);
-int git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr);
+int git_cache_init(git_cache *cache);
void git_cache_free(git_cache *cache);
+void git_cache_clear(git_cache *cache);
-void *git_cache_try_store(git_cache *cache, void *entry);
-void *git_cache_get(git_cache *cache, const git_oid *oid);
+void *git_cache_store_raw(git_cache *cache, git_odb_object *entry);
+void *git_cache_store_parsed(git_cache *cache, git_object *entry);
+git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid);
+git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid);
+void *git_cache_get_any(git_cache *cache, const git_oid *oid);
-GIT_INLINE(int) git_cached_obj_compare(git_cached_obj *obj, const git_oid *oid)
+GIT_INLINE(size_t) git_cache_size(git_cache *cache)
{
- return git_oid_cmp(&obj->oid, oid);
+ return (size_t)kh_size(cache->map);
}
-GIT_INLINE(void) git_cached_obj_incref(git_cached_obj *obj)
+GIT_INLINE(void) git_cached_obj_incref(void *_obj)
{
+ git_cached_obj *obj = _obj;
git_atomic_inc(&obj->refcount);
}
-GIT_INLINE(void) git_cached_obj_decref(git_cached_obj *obj, git_cached_obj_freeptr free_obj)
-{
- if (git_atomic_dec(&obj->refcount) == 0)
- free_obj(obj);
-}
-
-
+void git_cached_obj_decref(void *_obj);
#endif
diff --git a/src/cc-compat.h b/src/cc-compat.h
index cf6cccf12..e73cb6de8 100644
--- a/src/cc-compat.h
+++ b/src/cc-compat.h
@@ -1,73 +1,77 @@
/*
- * cc-compat.h - C compiler compat macros for internal use
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_compat_h__
#define INCLUDE_compat_h__
+#include <stdarg.h>
+
/*
* See if our compiler is known to support flexible array members.
*/
#ifndef GIT_FLEX_ARRAY
-# if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
-# define GIT_FLEX_ARRAY /* empty */
-# elif defined(__GNUC__)
-# if (__GNUC__ >= 3)
-# define GIT_FLEX_ARRAY /* empty */
-# else
-# define GIT_FLEX_ARRAY 0 /* older GNU extension */
-# endif
-# endif
+# if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
+# define GIT_FLEX_ARRAY /* empty */
+# elif defined(__GNUC__)
+# if (__GNUC__ >= 3)
+# define GIT_FLEX_ARRAY /* empty */
+# else
+# define GIT_FLEX_ARRAY 0 /* older GNU extension */
+# endif
+# endif
/* Default to safer but a bit wasteful traditional style */
-# ifndef GIT_FLEX_ARRAY
-# define GIT_FLEX_ARRAY 1
-# endif
+# ifndef GIT_FLEX_ARRAY
+# define GIT_FLEX_ARRAY 1
+# endif
#endif
#ifdef __GNUC__
-# define GIT_TYPEOF(x) (__typeof__(x))
-#else
-# define GIT_TYPEOF(x)
-#endif
-
-#ifdef __cplusplus
-# define GIT_UNUSED(x)
-#else
-# ifdef __GNUC__
-# define GIT_UNUSED(x) x __attribute__ ((__unused__))
-# else
-# define GIT_UNUSED(x) x
-# endif
-#endif
-
-#if defined(_MSC_VER)
-#define GIT_UNUSED_ARG(x) ((void)(x)); /* note trailing ; */
+# define GIT_TYPEOF(x) (__typeof__(x))
#else
-#define GIT_UNUSED_ARG(x)
+# define GIT_TYPEOF(x)
#endif
-/*
- * Does our compiler/platform support the C99 <inttypes.h> and
- * <stdint.h> header files. (C99 requires that <inttypes.h>
- * includes <stdint.h>).
- */
-#if !defined(_MSC_VER)
-# define GIT_HAVE_INTTYPES_H 1
-#endif
+#define GIT_UNUSED(x) ((void)(x))
/* Define the printf format specifer to use for size_t output */
#if defined(_MSC_VER) || defined(__MINGW32__)
-# define PRIuZ "Iu"
+# define PRIuZ "Iu"
+# define PRIxZ "Ix"
#else
-# define PRIuZ "zu"
+# define PRIuZ "zu"
+# define PRIxZ "zx"
#endif
/* Micosoft Visual C/C++ */
#if defined(_MSC_VER)
/* disable "deprecated function" warnings */
-# pragma warning ( disable : 4996 )
+# pragma warning ( disable : 4996 )
/* disable "conditional expression is constant" level 4 warnings */
-# pragma warning ( disable : 4127 )
+# pragma warning ( disable : 4127 )
+#endif
+
+#if defined (_MSC_VER)
+ typedef unsigned char bool;
+# ifndef true
+# define true 1
+# endif
+# ifndef false
+# define false 0
+# endif
+#else
+# include <stdbool.h>
+#endif
+
+#ifndef va_copy
+# ifdef __va_copy
+# define va_copy(dst, src) __va_copy(dst, src)
+# else
+# define va_copy(dst, src) ((dst) = (src))
+# endif
#endif
#endif /* INCLUDE_compat_h__ */
diff --git a/src/checkout.c b/src/checkout.c
new file mode 100644
index 000000000..20763fd35
--- /dev/null
+++ b/src/checkout.c
@@ -0,0 +1,2249 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include <assert.h>
+
+#include "checkout.h"
+
+#include "git2/repository.h"
+#include "git2/refs.h"
+#include "git2/tree.h"
+#include "git2/blob.h"
+#include "git2/config.h"
+#include "git2/diff.h"
+#include "git2/submodule.h"
+#include "git2/sys/index.h"
+
+#include "refs.h"
+#include "repository.h"
+#include "index.h"
+#include "filter.h"
+#include "blob.h"
+#include "diff.h"
+#include "pathspec.h"
+#include "buf_text.h"
+#include "merge_file.h"
+#include "path.h"
+
+/* See docs/checkout-internals.md for more information */
+
+enum {
+ CHECKOUT_ACTION__NONE = 0,
+ CHECKOUT_ACTION__REMOVE = 1,
+ CHECKOUT_ACTION__UPDATE_BLOB = 2,
+ CHECKOUT_ACTION__UPDATE_SUBMODULE = 4,
+ CHECKOUT_ACTION__CONFLICT = 8,
+ CHECKOUT_ACTION__UPDATE_CONFLICT = 16,
+ CHECKOUT_ACTION__MAX = 16,
+ CHECKOUT_ACTION__DEFER_REMOVE = 32,
+ CHECKOUT_ACTION__REMOVE_AND_UPDATE =
+ (CHECKOUT_ACTION__UPDATE_BLOB | CHECKOUT_ACTION__REMOVE),
+};
+
+typedef struct {
+ git_repository *repo;
+ git_diff *diff;
+ git_checkout_options opts;
+ bool opts_free_baseline;
+ char *pfx;
+ git_index *index;
+ git_pool pool;
+ git_vector removes;
+ git_vector conflicts;
+ git_buf path;
+ size_t workdir_len;
+ git_buf tmp;
+ unsigned int strategy;
+ int can_symlink;
+ bool reload_submodules;
+ size_t total_steps;
+ size_t completed_steps;
+} checkout_data;
+
+typedef struct {
+ const git_index_entry *ancestor;
+ const git_index_entry *ours;
+ const git_index_entry *theirs;
+
+ int name_collision:1,
+ directoryfile:1,
+ one_to_two:1,
+ binary:1,
+ submodule:1;
+} checkout_conflictdata;
+
+static int checkout_notify(
+ checkout_data *data,
+ git_checkout_notify_t why,
+ const git_diff_delta *delta,
+ const git_index_entry *wditem)
+{
+ git_diff_file wdfile;
+ const git_diff_file *baseline = NULL, *target = NULL, *workdir = NULL;
+ const char *path = NULL;
+
+ if (!data->opts.notify_cb ||
+ (why & data->opts.notify_flags) == 0)
+ return 0;
+
+ if (wditem) {
+ memset(&wdfile, 0, sizeof(wdfile));
+
+ git_oid_cpy(&wdfile.id, &wditem->id);
+ wdfile.path = wditem->path;
+ wdfile.size = wditem->file_size;
+ wdfile.flags = GIT_DIFF_FLAG_VALID_ID;
+ wdfile.mode = wditem->mode;
+
+ workdir = &wdfile;
+
+ path = wditem->path;
+ }
+
+ if (delta) {
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED:
+ case GIT_DELTA_MODIFIED:
+ case GIT_DELTA_TYPECHANGE:
+ default:
+ baseline = &delta->old_file;
+ target = &delta->new_file;
+ break;
+ case GIT_DELTA_ADDED:
+ case GIT_DELTA_IGNORED:
+ case GIT_DELTA_UNTRACKED:
+ target = &delta->new_file;
+ break;
+ case GIT_DELTA_DELETED:
+ baseline = &delta->old_file;
+ break;
+ }
+
+ path = delta->old_file.path;
+ }
+
+ {
+ int error = data->opts.notify_cb(
+ why, path, baseline, target, workdir, data->opts.notify_payload);
+
+ return giterr_set_after_callback_function(
+ error, "git_checkout notification");
+ }
+}
+
+static bool checkout_is_workdir_modified(
+ checkout_data *data,
+ const git_diff_file *baseitem,
+ const git_index_entry *wditem)
+{
+ git_oid oid;
+ const git_index_entry *ie;
+
+ /* handle "modified" submodule */
+ if (wditem->mode == GIT_FILEMODE_COMMIT) {
+ git_submodule *sm;
+ unsigned int sm_status = 0;
+ const git_oid *sm_oid = NULL;
+ bool rval = false;
+
+ if (git_submodule_lookup(&sm, data->repo, wditem->path) < 0) {
+ giterr_clear();
+ return true;
+ }
+
+ if (git_submodule_status(&sm_status, sm) < 0 ||
+ GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status))
+ rval = true;
+ else if ((sm_oid = git_submodule_wd_id(sm)) == NULL)
+ rval = false;
+ else
+ rval = (git_oid__cmp(&baseitem->id, sm_oid) != 0);
+
+ git_submodule_free(sm);
+ return rval;
+ }
+
+ /* Look at the cache to decide if the workdir is modified. If not,
+ * we can simply compare the oid in the cache to the baseitem instead
+ * of hashing the file.
+ */
+ if ((ie = git_index_get_bypath(data->index, wditem->path, 0)) != NULL) {
+ if (wditem->mtime.seconds == ie->mtime.seconds &&
+ wditem->mtime.nanoseconds == ie->mtime.nanoseconds &&
+ wditem->file_size == ie->file_size)
+ return (git_oid__cmp(&baseitem->id, &ie->id) != 0);
+ }
+
+ /* depending on where base is coming from, we may or may not know
+ * the actual size of the data, so we can't rely on this shortcut.
+ */
+ if (baseitem->size && wditem->file_size != baseitem->size)
+ return true;
+
+ if (git_diff__oid_for_entry(&oid, data->diff, wditem, NULL) < 0)
+ return false;
+
+ return (git_oid__cmp(&baseitem->id, &oid) != 0);
+}
+
+#define CHECKOUT_ACTION_IF(FLAG,YES,NO) \
+ ((data->strategy & GIT_CHECKOUT_##FLAG) ? CHECKOUT_ACTION__##YES : CHECKOUT_ACTION__##NO)
+
+static int checkout_action_common(
+ int *action,
+ checkout_data *data,
+ const git_diff_delta *delta,
+ const git_index_entry *wd)
+{
+ git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE;
+
+ if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)
+ *action = (*action & ~CHECKOUT_ACTION__REMOVE);
+
+ if ((*action & CHECKOUT_ACTION__UPDATE_BLOB) != 0) {
+ if (S_ISGITLINK(delta->new_file.mode))
+ *action = (*action & ~CHECKOUT_ACTION__UPDATE_BLOB) |
+ CHECKOUT_ACTION__UPDATE_SUBMODULE;
+
+ /* to "update" a symlink, we must remove the old one first */
+ if (delta->new_file.mode == GIT_FILEMODE_LINK && wd != NULL)
+ *action |= CHECKOUT_ACTION__REMOVE;
+
+ notify = GIT_CHECKOUT_NOTIFY_UPDATED;
+ }
+
+ if ((*action & CHECKOUT_ACTION__CONFLICT) != 0)
+ notify = GIT_CHECKOUT_NOTIFY_CONFLICT;
+
+ return checkout_notify(data, notify, delta, wd);
+}
+
+static int checkout_action_no_wd(
+ int *action,
+ checkout_data *data,
+ const git_diff_delta *delta)
+{
+ int error = 0;
+
+ *action = CHECKOUT_ACTION__NONE;
+
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED: /* case 12 */
+ error = checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL);
+ if (error)
+ return error;
+ *action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, NONE);
+ break;
+ case GIT_DELTA_ADDED: /* case 2 or 28 (and 5 but not really) */
+ *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ break;
+ case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */
+ *action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, CONFLICT);
+ break;
+ case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/
+ if (delta->new_file.mode == GIT_FILEMODE_TREE)
+ *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ break;
+ case GIT_DELTA_DELETED: /* case 8 or 25 */
+ *action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE);
+ break;
+ default: /* impossible */
+ break;
+ }
+
+ return checkout_action_common(action, data, delta, NULL);
+}
+
+static bool wd_item_is_removable(git_iterator *iter, const git_index_entry *wd)
+{
+ git_buf *full = NULL;
+
+ if (wd->mode != GIT_FILEMODE_TREE)
+ return true;
+ if (git_iterator_current_workdir_path(&full, iter) < 0)
+ return true;
+ return !full || !git_path_contains(full, DOT_GIT);
+}
+
+static int checkout_queue_remove(checkout_data *data, const char *path)
+{
+ char *copy = git_pool_strdup(&data->pool, path);
+ GITERR_CHECK_ALLOC(copy);
+ return git_vector_insert(&data->removes, copy);
+}
+
+/* note that this advances the iterator over the wd item */
+static int checkout_action_wd_only(
+ checkout_data *data,
+ git_iterator *workdir,
+ const git_index_entry **wditem,
+ git_vector *pathspec)
+{
+ int error = 0;
+ bool remove = false;
+ git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE;
+ const git_index_entry *wd = *wditem;
+
+ if (!git_pathspec__match(
+ pathspec, wd->path,
+ (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0,
+ git_iterator_ignore_case(workdir), NULL, NULL))
+ return git_iterator_advance(wditem, workdir);
+
+ /* check if item is tracked in the index but not in the checkout diff */
+ if (data->index != NULL) {
+ size_t pos;
+
+ error = git_index__find_pos(
+ &pos, data->index, wd->path, 0, GIT_INDEX_STAGE_ANY);
+
+ if (wd->mode != GIT_FILEMODE_TREE) {
+ if (!error) { /* found by git_index__find_pos call */
+ notify = GIT_CHECKOUT_NOTIFY_DIRTY;
+ remove = ((data->strategy & GIT_CHECKOUT_FORCE) != 0);
+ } else if (error != GIT_ENOTFOUND)
+ return error;
+ else
+ error = 0; /* git_index__find_pos does not set error msg */
+ } else {
+ /* for tree entries, we have to see if there are any index
+ * entries that are contained inside that tree
+ */
+ const git_index_entry *e = git_index_get_byindex(data->index, pos);
+
+ if (e != NULL && data->diff->pfxcomp(e->path, wd->path) == 0) {
+ notify = GIT_CHECKOUT_NOTIFY_DIRTY;
+ remove = ((data->strategy & GIT_CHECKOUT_FORCE) != 0);
+ }
+ }
+ }
+
+ if (notify != GIT_CHECKOUT_NOTIFY_NONE) {
+ /* if we found something in the index, notify and advance */
+ if ((error = checkout_notify(data, notify, NULL, wd)) != 0)
+ return error;
+
+ if (remove && wd_item_is_removable(workdir, wd))
+ error = checkout_queue_remove(data, wd->path);
+
+ if (!error)
+ error = git_iterator_advance(wditem, workdir);
+ } else {
+ /* untracked or ignored - can't know which until we advance through */
+ bool over = false, removable = wd_item_is_removable(workdir, wd);
+ git_iterator_status_t untracked_state;
+
+ /* copy the entry for issuing notification callback later */
+ git_index_entry saved_wd = *wd;
+ git_buf_sets(&data->tmp, wd->path);
+ saved_wd.path = data->tmp.ptr;
+
+ error = git_iterator_advance_over_with_status(
+ wditem, &untracked_state, workdir);
+ if (error == GIT_ITEROVER)
+ over = true;
+ else if (error < 0)
+ return error;
+
+ if (untracked_state == GIT_ITERATOR_STATUS_IGNORED) {
+ notify = GIT_CHECKOUT_NOTIFY_IGNORED;
+ remove = ((data->strategy & GIT_CHECKOUT_REMOVE_IGNORED) != 0);
+ } else {
+ notify = GIT_CHECKOUT_NOTIFY_UNTRACKED;
+ remove = ((data->strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0);
+ }
+
+ if ((error = checkout_notify(data, notify, NULL, &saved_wd)) != 0)
+ return error;
+
+ if (remove && removable)
+ error = checkout_queue_remove(data, saved_wd.path);
+
+ if (!error && over) /* restore ITEROVER if needed */
+ error = GIT_ITEROVER;
+ }
+
+ return error;
+}
+
+static bool submodule_is_config_only(
+ checkout_data *data,
+ const char *path)
+{
+ git_submodule *sm = NULL;
+ unsigned int sm_loc = 0;
+ bool rval = false;
+
+ if (git_submodule_lookup(&sm, data->repo, path) < 0)
+ return true;
+
+ if (git_submodule_location(&sm_loc, sm) < 0 ||
+ sm_loc == GIT_SUBMODULE_STATUS_IN_CONFIG)
+ rval = true;
+
+ git_submodule_free(sm);
+
+ return rval;
+}
+
+static int checkout_action_with_wd(
+ int *action,
+ checkout_data *data,
+ const git_diff_delta *delta,
+ git_iterator *workdir,
+ const git_index_entry *wd)
+{
+ *action = CHECKOUT_ACTION__NONE;
+
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED: /* case 14/15 or 33 */
+ if (checkout_is_workdir_modified(data, &delta->old_file, wd)) {
+ GITERR_CHECK_ERROR(
+ checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd) );
+ *action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, NONE);
+ }
+ break;
+ case GIT_DELTA_ADDED: /* case 3, 4 or 6 */
+ if (git_iterator_current_is_ignored(workdir))
+ *action = CHECKOUT_ACTION_IF(DONT_OVERWRITE_IGNORED, CONFLICT, UPDATE_BLOB);
+ else
+ *action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT);
+ break;
+ case GIT_DELTA_DELETED: /* case 9 or 10 (or 26 but not really) */
+ if (checkout_is_workdir_modified(data, &delta->old_file, wd))
+ *action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);
+ else
+ *action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE);
+ break;
+ case GIT_DELTA_MODIFIED: /* case 16, 17, 18 (or 36 but not really) */
+ if (checkout_is_workdir_modified(data, &delta->old_file, wd))
+ *action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT);
+ else
+ *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ break;
+ case GIT_DELTA_TYPECHANGE: /* case 22, 23, 29, 30 */
+ if (delta->old_file.mode == GIT_FILEMODE_TREE) {
+ if (wd->mode == GIT_FILEMODE_TREE)
+ /* either deleting items in old tree will delete the wd dir,
+ * or we'll get a conflict when we attempt blob update...
+ */
+ *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ else if (wd->mode == GIT_FILEMODE_COMMIT) {
+ /* workdir is possibly a "phantom" submodule - treat as a
+ * tree if the only submodule info came from the config
+ */
+ if (submodule_is_config_only(data, wd->path))
+ *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ else
+ *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
+ } else
+ *action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);
+ }
+ else if (checkout_is_workdir_modified(data, &delta->old_file, wd))
+ *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
+ else
+ *action = CHECKOUT_ACTION_IF(SAFE, REMOVE_AND_UPDATE, NONE);
+
+ /* don't update if the typechange is to a tree */
+ if (delta->new_file.mode == GIT_FILEMODE_TREE)
+ *action = (*action & ~CHECKOUT_ACTION__UPDATE_BLOB);
+ break;
+ default: /* impossible */
+ break;
+ }
+
+ return checkout_action_common(action, data, delta, wd);
+}
+
+static int checkout_action_with_wd_blocker(
+ int *action,
+ checkout_data *data,
+ const git_diff_delta *delta,
+ const git_index_entry *wd)
+{
+ *action = CHECKOUT_ACTION__NONE;
+
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED:
+ /* should show delta as dirty / deleted */
+ GITERR_CHECK_ERROR(
+ checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd) );
+ *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, NONE);
+ break;
+ case GIT_DELTA_ADDED:
+ case GIT_DELTA_MODIFIED:
+ *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
+ break;
+ case GIT_DELTA_DELETED:
+ *action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);
+ break;
+ case GIT_DELTA_TYPECHANGE:
+ /* not 100% certain about this... */
+ *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
+ break;
+ default: /* impossible */
+ break;
+ }
+
+ return checkout_action_common(action, data, delta, wd);
+}
+
+static int checkout_action_with_wd_dir(
+ int *action,
+ checkout_data *data,
+ const git_diff_delta *delta,
+ git_iterator *workdir,
+ const git_index_entry *wd)
+{
+ *action = CHECKOUT_ACTION__NONE;
+
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED: /* case 19 or 24 (or 34 but not really) */
+ GITERR_CHECK_ERROR(
+ checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL));
+ GITERR_CHECK_ERROR(
+ checkout_notify(data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd));
+ break;
+ case GIT_DELTA_ADDED:/* case 4 (and 7 for dir) */
+ case GIT_DELTA_MODIFIED: /* case 20 (or 37 but not really) */
+ if (delta->old_file.mode == GIT_FILEMODE_COMMIT)
+ /* expected submodule (and maybe found one) */;
+ else if (delta->new_file.mode != GIT_FILEMODE_TREE)
+ *action = git_iterator_current_is_ignored(workdir) ?
+ CHECKOUT_ACTION_IF(DONT_OVERWRITE_IGNORED, CONFLICT, REMOVE_AND_UPDATE) :
+ CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
+ break;
+ case GIT_DELTA_DELETED: /* case 11 (and 27 for dir) */
+ if (delta->old_file.mode != GIT_FILEMODE_TREE)
+ GITERR_CHECK_ERROR(
+ checkout_notify(data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd));
+ break;
+ case GIT_DELTA_TYPECHANGE: /* case 24 or 31 */
+ if (delta->old_file.mode == GIT_FILEMODE_TREE) {
+ /* For typechange from dir, remove dir and add blob, but it is
+ * not safe to remove dir if it contains modified files.
+ * However, safely removing child files will remove the parent
+ * directory if is it left empty, so we can defer removing the
+ * dir and it will succeed if no children are left.
+ */
+ *action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ if (*action != CHECKOUT_ACTION__NONE)
+ *action |= CHECKOUT_ACTION__DEFER_REMOVE;
+ }
+ else if (delta->new_file.mode != GIT_FILEMODE_TREE)
+ /* For typechange to dir, dir is already created so no action */
+ *action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
+ break;
+ default: /* impossible */
+ break;
+ }
+
+ return checkout_action_common(action, data, delta, wd);
+}
+
+static int checkout_action(
+ int *action,
+ checkout_data *data,
+ git_diff_delta *delta,
+ git_iterator *workdir,
+ const git_index_entry **wditem,
+ git_vector *pathspec)
+{
+ int cmp = -1, error;
+ int (*strcomp)(const char *, const char *) = data->diff->strcomp;
+ int (*pfxcomp)(const char *str, const char *pfx) = data->diff->pfxcomp;
+ int (*advance)(const git_index_entry **, git_iterator *) = NULL;
+
+ /* move workdir iterator to follow along with deltas */
+
+ while (1) {
+ const git_index_entry *wd = *wditem;
+
+ if (!wd)
+ return checkout_action_no_wd(action, data, delta);
+
+ cmp = strcomp(wd->path, delta->old_file.path);
+
+ /* 1. wd before delta ("a/a" before "a/b")
+ * 2. wd prefixes delta & should expand ("a/" before "a/b")
+ * 3. wd prefixes delta & cannot expand ("a/b" before "a/b/c")
+ * 4. wd equals delta ("a/b" and "a/b")
+ * 5. wd after delta & delta prefixes wd ("a/b/c" after "a/b/" or "a/b")
+ * 6. wd after delta ("a/c" after "a/b")
+ */
+
+ if (cmp < 0) {
+ cmp = pfxcomp(delta->old_file.path, wd->path);
+
+ if (cmp == 0) {
+ if (wd->mode == GIT_FILEMODE_TREE) {
+ /* case 2 - entry prefixed by workdir tree */
+ error = git_iterator_advance_into_or_over(wditem, workdir);
+ if (error < 0 && error != GIT_ITEROVER)
+ goto done;
+ continue;
+ }
+
+ /* case 3 maybe - wd contains non-dir where dir expected */
+ if (delta->old_file.path[strlen(wd->path)] == '/') {
+ error = checkout_action_with_wd_blocker(
+ action, data, delta, wd);
+ advance = git_iterator_advance;
+ goto done;
+ }
+ }
+
+ /* case 1 - handle wd item (if it matches pathspec) */
+ error = checkout_action_wd_only(data, workdir, wditem, pathspec);
+ if (error && error != GIT_ITEROVER)
+ goto done;
+ continue;
+ }
+
+ if (cmp == 0) {
+ /* case 4 */
+ error = checkout_action_with_wd(action, data, delta, workdir, wd);
+ advance = git_iterator_advance;
+ goto done;
+ }
+
+ cmp = pfxcomp(wd->path, delta->old_file.path);
+
+ if (cmp == 0) { /* case 5 */
+ if (wd->path[strlen(delta->old_file.path)] != '/')
+ return checkout_action_no_wd(action, data, delta);
+
+ if (delta->status == GIT_DELTA_TYPECHANGE) {
+ if (delta->old_file.mode == GIT_FILEMODE_TREE) {
+ error = checkout_action_with_wd(action, data, delta, workdir, wd);
+ advance = git_iterator_advance_into;
+ goto done;
+ }
+
+ if (delta->new_file.mode == GIT_FILEMODE_TREE ||
+ delta->new_file.mode == GIT_FILEMODE_COMMIT ||
+ delta->old_file.mode == GIT_FILEMODE_COMMIT)
+ {
+ error = checkout_action_with_wd(action, data, delta, workdir, wd);
+ advance = git_iterator_advance;
+ goto done;
+ }
+ }
+
+ return checkout_action_with_wd_dir(action, data, delta, workdir, wd);
+ }
+
+ /* case 6 - wd is after delta */
+ return checkout_action_no_wd(action, data, delta);
+ }
+
+done:
+ if (!error && advance != NULL &&
+ (error = advance(wditem, workdir)) < 0) {
+ *wditem = NULL;
+ if (error == GIT_ITEROVER)
+ error = 0;
+ }
+
+ return error;
+}
+
+static int checkout_remaining_wd_items(
+ checkout_data *data,
+ git_iterator *workdir,
+ const git_index_entry *wd,
+ git_vector *spec)
+{
+ int error = 0;
+
+ while (wd && !error)
+ error = checkout_action_wd_only(data, workdir, &wd, spec);
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ return error;
+}
+
+GIT_INLINE(int) checkout_idxentry_cmp(
+ const git_index_entry *a,
+ const git_index_entry *b)
+{
+ if (!a && !b)
+ return 0;
+ else if (!a && b)
+ return -1;
+ else if(a && !b)
+ return 1;
+ else
+ return strcmp(a->path, b->path);
+}
+
+static int checkout_conflictdata_cmp(const void *a, const void *b)
+{
+ const checkout_conflictdata *ca = a;
+ const checkout_conflictdata *cb = b;
+ int diff;
+
+ if ((diff = checkout_idxentry_cmp(ca->ancestor, cb->ancestor)) == 0 &&
+ (diff = checkout_idxentry_cmp(ca->ours, cb->theirs)) == 0)
+ diff = checkout_idxentry_cmp(ca->theirs, cb->theirs);
+
+ return diff;
+}
+
+int checkout_conflictdata_empty(
+ const git_vector *conflicts, size_t idx, void *payload)
+{
+ checkout_conflictdata *conflict;
+
+ GIT_UNUSED(payload);
+
+ if ((conflict = git_vector_get(conflicts, idx)) == NULL)
+ return -1;
+
+ if (conflict->ancestor || conflict->ours || conflict->theirs)
+ return 0;
+
+ git__free(conflict);
+ return 1;
+}
+
+GIT_INLINE(bool) conflict_pathspec_match(
+ checkout_data *data,
+ git_iterator *workdir,
+ git_vector *pathspec,
+ const git_index_entry *ancestor,
+ const git_index_entry *ours,
+ const git_index_entry *theirs)
+{
+ /* if the pathspec matches ours *or* theirs, proceed */
+ if (ours && git_pathspec__match(pathspec, ours->path,
+ (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0,
+ git_iterator_ignore_case(workdir), NULL, NULL))
+ return true;
+
+ if (theirs && git_pathspec__match(pathspec, theirs->path,
+ (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0,
+ git_iterator_ignore_case(workdir), NULL, NULL))
+ return true;
+
+ if (ancestor && git_pathspec__match(pathspec, ancestor->path,
+ (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0,
+ git_iterator_ignore_case(workdir), NULL, NULL))
+ return true;
+
+ return false;
+}
+
+GIT_INLINE(int) checkout_conflict_detect_submodule(checkout_conflictdata *conflict)
+{
+ conflict->submodule = ((conflict->ancestor && S_ISGITLINK(conflict->ancestor->mode)) ||
+ (conflict->ours && S_ISGITLINK(conflict->ours->mode)) ||
+ (conflict->theirs && S_ISGITLINK(conflict->theirs->mode)));
+ return 0;
+}
+
+GIT_INLINE(int) checkout_conflict_detect_binary(git_repository *repo, checkout_conflictdata *conflict)
+{
+ git_blob *ancestor_blob = NULL, *our_blob = NULL, *their_blob = NULL;
+ int error = 0;
+
+ if (conflict->submodule)
+ return 0;
+
+ if (conflict->ancestor) {
+ if ((error = git_blob_lookup(&ancestor_blob, repo, &conflict->ancestor->id)) < 0)
+ goto done;
+
+ conflict->binary = git_blob_is_binary(ancestor_blob);
+ }
+
+ if (!conflict->binary && conflict->ours) {
+ if ((error = git_blob_lookup(&our_blob, repo, &conflict->ours->id)) < 0)
+ goto done;
+
+ conflict->binary = git_blob_is_binary(our_blob);
+ }
+
+ if (!conflict->binary && conflict->theirs) {
+ if ((error = git_blob_lookup(&their_blob, repo, &conflict->theirs->id)) < 0)
+ goto done;
+
+ conflict->binary = git_blob_is_binary(their_blob);
+ }
+
+done:
+ git_blob_free(ancestor_blob);
+ git_blob_free(our_blob);
+ git_blob_free(their_blob);
+
+ return error;
+}
+
+static int checkout_conflicts_load(checkout_data *data, git_iterator *workdir, git_vector *pathspec)
+{
+ git_index_conflict_iterator *iterator = NULL;
+ const git_index_entry *ancestor, *ours, *theirs;
+ checkout_conflictdata *conflict;
+ int error = 0;
+
+ if ((error = git_index_conflict_iterator_new(&iterator, data->index)) < 0)
+ goto done;
+
+ data->conflicts._cmp = checkout_conflictdata_cmp;
+
+ /* Collect the conflicts */
+ while ((error = git_index_conflict_next(&ancestor, &ours, &theirs, iterator)) == 0) {
+ if (!conflict_pathspec_match(data, workdir, pathspec, ancestor, ours, theirs))
+ continue;
+
+ conflict = git__calloc(1, sizeof(checkout_conflictdata));
+ GITERR_CHECK_ALLOC(conflict);
+
+ conflict->ancestor = ancestor;
+ conflict->ours = ours;
+ conflict->theirs = theirs;
+
+ if ((error = checkout_conflict_detect_submodule(conflict)) < 0 ||
+ (error = checkout_conflict_detect_binary(data->repo, conflict)) < 0)
+ {
+ git__free(conflict);
+ goto done;
+ }
+
+ git_vector_insert(&data->conflicts, conflict);
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+done:
+ git_index_conflict_iterator_free(iterator);
+
+ return error;
+}
+
+GIT_INLINE(int) checkout_conflicts_cmp_entry(
+ const char *path,
+ const git_index_entry *entry)
+{
+ return strcmp((const char *)path, entry->path);
+}
+
+static int checkout_conflicts_cmp_ancestor(const void *p, const void *c)
+{
+ const char *path = p;
+ const checkout_conflictdata *conflict = c;
+
+ if (!conflict->ancestor)
+ return 1;
+
+ return checkout_conflicts_cmp_entry(path, conflict->ancestor);
+}
+
+static checkout_conflictdata *checkout_conflicts_search_ancestor(
+ checkout_data *data,
+ const char *path)
+{
+ size_t pos;
+
+ if (git_vector_bsearch2(&pos, &data->conflicts, checkout_conflicts_cmp_ancestor, path) < 0)
+ return NULL;
+
+ return git_vector_get(&data->conflicts, pos);
+}
+
+static checkout_conflictdata *checkout_conflicts_search_branch(
+ checkout_data *data,
+ const char *path)
+{
+ checkout_conflictdata *conflict;
+ size_t i;
+
+ git_vector_foreach(&data->conflicts, i, conflict) {
+ int cmp = -1;
+
+ if (conflict->ancestor)
+ break;
+
+ if (conflict->ours)
+ cmp = checkout_conflicts_cmp_entry(path, conflict->ours);
+ else if (conflict->theirs)
+ cmp = checkout_conflicts_cmp_entry(path, conflict->theirs);
+
+ if (cmp == 0)
+ return conflict;
+ }
+
+ return NULL;
+}
+
+static int checkout_conflicts_load_byname_entry(
+ checkout_conflictdata **ancestor_out,
+ checkout_conflictdata **ours_out,
+ checkout_conflictdata **theirs_out,
+ checkout_data *data,
+ const git_index_name_entry *name_entry)
+{
+ checkout_conflictdata *ancestor, *ours = NULL, *theirs = NULL;
+ int error = 0;
+
+ *ancestor_out = NULL;
+ *ours_out = NULL;
+ *theirs_out = NULL;
+
+ if (!name_entry->ancestor) {
+ giterr_set(GITERR_INDEX, "A NAME entry exists without an ancestor");
+ error = -1;
+ goto done;
+ }
+
+ if (!name_entry->ours && !name_entry->theirs) {
+ giterr_set(GITERR_INDEX, "A NAME entry exists without an ours or theirs");
+ error = -1;
+ goto done;
+ }
+
+ if ((ancestor = checkout_conflicts_search_ancestor(data,
+ name_entry->ancestor)) == NULL) {
+ giterr_set(GITERR_INDEX,
+ "A NAME entry referenced ancestor entry '%s' which does not exist in the main index",
+ name_entry->ancestor);
+ error = -1;
+ goto done;
+ }
+
+ if (name_entry->ours) {
+ if (strcmp(name_entry->ancestor, name_entry->ours) == 0)
+ ours = ancestor;
+ else if ((ours = checkout_conflicts_search_branch(data, name_entry->ours)) == NULL ||
+ ours->ours == NULL) {
+ giterr_set(GITERR_INDEX,
+ "A NAME entry referenced our entry '%s' which does not exist in the main index",
+ name_entry->ours);
+ error = -1;
+ goto done;
+ }
+ }
+
+ if (name_entry->theirs) {
+ if (strcmp(name_entry->ancestor, name_entry->theirs) == 0)
+ theirs = ancestor;
+ else if (name_entry->ours && strcmp(name_entry->ours, name_entry->theirs) == 0)
+ theirs = ours;
+ else if ((theirs = checkout_conflicts_search_branch(data, name_entry->theirs)) == NULL ||
+ theirs->theirs == NULL) {
+ giterr_set(GITERR_INDEX,
+ "A NAME entry referenced their entry '%s' which does not exist in the main index",
+ name_entry->theirs);
+ error = -1;
+ goto done;
+ }
+ }
+
+ *ancestor_out = ancestor;
+ *ours_out = ours;
+ *theirs_out = theirs;
+
+done:
+ return error;
+}
+
+static int checkout_conflicts_coalesce_renames(
+ checkout_data *data)
+{
+ const git_index_name_entry *name_entry;
+ checkout_conflictdata *ancestor_conflict, *our_conflict, *their_conflict;
+ size_t i, names;
+ int error = 0;
+
+ /* Juggle entries based on renames */
+ names = git_index_name_entrycount(data->index);
+
+ for (i = 0; i < names; i++) {
+ name_entry = git_index_name_get_byindex(data->index, i);
+
+ if ((error = checkout_conflicts_load_byname_entry(
+ &ancestor_conflict, &our_conflict, &their_conflict,
+ data, name_entry)) < 0)
+ goto done;
+
+ if (our_conflict && our_conflict != ancestor_conflict) {
+ ancestor_conflict->ours = our_conflict->ours;
+ our_conflict->ours = NULL;
+
+ if (our_conflict->theirs)
+ our_conflict->name_collision = 1;
+
+ if (our_conflict->name_collision)
+ ancestor_conflict->name_collision = 1;
+ }
+
+ if (their_conflict && their_conflict != ancestor_conflict) {
+ ancestor_conflict->theirs = their_conflict->theirs;
+ their_conflict->theirs = NULL;
+
+ if (their_conflict->ours)
+ their_conflict->name_collision = 1;
+
+ if (their_conflict->name_collision)
+ ancestor_conflict->name_collision = 1;
+ }
+
+ if (our_conflict && our_conflict != ancestor_conflict &&
+ their_conflict && their_conflict != ancestor_conflict)
+ ancestor_conflict->one_to_two = 1;
+ }
+
+ git_vector_remove_matching(
+ &data->conflicts, checkout_conflictdata_empty, NULL);
+
+done:
+ return error;
+}
+
+static int checkout_conflicts_mark_directoryfile(
+ checkout_data *data)
+{
+ checkout_conflictdata *conflict;
+ const git_index_entry *entry;
+ size_t i, j, len;
+ const char *path;
+ int prefixed, error = 0;
+
+ len = git_index_entrycount(data->index);
+
+ /* Find d/f conflicts */
+ git_vector_foreach(&data->conflicts, i, conflict) {
+ if ((conflict->ours && conflict->theirs) ||
+ (!conflict->ours && !conflict->theirs))
+ continue;
+
+ path = conflict->ours ?
+ conflict->ours->path : conflict->theirs->path;
+
+ if ((error = git_index_find(&j, data->index, path)) < 0) {
+ if (error == GIT_ENOTFOUND)
+ giterr_set(GITERR_INDEX,
+ "Index inconsistency, could not find entry for expected conflict '%s'", path);
+
+ goto done;
+ }
+
+ for (; j < len; j++) {
+ if ((entry = git_index_get_byindex(data->index, j)) == NULL) {
+ giterr_set(GITERR_INDEX,
+ "Index inconsistency, truncated index while loading expected conflict '%s'", path);
+ error = -1;
+ goto done;
+ }
+
+ prefixed = git_path_equal_or_prefixed(path, entry->path);
+
+ if (prefixed == GIT_PATH_EQUAL)
+ continue;
+
+ if (prefixed == GIT_PATH_PREFIX)
+ conflict->directoryfile = 1;
+
+ break;
+ }
+ }
+
+done:
+ return error;
+}
+
+static int checkout_get_conflicts(
+ checkout_data *data,
+ git_iterator *workdir,
+ git_vector *pathspec)
+{
+ int error = 0;
+
+ if (data->strategy & GIT_CHECKOUT_SKIP_UNMERGED)
+ return 0;
+
+ if ((error = checkout_conflicts_load(data, workdir, pathspec)) < 0 ||
+ (error = checkout_conflicts_coalesce_renames(data)) < 0 ||
+ (error = checkout_conflicts_mark_directoryfile(data)) < 0)
+ goto done;
+
+done:
+ return error;
+}
+
+static int checkout_get_actions(
+ uint32_t **actions_ptr,
+ size_t **counts_ptr,
+ checkout_data *data,
+ git_iterator *workdir)
+{
+ int error = 0, act;
+ const git_index_entry *wditem;
+ git_vector pathspec = GIT_VECTOR_INIT, *deltas;
+ git_pool pathpool = GIT_POOL_INIT_STRINGPOOL;
+ git_diff_delta *delta;
+ size_t i, *counts = NULL;
+ uint32_t *actions = NULL;
+
+ if (data->opts.paths.count > 0 &&
+ git_pathspec__vinit(&pathspec, &data->opts.paths, &pathpool) < 0)
+ return -1;
+
+ if ((error = git_iterator_current(&wditem, workdir)) < 0 &&
+ error != GIT_ITEROVER)
+ goto fail;
+
+ deltas = &data->diff->deltas;
+
+ *counts_ptr = counts = git__calloc(CHECKOUT_ACTION__MAX+1, sizeof(size_t));
+ *actions_ptr = actions = git__calloc(
+ deltas->length ? deltas->length : 1, sizeof(uint32_t));
+ if (!counts || !actions) {
+ error = -1;
+ goto fail;
+ }
+
+ git_vector_foreach(deltas, i, delta) {
+ error = checkout_action(&act, data, delta, workdir, &wditem, &pathspec);
+ if (error != 0)
+ goto fail;
+
+ actions[i] = act;
+
+ if (act & CHECKOUT_ACTION__REMOVE)
+ counts[CHECKOUT_ACTION__REMOVE]++;
+ if (act & CHECKOUT_ACTION__UPDATE_BLOB)
+ counts[CHECKOUT_ACTION__UPDATE_BLOB]++;
+ if (act & CHECKOUT_ACTION__UPDATE_SUBMODULE)
+ counts[CHECKOUT_ACTION__UPDATE_SUBMODULE]++;
+ if (act & CHECKOUT_ACTION__CONFLICT)
+ counts[CHECKOUT_ACTION__CONFLICT]++;
+ }
+
+ error = checkout_remaining_wd_items(data, workdir, wditem, &pathspec);
+ if (error)
+ goto fail;
+
+ counts[CHECKOUT_ACTION__REMOVE] += data->removes.length;
+
+ if (counts[CHECKOUT_ACTION__CONFLICT] > 0 &&
+ (data->strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) == 0)
+ {
+ giterr_set(GITERR_CHECKOUT, "%d %s checkout",
+ (int)counts[CHECKOUT_ACTION__CONFLICT],
+ counts[CHECKOUT_ACTION__CONFLICT] == 1 ?
+ "conflict prevents" : "conflicts prevent");
+ error = GIT_EMERGECONFLICT;
+ goto fail;
+ }
+
+
+ if ((error = checkout_get_conflicts(data, workdir, &pathspec)) < 0)
+ goto fail;
+
+ counts[CHECKOUT_ACTION__UPDATE_CONFLICT] = git_vector_length(&data->conflicts);
+
+ git_pathspec__vfree(&pathspec);
+ git_pool_clear(&pathpool);
+
+ return 0;
+
+fail:
+ *counts_ptr = NULL;
+ git__free(counts);
+ *actions_ptr = NULL;
+ git__free(actions);
+
+ git_pathspec__vfree(&pathspec);
+ git_pool_clear(&pathpool);
+
+ return error;
+}
+
+static int buffer_to_file(
+ struct stat *st,
+ git_buf *buf,
+ const char *path,
+ mode_t dir_mode,
+ int file_open_flags,
+ mode_t file_mode)
+{
+ int error;
+
+ if ((error = git_futils_mkpath2file(path, dir_mode)) < 0)
+ return error;
+
+ if ((error = git_futils_writebuffer(
+ buf, path, file_open_flags, file_mode)) < 0)
+ return error;
+
+ if (st != NULL && (error = p_stat(path, st)) < 0)
+ giterr_set(GITERR_OS, "Error statting '%s'", path);
+
+ else if (GIT_PERMS_IS_EXEC(file_mode) &&
+ (error = p_chmod(path, file_mode)) < 0)
+ giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path);
+
+ return error;
+}
+
+static int blob_content_to_file(
+ struct stat *st,
+ git_blob *blob,
+ const char *path,
+ const char * hint_path,
+ mode_t entry_filemode,
+ git_checkout_options *opts)
+{
+ int error = 0;
+ mode_t file_mode = opts->file_mode ? opts->file_mode : entry_filemode;
+ git_buf out = GIT_BUF_INIT;
+ git_filter_list *fl = NULL;
+
+ if (hint_path == NULL)
+ hint_path = path;
+
+ if (!opts->disable_filters)
+ error = git_filter_list_load(
+ &fl, git_blob_owner(blob), blob, hint_path,
+ GIT_FILTER_TO_WORKTREE, GIT_FILTER_OPT_DEFAULT);
+
+ if (!error)
+ error = git_filter_list_apply_to_blob(&out, fl, blob);
+
+ git_filter_list_free(fl);
+
+ if (!error) {
+ error = buffer_to_file(
+ st, &out, path, opts->dir_mode, opts->file_open_flags, file_mode);
+
+ st->st_mode = entry_filemode;
+
+ git_buf_free(&out);
+ }
+
+ return error;
+}
+
+static int blob_content_to_link(
+ struct stat *st,
+ git_blob *blob,
+ const char *path,
+ mode_t dir_mode,
+ int can_symlink)
+{
+ git_buf linktarget = GIT_BUF_INIT;
+ int error;
+
+ if ((error = git_futils_mkpath2file(path, dir_mode)) < 0)
+ return error;
+
+ if ((error = git_blob__getbuf(&linktarget, blob)) < 0)
+ return error;
+
+ if (can_symlink) {
+ if ((error = p_symlink(git_buf_cstr(&linktarget), path)) < 0)
+ giterr_set(GITERR_OS, "Could not create symlink %s\n", path);
+ } else {
+ error = git_futils_fake_symlink(git_buf_cstr(&linktarget), path);
+ }
+
+ if (!error) {
+ if ((error = p_lstat(path, st)) < 0)
+ giterr_set(GITERR_CHECKOUT, "Could not stat symlink %s", path);
+
+ st->st_mode = GIT_FILEMODE_LINK;
+ }
+
+ git_buf_free(&linktarget);
+
+ return error;
+}
+
+static int checkout_update_index(
+ checkout_data *data,
+ const git_diff_file *file,
+ struct stat *st)
+{
+ git_index_entry entry;
+
+ if (!data->index)
+ return 0;
+
+ memset(&entry, 0, sizeof(entry));
+ entry.path = (char *)file->path; /* cast to prevent warning */
+ git_index_entry__init_from_stat(&entry, st, true);
+ git_oid_cpy(&entry.id, &file->id);
+
+ return git_index_add(data->index, &entry);
+}
+
+static int checkout_submodule_update_index(
+ checkout_data *data,
+ const git_diff_file *file)
+{
+ struct stat st;
+
+ /* update the index unless prevented */
+ if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) != 0)
+ return 0;
+
+ git_buf_truncate(&data->path, data->workdir_len);
+ if (git_buf_puts(&data->path, file->path) < 0)
+ return -1;
+
+ if (p_stat(git_buf_cstr(&data->path), &st) < 0) {
+ giterr_set(
+ GITERR_CHECKOUT, "Could not stat submodule %s\n", file->path);
+ return GIT_ENOTFOUND;
+ }
+
+ st.st_mode = GIT_FILEMODE_COMMIT;
+
+ return checkout_update_index(data, file, &st);
+}
+
+static int checkout_submodule(
+ checkout_data *data,
+ const git_diff_file *file)
+{
+ int error = 0;
+
+ /* Until submodules are supported, UPDATE_ONLY means do nothing here */
+ if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)
+ return 0;
+
+ if ((error = git_futils_mkdir(
+ file->path, data->opts.target_directory,
+ data->opts.dir_mode, GIT_MKDIR_PATH)) < 0)
+ return error;
+
+ if ((error = git_submodule_lookup(NULL, data->repo, file->path)) < 0) {
+ /* I've observed repos with submodules in the tree that do not
+ * have a .gitmodules - core Git just makes an empty directory
+ */
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ return checkout_submodule_update_index(data, file);
+ }
+
+ return error;
+ }
+
+ /* TODO: Support checkout_strategy options. Two circumstances:
+ * 1 - submodule already checked out, but we need to move the HEAD
+ * to the new OID, or
+ * 2 - submodule not checked out and we should recursively check it out
+ *
+ * Checkout will not execute a pull on the submodule, but a clone
+ * command should probably be able to. Do we need a submodule callback?
+ */
+
+ return checkout_submodule_update_index(data, file);
+}
+
+static void report_progress(
+ checkout_data *data,
+ const char *path)
+{
+ if (data->opts.progress_cb)
+ data->opts.progress_cb(
+ path, data->completed_steps, data->total_steps,
+ data->opts.progress_payload);
+}
+
+static int checkout_safe_for_update_only(const char *path, mode_t expected_mode)
+{
+ struct stat st;
+
+ if (p_lstat(path, &st) < 0) {
+ /* if doesn't exist, then no error and no update */
+ if (errno == ENOENT || errno == ENOTDIR)
+ return 0;
+
+ /* otherwise, stat error and no update */
+ giterr_set(GITERR_OS, "Failed to stat file '%s'", path);
+ return -1;
+ }
+
+ /* only safe for update if this is the same type of file */
+ if ((st.st_mode & ~0777) == (expected_mode & ~0777))
+ return 1;
+
+ return 0;
+}
+
+static int checkout_write_content(
+ checkout_data *data,
+ const git_oid *oid,
+ const char *full_path,
+ const char *hint_path,
+ unsigned int mode,
+ struct stat *st)
+{
+ int error = 0;
+ git_blob *blob;
+
+ if ((error = git_blob_lookup(&blob, data->repo, oid)) < 0)
+ return error;
+
+ if (S_ISLNK(mode))
+ error = blob_content_to_link(
+ st, blob, full_path, data->opts.dir_mode, data->can_symlink);
+ else
+ error = blob_content_to_file(
+ st, blob, full_path, hint_path, mode, &data->opts);
+
+ git_blob_free(blob);
+
+ /* if we try to create the blob and an existing directory blocks it from
+ * being written, then there must have been a typechange conflict in a
+ * parent directory - suppress the error and try to continue.
+ */
+ if ((data->strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) != 0 &&
+ (error == GIT_ENOTFOUND || error == GIT_EEXISTS))
+ {
+ giterr_clear();
+ error = 0;
+ }
+
+ return error;
+}
+
+static int checkout_blob(
+ checkout_data *data,
+ const git_diff_file *file)
+{
+ int error = 0;
+ struct stat st;
+
+ git_buf_truncate(&data->path, data->workdir_len);
+ if (git_buf_puts(&data->path, file->path) < 0)
+ return -1;
+
+ if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) {
+ int rval = checkout_safe_for_update_only(
+ git_buf_cstr(&data->path), file->mode);
+ if (rval <= 0)
+ return rval;
+ }
+
+ error = checkout_write_content(
+ data, &file->id, git_buf_cstr(&data->path), NULL, file->mode, &st);
+
+ /* update the index unless prevented */
+ if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0)
+ error = checkout_update_index(data, file, &st);
+
+ /* update the submodule data if this was a new .gitmodules file */
+ if (!error && strcmp(file->path, ".gitmodules") == 0)
+ data->reload_submodules = true;
+
+ return error;
+}
+
+static int checkout_remove_the_old(
+ unsigned int *actions,
+ checkout_data *data)
+{
+ int error = 0;
+ git_diff_delta *delta;
+ const char *str;
+ size_t i;
+ const char *workdir = git_buf_cstr(&data->path);
+ uint32_t flg = GIT_RMDIR_EMPTY_PARENTS |
+ GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS;
+
+ if (data->opts.checkout_strategy & GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES)
+ flg |= GIT_RMDIR_SKIP_NONEMPTY;
+
+ git_buf_truncate(&data->path, data->workdir_len);
+
+ git_vector_foreach(&data->diff->deltas, i, delta) {
+ if (actions[i] & CHECKOUT_ACTION__REMOVE) {
+ error = git_futils_rmdir_r(delta->old_file.path, workdir, flg);
+ if (error < 0)
+ return error;
+
+ data->completed_steps++;
+ report_progress(data, delta->old_file.path);
+
+ if ((actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) == 0 &&
+ (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 &&
+ data->index != NULL)
+ {
+ (void)git_index_remove(data->index, delta->old_file.path, 0);
+ }
+ }
+ }
+
+ git_vector_foreach(&data->removes, i, str) {
+ error = git_futils_rmdir_r(str, workdir, flg);
+ if (error < 0)
+ return error;
+
+ data->completed_steps++;
+ report_progress(data, str);
+
+ if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 &&
+ data->index != NULL)
+ {
+ if (str[strlen(str) - 1] == '/')
+ (void)git_index_remove_directory(data->index, str, 0);
+ else
+ (void)git_index_remove(data->index, str, 0);
+ }
+ }
+
+ return 0;
+}
+
+static int checkout_deferred_remove(git_repository *repo, const char *path)
+{
+#if 0
+ int error = git_futils_rmdir_r(
+ path, data->opts.target_directory, GIT_RMDIR_EMPTY_PARENTS);
+
+ if (error == GIT_ENOTFOUND) {
+ error = 0;
+ giterr_clear();
+ }
+
+ return error;
+#else
+ GIT_UNUSED(repo);
+ GIT_UNUSED(path);
+ assert(false);
+ return 0;
+#endif
+}
+
+static int checkout_create_the_new(
+ unsigned int *actions,
+ checkout_data *data)
+{
+ int error = 0;
+ git_diff_delta *delta;
+ size_t i;
+
+ git_vector_foreach(&data->diff->deltas, i, delta) {
+ if (actions[i] & CHECKOUT_ACTION__DEFER_REMOVE) {
+ /* this had a blocker directory that should only be removed iff
+ * all of the contents of the directory were safely removed
+ */
+ if ((error = checkout_deferred_remove(
+ data->repo, delta->old_file.path)) < 0)
+ return error;
+ }
+
+ if (actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) {
+ error = checkout_blob(data, &delta->new_file);
+ if (error < 0)
+ return error;
+
+ data->completed_steps++;
+ report_progress(data, delta->new_file.path);
+ }
+ }
+
+ return 0;
+}
+
+static int checkout_create_submodules(
+ unsigned int *actions,
+ checkout_data *data)
+{
+ int error = 0;
+ git_diff_delta *delta;
+ size_t i;
+
+ /* initial reload of submodules if .gitmodules was changed */
+ if (data->reload_submodules &&
+ (error = git_submodule_reload_all(data->repo, 1)) < 0)
+ return error;
+
+ git_vector_foreach(&data->diff->deltas, i, delta) {
+ if (actions[i] & CHECKOUT_ACTION__DEFER_REMOVE) {
+ /* this has a blocker directory that should only be removed iff
+ * all of the contents of the directory were safely removed
+ */
+ if ((error = checkout_deferred_remove(
+ data->repo, delta->old_file.path)) < 0)
+ return error;
+ }
+
+ if (actions[i] & CHECKOUT_ACTION__UPDATE_SUBMODULE) {
+ int error = checkout_submodule(data, &delta->new_file);
+ if (error < 0)
+ return error;
+
+ data->completed_steps++;
+ report_progress(data, delta->new_file.path);
+ }
+ }
+
+ /* final reload once submodules have been updated */
+ return git_submodule_reload_all(data->repo, 1);
+}
+
+static int checkout_lookup_head_tree(git_tree **out, git_repository *repo)
+{
+ int error = 0;
+ git_reference *ref = NULL;
+ git_object *head;
+
+ if (!(error = git_repository_head(&ref, repo)) &&
+ !(error = git_reference_peel(&head, ref, GIT_OBJ_TREE)))
+ *out = (git_tree *)head;
+
+ git_reference_free(ref);
+
+ return error;
+}
+
+
+static int conflict_entry_name(
+ git_buf *out,
+ const char *side_name,
+ const char *filename)
+{
+ if (git_buf_puts(out, side_name) < 0 ||
+ git_buf_putc(out, ':') < 0 ||
+ git_buf_puts(out, filename) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int checkout_path_suffixed(git_buf *path, const char *suffix)
+{
+ size_t path_len;
+ int i = 0, error = 0;
+
+ if ((error = git_buf_putc(path, '~')) < 0 || (error = git_buf_puts(path, suffix)) < 0)
+ return -1;
+
+ path_len = git_buf_len(path);
+
+ while (git_path_exists(git_buf_cstr(path)) && i < INT_MAX) {
+ git_buf_truncate(path, path_len);
+
+ if ((error = git_buf_putc(path, '_')) < 0 ||
+ (error = git_buf_printf(path, "%d", i)) < 0)
+ return error;
+
+ i++;
+ }
+
+ if (i == INT_MAX) {
+ git_buf_truncate(path, path_len);
+
+ giterr_set(GITERR_CHECKOUT, "Could not write '%s': working directory file exists", path);
+ return GIT_EEXISTS;
+ }
+
+ return 0;
+}
+
+static int checkout_write_entry(
+ checkout_data *data,
+ checkout_conflictdata *conflict,
+ const git_index_entry *side)
+{
+ const char *hint_path = NULL, *suffix;
+ struct stat st;
+ int error;
+
+ assert (side == conflict->ours || side == conflict->theirs);
+
+ git_buf_truncate(&data->path, data->workdir_len);
+ if (git_buf_puts(&data->path, side->path) < 0)
+ return -1;
+
+ if ((conflict->name_collision || conflict->directoryfile) &&
+ (data->strategy & GIT_CHECKOUT_USE_OURS) == 0 &&
+ (data->strategy & GIT_CHECKOUT_USE_THEIRS) == 0) {
+
+ if (side == conflict->ours)
+ suffix = data->opts.our_label ? data->opts.our_label :
+ "ours";
+ else
+ suffix = data->opts.their_label ? data->opts.their_label :
+ "theirs";
+
+ if (checkout_path_suffixed(&data->path, suffix) < 0)
+ return -1;
+
+ hint_path = side->path;
+ }
+
+ if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 &&
+ (error = checkout_safe_for_update_only(git_buf_cstr(&data->path), side->mode)) <= 0)
+ return error;
+
+ return checkout_write_content(data,
+ &side->id, git_buf_cstr(&data->path), hint_path, side->mode, &st);
+}
+
+static int checkout_write_entries(
+ checkout_data *data,
+ checkout_conflictdata *conflict)
+{
+ int error = 0;
+
+ if ((error = checkout_write_entry(data, conflict, conflict->ours)) >= 0)
+ error = checkout_write_entry(data, conflict, conflict->theirs);
+
+ return error;
+}
+
+static int checkout_merge_path(
+ git_buf *out,
+ checkout_data *data,
+ checkout_conflictdata *conflict,
+ git_merge_file_result *result)
+{
+ const char *our_label_raw, *their_label_raw, *suffix;
+ int error = 0;
+
+ if ((error = git_buf_joinpath(out, git_repository_workdir(data->repo), result->path)) < 0)
+ return error;
+
+ /* Most conflicts simply use the filename in the index */
+ if (!conflict->name_collision)
+ return 0;
+
+ /* Rename 2->1 conflicts need the branch name appended */
+ our_label_raw = data->opts.our_label ? data->opts.our_label : "ours";
+ their_label_raw = data->opts.their_label ? data->opts.their_label : "theirs";
+ suffix = strcmp(result->path, conflict->ours->path) == 0 ? our_label_raw : their_label_raw;
+
+ if ((error = checkout_path_suffixed(out, suffix)) < 0)
+ return error;
+
+ return 0;
+}
+
+static int checkout_write_merge(
+ checkout_data *data,
+ checkout_conflictdata *conflict)
+{
+ git_buf our_label = GIT_BUF_INIT, their_label = GIT_BUF_INIT,
+ path_suffixed = GIT_BUF_INIT, path_workdir = GIT_BUF_INIT;
+ git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT;
+ git_merge_file_result result = {0};
+ git_filebuf output = GIT_FILEBUF_INIT;
+ int error = 0;
+
+ if (data->opts.checkout_strategy & GIT_CHECKOUT_CONFLICT_STYLE_DIFF3)
+ opts.flags |= GIT_MERGE_FILE_STYLE_DIFF3;
+
+ opts.ancestor_label = data->opts.ancestor_label ?
+ data->opts.ancestor_label : "ancestor";
+ opts.our_label = data->opts.our_label ?
+ data->opts.our_label : "ours";
+ opts.their_label = data->opts.their_label ?
+ data->opts.their_label : "theirs";
+
+ /* If all the paths are identical, decorate the diff3 file with the branch
+ * names. Otherwise, append branch_name:path.
+ */
+ if (conflict->ours && conflict->theirs &&
+ strcmp(conflict->ours->path, conflict->theirs->path) != 0) {
+
+ if ((error = conflict_entry_name(
+ &our_label, opts.our_label, conflict->ours->path)) < 0 ||
+ (error = conflict_entry_name(
+ &their_label, opts.their_label, conflict->theirs->path)) < 0)
+ goto done;
+
+ opts.our_label = git_buf_cstr(&our_label);
+ opts.their_label = git_buf_cstr(&their_label);
+ }
+
+ if ((error = git_merge_file_from_index(&result, data->repo,
+ conflict->ancestor, conflict->ours, conflict->theirs, &opts)) < 0)
+ goto done;
+
+ if (result.path == NULL || result.mode == 0) {
+ giterr_set(GITERR_CHECKOUT, "Could not merge contents of file");
+ error = GIT_EMERGECONFLICT;
+ goto done;
+ }
+
+ if ((error = checkout_merge_path(&path_workdir, data, conflict, &result)) < 0)
+ goto done;
+
+ if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 &&
+ (error = checkout_safe_for_update_only(git_buf_cstr(&path_workdir), result.mode)) <= 0)
+ goto done;
+
+ if ((error = git_futils_mkpath2file(path_workdir.ptr, 0755)) < 0 ||
+ (error = git_filebuf_open(&output, path_workdir.ptr, GIT_FILEBUF_DO_NOT_BUFFER, result.mode)) < 0 ||
+ (error = git_filebuf_write(&output, result.ptr, result.len)) < 0 ||
+ (error = git_filebuf_commit(&output)) < 0)
+ goto done;
+
+done:
+ git_buf_free(&our_label);
+ git_buf_free(&their_label);
+
+ git_merge_file_result_free(&result);
+ git_buf_free(&path_workdir);
+ git_buf_free(&path_suffixed);
+
+ return error;
+}
+
+static int checkout_create_conflicts(checkout_data *data)
+{
+ checkout_conflictdata *conflict;
+ size_t i;
+ int error = 0;
+
+ git_vector_foreach(&data->conflicts, i, conflict) {
+
+ /* Both deleted: nothing to do */
+ if (conflict->ours == NULL && conflict->theirs == NULL)
+ error = 0;
+
+ else if ((data->strategy & GIT_CHECKOUT_USE_OURS) &&
+ conflict->ours)
+ error = checkout_write_entry(data, conflict, conflict->ours);
+ else if ((data->strategy & GIT_CHECKOUT_USE_THEIRS) &&
+ conflict->theirs)
+ error = checkout_write_entry(data, conflict, conflict->theirs);
+
+ /* Ignore the other side of name collisions. */
+ else if ((data->strategy & GIT_CHECKOUT_USE_OURS) &&
+ !conflict->ours && conflict->name_collision)
+ error = 0;
+ else if ((data->strategy & GIT_CHECKOUT_USE_THEIRS) &&
+ !conflict->theirs && conflict->name_collision)
+ error = 0;
+
+ /* For modify/delete, name collisions and d/f conflicts, write
+ * the file (potentially with the name mangled.
+ */
+ else if (conflict->ours != NULL && conflict->theirs == NULL)
+ error = checkout_write_entry(data, conflict, conflict->ours);
+ else if (conflict->ours == NULL && conflict->theirs != NULL)
+ error = checkout_write_entry(data, conflict, conflict->theirs);
+
+ /* Add/add conflicts and rename 1->2 conflicts, write the
+ * ours/theirs sides (potentially name mangled).
+ */
+ else if (conflict->one_to_two)
+ error = checkout_write_entries(data, conflict);
+
+ /* If all sides are links, write the ours side */
+ else if (S_ISLNK(conflict->ours->mode) &&
+ S_ISLNK(conflict->theirs->mode))
+ error = checkout_write_entry(data, conflict, conflict->ours);
+ /* Link/file conflicts, write the file side */
+ else if (S_ISLNK(conflict->ours->mode))
+ error = checkout_write_entry(data, conflict, conflict->theirs);
+ else if (S_ISLNK(conflict->theirs->mode))
+ error = checkout_write_entry(data, conflict, conflict->ours);
+
+ /* If any side is a gitlink, do nothing. */
+ else if (conflict->submodule)
+ error = 0;
+
+ /* If any side is binary, write the ours side */
+ else if (conflict->binary)
+ error = checkout_write_entry(data, conflict, conflict->ours);
+
+ else if (!error)
+ error = checkout_write_merge(data, conflict);
+
+ if (error)
+ break;
+
+ data->completed_steps++;
+ report_progress(data,
+ conflict->ours ? conflict->ours->path :
+ (conflict->theirs ? conflict->theirs->path : conflict->ancestor->path));
+ }
+
+ return error;
+}
+
+
+static void checkout_data_clear(checkout_data *data)
+{
+ if (data->opts_free_baseline) {
+ git_tree_free(data->opts.baseline);
+ data->opts.baseline = NULL;
+ }
+
+ git_vector_free(&data->removes);
+ git_pool_clear(&data->pool);
+
+ git_vector_free_deep(&data->conflicts);
+
+ git__free(data->pfx);
+ data->pfx = NULL;
+
+ git_buf_free(&data->path);
+ git_buf_free(&data->tmp);
+
+ git_index_free(data->index);
+ data->index = NULL;
+}
+
+static int checkout_data_init(
+ checkout_data *data,
+ git_iterator *target,
+ const git_checkout_options *proposed)
+{
+ int error = 0;
+ git_repository *repo = git_iterator_owner(target);
+
+ memset(data, 0, sizeof(*data));
+
+ if (!repo) {
+ giterr_set(GITERR_CHECKOUT, "Cannot checkout nothing");
+ return -1;
+ }
+
+ if ((!proposed || !proposed->target_directory) &&
+ (error = git_repository__ensure_not_bare(repo, "checkout")) < 0)
+ return error;
+
+ data->repo = repo;
+
+ GITERR_CHECK_VERSION(
+ proposed, GIT_CHECKOUT_OPTIONS_VERSION, "git_checkout_options");
+
+ if (!proposed)
+ GIT_INIT_STRUCTURE(&data->opts, GIT_CHECKOUT_OPTIONS_VERSION);
+ else
+ memmove(&data->opts, proposed, sizeof(git_checkout_options));
+
+ if (!data->opts.target_directory)
+ data->opts.target_directory = git_repository_workdir(repo);
+ else if (!git_path_isdir(data->opts.target_directory) &&
+ (error = git_futils_mkdir(data->opts.target_directory, NULL,
+ GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR)) < 0)
+ goto cleanup;
+
+ /* refresh config and index content unless NO_REFRESH is given */
+ if ((data->opts.checkout_strategy & GIT_CHECKOUT_NO_REFRESH) == 0) {
+ git_config *cfg;
+
+ if ((error = git_repository_config__weakptr(&cfg, repo)) < 0 ||
+ (error = git_config_refresh(cfg)) < 0)
+ goto cleanup;
+
+ /* if we are checking out the index, don't reload,
+ * otherwise get index and force reload
+ */
+ if ((data->index = git_iterator_get_index(target)) != NULL) {
+ GIT_REFCOUNT_INC(data->index);
+ } else {
+ /* otherwise, grab and reload the index */
+ if ((error = git_repository_index(&data->index, data->repo)) < 0 ||
+ (error = git_index_read(data->index, true)) < 0)
+ goto cleanup;
+
+ /* cannot checkout if unresolved conflicts exist */
+ if ((data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) == 0 &&
+ git_index_has_conflicts(data->index)) {
+ error = GIT_EMERGECONFLICT;
+ giterr_set(GITERR_CHECKOUT,
+ "unresolved conflicts exist in the index");
+ goto cleanup;
+ }
+
+ /* clean conflict data when doing a tree or commit checkout */
+ git_index_name_clear(data->index);
+ git_index_reuc_clear(data->index);
+ }
+ }
+
+ /* if you are forcing, definitely allow safe updates */
+ if ((data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) != 0)
+ data->opts.checkout_strategy |= GIT_CHECKOUT_SAFE_CREATE;
+ if ((data->opts.checkout_strategy & GIT_CHECKOUT_SAFE_CREATE) != 0)
+ data->opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
+
+ data->strategy = data->opts.checkout_strategy;
+
+ /* opts->disable_filters is false by default */
+
+ if (!data->opts.dir_mode)
+ data->opts.dir_mode = GIT_DIR_MODE;
+
+ if (!data->opts.file_open_flags)
+ data->opts.file_open_flags = O_CREAT | O_TRUNC | O_WRONLY;
+
+ data->pfx = git_pathspec_prefix(&data->opts.paths);
+
+ if ((error = git_repository__cvar(
+ &data->can_symlink, repo, GIT_CVAR_SYMLINKS)) < 0)
+ goto cleanup;
+
+ if (!data->opts.baseline) {
+ data->opts_free_baseline = true;
+
+ error = checkout_lookup_head_tree(&data->opts.baseline, repo);
+
+ if (error == GIT_EUNBORNBRANCH) {
+ error = 0;
+ giterr_clear();
+ }
+
+ if (error < 0)
+ goto cleanup;
+ }
+
+ if ((data->opts.checkout_strategy &
+ (GIT_CHECKOUT_CONFLICT_STYLE_MERGE | GIT_CHECKOUT_CONFLICT_STYLE_DIFF3)) == 0) {
+ const char *conflict_style;
+ git_config *cfg = NULL;
+
+ if ((error = git_repository_config__weakptr(&cfg, repo)) < 0 ||
+ (error = git_config_get_string(&conflict_style, cfg, "merge.conflictstyle")) < 0 ||
+ error == GIT_ENOTFOUND)
+ ;
+ else if (error)
+ goto cleanup;
+ else if (strcmp(conflict_style, "merge") == 0)
+ data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_MERGE;
+ else if (strcmp(conflict_style, "diff3") == 0)
+ data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_DIFF3;
+ else {
+ giterr_set(GITERR_CHECKOUT, "unknown style '%s' given for 'merge.conflictstyle'",
+ conflict_style);
+ error = -1;
+ goto cleanup;
+ }
+ }
+
+ if ((error = git_vector_init(&data->removes, 0, git__strcmp_cb)) < 0 ||
+ (error = git_vector_init(&data->conflicts, 0, NULL)) < 0 ||
+ (error = git_pool_init(&data->pool, 1, 0)) < 0 ||
+ (error = git_buf_puts(&data->path, data->opts.target_directory)) < 0 ||
+ (error = git_path_to_dir(&data->path)) < 0)
+ goto cleanup;
+
+ data->workdir_len = git_buf_len(&data->path);
+
+cleanup:
+ if (error < 0)
+ checkout_data_clear(data);
+
+ return error;
+}
+
+int git_checkout_iterator(
+ git_iterator *target,
+ const git_checkout_options *opts)
+{
+ int error = 0;
+ git_iterator *baseline = NULL, *workdir = NULL;
+ checkout_data data = {0};
+ git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
+ uint32_t *actions = NULL;
+ size_t *counts = NULL;
+ git_iterator_flag_t iterflags = 0;
+
+ /* initialize structures and options */
+ error = checkout_data_init(&data, target, opts);
+ if (error < 0)
+ return error;
+
+ diff_opts.flags =
+ GIT_DIFF_INCLUDE_UNMODIFIED |
+ GIT_DIFF_INCLUDE_UNTRACKED |
+ GIT_DIFF_RECURSE_UNTRACKED_DIRS | /* needed to match baseline */
+ GIT_DIFF_INCLUDE_IGNORED |
+ GIT_DIFF_INCLUDE_TYPECHANGE |
+ GIT_DIFF_INCLUDE_TYPECHANGE_TREES |
+ GIT_DIFF_SKIP_BINARY_CHECK;
+ if (data.opts.checkout_strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH)
+ diff_opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH;
+ if (data.opts.paths.count > 0)
+ diff_opts.pathspec = data.opts.paths;
+
+ /* set up iterators */
+
+ iterflags = git_iterator_ignore_case(target) ?
+ GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ if ((error = git_iterator_reset(target, data.pfx, data.pfx)) < 0 ||
+ (error = git_iterator_for_workdir_ext(
+ &workdir, data.repo, data.opts.target_directory,
+ iterflags | GIT_ITERATOR_DONT_AUTOEXPAND,
+ data.pfx, data.pfx)) < 0 ||
+ (error = git_iterator_for_tree(
+ &baseline, data.opts.baseline,
+ iterflags, data.pfx, data.pfx)) < 0)
+ goto cleanup;
+
+ /* Should not have case insensitivity mismatch */
+ assert(git_iterator_ignore_case(workdir) == git_iterator_ignore_case(baseline));
+
+ /* Generate baseline-to-target diff which will include an entry for
+ * every possible update that might need to be made.
+ */
+ if ((error = git_diff__from_iterators(
+ &data.diff, data.repo, baseline, target, &diff_opts)) < 0)
+ goto cleanup;
+
+ /* Loop through diff (and working directory iterator) building a list of
+ * actions to be taken, plus look for conflicts and send notifications,
+ * then loop through conflicts.
+ */
+ if ((error = checkout_get_actions(&actions, &counts, &data, workdir)) != 0)
+ goto cleanup;
+
+ data.total_steps = counts[CHECKOUT_ACTION__REMOVE] +
+ counts[CHECKOUT_ACTION__UPDATE_BLOB] +
+ counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] +
+ counts[CHECKOUT_ACTION__UPDATE_CONFLICT];
+
+ report_progress(&data, NULL); /* establish 0 baseline */
+
+ /* To deal with some order dependencies, perform remaining checkout
+ * in three passes: removes, then update blobs, then update submodules.
+ */
+ if (counts[CHECKOUT_ACTION__REMOVE] > 0 &&
+ (error = checkout_remove_the_old(actions, &data)) < 0)
+ goto cleanup;
+
+ if (counts[CHECKOUT_ACTION__UPDATE_BLOB] > 0 &&
+ (error = checkout_create_the_new(actions, &data)) < 0)
+ goto cleanup;
+
+ if (counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] > 0 &&
+ (error = checkout_create_submodules(actions, &data)) < 0)
+ goto cleanup;
+
+ if (counts[CHECKOUT_ACTION__UPDATE_CONFLICT] > 0 &&
+ (error = checkout_create_conflicts(&data)) < 0)
+ goto cleanup;
+
+ assert(data.completed_steps == data.total_steps);
+
+cleanup:
+ if (!error && data.index != NULL &&
+ (data.strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0)
+ error = git_index_write(data.index);
+
+ git_diff_free(data.diff);
+ git_iterator_free(workdir);
+ git_iterator_free(baseline);
+ git__free(actions);
+ git__free(counts);
+ checkout_data_clear(&data);
+
+ return error;
+}
+
+int git_checkout_index(
+ git_repository *repo,
+ git_index *index,
+ const git_checkout_options *opts)
+{
+ int error;
+ git_iterator *index_i;
+
+ if (!index && !repo) {
+ giterr_set(GITERR_CHECKOUT,
+ "Must provide either repository or index to checkout");
+ return -1;
+ }
+ if (index && repo && git_index_owner(index) != repo) {
+ giterr_set(GITERR_CHECKOUT,
+ "Index to checkout does not match repository");
+ return -1;
+ }
+
+ if (!repo)
+ repo = git_index_owner(index);
+
+ if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0)
+ return error;
+ GIT_REFCOUNT_INC(index);
+
+ if (!(error = git_iterator_for_index(&index_i, index, 0, NULL, NULL)))
+ error = git_checkout_iterator(index_i, opts);
+
+ git_iterator_free(index_i);
+ git_index_free(index);
+
+ return error;
+}
+
+int git_checkout_tree(
+ git_repository *repo,
+ const git_object *treeish,
+ const git_checkout_options *opts)
+{
+ int error;
+ git_tree *tree = NULL;
+ git_iterator *tree_i = NULL;
+
+ if (!treeish && !repo) {
+ giterr_set(GITERR_CHECKOUT,
+ "Must provide either repository or tree to checkout");
+ return -1;
+ }
+ if (treeish && repo && git_object_owner(treeish) != repo) {
+ giterr_set(GITERR_CHECKOUT,
+ "Object to checkout does not match repository");
+ return -1;
+ }
+
+ if (!repo)
+ repo = git_object_owner(treeish);
+
+ if (treeish) {
+ if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) {
+ giterr_set(
+ GITERR_CHECKOUT, "Provided object cannot be peeled to a tree");
+ return -1;
+ }
+ }
+ else {
+ if ((error = checkout_lookup_head_tree(&tree, repo)) < 0) {
+ if (error != GIT_EUNBORNBRANCH)
+ giterr_set(
+ GITERR_CHECKOUT,
+ "HEAD could not be peeled to a tree and no treeish given");
+ return error;
+ }
+ }
+
+ if (!(error = git_iterator_for_tree(&tree_i, tree, 0, NULL, NULL)))
+ error = git_checkout_iterator(tree_i, opts);
+
+ git_iterator_free(tree_i);
+ git_tree_free(tree);
+
+ return error;
+}
+
+int git_checkout_head(
+ git_repository *repo,
+ const git_checkout_options *opts)
+{
+ assert(repo);
+ return git_checkout_tree(repo, NULL, opts);
+}
+
+int git_checkout_init_options(git_checkout_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_checkout_options, GIT_CHECKOUT_OPTIONS_INIT);
+ return 0;
+}
diff --git a/src/checkout.h b/src/checkout.h
new file mode 100644
index 000000000..f1fe69628
--- /dev/null
+++ b/src/checkout.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_checkout_h__
+#define INCLUDE_checkout_h__
+
+#include "git2/checkout.h"
+#include "iterator.h"
+
+#define GIT_CHECKOUT__NOTIFY_CONFLICT_TREE (1u << 12)
+
+/**
+ * Update the working directory to match the target iterator. The
+ * expected baseline value can be passed in via the checkout options
+ * or else will default to the HEAD commit.
+ */
+extern int git_checkout_iterator(
+ git_iterator *target,
+ const git_checkout_options *opts);
+
+#endif
diff --git a/src/cherrypick.c b/src/cherrypick.c
new file mode 100644
index 000000000..e02348a03
--- /dev/null
+++ b/src/cherrypick.c
@@ -0,0 +1,226 @@
+/*
+* Copyright (C) the libgit2 contributors. All rights reserved.
+*
+* 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 "filebuf.h"
+#include "merge.h"
+#include "vector.h"
+
+#include "git2/types.h"
+#include "git2/merge.h"
+#include "git2/cherrypick.h"
+#include "git2/commit.h"
+#include "git2/sys/commit.h"
+
+#define GIT_CHERRY_PICK_FILE_MODE 0666
+
+static int write_cherry_pick_head(
+ git_repository *repo,
+ const char *commit_oidstr)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ int error = 0;
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_CHERRY_PICK_HEAD_FILE)) >= 0 &&
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRY_PICK_FILE_MODE)) >= 0 &&
+ (error = git_filebuf_printf(&file, "%s\n", commit_oidstr)) >= 0)
+ error = git_filebuf_commit(&file);
+
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ return error;
+}
+
+static int write_merge_msg(
+ git_repository *repo,
+ const char *commit_msg)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ int error = 0;
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 ||
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRY_PICK_FILE_MODE)) < 0 ||
+ (error = git_filebuf_printf(&file, "%s", commit_msg)) < 0)
+ goto cleanup;
+
+ error = git_filebuf_commit(&file);
+
+cleanup:
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ return error;
+}
+
+static int cherry_pick_normalize_opts(
+ git_repository *repo,
+ git_cherry_pick_options *opts,
+ const git_cherry_pick_options *given,
+ const char *their_label)
+{
+ int error = 0;
+ unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE_CREATE |
+ GIT_CHECKOUT_ALLOW_CONFLICTS;
+
+ GIT_UNUSED(repo);
+
+ if (given != NULL)
+ memcpy(opts, given, sizeof(git_cherry_pick_options));
+ else {
+ git_cherry_pick_options default_opts = GIT_CHERRY_PICK_OPTIONS_INIT;
+ memcpy(opts, &default_opts, sizeof(git_cherry_pick_options));
+ }
+
+ if (!opts->checkout_opts.checkout_strategy)
+ opts->checkout_opts.checkout_strategy = default_checkout_strategy;
+
+ if (!opts->checkout_opts.our_label)
+ opts->checkout_opts.our_label = "HEAD";
+
+ if (!opts->checkout_opts.their_label)
+ opts->checkout_opts.their_label = their_label;
+
+ return error;
+}
+
+static int cherry_pick_state_cleanup(git_repository *repo)
+{
+ const char *state_files[] = { GIT_CHERRY_PICK_HEAD_FILE, GIT_MERGE_MSG_FILE };
+
+ return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files));
+}
+
+static int cherry_pick_seterr(git_commit *commit, const char *fmt)
+{
+ char commit_oidstr[GIT_OID_HEXSZ + 1];
+
+ giterr_set(GITERR_CHERRYPICK, fmt,
+ git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit)));
+
+ return -1;
+}
+
+int git_cherry_pick_commit(
+ git_index **out,
+ git_repository *repo,
+ git_commit *cherry_pick_commit,
+ git_commit *our_commit,
+ unsigned int mainline,
+ const git_merge_options *merge_opts)
+{
+ git_commit *parent_commit = NULL;
+ git_tree *parent_tree = NULL, *our_tree = NULL, *cherry_pick_tree = NULL;
+ int parent = 0, error = 0;
+
+ assert(out && repo && cherry_pick_commit && our_commit);
+
+ if (git_commit_parentcount(cherry_pick_commit) > 1) {
+ if (!mainline)
+ return cherry_pick_seterr(cherry_pick_commit,
+ "Mainline branch is not specified but %s is a merge commit");
+
+ parent = mainline;
+ } else {
+ if (mainline)
+ return cherry_pick_seterr(cherry_pick_commit,
+ "Mainline branch specified but %s is not a merge commit");
+
+ parent = git_commit_parentcount(cherry_pick_commit);
+ }
+
+ if (parent &&
+ ((error = git_commit_parent(&parent_commit, cherry_pick_commit, (parent - 1))) < 0 ||
+ (error = git_commit_tree(&parent_tree, parent_commit)) < 0))
+ goto done;
+
+ if ((error = git_commit_tree(&cherry_pick_tree, cherry_pick_commit)) < 0 ||
+ (error = git_commit_tree(&our_tree, our_commit)) < 0)
+ goto done;
+
+ error = git_merge_trees(out, repo, parent_tree, our_tree, cherry_pick_tree, merge_opts);
+
+done:
+ git_tree_free(parent_tree);
+ git_tree_free(our_tree);
+ git_tree_free(cherry_pick_tree);
+ git_commit_free(parent_commit);
+
+ return error;
+}
+
+int git_cherry_pick(
+ git_repository *repo,
+ git_commit *commit,
+ const git_cherry_pick_options *given_opts)
+{
+ git_cherry_pick_options opts;
+ git_reference *our_ref = NULL;
+ git_commit *our_commit = NULL;
+ char commit_oidstr[GIT_OID_HEXSZ + 1];
+ const char *commit_msg, *commit_summary;
+ git_buf their_label = GIT_BUF_INIT;
+ git_index *index_new = NULL, *index_repo = NULL;
+ int error = 0;
+
+ assert(repo && commit);
+
+ GITERR_CHECK_VERSION(given_opts, GIT_CHERRY_PICK_OPTIONS_VERSION, "git_cherry_pick_options");
+
+ if ((error = git_repository__ensure_not_bare(repo, "cherry-pick")) < 0)
+ return error;
+
+ if ((commit_msg = git_commit_message(commit)) == NULL ||
+ (commit_summary = git_commit_summary(commit)) == NULL) {
+ error = -1;
+ goto on_error;
+ }
+
+ git_oid_nfmt(commit_oidstr, sizeof(commit_oidstr), git_commit_id(commit));
+
+ if ((error = write_merge_msg(repo, commit_msg)) < 0 ||
+ (error = git_buf_printf(&their_label, "%.7s... %s", commit_oidstr, commit_summary)) < 0 ||
+ (error = cherry_pick_normalize_opts(repo, &opts, given_opts, git_buf_cstr(&their_label))) < 0 ||
+ (error = write_cherry_pick_head(repo, commit_oidstr)) < 0 ||
+ (error = git_repository_head(&our_ref, repo)) < 0 ||
+ (error = git_reference_peel((git_object **)&our_commit, our_ref, GIT_OBJ_COMMIT)) < 0 ||
+ (error = git_cherry_pick_commit(&index_new, repo, commit, our_commit, opts.mainline, &opts.merge_opts)) < 0 ||
+ (error = git_merge__indexes(repo, index_new)) < 0 ||
+ (error = git_repository_index(&index_repo, repo)) < 0 ||
+ (error = git_merge__append_conflicts_to_merge_msg(repo, index_repo)) < 0 ||
+ (error = git_checkout_index(repo, index_repo, &opts.checkout_opts)) < 0)
+ goto on_error;
+
+ goto done;
+
+on_error:
+ cherry_pick_state_cleanup(repo);
+
+done:
+ git_index_free(index_new);
+ git_index_free(index_repo);
+ git_commit_free(our_commit);
+ git_reference_free(our_ref);
+ git_buf_free(&their_label);
+
+ return error;
+}
+
+int git_cherry_pick_init_options(
+ git_cherry_pick_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_cherry_pick_options, GIT_CHERRY_PICK_OPTIONS_INIT);
+ return 0;
+}
diff --git a/src/clone.c b/src/clone.c
new file mode 100644
index 000000000..6c4fb6727
--- /dev/null
+++ b/src/clone.c
@@ -0,0 +1,530 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include <assert.h>
+
+#include "git2/clone.h"
+#include "git2/remote.h"
+#include "git2/revparse.h"
+#include "git2/branch.h"
+#include "git2/config.h"
+#include "git2/checkout.h"
+#include "git2/commit.h"
+#include "git2/tree.h"
+
+#include "common.h"
+#include "remote.h"
+#include "fileops.h"
+#include "refs.h"
+#include "path.h"
+#include "repository.h"
+#include "odb.h"
+
+static int create_branch(
+ git_reference **branch,
+ git_repository *repo,
+ const git_oid *target,
+ const char *name,
+ const git_signature *signature,
+ const char *log_message)
+{
+ git_commit *head_obj = NULL;
+ git_reference *branch_ref = NULL;
+ int error;
+
+ /* Find the target commit */
+ if ((error = git_commit_lookup(&head_obj, repo, target)) < 0)
+ return error;
+
+ /* Create the new branch */
+ error = git_branch_create(&branch_ref, repo, name, head_obj, 0, signature, log_message);
+
+ git_commit_free(head_obj);
+
+ if (!error)
+ *branch = branch_ref;
+ else
+ git_reference_free(branch_ref);
+
+ return error;
+}
+
+static int setup_tracking_config(
+ git_repository *repo,
+ const char *branch_name,
+ const char *remote_name,
+ const char *merge_target)
+{
+ git_config *cfg;
+ git_buf remote_key = GIT_BUF_INIT, merge_key = GIT_BUF_INIT;
+ int error = -1;
+
+ if (git_repository_config__weakptr(&cfg, repo) < 0)
+ return -1;
+
+ if (git_buf_printf(&remote_key, "branch.%s.remote", branch_name) < 0)
+ goto cleanup;
+
+ if (git_buf_printf(&merge_key, "branch.%s.merge", branch_name) < 0)
+ goto cleanup;
+
+ if (git_config_set_string(cfg, git_buf_cstr(&remote_key), remote_name) < 0)
+ goto cleanup;
+
+ if (git_config_set_string(cfg, git_buf_cstr(&merge_key), merge_target) < 0)
+ goto cleanup;
+
+ error = 0;
+
+cleanup:
+ git_buf_free(&remote_key);
+ git_buf_free(&merge_key);
+ return error;
+}
+
+static int create_tracking_branch(
+ git_reference **branch,
+ git_repository *repo,
+ const git_oid *target,
+ const char *branch_name,
+ const git_signature *signature,
+ const char *log_message)
+{
+ int error;
+
+ if ((error = create_branch(branch, repo, target, branch_name, signature, log_message)) < 0)
+ return error;
+
+ return setup_tracking_config(
+ repo,
+ branch_name,
+ GIT_REMOTE_ORIGIN,
+ git_reference_name(*branch));
+}
+
+static int update_head_to_new_branch(
+ git_repository *repo,
+ const git_oid *target,
+ const char *name,
+ const git_signature *signature,
+ const char *reflog_message)
+{
+ git_reference *tracking_branch = NULL;
+ int error;
+
+ if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR))
+ name += strlen(GIT_REFS_HEADS_DIR);
+
+ error = create_tracking_branch(&tracking_branch, repo, target, name,
+ signature, reflog_message);
+
+ if (!error)
+ error = git_repository_set_head(
+ repo, git_reference_name(tracking_branch),
+ signature, reflog_message);
+
+ git_reference_free(tracking_branch);
+
+ /* if it already existed, then the user's refspec created it for us, ignore it' */
+ if (error == GIT_EEXISTS)
+ error = 0;
+
+ return error;
+}
+
+static int update_head_to_remote(
+ git_repository *repo,
+ git_remote *remote,
+ const git_signature *signature,
+ const char *reflog_message)
+{
+ int error = 0, found_branch = 0;
+ size_t refs_len;
+ git_refspec dummy_spec, *refspec;
+ const git_remote_head *remote_head, **refs;
+ const git_oid *remote_head_id;
+ git_buf remote_master_name = GIT_BUF_INIT;
+ git_buf branch = GIT_BUF_INIT;
+
+ if ((error = git_remote_ls(&refs, &refs_len, remote)) < 0)
+ return error;
+
+ /* Did we just clone an empty repository? */
+ if (refs_len == 0)
+ return setup_tracking_config(
+ repo, "master", GIT_REMOTE_ORIGIN, GIT_REFS_HEADS_MASTER_FILE);
+
+ error = git_remote_default_branch(&branch, remote);
+ if (error == GIT_ENOTFOUND) {
+ git_buf_puts(&branch, GIT_REFS_HEADS_MASTER_FILE);
+ } else {
+ found_branch = 1;
+ }
+
+ /* Get the remote's HEAD. This is always the first ref in the list. */
+ remote_head = refs[0];
+ assert(remote_head);
+
+ remote_head_id = &remote_head->oid;
+ refspec = git_remote__matching_refspec(remote, git_buf_cstr(&branch));
+
+ if (refspec == NULL) {
+ memset(&dummy_spec, 0, sizeof(git_refspec));
+ refspec = &dummy_spec;
+ }
+
+ /* Determine the remote tracking reference name from the local master */
+ if ((error = git_refspec_transform(
+ &remote_master_name,
+ refspec,
+ git_buf_cstr(&branch))) < 0)
+ return error;
+
+ if (found_branch) {
+ error = update_head_to_new_branch(
+ repo,
+ remote_head_id,
+ git_buf_cstr(&branch),
+ signature, reflog_message);
+ } else {
+ error = git_repository_set_head_detached(
+ repo, remote_head_id, signature, reflog_message);
+ }
+
+ git_buf_free(&remote_master_name);
+ git_buf_free(&branch);
+ return error;
+}
+
+static int update_head_to_branch(
+ git_repository *repo,
+ const char *remote_name,
+ const char *branch,
+ const git_signature *signature,
+ const char *reflog_message)
+{
+ int retcode;
+ git_buf remote_branch_name = GIT_BUF_INIT;
+ git_reference* remote_ref = NULL;
+
+ assert(remote_name && branch);
+
+ if ((retcode = git_buf_printf(&remote_branch_name, GIT_REFS_REMOTES_DIR "%s/%s",
+ remote_name, branch)) < 0 )
+ goto cleanup;
+
+ if ((retcode = git_reference_lookup(&remote_ref, repo, git_buf_cstr(&remote_branch_name))) < 0)
+ goto cleanup;
+
+ retcode = update_head_to_new_branch(repo, git_reference_target(remote_ref), branch,
+ signature, reflog_message);
+
+cleanup:
+ git_reference_free(remote_ref);
+ git_buf_free(&remote_branch_name);
+ return retcode;
+}
+
+/*
+ * submodules?
+ */
+
+static int create_and_configure_origin(
+ git_remote **out,
+ git_repository *repo,
+ const char *url,
+ const git_clone_options *options)
+{
+ int error;
+ git_remote *origin = NULL;
+ const char *name;
+ char buf[GIT_PATH_MAX];
+
+ /* If the path exists and is a dir, the url should be the absolute path */
+ if (git_path_root(url) < 0 && git_path_exists(url) && git_path_isdir(url)) {
+ if (p_realpath(url, buf) == NULL)
+ return -1;
+
+ url = buf;
+ }
+
+ name = options->remote_name ? options->remote_name : "origin";
+ if ((error = git_remote_create(&origin, repo, name, url)) < 0)
+ goto on_error;
+
+ if (options->ignore_cert_errors)
+ git_remote_check_cert(origin, 0);
+
+ if ((error = git_remote_set_callbacks(origin, &options->remote_callbacks)) < 0)
+ goto on_error;
+
+ if ((error = git_remote_save(origin)) < 0)
+ goto on_error;
+
+ *out = origin;
+ return 0;
+
+on_error:
+ git_remote_free(origin);
+ return error;
+}
+
+static bool should_checkout(
+ git_repository *repo,
+ bool is_bare,
+ const git_checkout_options *opts)
+{
+ if (is_bare)
+ return false;
+
+ if (!opts)
+ return false;
+
+ if (opts->checkout_strategy == GIT_CHECKOUT_NONE)
+ return false;
+
+ return !git_repository_head_unborn(repo);
+}
+
+static int checkout_branch(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature, const char *reflog_message)
+{
+ int error;
+
+ if (branch)
+ error = update_head_to_branch(repo, git_remote_name(remote), branch,
+ signature, reflog_message);
+ /* Point HEAD to the same ref as the remote's head */
+ else
+ error = update_head_to_remote(repo, remote, signature, reflog_message);
+
+ if (!error && should_checkout(repo, git_repository_is_bare(repo), co_opts))
+ error = git_checkout_head(repo, co_opts);
+
+ return error;
+}
+
+int git_clone_into(git_repository *repo, git_remote *_remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature)
+{
+ int error;
+ git_buf reflog_message = GIT_BUF_INIT;
+ git_remote *remote;
+ const git_remote_callbacks *callbacks;
+
+ assert(repo && _remote);
+
+ if (!git_repository_is_empty(repo)) {
+ giterr_set(GITERR_INVALID, "the repository is not empty");
+ return -1;
+ }
+
+ if ((error = git_remote_dup(&remote, _remote)) < 0)
+ return error;
+
+ callbacks = git_remote_get_callbacks(_remote);
+ if (!giterr__check_version(callbacks, 1, "git_remote_callbacks") &&
+ (error = git_remote_set_callbacks(remote, callbacks)) < 0)
+ goto cleanup;
+
+ if ((error = git_remote_add_fetch(remote, "refs/tags/*:refs/tags/*")) < 0)
+ goto cleanup;
+
+ git_remote_set_update_fetchhead(remote, 0);
+ git_buf_printf(&reflog_message, "clone: from %s", git_remote_url(remote));
+
+ if ((error = git_remote_fetch(remote, signature, git_buf_cstr(&reflog_message))) != 0)
+ goto cleanup;
+
+ error = checkout_branch(repo, remote, co_opts, branch, signature, git_buf_cstr(&reflog_message));
+
+cleanup:
+ git_remote_free(remote);
+ git_buf_free(&reflog_message);
+
+ return error;
+}
+
+int git_clone__should_clone_local(const char *url, git_clone_local_t local)
+{
+ const char *path;
+ int is_url;
+
+ if (local == GIT_CLONE_NO_LOCAL)
+ return false;
+
+ is_url = !git__prefixcmp(url, "file://");
+
+ if (is_url && local != GIT_CLONE_LOCAL && local != GIT_CLONE_LOCAL_NO_LINKS )
+ return false;
+
+ path = url;
+ if (is_url)
+ path = url + strlen("file://");
+
+ if ((git_path_exists(path) && git_path_isdir(path)) && local != GIT_CLONE_NO_LOCAL)
+ return true;
+
+ return false;
+}
+
+int git_clone(
+ git_repository **out,
+ const char *url,
+ const char *local_path,
+ const git_clone_options *_options)
+{
+ int error = 0;
+ git_repository *repo = NULL;
+ git_remote *origin;
+ git_clone_options options = GIT_CLONE_OPTIONS_INIT;
+ uint32_t rmdir_flags = GIT_RMDIR_REMOVE_FILES;
+
+ assert(out && url && local_path);
+
+ if (_options)
+ memcpy(&options, _options, sizeof(git_clone_options));
+
+ GITERR_CHECK_VERSION(&options, GIT_CLONE_OPTIONS_VERSION, "git_clone_options");
+
+ /* Only clone to a new directory or an empty directory */
+ if (git_path_exists(local_path) && !git_path_is_empty_dir(local_path)) {
+ giterr_set(GITERR_INVALID,
+ "'%s' exists and is not an empty directory", local_path);
+ return GIT_EEXISTS;
+ }
+
+ /* Only remove the root directory on failure if we create it */
+ if (git_path_exists(local_path))
+ rmdir_flags |= GIT_RMDIR_SKIP_ROOT;
+
+ if ((error = git_repository_init(&repo, local_path, options.bare)) < 0)
+ return error;
+
+ if (!(error = create_and_configure_origin(&origin, repo, url, &options))) {
+ if (git_clone__should_clone_local(url, options.local)) {
+ int link = options.local != GIT_CLONE_LOCAL_NO_LINKS;
+ error = git_clone_local_into(
+ repo, origin, &options.checkout_opts,
+ options.checkout_branch, link, options.signature);
+ } else {
+ error = git_clone_into(
+ repo, origin, &options.checkout_opts,
+ options.checkout_branch, options.signature);
+ }
+
+ git_remote_free(origin);
+ }
+
+ if (error != 0) {
+ git_error_state last_error = {0};
+ giterr_capture(&last_error, error);
+
+ git_repository_free(repo);
+ repo = NULL;
+
+ (void)git_futils_rmdir_r(local_path, NULL, rmdir_flags);
+
+ giterr_restore(&last_error);
+ }
+
+ *out = repo;
+ return error;
+}
+
+int git_clone_init_options(git_clone_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_clone_options, GIT_CLONE_OPTIONS_INIT);
+ return 0;
+}
+
+static const char *repository_base(git_repository *repo)
+{
+ if (git_repository_is_bare(repo))
+ return git_repository_path(repo);
+
+ return git_repository_workdir(repo);
+}
+
+static bool can_link(const char *src, const char *dst, int link)
+{
+#ifdef GIT_WIN32
+ return false;
+#else
+
+ struct stat st_src, st_dst;
+
+ if (!link)
+ return false;
+
+ if (p_stat(src, &st_src) < 0)
+ return false;
+
+ if (p_stat(dst, &st_dst) < 0)
+ return false;
+
+ return st_src.st_dev == st_dst.st_dev;
+#endif
+}
+
+int git_clone_local_into(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, int link, const git_signature *signature)
+{
+ int error, flags;
+ git_repository *src;
+ git_buf src_odb = GIT_BUF_INIT, dst_odb = GIT_BUF_INIT, src_path = GIT_BUF_INIT;
+ git_buf reflog_message = GIT_BUF_INIT;
+
+ assert(repo && remote);
+
+ if (!git_repository_is_empty(repo)) {
+ giterr_set(GITERR_INVALID, "the repository is not empty");
+ return -1;
+ }
+
+ /*
+ * Let's figure out what path we should use for the source
+ * repo, if it's not rooted, the path should be relative to
+ * the repository's worktree/gitdir.
+ */
+ if ((error = git_path_from_url_or_path(&src_path, git_remote_url(remote))) < 0)
+ return error;
+
+ /* Copy .git/objects/ from the source to the target */
+ if ((error = git_repository_open(&src, git_buf_cstr(&src_path))) < 0) {
+ git_buf_free(&src_path);
+ return error;
+ }
+
+ git_buf_joinpath(&src_odb, git_repository_path(src), GIT_OBJECTS_DIR);
+ git_buf_joinpath(&dst_odb, git_repository_path(repo), GIT_OBJECTS_DIR);
+ if (git_buf_oom(&src_odb) || git_buf_oom(&dst_odb)) {
+ error = -1;
+ goto cleanup;
+ }
+
+ flags = 0;
+ if (can_link(git_repository_path(src), git_repository_path(repo), link))
+ flags |= GIT_CPDIR_LINK_FILES;
+
+ if ((error = git_futils_cp_r(git_buf_cstr(&src_odb), git_buf_cstr(&dst_odb),
+ flags, GIT_OBJECT_DIR_MODE)) < 0)
+ goto cleanup;
+
+ git_buf_printf(&reflog_message, "clone: from %s", git_remote_url(remote));
+
+ if ((error = git_remote_fetch(remote, signature, git_buf_cstr(&reflog_message))) != 0)
+ goto cleanup;
+
+ error = checkout_branch(repo, remote, co_opts, branch, signature, git_buf_cstr(&reflog_message));
+
+cleanup:
+ git_buf_free(&reflog_message);
+ git_buf_free(&src_path);
+ git_buf_free(&src_odb);
+ git_buf_free(&dst_odb);
+ git_repository_free(src);
+ return error;
+}
diff --git a/src/clone.h b/src/clone.h
new file mode 100644
index 000000000..14ca5d44c
--- /dev/null
+++ b/src/clone.h
@@ -0,0 +1,12 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_clone_h__
+#define INCLUDE_clone_h__
+
+extern int git_clone__should_clone_local(const char *url, git_clone_local_t local);
+
+#endif
diff --git a/src/commit.c b/src/commit.c
index 7c0965cb1..227d5c4a5 100644
--- a/src/commit.c
+++ b/src/commit.c
@@ -1,264 +1,418 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
*/
#include "git2/common.h"
#include "git2/object.h"
#include "git2/repository.h"
#include "git2/signature.h"
+#include "git2/sys/commit.h"
#include "common.h"
#include "odb.h"
#include "commit.h"
#include "signature.h"
+#include "message.h"
+
+void git_commit__free(void *_commit)
+{
+ git_commit *commit = _commit;
-#include <stdarg.h>
+ git_array_clear(commit->parent_ids);
-#define COMMIT_BASIC_PARSE 0x0
-#define COMMIT_FULL_PARSE 0x1
+ git_signature_free(commit->author);
+ git_signature_free(commit->committer);
-#define COMMIT_PRINT(commit) {\
- char oid[41]; oid[40] = 0;\
- git_oid_fmt(oid, &commit->object.id);\
- printf("Oid: %s | In degree: %d | Time: %u\n", oid, commit->in_degree, commit->commit_time);\
+ git__free(commit->raw_header);
+ git__free(commit->raw_message);
+ git__free(commit->message_encoding);
+ git__free(commit->summary);
+
+ git__free(commit);
}
-static void clear_parents(git_commit *commit)
+static int update_ref_for_commit(git_repository *repo, git_reference *ref, const char *update_ref, const git_oid *id, const git_signature *committer)
{
- unsigned int i;
+ git_reference *ref2 = NULL;
+ int error;
+ git_commit *c;
+ const char *shortmsg;
+ git_buf reflog_msg = GIT_BUF_INIT;
- for (i = 0; i < commit->parent_oids.length; ++i) {
- git_oid *parent = git_vector_get(&commit->parent_oids, i);
- free(parent);
+ if ((error = git_commit_lookup(&c, repo, id)) < 0) {
+ return error;
}
- git_vector_clear(&commit->parent_oids);
+ shortmsg = git_commit_summary(c);
+ git_buf_printf(&reflog_msg, "commit%s: %s",
+ git_commit_parentcount(c) == 0 ? " (initial)" : "",
+ shortmsg);
+ git_commit_free(c);
+
+ if (ref) {
+ error = git_reference_set_target(&ref2, ref, id, committer, git_buf_cstr(&reflog_msg));
+ git_reference_free(ref2);
+ } else {
+ error = git_reference__update_terminal(repo, update_ref, id, committer, git_buf_cstr(&reflog_msg));
+ }
+
+ git_buf_free(&reflog_msg);
+ return error;
}
-void git_commit__free(git_commit *commit)
+int git_commit_create_from_callback(
+ git_oid *id,
+ git_repository *repo,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_oid *tree,
+ git_commit_parent_callback parent_cb,
+ void *parent_payload)
{
- clear_parents(commit);
- git_vector_free(&commit->parent_oids);
+ git_reference *ref = NULL;
+ int error = 0, matched_parent = 0;
+ const git_oid *current_id = NULL;
+ git_buf commit = GIT_BUF_INIT;
+ size_t i = 0;
+ git_odb *odb;
+ const git_oid *parent;
- git_signature_free(commit->author);
- git_signature_free(commit->committer);
+ assert(id && repo && tree && parent_cb);
+
+ if (update_ref) {
+ error = git_reference_lookup_resolved(&ref, repo, update_ref, 10);
+ if (error < 0 && error != GIT_ENOTFOUND)
+ return error;
+ }
+ giterr_clear();
+
+ if (ref)
+ current_id = git_reference_target(ref);
+
+ git_oid__writebuf(&commit, "tree ", tree);
+
+ while ((parent = parent_cb(i, parent_payload)) != NULL) {
+ git_oid__writebuf(&commit, "parent ", parent);
+ if (i == 0 && current_id && git_oid_equal(current_id, parent))
+ matched_parent = 1;
+ i++;
+ }
+
+ if (ref && !matched_parent) {
+ git_reference_free(ref);
+ git_buf_free(&commit);
+ giterr_set(GITERR_OBJECT, "failed to create commit: current tip is not the first parent");
+ return GIT_EMODIFIED;
+ }
+
+ git_signature__writebuf(&commit, "author ", author);
+ git_signature__writebuf(&commit, "committer ", committer);
+
+ if (message_encoding != NULL)
+ git_buf_printf(&commit, "encoding %s\n", message_encoding);
+
+ git_buf_putc(&commit, '\n');
+
+ if (git_buf_puts(&commit, message) < 0)
+ goto on_error;
+
+ if (git_repository_odb__weakptr(&odb, repo) < 0)
+ goto on_error;
- free(commit->message);
- free(commit->message_short);
- free(commit);
+ if (git_odb_write(id, odb, commit.ptr, commit.size, GIT_OBJ_COMMIT) < 0)
+ goto on_error;
+
+ git_buf_free(&commit);
+
+ if (update_ref != NULL) {
+ error = update_ref_for_commit(repo, ref, update_ref, id, committer);
+ git_reference_free(ref);
+ return error;
+ }
+
+ return 0;
+
+on_error:
+ git_buf_free(&commit);
+ giterr_set(GITERR_OBJECT, "Failed to create commit.");
+ return -1;
}
-const git_oid *git_commit_id(git_commit *c)
+typedef struct {
+ size_t total;
+ va_list args;
+} commit_parent_varargs;
+
+static const git_oid *commit_parent_from_varargs(size_t curr, void *payload)
{
- return git_object_id((git_object *)c);
+ commit_parent_varargs *data = payload;
+ const git_commit *commit;
+ if (curr >= data->total)
+ return NULL;
+ commit = va_arg(data->args, const git_commit *);
+ return commit ? git_commit_id(commit) : NULL;
}
int git_commit_create_v(
- git_oid *oid,
- git_repository *repo,
- const char *update_ref,
- const git_signature *author,
- const git_signature *committer,
- const char *message,
- const git_tree *tree,
- int parent_count,
- ...)
+ git_oid *id,
+ git_repository *repo,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_tree *tree,
+ size_t parent_count,
+ ...)
{
- va_list ap;
- int i, error;
- const git_commit **parents;
-
- parents = git__malloc(parent_count * sizeof(git_commit *));
+ int error = 0;
+ commit_parent_varargs data;
- va_start(ap, parent_count);
- for (i = 0; i < parent_count; ++i)
- parents[i] = va_arg(ap, const git_commit *);
- va_end(ap);
+ assert(tree && git_tree_owner(tree) == repo);
- error = git_commit_create(
- oid, repo, update_ref, author, committer, message,
- tree, parent_count, parents);
+ data.total = parent_count;
+ va_start(data.args, parent_count);
- free((void *)parents);
+ error = git_commit_create_from_callback(
+ id, repo, update_ref, author, committer,
+ message_encoding, message, git_tree_id(tree),
+ commit_parent_from_varargs, &data);
+ va_end(data.args);
return error;
}
-int git_commit_create(
- git_oid *oid,
- git_repository *repo,
- const char *update_ref,
- const git_signature *author,
- const git_signature *committer,
- const char *message,
- const git_tree *tree,
- int parent_count,
- const git_commit *parents[])
+typedef struct {
+ size_t total;
+ const git_oid **parents;
+} commit_parent_oids;
+
+static const git_oid *commit_parent_from_ids(size_t curr, void *payload)
{
- git_buf commit = GIT_BUF_INIT;
- int error, i;
+ commit_parent_oids *data = payload;
+ return (curr < data->total) ? data->parents[curr] : NULL;
+}
- if (git_object_owner((const git_object *)tree) != repo)
- return git__throw(GIT_EINVALIDARGS, "The given tree does not belong to this repository");
+int git_commit_create_from_ids(
+ git_oid *id,
+ git_repository *repo,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_oid *tree,
+ size_t parent_count,
+ const git_oid *parents[])
+{
+ commit_parent_oids data = { parent_count, parents };
- git_oid__writebuf(&commit, "tree ", git_object_id((const git_object *)tree));
+ return git_commit_create_from_callback(
+ id, repo, update_ref, author, committer,
+ message_encoding, message, tree,
+ commit_parent_from_ids, &data);
+}
- for (i = 0; i < parent_count; ++i) {
- if (git_object_owner((const git_object *)parents[i]) != repo) {
- error = git__throw(GIT_EINVALIDARGS, "The given parent does not belong to this repository");
- goto cleanup;
- }
+typedef struct {
+ size_t total;
+ const git_commit **parents;
+ git_repository *repo;
+} commit_parent_data;
- git_oid__writebuf(&commit, "parent ", git_object_id((const git_object *)parents[i]));
- }
+static const git_oid *commit_parent_from_array(size_t curr, void *payload)
+{
+ commit_parent_data *data = payload;
+ const git_commit *commit;
+ if (curr >= data->total)
+ return NULL;
+ commit = data->parents[curr];
+ if (git_commit_owner(commit) != data->repo)
+ return NULL;
+ return git_commit_id(commit);
+}
- git_signature__writebuf(&commit, "author ", author);
- git_signature__writebuf(&commit, "committer ", committer);
+int git_commit_create(
+ git_oid *id,
+ git_repository *repo,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_tree *tree,
+ size_t parent_count,
+ const git_commit *parents[])
+{
+ commit_parent_data data = { parent_count, parents, repo };
- git_buf_putc(&commit, '\n');
- git_buf_puts(&commit, message);
+ assert(tree && git_tree_owner(tree) == repo);
- if (git_buf_oom(&commit)) {
- error = git__throw(GIT_ENOMEM, "Not enough memory to build the commit data");
- goto cleanup;
+ return git_commit_create_from_callback(
+ id, repo, update_ref, author, committer,
+ message_encoding, message, git_tree_id(tree),
+ commit_parent_from_array, &data);
+}
+
+static const git_oid *commit_parent_for_amend(size_t curr, void *payload)
+{
+ const git_commit *commit_to_amend = payload;
+ if (curr >= git_array_size(commit_to_amend->parent_ids))
+ return NULL;
+ return git_array_get(commit_to_amend->parent_ids, curr);
+}
+
+int git_commit_amend(
+ git_oid *id,
+ const git_commit *commit_to_amend,
+ const char *update_ref,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *message_encoding,
+ const char *message,
+ const git_tree *tree)
+{
+ git_repository *repo;
+ git_oid tree_id;
+ git_reference *ref;
+ int error;
+
+ assert(id && commit_to_amend);
+
+ repo = git_commit_owner(commit_to_amend);
+
+ if (!author)
+ author = git_commit_author(commit_to_amend);
+ if (!committer)
+ committer = git_commit_committer(commit_to_amend);
+ if (!message_encoding)
+ message_encoding = git_commit_message_encoding(commit_to_amend);
+ if (!message)
+ message = git_commit_message(commit_to_amend);
+
+ if (!tree) {
+ git_tree *old_tree;
+ GITERR_CHECK_ERROR( git_commit_tree(&old_tree, commit_to_amend) );
+ git_oid_cpy(&tree_id, git_tree_id(old_tree));
+ git_tree_free(old_tree);
+ } else {
+ assert(git_tree_owner(tree) == repo);
+ git_oid_cpy(&tree_id, git_tree_id(tree));
}
- error = git_odb_write(oid, git_repository_database(repo), commit.ptr, commit.size, GIT_OBJ_COMMIT);
- git_buf_free(&commit);
+ if (update_ref) {
+ if ((error = git_reference_lookup_resolved(&ref, repo, update_ref, 5)) < 0)
+ return error;
- if (error == GIT_SUCCESS && update_ref != NULL) {
- git_reference *head;
-
- error = git_reference_lookup(&head, repo, update_ref);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to create commit");
-
- error = git_reference_resolve(&head, head);
- if (error < GIT_SUCCESS) {
- if (error != GIT_ENOTFOUND)
- return git__rethrow(error, "Failed to create commit");
- /*
- * The target of the reference was not found. This can happen
- * just after a repository has been initialized (the master
- * branch doesn't exist yet, as it doesn't have anything to
- * point to) or after an orphan checkout, so if the target
- * branch doesn't exist yet, create it and return.
- */
- return git_reference_create_oid(&head, repo, git_reference_target(head), oid, 1);
+ if (git_oid_cmp(git_commit_id(commit_to_amend), git_reference_target(ref))) {
+ git_reference_free(ref);
+ giterr_set(GITERR_REFERENCE, "commit to amend is not the tip of the given branch");
+ return -1;
}
-
- error = git_reference_set_oid(head, oid);
}
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to create commit");
+ error = git_commit_create_from_callback(
+ id, repo, NULL, author, committer, message_encoding, message,
+ &tree_id, commit_parent_for_amend, (void *)commit_to_amend);
- return GIT_SUCCESS;
+ if (!error && update_ref) {
+ error = update_ref_for_commit(repo, ref, NULL, id, committer);
+ git_reference_free(ref);
+ }
-cleanup:
- git_buf_free(&commit);
return error;
}
-int commit_parse_buffer(git_commit *commit, const void *data, size_t len)
+int git_commit__parse(void *_commit, git_odb_object *odb_obj)
{
- const char *buffer = data;
- const char *buffer_end = (const char *)data + len;
+ git_commit *commit = _commit;
+ const char *buffer_start = git_odb_object_data(odb_obj), *buffer;
+ const char *buffer_end = buffer_start + git_odb_object_size(odb_obj);
+ git_oid parent_id;
+ size_t header_len;
- git_oid parent_oid;
- int error;
+ buffer = buffer_start;
- git_vector_init(&commit->parent_oids, 4, NULL);
+ /* Allocate for one, which will allow not to realloc 90% of the time */
+ git_array_init_to_size(commit->parent_ids, 1);
+ GITERR_CHECK_ARRAY(commit->parent_ids);
- if ((error = git_oid__parse(&commit->tree_oid, &buffer, buffer_end, "tree ")) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to parse buffer");
+ /* The tree is always the first field */
+ if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0)
+ goto bad_buffer;
/*
* TODO: commit grafts!
*/
- while (git_oid__parse(&parent_oid, &buffer, buffer_end, "parent ") == GIT_SUCCESS) {
- git_oid *new_oid;
+ while (git_oid__parse(&parent_id, &buffer, buffer_end, "parent ") == 0) {
+ git_oid *new_id = git_array_alloc(commit->parent_ids);
+ GITERR_CHECK_ALLOC(new_id);
- new_oid = git__malloc(sizeof(git_oid));
- git_oid_cpy(new_oid, &parent_oid);
-
- if (git_vector_insert(&commit->parent_oids, new_oid) < GIT_SUCCESS)
- return GIT_ENOMEM;
+ git_oid_cpy(new_id, &parent_id);
}
commit->author = git__malloc(sizeof(git_signature));
- if ((error = git_signature__parse(commit->author, &buffer, buffer_end, "author ", '\n')) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to parse buffer");
+ GITERR_CHECK_ALLOC(commit->author);
+
+ if (git_signature__parse(commit->author, &buffer, buffer_end, "author ", '\n') < 0)
+ return -1;
/* Always parse the committer; we need the commit time */
commit->committer = git__malloc(sizeof(git_signature));
- if ((error = git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n')) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to parse buffer");
-
- /* parse commit message */
- while (buffer <= buffer_end && *buffer == '\n')
- buffer++;
-
- if (buffer < buffer_end) {
- const char *line_end;
- unsigned int i;
- size_t message_len;
-
- /* Long message */
- message_len = buffer_end - buffer;
- commit->message = git__malloc(message_len + 1);
- memcpy(commit->message, buffer, message_len);
- commit->message[message_len] = 0;
-
- /* Short message */
- if((line_end = strstr(buffer, "\n\n")) == NULL) {
- /* Cut the last '\n' if there is one */
- if (message_len && buffer[message_len - 1] == '\n')
- line_end = buffer_end - 1;
- else
- line_end = buffer_end;
- }
- message_len = line_end - buffer;
- commit->message_short = git__malloc(message_len + 1);
- for (i = 0; i < message_len; ++i) {
- commit->message_short[i] = (buffer[i] == '\n') ? ' ' : buffer[i];
+ GITERR_CHECK_ALLOC(commit->committer);
+
+ if (git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n') < 0)
+ return -1;
+
+ /* Parse add'l header entries */
+ while (buffer < buffer_end) {
+ const char *eoln = buffer;
+ if (buffer[-1] == '\n' && buffer[0] == '\n')
+ break;
+
+ while (eoln < buffer_end && *eoln != '\n')
+ ++eoln;
+
+ if (git__prefixcmp(buffer, "encoding ") == 0) {
+ buffer += strlen("encoding ");
+
+ commit->message_encoding = git__strndup(buffer, eoln - buffer);
+ GITERR_CHECK_ALLOC(commit->message_encoding);
}
- commit->message_short[message_len] = 0;
+
+ if (eoln < buffer_end && *eoln == '\n')
+ ++eoln;
+ buffer = eoln;
}
- return GIT_SUCCESS;
-}
+ header_len = buffer - buffer_start;
+ commit->raw_header = git__strndup(buffer_start, header_len);
+ GITERR_CHECK_ALLOC(commit->raw_header);
-int git_commit__parse(git_commit *commit, git_odb_object *obj)
-{
- assert(commit);
- return commit_parse_buffer(commit, obj->raw.data, obj->raw.len);
+ /* point "buffer" to data after header, +1 for the final LF */
+ buffer = buffer_start + header_len + 1;
+
+ /* extract commit message */
+ if (buffer <= buffer_end) {
+ commit->raw_message = git__strndup(buffer, buffer_end - buffer);
+ GITERR_CHECK_ALLOC(commit->raw_message);
+ }
+
+ return 0;
+
+bad_buffer:
+ giterr_set(GITERR_OBJECT, "Failed to parse bad commit object");
+ return -1;
}
#define GIT_COMMIT_GETTER(_rvalue, _name, _return) \
- _rvalue git_commit_##_name(git_commit *commit) \
+ _rvalue git_commit_##_name(const git_commit *commit) \
{\
assert(commit); \
return _return; \
@@ -266,35 +420,117 @@ int git_commit__parse(git_commit *commit, git_odb_object *obj)
GIT_COMMIT_GETTER(const git_signature *, author, commit->author)
GIT_COMMIT_GETTER(const git_signature *, committer, commit->committer)
-GIT_COMMIT_GETTER(const char *, message, commit->message)
-GIT_COMMIT_GETTER(const char *, message_short, commit->message_short)
+GIT_COMMIT_GETTER(const char *, message_raw, commit->raw_message)
+GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding)
+GIT_COMMIT_GETTER(const char *, raw_header, commit->raw_header)
GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time)
GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset)
-GIT_COMMIT_GETTER(unsigned int, parentcount, commit->parent_oids.length)
-GIT_COMMIT_GETTER(const git_oid *, tree_oid, &commit->tree_oid);
+GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)git_array_size(commit->parent_ids))
+GIT_COMMIT_GETTER(const git_oid *, tree_id, &commit->tree_id);
+
+const char *git_commit_message(const git_commit *commit)
+{
+ const char *message;
+
+ assert(commit);
+
+ message = commit->raw_message;
+ /* trim leading newlines from raw message */
+ while (*message && *message == '\n')
+ ++message;
+
+ return message;
+}
-int git_commit_tree(git_tree **tree_out, git_commit *commit)
+const char *git_commit_summary(git_commit *commit)
{
+ git_buf summary = GIT_BUF_INIT;
+ const char *msg, *space;
+
assert(commit);
- return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_oid);
+
+ if (!commit->summary) {
+ for (msg = git_commit_message(commit), space = NULL; *msg; ++msg) {
+ if (msg[0] == '\n' && (!msg[1] || msg[1] == '\n'))
+ break;
+ else if (msg[0] == '\n')
+ git_buf_putc(&summary, ' ');
+ else if (git__isspace(msg[0]))
+ space = space ? space : msg;
+ else if (space) {
+ git_buf_put(&summary, space, (msg - space) + 1);
+ space = NULL;
+ } else
+ git_buf_putc(&summary, *msg);
+ }
+
+ commit->summary = git_buf_detach(&summary);
+ if (!commit->summary)
+ commit->summary = git__strdup("");
+ }
+
+ return commit->summary;
}
-int git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n)
+int git_commit_tree(git_tree **tree_out, const git_commit *commit)
{
- git_oid *parent_oid;
assert(commit);
+ return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_id);
+}
- parent_oid = git_vector_get(&commit->parent_oids, n);
- if (parent_oid == NULL)
- return git__throw(GIT_ENOTFOUND, "Parent %u does not exist", n);
+const git_oid *git_commit_parent_id(
+ const git_commit *commit, unsigned int n)
+{
+ assert(commit);
- return git_commit_lookup(parent, commit->object.repo, parent_oid);
+ return git_array_get(commit->parent_ids, n);
}
-const git_oid *git_commit_parent_oid(git_commit *commit, unsigned int n)
+int git_commit_parent(
+ git_commit **parent, const git_commit *commit, unsigned int n)
{
+ const git_oid *parent_id;
assert(commit);
- return git_vector_get(&commit->parent_oids, n);
+ parent_id = git_commit_parent_id(commit, n);
+ if (parent_id == NULL) {
+ giterr_set(GITERR_INVALID, "Parent %u does not exist", n);
+ return GIT_ENOTFOUND;
+ }
+
+ return git_commit_lookup(parent, commit->object.repo, parent_id);
+}
+
+int git_commit_nth_gen_ancestor(
+ git_commit **ancestor,
+ const git_commit *commit,
+ unsigned int n)
+{
+ git_commit *current, *parent = NULL;
+ int error;
+
+ assert(ancestor && commit);
+
+ if (git_object_dup((git_object **) &current, (git_object *) commit) < 0)
+ return -1;
+
+ if (n == 0) {
+ *ancestor = current;
+ return 0;
+ }
+
+ while (n--) {
+ error = git_commit_parent(&parent, current, 0);
+
+ git_commit_free(current);
+
+ if (error < 0)
+ return error;
+
+ current = parent;
+ }
+
+ *ancestor = parent;
+ return 0;
}
diff --git a/src/commit.h b/src/commit.h
index 3d15c5044..efb080b50 100644
--- a/src/commit.h
+++ b/src/commit.h
@@ -1,27 +1,36 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
#ifndef INCLUDE_commit_h__
#define INCLUDE_commit_h__
#include "git2/commit.h"
#include "tree.h"
#include "repository.h"
-#include "vector.h"
+#include "array.h"
#include <time.h>
struct git_commit {
git_object object;
- git_vector parent_oids;
- git_oid tree_oid;
+ git_array_t(git_oid) parent_ids;
+ git_oid tree_id;
git_signature *author;
git_signature *committer;
- char *message;
- char *message_short;
+ char *message_encoding;
+ char *raw_message;
+ char *raw_header;
+
+ char *summary;
};
-void git_commit__free(git_commit *c);
-int git_commit__parse(git_commit *commit, git_odb_object *obj);
+void git_commit__free(void *commit);
+int git_commit__parse(void *commit, git_odb_object *obj);
#endif
diff --git a/src/commit_list.c b/src/commit_list.c
new file mode 100644
index 000000000..9db3f5633
--- /dev/null
+++ b/src/commit_list.c
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "commit_list.h"
+#include "common.h"
+#include "revwalk.h"
+#include "pool.h"
+#include "odb.h"
+
+int git_commit_list_time_cmp(const void *a, const void *b)
+{
+ const git_commit_list_node *commit_a = a;
+ const git_commit_list_node *commit_b = b;
+
+ return (commit_a->time < commit_b->time);
+}
+
+git_commit_list *git_commit_list_insert(git_commit_list_node *item, git_commit_list **list_p)
+{
+ git_commit_list *new_list = git__malloc(sizeof(git_commit_list));
+ if (new_list != NULL) {
+ new_list->item = item;
+ new_list->next = *list_p;
+ }
+ *list_p = new_list;
+ return new_list;
+}
+
+git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_commit_list **list_p)
+{
+ git_commit_list **pp = list_p;
+ git_commit_list *p;
+
+ while ((p = *pp) != NULL) {
+ if (git_commit_list_time_cmp(p->item, item) > 0)
+ break;
+
+ pp = &p->next;
+ }
+
+ return git_commit_list_insert(item, pp);
+}
+
+git_commit_list_node *git_commit_list_alloc_node(git_revwalk *walk)
+{
+ return (git_commit_list_node *)git_pool_malloc(&walk->commit_pool, COMMIT_ALLOC);
+}
+
+static int commit_error(git_commit_list_node *commit, const char *msg)
+{
+ char commit_oid[GIT_OID_HEXSZ + 1];
+ git_oid_fmt(commit_oid, &commit->oid);
+ commit_oid[GIT_OID_HEXSZ] = '\0';
+
+ giterr_set(GITERR_ODB, "Failed to parse commit %s - %s", commit_oid, msg);
+
+ return -1;
+}
+
+static git_commit_list_node **alloc_parents(
+ git_revwalk *walk, git_commit_list_node *commit, size_t n_parents)
+{
+ if (n_parents <= PARENTS_PER_COMMIT)
+ return (git_commit_list_node **)((char *)commit + sizeof(git_commit_list_node));
+
+ return (git_commit_list_node **)git_pool_malloc(
+ &walk->commit_pool, (uint32_t)(n_parents * sizeof(git_commit_list_node *)));
+}
+
+
+void git_commit_list_free(git_commit_list **list_p)
+{
+ git_commit_list *list = *list_p;
+
+ if (list == NULL)
+ return;
+
+ while (list) {
+ git_commit_list *temp = list;
+ list = temp->next;
+ git__free(temp);
+ }
+
+ *list_p = NULL;
+}
+
+git_commit_list_node *git_commit_list_pop(git_commit_list **stack)
+{
+ git_commit_list *top = *stack;
+ git_commit_list_node *item = top ? top->item : NULL;
+
+ if (top) {
+ *stack = top->next;
+ git__free(top);
+ }
+ return item;
+}
+
+static int commit_quick_parse(
+ git_revwalk *walk,
+ git_commit_list_node *commit,
+ const uint8_t *buffer,
+ size_t buffer_len)
+{
+ const size_t parent_len = strlen("parent ") + GIT_OID_HEXSZ + 1;
+ const uint8_t *buffer_end = buffer + buffer_len;
+ const uint8_t *parents_start, *committer_start;
+ int i, parents = 0;
+ int commit_time;
+
+ buffer += strlen("tree ") + GIT_OID_HEXSZ + 1;
+
+ parents_start = buffer;
+ while (buffer + parent_len < buffer_end && memcmp(buffer, "parent ", strlen("parent ")) == 0) {
+ parents++;
+ buffer += parent_len;
+ }
+
+ commit->parents = alloc_parents(walk, commit, parents);
+ GITERR_CHECK_ALLOC(commit->parents);
+
+ buffer = parents_start;
+ for (i = 0; i < parents; ++i) {
+ git_oid oid;
+
+ if (git_oid_fromstr(&oid, (const char *)buffer + strlen("parent ")) < 0)
+ return -1;
+
+ commit->parents[i] = git_revwalk__commit_lookup(walk, &oid);
+ if (commit->parents[i] == NULL)
+ return -1;
+
+ buffer += parent_len;
+ }
+
+ commit->out_degree = (unsigned short)parents;
+
+ if ((committer_start = buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL)
+ return commit_error(commit, "object is corrupted");
+
+ buffer++;
+
+ if ((buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL)
+ return commit_error(commit, "object is corrupted");
+
+ /* Skip trailing spaces */
+ while (buffer > committer_start && git__isspace(*buffer))
+ buffer--;
+
+ /* Seek for the begining of the pack of digits */
+ while (buffer > committer_start && git__isdigit(*buffer))
+ buffer--;
+
+ /* Skip potential timezone offset */
+ if ((buffer > committer_start) && (*buffer == '+' || *buffer == '-')) {
+ buffer--;
+
+ while (buffer > committer_start && git__isspace(*buffer))
+ buffer--;
+
+ while (buffer > committer_start && git__isdigit(*buffer))
+ buffer--;
+ }
+
+ if ((buffer == committer_start) || (git__strtol32(&commit_time, (char *)(buffer + 1), NULL, 10) < 0))
+ return commit_error(commit, "cannot parse commit time");
+
+ commit->time = (time_t)commit_time;
+ commit->parsed = 1;
+ return 0;
+}
+
+int git_commit_list_parse(git_revwalk *walk, git_commit_list_node *commit)
+{
+ git_odb_object *obj;
+ int error;
+
+ if (commit->parsed)
+ return 0;
+
+ if ((error = git_odb_read(&obj, walk->odb, &commit->oid)) < 0)
+ return error;
+
+ if (obj->cached.type != GIT_OBJ_COMMIT) {
+ giterr_set(GITERR_INVALID, "Object is no commit object");
+ error = -1;
+ } else
+ error = commit_quick_parse(
+ walk, commit,
+ (const uint8_t *)git_odb_object_data(obj),
+ git_odb_object_size(obj));
+
+ git_odb_object_free(obj);
+ return error;
+}
+
diff --git a/src/commit_list.h b/src/commit_list.h
new file mode 100644
index 000000000..490d841be
--- /dev/null
+++ b/src/commit_list.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_commit_list_h__
+#define INCLUDE_commit_list_h__
+
+#include "git2/oid.h"
+
+#define PARENT1 (1 << 0)
+#define PARENT2 (1 << 1)
+#define RESULT (1 << 2)
+#define STALE (1 << 3)
+
+#define PARENTS_PER_COMMIT 2
+#define COMMIT_ALLOC \
+ (sizeof(git_commit_list_node) + PARENTS_PER_COMMIT * sizeof(git_commit_list_node *))
+
+typedef struct git_commit_list_node {
+ git_oid oid;
+ uint32_t time;
+ unsigned int seen:1,
+ uninteresting:1,
+ topo_delay:1,
+ parsed:1,
+ flags : 4;
+
+ unsigned short in_degree;
+ unsigned short out_degree;
+
+ struct git_commit_list_node **parents;
+} git_commit_list_node;
+
+typedef struct git_commit_list {
+ git_commit_list_node *item;
+ struct git_commit_list *next;
+} git_commit_list;
+
+git_commit_list_node *git_commit_list_alloc_node(git_revwalk *walk);
+int git_commit_list_time_cmp(const void *a, const void *b);
+void git_commit_list_free(git_commit_list **list_p);
+git_commit_list *git_commit_list_insert(git_commit_list_node *item, git_commit_list **list_p);
+git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_commit_list **list_p);
+int git_commit_list_parse(git_revwalk *walk, git_commit_list_node *commit);
+git_commit_list_node *git_commit_list_pop(git_commit_list **stack);
+
+#endif
diff --git a/src/common.h b/src/common.h
index e1e7f0035..807e5fa39 100644
--- a/src/common.h
+++ b/src/common.h
@@ -1,13 +1,22 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
#ifndef INCLUDE_common_h__
#define INCLUDE_common_h__
#include "git2/common.h"
-#include "git2/thread-utils.h"
#include "cc-compat.h"
-#ifdef GIT_HAVE_INTTYPES_H
-# include <inttypes.h>
+/** Declare a function as always inlined. */
+#if defined(_MSC_VER)
+# define GIT_INLINE(type) static __inline type
+#else
+# define GIT_INLINE(type) static inline type
#endif
+
#include <assert.h>
#include <errno.h>
#include <limits.h>
@@ -22,23 +31,28 @@
# include <io.h>
# include <direct.h>
+# include <winsock2.h>
# include <windows.h>
# include "win32/msvc-compat.h"
# include "win32/mingw-compat.h"
+# include "win32/error.h"
+# include "win32/version.h"
# ifdef GIT_THREADS
-# include "win32/pthread.h"
-#endif
-
-# define snprintf _snprintf
-
-typedef SSIZE_T ssize_t;
+# include "win32/pthread.h"
+# endif
#else
-# include <unistd.h>
+# include <unistd.h>
+# include <strings.h>
# ifdef GIT_THREADS
-# include <pthread.h>
+# include <pthread.h>
+# include <sched.h>
# endif
+#define GIT_STDLIB_CALL
+
+# include <arpa/inet.h>
+
#endif
#include "git2/types.h"
@@ -46,8 +60,122 @@ typedef SSIZE_T ssize_t;
#include "thread-utils.h"
#include "bswap.h"
-extern int git__throw(int error, const char *, ...) GIT_FORMAT_PRINTF(2, 3);
-extern int git__rethrow(int error, const char *, ...) GIT_FORMAT_PRINTF(2, 3);
+#include <regex.h>
+
+/**
+ * Check a pointer allocation result, returning -1 if it failed.
+ */
+#define GITERR_CHECK_ALLOC(ptr) if (ptr == NULL) { return -1; }
+
+/**
+ * Check a return value and propogate result if non-zero.
+ */
+#define GITERR_CHECK_ERROR(code) \
+ do { int _err = (code); if (_err) return _err; } while (0)
+
+/**
+ * Set the error message for this thread, formatting as needed.
+ */
+void giterr_set(int error_class, const char *string, ...);
+
+/**
+ * Set the error message for a regex failure, using the internal regex
+ * error code lookup and return a libgit error code.
+ */
+int giterr_set_regex(const regex_t *regex, int error_code);
+
+/**
+ * Set error message for user callback if needed.
+ *
+ * If the error code in non-zero and no error message is set, this
+ * sets a generic error message.
+ *
+ * @return This always returns the `error_code` parameter.
+ */
+GIT_INLINE(int) giterr_set_after_callback_function(
+ int error_code, const char *action)
+{
+ if (error_code) {
+ const git_error *e = giterr_last();
+ if (!e || !e->message)
+ giterr_set(e ? e->klass : GITERR_CALLBACK,
+ "%s callback returned %d", action, error_code);
+ }
+ return error_code;
+}
+
+#ifdef GIT_WIN32
+#define giterr_set_after_callback(code) \
+ giterr_set_after_callback_function((code), __FUNCTION__)
+#else
+#define giterr_set_after_callback(code) \
+ giterr_set_after_callback_function((code), __func__)
+#endif
+
+/**
+ * Gets the system error code for this thread.
+ */
+int giterr_system_last(void);
+
+/**
+ * Sets the system error code for this thread.
+ */
+void giterr_system_set(int code);
+
+/**
+ * Structure to preserve libgit2 error state
+ */
+typedef struct {
+ int error_code;
+ git_error error_msg;
+} git_error_state;
+
+/**
+ * Capture current error state to restore later, returning error code.
+ * If `error_code` is zero, this does nothing and returns zero.
+ */
+int giterr_capture(git_error_state *state, int error_code);
+
+/**
+ * Restore error state to a previous value, returning saved error code.
+ */
+int giterr_restore(git_error_state *state);
+
+/**
+ * Check a versioned structure for validity
+ */
+GIT_INLINE(int) giterr__check_version(const void *structure, unsigned int expected_max, const char *name)
+{
+ unsigned int actual;
+
+ if (!structure)
+ return 0;
+
+ actual = *(const unsigned int*)structure;
+ if (actual > 0 && actual <= expected_max)
+ return 0;
+
+ giterr_set(GITERR_INVALID, "Invalid version %d on %s", actual, name);
+ return -1;
+}
+#define GITERR_CHECK_VERSION(S,V,N) if (giterr__check_version(S,V,N) < 0) return -1
+
+/**
+ * Initialize a structure with a version.
+ */
+GIT_INLINE(void) git__init_structure(void *structure, size_t len, unsigned int version)
+{
+ memset(structure, 0, len);
+ *((int*)structure) = version;
+}
+#define GIT_INIT_STRUCTURE(S,V) git__init_structure(S, sizeof(*S), V)
+
+#define GIT_INIT_STRUCTURE_FROM_TEMPLATE(PTR,VERSION,TYPE,TPL) do { \
+ TYPE _tmpl = TPL; \
+ GITERR_CHECK_VERSION(&(VERSION), _tmpl.version, #TYPE); \
+ memcpy((PTR), &_tmpl, sizeof(_tmpl)); } while (0)
+
+/* NOTE: other giterr functions are in the public errors.h header file */
#include "util.h"
diff --git a/src/config.c b/src/config.c
index c48376f7c..8a0fb653c 100644
--- a/src/config.c
+++ b/src/config.c
@@ -1,57 +1,62 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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 "fileops.h"
-#include "hashtable.h"
+#include "sysdir.h"
#include "config.h"
#include "git2/config.h"
+#include "git2/sys/config.h"
#include "vector.h"
+#include "buf_text.h"
+#include "config_file.h"
+#if GIT_WIN32
+# include <windows.h>
+#endif
#include <ctype.h>
typedef struct {
- git_config_file *file;
- int priority;
+ git_refcount rc;
+
+ git_config_backend *file;
+ git_config_level_t level;
} file_internal;
-void git_config_free(git_config *cfg)
+static void file_internal_free(file_internal *internal)
+{
+ git_config_backend *file;
+
+ file = internal->file;
+ file->free(file);
+ git__free(internal);
+}
+
+static void config_free(git_config *cfg)
{
- unsigned int i;
- git_config_file *file;
+ size_t i;
file_internal *internal;
- for(i = 0; i < cfg->files.length; ++i){
+ for (i = 0; i < cfg->files.length; ++i) {
internal = git_vector_get(&cfg->files, i);
- file = internal->file;
- file->free(file);
- free(internal);
+ GIT_REFCOUNT_DEC(internal, file_internal_free);
}
git_vector_free(&cfg->files);
- free(cfg);
+
+ git__memzero(cfg, sizeof(*cfg));
+ git__free(cfg);
+}
+
+void git_config_free(git_config *cfg)
+{
+ if (cfg == NULL)
+ return;
+
+ GIT_REFCOUNT_DEC(cfg, config_free);
}
static int config_backend_cmp(const void *a, const void *b)
@@ -59,7 +64,7 @@ static int config_backend_cmp(const void *a, const void *b)
const file_internal *bk_a = (const file_internal *)(a);
const file_internal *bk_b = (const file_internal *)(b);
- return bk_b->priority - bk_a->priority;
+ return bk_b->level - bk_a->level;
}
int git_config_new(git_config **out)
@@ -67,125 +72,550 @@ int git_config_new(git_config **out)
git_config *cfg;
cfg = git__malloc(sizeof(git_config));
- if (cfg == NULL)
- return GIT_ENOMEM;
+ GITERR_CHECK_ALLOC(cfg);
memset(cfg, 0x0, sizeof(git_config));
if (git_vector_init(&cfg->files, 3, config_backend_cmp) < 0) {
- free(cfg);
- return GIT_ENOMEM;
+ git__free(cfg);
+ return -1;
}
*out = cfg;
-
- return GIT_SUCCESS;
+ GIT_REFCOUNT_INC(cfg);
+ return 0;
}
-int git_config_add_file_ondisk(git_config *cfg, const char *path, int priority)
+int git_config_add_file_ondisk(
+ git_config *cfg,
+ const char *path,
+ git_config_level_t level,
+ int force)
{
- git_config_file *file = NULL;
- int error;
+ git_config_backend *file = NULL;
+ struct stat st;
+ int res;
- error = git_config_file__ondisk(&file, path);
- if (error < GIT_SUCCESS)
- return error;
+ assert(cfg && path);
+
+ res = p_stat(path, &st);
+ if (res < 0 && errno != ENOENT) {
+ giterr_set(GITERR_CONFIG, "Error stat'ing config file '%s'", path);
+ return -1;
+ }
+
+ if (git_config_file__ondisk(&file, path) < 0)
+ return -1;
- error = git_config_add_file(cfg, file, priority);
- if (error < GIT_SUCCESS) {
+ if ((res = git_config_add_backend(cfg, file, level, force)) < 0) {
/*
* free manually; the file is not owned by the config
* instance yet and will not be freed on cleanup
*/
file->free(file);
- return error;
+ return res;
}
- return GIT_SUCCESS;
+ return 0;
}
-int git_config_open_ondisk(git_config **cfg, const char *path)
+int git_config_open_ondisk(git_config **out, const char *path)
{
int error;
+ git_config *config;
- error = git_config_new(cfg);
- if (error < GIT_SUCCESS)
- return error;
+ *out = NULL;
+
+ if (git_config_new(&config) < 0)
+ return -1;
- error = git_config_add_file_ondisk(*cfg, path, 1);
- if (error < GIT_SUCCESS)
- git_config_free(*cfg);
+ if ((error = git_config_add_file_ondisk(config, path, GIT_CONFIG_LEVEL_LOCAL, 0)) < 0)
+ git_config_free(config);
+ else
+ *out = config;
return error;
}
-int git_config_add_file(git_config *cfg, git_config_file *file, int priority)
+int git_config_snapshot(git_config **out, git_config *in)
{
+ int error = 0;
+ size_t i;
file_internal *internal;
- int error;
+ git_config *config;
+
+ *out = NULL;
+
+ if (git_config_new(&config) < 0)
+ return -1;
+
+ git_vector_foreach(&in->files, i, internal) {
+ git_config_backend *b;
+
+ if ((error = internal->file->snapshot(&b, internal->file)) < 0)
+ break;
+
+ if ((error = git_config_add_backend(config, b, internal->level, 0)) < 0) {
+ b->free(b);
+ break;
+ }
+ }
+
+ if (error < 0)
+ git_config_free(config);
+ else
+ *out = config;
+
+ return error;
+}
+
+static int find_internal_file_by_level(
+ file_internal **internal_out,
+ const git_config *cfg,
+ git_config_level_t level)
+{
+ int pos = -1;
+ file_internal *internal;
+ size_t i;
+
+ /* when passing GIT_CONFIG_HIGHEST_LEVEL, the idea is to get the config file
+ * which has the highest level. As config files are stored in a vector
+ * sorted by decreasing order of level, getting the file at position 0
+ * will do the job.
+ */
+ if (level == GIT_CONFIG_HIGHEST_LEVEL) {
+ pos = 0;
+ } else {
+ git_vector_foreach(&cfg->files, i, internal) {
+ if (internal->level == level)
+ pos = (int)i;
+ }
+ }
+
+ if (pos == -1) {
+ giterr_set(GITERR_CONFIG,
+ "No config file exists for the given level '%i'", (int)level);
+ return GIT_ENOTFOUND;
+ }
+
+ *internal_out = git_vector_get(&cfg->files, pos);
+
+ return 0;
+}
+
+static int duplicate_level(void **old_raw, void *new_raw)
+{
+ file_internal **old = (file_internal **)old_raw;
+
+ GIT_UNUSED(new_raw);
+
+ giterr_set(GITERR_CONFIG, "A file with the same level (%i) has already been added to the config", (int)(*old)->level);
+ return GIT_EEXISTS;
+}
+
+static void try_remove_existing_file_internal(
+ git_config *cfg,
+ git_config_level_t level)
+{
+ int pos = -1;
+ file_internal *internal;
+ size_t i;
+
+ git_vector_foreach(&cfg->files, i, internal) {
+ if (internal->level == level)
+ pos = (int)i;
+ }
+
+ if (pos == -1)
+ return;
+
+ internal = git_vector_get(&cfg->files, pos);
+
+ if (git_vector_remove(&cfg->files, pos) < 0)
+ return;
+
+ GIT_REFCOUNT_DEC(internal, file_internal_free);
+}
+
+static int git_config__add_internal(
+ git_config *cfg,
+ file_internal *internal,
+ git_config_level_t level,
+ int force)
+{
+ int result;
+
+ /* delete existing config file for level if it exists */
+ if (force)
+ try_remove_existing_file_internal(cfg, level);
+
+ if ((result = git_vector_insert_sorted(&cfg->files,
+ internal, &duplicate_level)) < 0)
+ return result;
+
+ git_vector_sort(&cfg->files);
+ internal->file->cfg = cfg;
+
+ GIT_REFCOUNT_INC(internal);
+
+ return 0;
+}
+
+int git_config_open_global(git_config **cfg_out, git_config *cfg)
+{
+ if (!git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_XDG))
+ return 0;
+
+ return git_config_open_level(cfg_out, cfg, GIT_CONFIG_LEVEL_GLOBAL);
+}
+
+int git_config_open_level(
+ git_config **cfg_out,
+ const git_config *cfg_parent,
+ git_config_level_t level)
+{
+ git_config *cfg;
+ file_internal *internal;
+ int res;
+
+ if ((res = find_internal_file_by_level(&internal, cfg_parent, level)) < 0)
+ return res;
+
+ if ((res = git_config_new(&cfg)) < 0)
+ return res;
+
+ if ((res = git_config__add_internal(cfg, internal, level, true)) < 0) {
+ git_config_free(cfg);
+ return res;
+ }
+
+ *cfg_out = cfg;
+
+ return 0;
+}
+
+int git_config_add_backend(
+ git_config *cfg,
+ git_config_backend *file,
+ git_config_level_t level,
+ int force)
+{
+ file_internal *internal;
+ int result;
assert(cfg && file);
- if ((error = file->open(file)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to open config file");
+ GITERR_CHECK_VERSION(file, GIT_CONFIG_BACKEND_VERSION, "git_config_backend");
+
+ if ((result = file->open(file, level)) < 0)
+ return result;
internal = git__malloc(sizeof(file_internal));
- if (internal == NULL)
- return GIT_ENOMEM;
+ GITERR_CHECK_ALLOC(internal);
+
+ memset(internal, 0x0, sizeof(file_internal));
internal->file = file;
- internal->priority = priority;
+ internal->level = level;
- if (git_vector_insert(&cfg->files, internal) < 0) {
- free(internal);
- return GIT_ENOMEM;
+ if ((result = git_config__add_internal(cfg, internal, level, force)) < 0) {
+ git__free(internal);
+ return result;
}
- git_vector_sort(&cfg->files);
- internal->file->cfg = cfg;
+ return 0;
+}
- return GIT_SUCCESS;
+int git_config_refresh(git_config *cfg)
+{
+ int error = 0;
+ size_t i;
+
+ for (i = 0; i < cfg->files.length && !error; ++i) {
+ file_internal *internal = git_vector_get(&cfg->files, i);
+ git_config_backend *file = internal->file;
+ error = file->refresh(file);
+ }
+
+ if (!error && GIT_REFCOUNT_OWNER(cfg) != NULL)
+ git_repository__cvar_cache_clear(GIT_REFCOUNT_OWNER(cfg));
+
+ return error;
}
/*
* Loop over all the variables
*/
-int git_config_foreach(git_config *cfg, int (*fn)(const char *, const char *, void *), void *data)
+typedef struct {
+ git_config_iterator parent;
+ git_config_iterator *current;
+ const git_config *cfg;
+ regex_t regex;
+ int has_regex;
+ size_t i;
+} all_iter;
+
+static int find_next_backend(size_t *out, const git_config *cfg, size_t i)
{
- int ret = GIT_SUCCESS;
- unsigned int i;
file_internal *internal;
- git_config_file *file;
- for(i = 0; i < cfg->files.length && ret == 0; ++i) {
- internal = git_vector_get(&cfg->files, i);
- file = internal->file;
- ret = file->foreach(file, fn, data);
+ for (; i > 0; --i) {
+ internal = git_vector_get(&cfg->files, i - 1);
+ if (!internal || !internal->file)
+ continue;
+
+ *out = i;
+ return 0;
}
- return ret;
+ return -1;
+}
+
+static int all_iter_next(git_config_entry **entry, git_config_iterator *_iter)
+{
+ all_iter *iter = (all_iter *) _iter;
+ file_internal *internal;
+ git_config_backend *backend;
+ size_t i;
+ int error = 0;
+
+ if (iter->current != NULL &&
+ (error = iter->current->next(entry, iter->current)) == 0) {
+ return 0;
+ }
+
+ if (error < 0 && error != GIT_ITEROVER)
+ return error;
+
+ do {
+ if (find_next_backend(&i, iter->cfg, iter->i) < 0)
+ return GIT_ITEROVER;
+
+ internal = git_vector_get(&iter->cfg->files, i - 1);
+ backend = internal->file;
+ iter->i = i - 1;
+
+ if (iter->current)
+ iter->current->free(iter->current);
+
+ iter->current = NULL;
+ error = backend->iterator(&iter->current, backend);
+ if (error == GIT_ENOTFOUND)
+ continue;
+
+ if (error < 0)
+ return error;
+
+ error = iter->current->next(entry, iter->current);
+ /* If this backend is empty, then keep going */
+ if (error == GIT_ITEROVER)
+ continue;
+
+ return error;
+
+ } while(1);
+
+ return GIT_ITEROVER;
+}
+
+static int all_iter_glob_next(git_config_entry **entry, git_config_iterator *_iter)
+{
+ int error;
+ all_iter *iter = (all_iter *) _iter;
+
+ /*
+ * We use the "normal" function to grab the next one across
+ * backends and then apply the regex
+ */
+ while ((error = all_iter_next(entry, _iter)) == 0) {
+ /* skip non-matching keys if regexp was provided */
+ if (regexec(&iter->regex, (*entry)->name, 0, NULL, 0) != 0)
+ continue;
+
+ /* and simply return if we like the entry's name */
+ return 0;
+ }
+
+ return error;
+}
+
+static void all_iter_free(git_config_iterator *_iter)
+{
+ all_iter *iter = (all_iter *) _iter;
+
+ if (iter->current)
+ iter->current->free(iter->current);
+
+ git__free(iter);
+}
+
+static void all_iter_glob_free(git_config_iterator *_iter)
+{
+ all_iter *iter = (all_iter *) _iter;
+
+ regfree(&iter->regex);
+ all_iter_free(_iter);
+}
+
+int git_config_iterator_new(git_config_iterator **out, const git_config *cfg)
+{
+ all_iter *iter;
+
+ iter = git__calloc(1, sizeof(all_iter));
+ GITERR_CHECK_ALLOC(iter);
+
+ iter->parent.free = all_iter_free;
+ iter->parent.next = all_iter_next;
+
+ iter->i = cfg->files.length;
+ iter->cfg = cfg;
+
+ *out = (git_config_iterator *) iter;
+
+ return 0;
+}
+
+int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cfg, const char *regexp)
+{
+ all_iter *iter;
+ int result;
+
+ if (regexp == NULL)
+ return git_config_iterator_new(out, cfg);
+
+ iter = git__calloc(1, sizeof(all_iter));
+ GITERR_CHECK_ALLOC(iter);
+
+ if ((result = regcomp(&iter->regex, regexp, REG_EXTENDED)) < 0) {
+ giterr_set_regex(&iter->regex, result);
+ regfree(&iter->regex);
+ git__free(iter);
+ return -1;
+ }
+
+ iter->parent.next = all_iter_glob_next;
+ iter->parent.free = all_iter_glob_free;
+ iter->i = cfg->files.length;
+ iter->cfg = cfg;
+
+ *out = (git_config_iterator *) iter;
+
+ return 0;
}
-int git_config_delete(git_config *cfg, const char *name)
+int git_config_foreach(
+ const git_config *cfg, git_config_foreach_cb cb, void *payload)
{
- return git_config_set_string(cfg, name, NULL);
+ return git_config_foreach_match(cfg, NULL, cb, payload);
+}
+
+int git_config_backend_foreach_match(
+ git_config_backend *backend,
+ const char *regexp,
+ git_config_foreach_cb cb,
+ void *payload)
+{
+ git_config_entry *entry;
+ git_config_iterator* iter;
+ regex_t regex;
+ int error = 0;
+
+ if (regexp != NULL) {
+ if ((error = regcomp(&regex, regexp, REG_EXTENDED)) < 0) {
+ giterr_set_regex(&regex, error);
+ regfree(&regex);
+ return -1;
+ }
+ }
+
+ if ((error = backend->iterator(&iter, backend)) < 0) {
+ iter = NULL;
+ return -1;
+ }
+
+ while (!(iter->next(&entry, iter) < 0)) {
+ /* skip non-matching keys if regexp was provided */
+ if (regexp && regexec(&regex, entry->name, 0, NULL, 0) != 0)
+ continue;
+
+ /* abort iterator on non-zero return value */
+ if ((error = cb(entry, payload)) != 0) {
+ giterr_set_after_callback(error);
+ break;
+ }
+ }
+
+ if (regexp != NULL)
+ regfree(&regex);
+
+ iter->free(iter);
+
+ return error;
+}
+
+int git_config_foreach_match(
+ const git_config *cfg,
+ const char *regexp,
+ git_config_foreach_cb cb,
+ void *payload)
+{
+ int error;
+ git_config_iterator *iter;
+ git_config_entry *entry;
+
+ if ((error = git_config_iterator_glob_new(&iter, cfg, regexp)) < 0)
+ return error;
+
+ while (!(error = git_config_next(&entry, iter))) {
+ if ((error = cb(entry, payload)) != 0) {
+ giterr_set_after_callback(error);
+ break;
+ }
+ }
+
+ git_config_iterator_free(iter);
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ return error;
}
/**************
* Setters
**************/
-int git_config_set_long(git_config *cfg, const char *name, long int value)
+static int config_error_nofiles(const char *name)
+{
+ giterr_set(GITERR_CONFIG,
+ "Cannot set value for '%s' when no config files exist", name);
+ return GIT_ENOTFOUND;
+}
+
+int git_config_delete_entry(git_config *cfg, const char *name)
+{
+ git_config_backend *file;
+ file_internal *internal;
+
+ internal = git_vector_get(&cfg->files, 0);
+ if (!internal || !internal->file)
+ return config_error_nofiles(name);
+ file = internal->file;
+
+ return file->del(file, name);
+}
+
+int git_config_set_int64(git_config *cfg, const char *name, int64_t value)
{
char str_value[32]; /* All numbers should fit in here */
- snprintf(str_value, sizeof(str_value), "%ld", value);
+ p_snprintf(str_value, sizeof(str_value), "%" PRId64, value);
return git_config_set_string(cfg, name, str_value);
}
-int git_config_set_int(git_config *cfg, const char *name, int value)
+int git_config_set_int32(git_config *cfg, const char *name, int32_t value)
{
- return git_config_set_long(cfg, name, value);
+ return git_config_set_int64(cfg, name, (int64_t)value);
}
int git_config_set_bool(git_config *cfg, const char *name, int value)
@@ -195,160 +625,693 @@ int git_config_set_bool(git_config *cfg, const char *name, int value)
int git_config_set_string(git_config *cfg, const char *name, const char *value)
{
+ int error;
+ git_config_backend *file;
file_internal *internal;
- git_config_file *file;
- if (cfg->files.length == 0)
- return git__throw(GIT_EINVALIDARGS, "Cannot set variable value; no files open in the `git_config` instance");
+ if (!value) {
+ giterr_set(GITERR_CONFIG, "The value to set cannot be NULL");
+ return -1;
+ }
internal = git_vector_get(&cfg->files, 0);
+ if (!internal || !internal->file)
+ return config_error_nofiles(name);
file = internal->file;
- return file->set(file, name, value);
+ error = file->set(file, name, value);
+
+ if (!error && GIT_REFCOUNT_OWNER(cfg) != NULL)
+ git_repository__cvar_cache_clear(GIT_REFCOUNT_OWNER(cfg));
+
+ return error;
+}
+
+int git_config__update_entry(
+ git_config *config,
+ const char *key,
+ const char *value,
+ bool overwrite_existing,
+ bool only_if_existing)
+{
+ int error = 0;
+ const git_config_entry *ce = NULL;
+
+ if ((error = git_config__lookup_entry(&ce, config, key, false)) < 0)
+ return error;
+
+ if (!ce && only_if_existing) /* entry doesn't exist */
+ return 0;
+ if (ce && !overwrite_existing) /* entry would be overwritten */
+ return 0;
+ if (value && ce && ce->value && !strcmp(ce->value, value)) /* no change */
+ return 0;
+ if (!value && (!ce || !ce->value)) /* asked to delete absent entry */
+ return 0;
+
+ if (!value)
+ error = git_config_delete_entry(config, key);
+ else
+ error = git_config_set_string(config, key, value);
+
+ return error;
}
/***********
* Getters
***********/
-int git_config_get_long(git_config *cfg, const char *name, long int *out)
+static int config_error_notfound(const char *name)
{
- const char *value, *num_end;
- int ret;
- long int num;
+ giterr_set(GITERR_CONFIG, "Config value '%s' was not found", name);
+ return GIT_ENOTFOUND;
+}
- ret = git_config_get_string(cfg, name, &value);
- if (ret < GIT_SUCCESS)
- return git__rethrow(ret, "Failed to get value for %s", name);
+enum {
+ GET_ALL_ERRORS = 0,
+ GET_NO_MISSING = 1,
+ GET_NO_ERRORS = 2
+};
+
+static int get_entry(
+ const git_config_entry **out,
+ const git_config *cfg,
+ const char *name,
+ bool normalize_name,
+ int want_errors)
+{
+ int res = GIT_ENOTFOUND;
+ const char *key = name;
+ char *normalized = NULL;
+ size_t i;
+ file_internal *internal;
- ret = git__strtol32(&num, value, &num_end, 0);
- if (ret < GIT_SUCCESS)
- return git__rethrow(ret, "Failed to get value for %s", name);
+ *out = NULL;
- switch (*num_end) {
- case '\0':
- break;
- case 'k':
- case 'K':
- num *= 1024;
- break;
- case 'm':
- case 'M':
- num *= 1024 * 1024;
- break;
- case 'g':
- case 'G':
- num *= 1024 * 1024 * 1024;
- break;
- default:
- return git__throw(GIT_EINVALIDTYPE, "Failed to get value for %s. Value is of invalid type", name);
+ if (normalize_name) {
+ if ((res = git_config__normalize_name(name, &normalized)) < 0)
+ goto cleanup;
+ key = normalized;
+ }
+
+ res = GIT_ENOTFOUND;
+ git_vector_foreach(&cfg->files, i, internal) {
+ if (!internal || !internal->file)
+ continue;
+
+ res = internal->file->get(internal->file, key, out);
+ if (res != GIT_ENOTFOUND)
+ break;
}
- *out = num;
+ git__free(normalized);
- return GIT_SUCCESS;
+cleanup:
+ if (res == GIT_ENOTFOUND)
+ res = (want_errors > GET_ALL_ERRORS) ? 0 : config_error_notfound(name);
+ else if (res && (want_errors == GET_NO_ERRORS)) {
+ giterr_clear();
+ res = 0;
+ }
+
+ return res;
}
-int git_config_get_int(git_config *cfg, const char *name, int *out)
+int git_config_get_entry(
+ const git_config_entry **out, const git_config *cfg, const char *name)
{
- long int tmp;
+ return get_entry(out, cfg, name, true, GET_ALL_ERRORS);
+}
+
+int git_config__lookup_entry(
+ const git_config_entry **out,
+ const git_config *cfg,
+ const char *key,
+ bool no_errors)
+{
+ return get_entry(
+ out, cfg, key, false, no_errors ? GET_NO_ERRORS : GET_NO_MISSING);
+}
+
+int git_config_get_mapped(
+ int *out,
+ const git_config *cfg,
+ const char *name,
+ const git_cvar_map *maps,
+ size_t map_n)
+{
+ const git_config_entry *entry;
int ret;
- ret = git_config_get_long(cfg, name, &tmp);
+ if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0)
+ return ret;
+
+ return git_config_lookup_map_value(out, maps, map_n, entry->value);
+}
+
+int git_config_get_int64(int64_t *out, const git_config *cfg, const char *name)
+{
+ const git_config_entry *entry;
+ int ret;
- *out = (int) tmp;
+ if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0)
+ return ret;
+ return git_config_parse_int64(out, entry->value);
+}
+
+int git_config_get_int32(int32_t *out, const git_config *cfg, const char *name)
+{
+ const git_config_entry *entry;
+ int ret;
+
+ if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0)
+ return ret;
+
+ return git_config_parse_int32(out, entry->value);
+}
+
+int git_config_get_bool(int *out, const git_config *cfg, const char *name)
+{
+ const git_config_entry *entry;
+ int ret;
+
+ if ((ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS)) < 0)
+ return ret;
+
+ return git_config_parse_bool(out, entry->value);
+}
+
+int git_config_get_string(
+ const char **out, const git_config *cfg, const char *name)
+{
+ const git_config_entry *entry;
+ int ret = get_entry(&entry, cfg, name, true, GET_ALL_ERRORS);
+ *out = !ret ? (entry->value ? entry->value : "") : NULL;
return ret;
}
-int git_config_get_bool(git_config *cfg, const char *name, int *out)
+const char *git_config__get_string_force(
+ const git_config *cfg, const char *key, const char *fallback_value)
+{
+ const git_config_entry *entry;
+ get_entry(&entry, cfg, key, false, GET_NO_ERRORS);
+ return (entry && entry->value) ? entry->value : fallback_value;
+}
+
+int git_config__get_bool_force(
+ const git_config *cfg, const char *key, int fallback_value)
+{
+ int val = fallback_value;
+ const git_config_entry *entry;
+
+ get_entry(&entry, cfg, key, false, GET_NO_ERRORS);
+
+ if (entry && git_config_parse_bool(&val, entry->value) < 0)
+ giterr_clear();
+
+ return val;
+}
+
+int git_config__get_int_force(
+ const git_config *cfg, const char *key, int fallback_value)
+{
+ int32_t val = (int32_t)fallback_value;
+ const git_config_entry *entry;
+
+ get_entry(&entry, cfg, key, false, GET_NO_ERRORS);
+
+ if (entry && git_config_parse_int32(&val, entry->value) < 0)
+ giterr_clear();
+
+ return (int)val;
+}
+
+int git_config_get_multivar_foreach(
+ const git_config *cfg, const char *name, const char *regexp,
+ git_config_foreach_cb cb, void *payload)
{
- const char *value;
- int error = GIT_SUCCESS;
+ int err, found;
+ git_config_iterator *iter;
+ git_config_entry *entry;
- error = git_config_get_string(cfg, name, &value);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to get value for %s", name);
+ if ((err = git_config_multivar_iterator_new(&iter, cfg, name, regexp)) < 0)
+ return err;
- /* A missing value means true */
- if (value == NULL) {
- *out = 1;
- return GIT_SUCCESS;
+ found = 0;
+ while ((err = iter->next(&entry, iter)) == 0) {
+ found = 1;
+
+ if ((err = cb(entry, payload)) != 0) {
+ giterr_set_after_callback(err);
+ break;
+ }
}
- if (!strcasecmp(value, "true") ||
- !strcasecmp(value, "yes") ||
- !strcasecmp(value, "on")) {
- *out = 1;
- return GIT_SUCCESS;
+ iter->free(iter);
+ if (err == GIT_ITEROVER)
+ err = 0;
+
+ if (found == 0 && err == 0)
+ err = config_error_notfound(name);
+
+ return err;
+}
+
+typedef struct {
+ git_config_iterator parent;
+ git_config_iterator *iter;
+ char *name;
+ regex_t regex;
+ int have_regex;
+} multivar_iter;
+
+static int multivar_iter_next(git_config_entry **entry, git_config_iterator *_iter)
+{
+ multivar_iter *iter = (multivar_iter *) _iter;
+ int error = 0;
+
+ while ((error = iter->iter->next(entry, iter->iter)) == 0) {
+ if (git__strcmp(iter->name, (*entry)->name))
+ continue;
+
+ if (!iter->have_regex)
+ return 0;
+
+ if (regexec(&iter->regex, (*entry)->value, 0, NULL, 0) == 0)
+ return 0;
}
- if (!strcasecmp(value, "false") ||
- !strcasecmp(value, "no") ||
- !strcasecmp(value, "off")) {
- *out = 0;
- return GIT_SUCCESS;
+
+ return error;
+}
+
+void multivar_iter_free(git_config_iterator *_iter)
+{
+ multivar_iter *iter = (multivar_iter *) _iter;
+
+ iter->iter->free(iter->iter);
+
+ git__free(iter->name);
+ regfree(&iter->regex);
+ git__free(iter);
+}
+
+int git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp)
+{
+ multivar_iter *iter = NULL;
+ git_config_iterator *inner = NULL;
+ int error;
+
+ if ((error = git_config_iterator_new(&inner, cfg)) < 0)
+ return error;
+
+ iter = git__calloc(1, sizeof(multivar_iter));
+ GITERR_CHECK_ALLOC(iter);
+
+ if ((error = git_config__normalize_name(name, &iter->name)) < 0)
+ goto on_error;
+
+ if (regexp != NULL) {
+ error = regcomp(&iter->regex, regexp, REG_EXTENDED);
+ if (error < 0) {
+ giterr_set_regex(&iter->regex, error);
+ error = -1;
+ regfree(&iter->regex);
+ goto on_error;
+ }
+
+ iter->have_regex = 1;
}
- /* Try to parse it as an integer */
- error = git_config_get_int(cfg, name, out);
- if (error == GIT_SUCCESS)
- *out = !!(*out);
+ iter->iter = inner;
+ iter->parent.free = multivar_iter_free;
+ iter->parent.next = multivar_iter_next;
+
+ *out = (git_config_iterator *) iter;
+
+ return 0;
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to get value for %s", name);
+on_error:
+
+ inner->free(inner);
+ git__free(iter);
return error;
}
-int git_config_get_string(git_config *cfg, const char *name, const char **out)
+int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value)
{
+ git_config_backend *file;
file_internal *internal;
- git_config_file *file;
- int error = GIT_ENOTFOUND;
- unsigned int i;
- if (cfg->files.length == 0)
- return git__throw(GIT_EINVALIDARGS, "Cannot get variable value; no files open in the `git_config` instance");
+ internal = git_vector_get(&cfg->files, 0);
+ if (!internal || !internal->file)
+ return config_error_nofiles(name);
+ file = internal->file;
- for (i = 0; i < cfg->files.length; ++i) {
- internal = git_vector_get(&cfg->files, i);
- file = internal->file;
- if ((error = file->get(file, name, out)) == GIT_SUCCESS)
- return GIT_SUCCESS;
- }
+ return file->set_multivar(file, name, regexp, value);
+}
+
+int git_config_delete_multivar(git_config *cfg, const char *name, const char *regexp)
+{
+ git_config_backend *file;
+ file_internal *internal;
+
+ internal = git_vector_get(&cfg->files, 0);
+ if (!internal || !internal->file)
+ return config_error_nofiles(name);
+ file = internal->file;
- return git__throw(error, "Config value '%s' not found", name);
+ return file->del_multivar(file, name, regexp);
}
-int git_config_find_global(char *global_config_path)
+int git_config_next(git_config_entry **entry, git_config_iterator *iter)
{
- const char *home;
+ return iter->next(entry, iter);
+}
- home = getenv("HOME");
+void git_config_iterator_free(git_config_iterator *iter)
+{
+ if (iter == NULL)
+ return;
-#ifdef GIT_WIN32
- if (home == NULL)
- home = getenv("USERPROFILE");
-#endif
+ iter->free(iter);
+}
- if (home == NULL)
- return git__throw(GIT_EOSERR, "Failed to open global config file. Cannot locate the user's home directory");
+int git_config_find_global(git_buf *path)
+{
+ git_buf_sanitize(path);
+ return git_sysdir_find_global_file(path, GIT_CONFIG_FILENAME_GLOBAL);
+}
- git_path_join(global_config_path, home, GIT_CONFIG_FILENAME);
+int git_config_find_xdg(git_buf *path)
+{
+ git_buf_sanitize(path);
+ return git_sysdir_find_xdg_file(path, GIT_CONFIG_FILENAME_XDG);
+}
+
+int git_config_find_system(git_buf *path)
+{
+ git_buf_sanitize(path);
+ return git_sysdir_find_system_file(path, GIT_CONFIG_FILENAME_SYSTEM);
+}
+
+int git_config__global_location(git_buf *buf)
+{
+ const git_buf *paths;
+ const char *sep, *start;
- if (git_futils_exists(global_config_path) < GIT_SUCCESS)
- return git__throw(GIT_EOSERR, "Failed to open global config file. The file does not exist");
+ if (git_sysdir_get(&paths, GIT_SYSDIR_GLOBAL) < 0)
+ return -1;
- return GIT_SUCCESS;
+ /* no paths, so give up */
+ if (!paths || !git_buf_len(paths))
+ return -1;
+
+ /* find unescaped separator or end of string */
+ for (sep = start = git_buf_cstr(paths); *sep; ++sep) {
+ if (*sep == GIT_PATH_LIST_SEPARATOR &&
+ (sep <= start || sep[-1] != '\\'))
+ break;
+ }
+
+ if (git_buf_set(buf, start, (size_t)(sep - start)) < 0)
+ return -1;
+
+ return git_buf_joinpath(buf, buf->ptr, GIT_CONFIG_FILENAME_GLOBAL);
}
-int git_config_open_global(git_config **out)
+int git_config_open_default(git_config **out)
{
int error;
- char global_path[GIT_PATH_MAX];
+ git_config *cfg = NULL;
+ git_buf buf = GIT_BUF_INIT;
- if ((error = git_config_find_global(global_path)) < GIT_SUCCESS)
+ if ((error = git_config_new(&cfg)) < 0)
return error;
- return git_config_open_ondisk(out, global_path);
+ if (!git_config_find_global(&buf) || !git_config__global_location(&buf)) {
+ error = git_config_add_file_ondisk(cfg, buf.ptr,
+ GIT_CONFIG_LEVEL_GLOBAL, 0);
+ }
+
+ if (!error && !git_config_find_xdg(&buf))
+ error = git_config_add_file_ondisk(cfg, buf.ptr,
+ GIT_CONFIG_LEVEL_XDG, 0);
+
+ if (!error && !git_config_find_system(&buf))
+ error = git_config_add_file_ondisk(cfg, buf.ptr,
+ GIT_CONFIG_LEVEL_SYSTEM, 0);
+
+ git_buf_free(&buf);
+
+ if (error) {
+ git_config_free(cfg);
+ cfg = NULL;
+ }
+
+ *out = cfg;
+
+ return error;
}
+/***********
+ * Parsers
+ ***********/
+
+int git_config_lookup_map_value(
+ int *out,
+ const git_cvar_map *maps,
+ size_t map_n,
+ const char *value)
+{
+ size_t i;
+
+ if (!value)
+ goto fail_parse;
+
+ for (i = 0; i < map_n; ++i) {
+ const git_cvar_map *m = maps + i;
+
+ switch (m->cvar_type) {
+ case GIT_CVAR_FALSE:
+ case GIT_CVAR_TRUE: {
+ int bool_val;
+
+ if (git__parse_bool(&bool_val, value) == 0 &&
+ bool_val == (int)m->cvar_type) {
+ *out = m->map_value;
+ return 0;
+ }
+ break;
+ }
+
+ case GIT_CVAR_INT32:
+ if (git_config_parse_int32(out, value) == 0)
+ return 0;
+ break;
+
+ case GIT_CVAR_STRING:
+ if (strcasecmp(value, m->str_match) == 0) {
+ *out = m->map_value;
+ return 0;
+ }
+ break;
+ }
+ }
+
+fail_parse:
+ giterr_set(GITERR_CONFIG, "Failed to map '%s'", value);
+ return -1;
+}
+
+int git_config_parse_bool(int *out, const char *value)
+{
+ if (git__parse_bool(out, value) == 0)
+ return 0;
+
+ if (git_config_parse_int32(out, value) == 0) {
+ *out = !!(*out);
+ return 0;
+ }
+
+ giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a boolean value", value);
+ return -1;
+}
+
+int git_config_parse_int64(int64_t *out, const char *value)
+{
+ const char *num_end;
+ int64_t num;
+
+ if (!value || git__strtol64(&num, value, &num_end, 0) < 0)
+ goto fail_parse;
+
+ switch (*num_end) {
+ case 'g':
+ case 'G':
+ num *= 1024;
+ /* fallthrough */
+
+ case 'm':
+ case 'M':
+ num *= 1024;
+ /* fallthrough */
+
+ case 'k':
+ case 'K':
+ num *= 1024;
+
+ /* check that that there are no more characters after the
+ * given modifier suffix */
+ if (num_end[1] != '\0')
+ return -1;
+
+ /* fallthrough */
+
+ case '\0':
+ *out = num;
+ return 0;
+
+ default:
+ goto fail_parse;
+ }
+
+fail_parse:
+ giterr_set(GITERR_CONFIG, "Failed to parse '%s' as an integer", value ? value : "(null)");
+ return -1;
+}
+
+int git_config_parse_int32(int32_t *out, const char *value)
+{
+ int64_t tmp;
+ int32_t truncate;
+
+ if (git_config_parse_int64(&tmp, value) < 0)
+ goto fail_parse;
+
+ truncate = tmp & 0xFFFFFFFF;
+ if (truncate != tmp)
+ goto fail_parse;
+
+ *out = truncate;
+ return 0;
+
+fail_parse:
+ giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a 32-bit integer", value ? value : "(null)");
+ return -1;
+}
+
+/* Take something the user gave us and make it nice for our hash function */
+int git_config__normalize_name(const char *in, char **out)
+{
+ char *name, *fdot, *ldot;
+
+ assert(in && out);
+
+ name = git__strdup(in);
+ GITERR_CHECK_ALLOC(name);
+
+ fdot = strchr(name, '.');
+ ldot = strrchr(name, '.');
+
+ if (fdot == NULL || fdot == name || ldot == NULL || !ldot[1])
+ goto invalid;
+
+ /* Validate and downcase up to first dot and after last dot */
+ if (git_config_file_normalize_section(name, fdot) < 0 ||
+ git_config_file_normalize_section(ldot + 1, NULL) < 0)
+ goto invalid;
+
+ /* If there is a middle range, make sure it doesn't have newlines */
+ while (fdot < ldot)
+ if (*fdot++ == '\n')
+ goto invalid;
+
+ *out = name;
+ return 0;
+
+invalid:
+ git__free(name);
+ giterr_set(GITERR_CONFIG, "Invalid config item name '%s'", in);
+ return GIT_EINVALIDSPEC;
+}
+
+struct rename_data {
+ git_config *config;
+ git_buf *name;
+ size_t old_len;
+};
+
+static int rename_config_entries_cb(
+ const git_config_entry *entry,
+ void *payload)
+{
+ int error = 0;
+ struct rename_data *data = (struct rename_data *)payload;
+ size_t base_len = git_buf_len(data->name);
+
+ if (base_len > 0 &&
+ !(error = git_buf_puts(data->name, entry->name + data->old_len)))
+ {
+ error = git_config_set_string(
+ data->config, git_buf_cstr(data->name), entry->value);
+
+ git_buf_truncate(data->name, base_len);
+ }
+
+ if (!error)
+ error = git_config_delete_entry(data->config, entry->name);
+
+ return error;
+}
+
+int git_config_rename_section(
+ git_repository *repo,
+ const char *old_section_name,
+ const char *new_section_name)
+{
+ git_config *config;
+ git_buf pattern = GIT_BUF_INIT, replace = GIT_BUF_INIT;
+ int error = 0;
+ struct rename_data data;
+
+ git_buf_text_puts_escape_regex(&pattern, old_section_name);
+
+ if ((error = git_buf_puts(&pattern, "\\..+")) < 0)
+ goto cleanup;
+
+ if ((error = git_repository_config__weakptr(&config, repo)) < 0)
+ goto cleanup;
+
+ data.config = config;
+ data.name = &replace;
+ data.old_len = strlen(old_section_name) + 1;
+
+ if ((error = git_buf_join(&replace, '.', new_section_name, "")) < 0)
+ goto cleanup;
+
+ if (new_section_name != NULL &&
+ (error = git_config_file_normalize_section(
+ replace.ptr, strchr(replace.ptr, '.'))) < 0)
+ {
+ giterr_set(
+ GITERR_CONFIG, "Invalid config section '%s'", new_section_name);
+ goto cleanup;
+ }
+
+ error = git_config_foreach_match(
+ config, git_buf_cstr(&pattern), rename_config_entries_cb, &data);
+
+cleanup:
+ git_buf_free(&pattern);
+ git_buf_free(&replace);
+
+ return error;
+}
+
+int git_config_init_backend(git_config_backend *backend, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ backend, version, git_config_backend, GIT_CONFIG_BACKEND_INIT);
+ return 0;
+}
diff --git a/src/config.h b/src/config.h
index 673887e39..b0dcb49ac 100644
--- a/src/config.h
+++ b/src/config.h
@@ -1,15 +1,85 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
#ifndef INCLUDE_config_h__
#define INCLUDE_config_h__
#include "git2.h"
#include "git2/config.h"
#include "vector.h"
+#include "repository.h"
+
+#define GIT_CONFIG_FILENAME_SYSTEM "gitconfig"
+#define GIT_CONFIG_FILENAME_GLOBAL ".gitconfig"
+#define GIT_CONFIG_FILENAME_XDG "config"
-#define GIT_CONFIG_FILENAME ".gitconfig"
#define GIT_CONFIG_FILENAME_INREPO "config"
+#define GIT_CONFIG_FILE_MODE 0666
struct git_config {
+ git_refcount rc;
git_vector files;
};
+extern int git_config__global_location(git_buf *buf);
+
+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(git_config_backend **out, const char *path);
+
+extern int git_config__normalize_name(const char *in, char **out);
+
+/* internal only: does not normalize key and sets out to NULL if not found */
+extern int git_config__lookup_entry(
+ const git_config_entry **out,
+ const git_config *cfg,
+ const char *key,
+ bool no_errors);
+
+/* internal only: update and/or delete entry string with constraints */
+extern int git_config__update_entry(
+ git_config *cfg,
+ const char *key,
+ const char *value,
+ bool overwrite_existing,
+ bool only_if_existing);
+
+/*
+ * Lookup functions that cannot fail. These functions look up a config
+ * value and return a fallback value if the value is missing or if any
+ * failures occur while trying to access the value.
+ */
+
+extern const char *git_config__get_string_force(
+ const git_config *cfg, const char *key, const char *fallback_value);
+
+extern int git_config__get_bool_force(
+ const git_config *cfg, const char *key, int fallback_value);
+
+extern int git_config__get_int_force(
+ const git_config *cfg, const char *key, int fallback_value);
+
+/* API for repository cvar-style lookups from config - not cached, but
+ * uses cvar value maps and fallbacks
+ */
+extern int git_config__cvar(
+ int *out, git_config *config, git_cvar_cached cvar);
+
#endif
diff --git a/src/config_cache.c b/src/config_cache.c
new file mode 100644
index 000000000..45c39ce17
--- /dev/null
+++ b/src/config_cache.c
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "fileops.h"
+#include "repository.h"
+#include "config.h"
+#include "git2/config.h"
+#include "vector.h"
+#include "filter.h"
+
+struct map_data {
+ const char *cvar_name;
+ git_cvar_map *maps;
+ size_t map_count;
+ int default_value;
+};
+
+/*
+ * core.eol
+ * Sets the line ending type to use in the working directory for
+ * files that have the text property set. Alternatives are lf, crlf
+ * and native, which uses the platform's native line ending. The default
+ * value is native. See gitattributes(5) for more information on
+ * end-of-line conversion.
+ */
+static git_cvar_map _cvar_map_eol[] = {
+ {GIT_CVAR_FALSE, NULL, GIT_EOL_UNSET},
+ {GIT_CVAR_STRING, "lf", GIT_EOL_LF},
+ {GIT_CVAR_STRING, "crlf", GIT_EOL_CRLF},
+ {GIT_CVAR_STRING, "native", GIT_EOL_NATIVE}
+};
+
+/*
+ * core.autocrlf
+ * Setting this variable to "true" is almost the same as setting
+ * the text attribute to "auto" on all files except that text files are
+ * not guaranteed to be normalized: files that contain CRLF in the
+ * repository will not be touched. Use this setting if you want to have
+ * CRLF line endings in your working directory even though the repository
+ * does not have normalized line endings. This variable can be set to input,
+ * in which case no output conversion is performed.
+ */
+static git_cvar_map _cvar_map_autocrlf[] = {
+ {GIT_CVAR_FALSE, NULL, GIT_AUTO_CRLF_FALSE},
+ {GIT_CVAR_TRUE, NULL, GIT_AUTO_CRLF_TRUE},
+ {GIT_CVAR_STRING, "input", GIT_AUTO_CRLF_INPUT}
+};
+
+static git_cvar_map _cvar_map_safecrlf[] = {
+ {GIT_CVAR_FALSE, NULL, GIT_SAFE_CRLF_FALSE},
+ {GIT_CVAR_TRUE, NULL, GIT_SAFE_CRLF_FAIL},
+ {GIT_CVAR_STRING, "warn", GIT_SAFE_CRLF_WARN}
+};
+
+/*
+ * Generic map for integer values
+ */
+static git_cvar_map _cvar_map_int[] = {
+ {GIT_CVAR_INT32, NULL, 0},
+};
+
+static struct map_data _cvar_maps[] = {
+ {"core.autocrlf", _cvar_map_autocrlf, ARRAY_SIZE(_cvar_map_autocrlf), GIT_AUTO_CRLF_DEFAULT},
+ {"core.eol", _cvar_map_eol, ARRAY_SIZE(_cvar_map_eol), GIT_EOL_DEFAULT},
+ {"core.symlinks", NULL, 0, GIT_SYMLINKS_DEFAULT },
+ {"core.ignorecase", NULL, 0, GIT_IGNORECASE_DEFAULT },
+ {"core.filemode", NULL, 0, GIT_FILEMODE_DEFAULT },
+ {"core.ignorestat", NULL, 0, GIT_IGNORESTAT_DEFAULT },
+ {"core.trustctime", NULL, 0, GIT_TRUSTCTIME_DEFAULT },
+ {"core.abbrev", _cvar_map_int, 1, GIT_ABBREV_DEFAULT },
+ {"core.precomposeunicode", NULL, 0, GIT_PRECOMPOSE_DEFAULT },
+ {"core.safecrlf", _cvar_map_safecrlf, ARRAY_SIZE(_cvar_map_safecrlf), GIT_SAFE_CRLF_DEFAULT},
+ {"core.logallrefupdates", NULL, 0, GIT_LOGALLREFUPDATES_DEFAULT },
+};
+
+int git_config__cvar(int *out, git_config *config, git_cvar_cached cvar)
+{
+ int error = 0;
+ struct map_data *data = &_cvar_maps[(int)cvar];
+ const git_config_entry *entry;
+
+ git_config__lookup_entry(&entry, config, data->cvar_name, false);
+
+ if (!entry)
+ *out = data->default_value;
+ else if (data->maps)
+ error = git_config_lookup_map_value(
+ out, data->maps, data->map_count, entry->value);
+ else
+ error = git_config_parse_bool(out, entry->value);
+
+ return error;
+}
+
+int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar)
+{
+ *out = repo->cvar_cache[(int)cvar];
+
+ if (*out == GIT_CVAR_NOT_CACHED) {
+ int error;
+ git_config *config;
+
+ if ((error = git_repository_config__weakptr(&config, repo)) < 0 ||
+ (error = git_config__cvar(out, config, cvar)) < 0)
+ return error;
+
+ repo->cvar_cache[(int)cvar] = *out;
+ }
+
+ return 0;
+}
+
+void git_repository__cvar_cache_clear(git_repository *repo)
+{
+ int i;
+
+ for (i = 0; i < GIT_CVAR_CACHE_MAX; ++i)
+ repo->cvar_cache[i] = GIT_CVAR_NOT_CACHED;
+}
+
diff --git a/src/config_file.c b/src/config_file.c
index 044a24310..56271144b 100644
--- a/src/config_file.c
+++ b/src/config_file.c
@@ -1,49 +1,42 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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 "config.h"
-#include "fileops.h"
#include "filebuf.h"
+#include "sysdir.h"
+#include "buffer.h"
+#include "buf_text.h"
#include "git2/config.h"
+#include "git2/sys/config.h"
#include "git2/types.h"
-
+#include "strmap.h"
+#include "array.h"
#include <ctype.h>
+#include <sys/types.h>
+#include <regex.h>
+
+GIT__USE_STRMAP;
typedef struct cvar_t {
struct cvar_t *next;
- char *section;
- char *name;
- char *value;
+ git_config_entry *entry;
+ bool included; /* whether this is part of [include] */
} cvar_t;
-typedef struct {
- struct cvar_t *head;
- struct cvar_t *tail;
-} cvar_t_list;
+typedef struct git_config_file_iter {
+ git_config_iterator parent;
+ git_strmap_iter iter;
+ cvar_t* next_var;
+} git_config_file_iter;
+
+/* Max depth for [include] directives */
+#define MAX_INCLUDE_DEPTH 10
#define CVAR_LIST_HEAD(list) ((list)->head)
@@ -83,392 +76,757 @@ typedef struct {
(iter) && (((tmp) = CVAR_LIST_NEXT(iter) || 1));\
(iter) = (tmp))
+struct reader {
+ time_t file_mtime;
+ size_t file_size;
+ char *file_path;
+ git_buf buffer;
+ char *read_ptr;
+ int line_number;
+ int eof;
+};
+
+typedef struct {
+ git_atomic refcount;
+ git_strmap *values;
+} refcounted_strmap;
+
typedef struct {
- git_config_file parent;
+ git_config_backend parent;
+ /* mutex to coordinate accessing the values */
+ git_mutex values_mutex;
+ refcounted_strmap *values;
+ int readonly;
+} diskfile_header;
- cvar_t_list var_list;
+typedef struct {
+ diskfile_header header;
- struct {
- git_fbuffer buffer;
- char *read_ptr;
- int line_number;
- int eof;
- } reader;
+ git_config_level_t level;
- char *file_path;
+ git_array_t(struct reader) readers;
+
+ char *file_path;
} diskfile_backend;
-static int config_parse(diskfile_backend *cfg_file);
-static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value);
-static int config_write(diskfile_backend *cfg, cvar_t *var);
+typedef struct {
+ diskfile_header header;
+
+ diskfile_backend *snapshot_from;
+} diskfile_readonly_backend;
+
+static int config_parse(git_strmap *values, diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth);
+static int parse_variable(struct reader *reader, char **var_name, char **var_value);
+static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *value);
+static char *escape_value(const char *ptr);
+
+int git_config_file__snapshot(git_config_backend **out, diskfile_backend *in);
+static int config_snapshot(git_config_backend **out, git_config_backend *in);
+
+static void set_parse_error(struct reader *reader, int col, const char *error_str)
+{
+ giterr_set(GITERR_CONFIG, "Failed to parse config file: %s (in %s:%d, column %d)",
+ error_str, reader->file_path, reader->line_number, col);
+}
+
+static int config_error_readonly(void)
+{
+ giterr_set(GITERR_CONFIG, "this backend is read-only");
+ return -1;
+}
static void cvar_free(cvar_t *var)
{
if (var == NULL)
return;
- free(var->section);
- free(var->name);
- free(var->value);
- free(var);
+ git__free((char*)var->entry->name);
+ git__free((char *)var->entry->value);
+ git__free(var->entry);
+ git__free(var);
}
-static void cvar_list_free(cvar_t_list *list)
+int git_config_file_normalize_section(char *start, char *end)
{
- cvar_t *cur;
+ char *scan;
+
+ if (start == end)
+ return GIT_EINVALIDSPEC;
- while (!CVAR_LIST_EMPTY(list)) {
- cur = CVAR_LIST_HEAD(list);
- CVAR_LIST_REMOVE_HEAD(list);
- cvar_free(cur);
+ /* Validate and downcase range */
+ for (scan = start; *scan; ++scan) {
+ if (end && scan >= end)
+ break;
+ if (isalnum(*scan))
+ *scan = (char)tolower(*scan);
+ else if (*scan != '-' || scan == start)
+ return GIT_EINVALIDSPEC;
}
+
+ if (scan == start)
+ return GIT_EINVALIDSPEC;
+
+ return 0;
}
-/*
- * Compare two strings according to the git section-subsection
- * rules. The order of the strings is important because local is
- * assumed to have the internal format (only the section name and with
- * case information) and input the normalized one (only dots, no case
- * information).
- */
-static int cvar_match_section(const char *local, const char *input)
+/* Add or append the new config option */
+static int append_entry(git_strmap *values, cvar_t *var)
{
- char *first_dot;
- char *local_sp = strchr(local, ' ');
- int comparison_len;
+ git_strmap_iter pos;
+ cvar_t *existing;
+ int error = 0;
- /*
- * If the local section name doesn't contain a space, then we can
- * just do a case-insensitive compare.
- */
- if (local_sp == NULL)
- return !strncasecmp(local, input, strlen(local));
+ pos = git_strmap_lookup_index(values, var->entry->name);
+ if (!git_strmap_valid_index(values, pos)) {
+ git_strmap_insert(values, var->entry->name, var, error);
+ } else {
+ existing = git_strmap_value_at(values, pos);
+ while (existing->next != NULL) {
+ existing = existing->next;
+ }
+ existing->next = var;
+ }
- /*
- * From here onwards, there is a space diving the section and the
- * subsection. Anything before the space in local is
- * case-insensitive.
- */
- if (strncasecmp(local, input, local_sp - local))
- return 0;
+ if (error > 0)
+ error = 0;
- /*
- * We compare starting from the first character after the
- * quotation marks, which is two characters beyond the space. For
- * the input, we start one character beyond the dot. If the names
- * have different lengths, then we can fail early, as we know they
- * can't be the same.
- * The length is given by the length between the quotation marks.
- */
+ return error;
+}
+
+static void free_vars(git_strmap *values)
+{
+ cvar_t *var = NULL;
+
+ if (values == NULL)
+ return;
- first_dot = strchr(input, '.');
- comparison_len = strlen(local_sp + 2) - 1;
+ git_strmap_foreach_value(values, var,
+ while (var != NULL) {
+ cvar_t *next = CVAR_LIST_NEXT(var);
+ cvar_free(var);
+ var = next;
+ });
- return !strncmp(local_sp + 2, first_dot + 1, comparison_len);
+ git_strmap_free(values);
}
-static int cvar_match_name(const cvar_t *var, const char *str)
+static void refcounted_strmap_free(refcounted_strmap *map)
{
- const char *name_start;
+ if (!map)
+ return;
- if (!cvar_match_section(var->section, str)) {
- return 0;
- }
- /* Early exit if the lengths are different */
- name_start = strrchr(str, '.') + 1;
- if (strlen(var->name) != strlen(name_start))
- return 0;
+ if (git_atomic_dec(&map->refcount) != 0)
+ return;
- return !strcasecmp(var->name, name_start);
+ free_vars(map->values);
+ git__free(map);
}
-static cvar_t *cvar_list_find(cvar_t_list *list, const char *name)
+/**
+ * Take the current values map from the backend and increase its
+ * refcount. This is its own function to make sure we use the mutex to
+ * avoid the map pointer from changing under us.
+ */
+static refcounted_strmap *refcounted_strmap_take(diskfile_header *h)
{
- cvar_t *iter;
+ refcounted_strmap *map;
- CVAR_LIST_FOREACH (list, iter) {
- if (cvar_match_name(iter, name))
- return iter;
- }
+ git_mutex_lock(&h->values_mutex);
+
+ map = h->values;
+ git_atomic_inc(&map->refcount);
- return NULL;
+ git_mutex_unlock(&h->values_mutex);
+
+ return map;
}
-static int cvar_normalize_name(cvar_t *var, char **output)
+static int refcounted_strmap_alloc(refcounted_strmap **out)
{
- char *section_sp = strchr(var->section, ' ');
- char *quote, *name;
- int len, ret;
+ refcounted_strmap *map;
+ int error;
- /*
- * The final string is going to be at most one char longer than
- * the input
- */
- len = strlen(var->section) + strlen(var->name) + 1;
- name = git__malloc(len + 1);
- if (name == NULL)
- return GIT_ENOMEM;
-
- /* If there aren't any spaces in the section, it's easy */
- if (section_sp == NULL) {
- ret = snprintf(name, len + 1, "%s.%s", var->section, var->name);
- if (ret < 0)
- return git__throw(GIT_EOSERR, "Failed to normalize name. OS err: %s", strerror(errno));
-
- *output = name;
- return GIT_SUCCESS;
- }
+ map = git__calloc(1, sizeof(refcounted_strmap));
+ GITERR_CHECK_ALLOC(map);
- /*
- * If there are spaces, we replace the space by a dot, move
- * section name so it overwrites the first quotation mark and
- * replace the last quotation mark by a dot. We then append the
- * variable name.
- */
- strcpy(name, var->section);
- section_sp = strchr(name, ' ');
- *section_sp = '.';
- /* Remove first quote */
- quote = strchr(name, '"');
- memmove(quote, quote+1, strlen(quote+1));
- /* Remove second quote */
- quote = strchr(name, '"');
- *quote = '.';
- strcpy(quote+1, var->name);
-
- *output = name;
- return GIT_SUCCESS;
+ git_atomic_set(&map->refcount, 1);
+
+ if ((error = git_strmap_alloc(&map->values)) < 0)
+ git__free(map);
+ else
+ *out = map;
+
+ return error;
}
-static char *interiorize_section(const char *orig)
+static int config_open(git_config_backend *cfg, git_config_level_t level)
{
- char *dot, *last_dot, *section, *ret;
- int len;
+ int res;
+ struct reader *reader;
+ diskfile_backend *b = (diskfile_backend *)cfg;
- dot = strchr(orig, '.');
- last_dot = strrchr(orig, '.');
- len = last_dot - orig;
+ b->level = level;
- /* No subsection, this is easy */
- if (last_dot == dot)
- return git__strndup(orig, dot - orig);
+ if ((res = refcounted_strmap_alloc(&b->header.values)) < 0)
+ return res;
- section = git__malloc(len + 4);
- if (section == NULL)
- return NULL;
+ git_array_init(b->readers);
+ reader = git_array_alloc(b->readers);
+ if (!reader) {
+ refcounted_strmap_free(b->header.values);
+ return -1;
+ }
+ memset(reader, 0, sizeof(struct reader));
- memset(section, 0x0, len + 4);
- ret = section;
- len = dot - orig;
- memcpy(section, orig, len);
- section += len;
- len = STRLEN(" \"");
- memcpy(section, " \"", len);
- section += len;
- len = last_dot - dot - 1;
- memcpy(section, dot + 1, len);
- section += len;
- *section = '"';
+ reader->file_path = git__strdup(b->file_path);
+ GITERR_CHECK_ALLOC(reader->file_path);
- return ret;
+ git_buf_init(&reader->buffer, 0);
+ res = git_futils_readbuffer_updated(
+ &reader->buffer, b->file_path, &reader->file_mtime, &reader->file_size, NULL);
+
+ /* It's fine if the file doesn't exist */
+ if (res == GIT_ENOTFOUND)
+ return 0;
+
+ if (res < 0 || (res = config_parse(b->header.values->values, b, reader, level, 0)) < 0) {
+ refcounted_strmap_free(b->header.values);
+ b->header.values = NULL;
+ }
+
+ reader = git_array_get(b->readers, 0);
+ git_buf_free(&reader->buffer);
+
+ return res;
}
-static int config_open(git_config_file *cfg)
+/* The meat of the refresh, as we want to use it in different places */
+static int config__refresh(git_config_backend *cfg)
{
- int error;
+ refcounted_strmap *values = NULL, *tmp;
diskfile_backend *b = (diskfile_backend *)cfg;
+ struct reader *reader = NULL;
+ int error = 0;
+
+ if ((error = refcounted_strmap_alloc(&values)) < 0)
+ goto out;
- error = git_futils_readbuffer(&b->reader.buffer, b->file_path);
- if(error < GIT_SUCCESS)
- goto cleanup;
+ reader = git_array_get(b->readers, git_array_size(b->readers) - 1);
+ GITERR_CHECK_ALLOC(reader);
- error = config_parse(b);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ if ((error = config_parse(values->values, b, reader, b->level, 0)) < 0)
+ goto out;
+
+ git_mutex_lock(&b->header.values_mutex);
+
+ tmp = b->header.values;
+ b->header.values = values;
+ values = tmp;
- git_futils_freebuffer(&b->reader.buffer);
+ git_mutex_unlock(&b->header.values_mutex);
+out:
+ refcounted_strmap_free(values);
+ if (reader)
+ git_buf_free(&reader->buffer);
return error;
+}
+
+static int config_refresh(git_config_backend *cfg)
+{
+ int error = 0, updated = 0, any_updated = 0;
+ diskfile_backend *b = (diskfile_backend *)cfg;
+ struct reader *reader = NULL;
+ uint32_t i;
+
+ for (i = 0; i < git_array_size(b->readers); i++) {
+ reader = git_array_get(b->readers, i);
+ error = git_futils_readbuffer_updated(
+ &reader->buffer, reader->file_path,
+ &reader->file_mtime, &reader->file_size, &updated);
+
+ if (error < 0 && error != GIT_ENOTFOUND)
+ return error;
- cleanup:
- cvar_list_free(&b->var_list);
- git_futils_freebuffer(&b->reader.buffer);
+ if (updated)
+ any_updated = 1;
+ }
+
+ if (!any_updated)
+ return (error == GIT_ENOTFOUND) ? 0 : error;
- return git__rethrow(error, "Failed to open config");
+ return config__refresh(cfg);
}
-static void backend_free(git_config_file *_backend)
+static void backend_free(git_config_backend *_backend)
{
diskfile_backend *backend = (diskfile_backend *)_backend;
+ uint32_t i;
if (backend == NULL)
return;
- free(backend->file_path);
- cvar_list_free(&backend->var_list);
+ for (i = 0; i < git_array_size(backend->readers); i++) {
+ struct reader *r = git_array_get(backend->readers, i);
+ git__free(r->file_path);
+ }
+ git_array_clear(backend->readers);
- free(backend);
+ git__free(backend->file_path);
+ refcounted_strmap_free(backend->header.values);
+ git_mutex_free(&backend->header.values_mutex);
+ git__free(backend);
}
-static int file_foreach(git_config_file *backend, int (*fn)(const char *, const char *, void *), void *data)
+static void config_iterator_free(
+ git_config_iterator* iter)
{
- int ret = GIT_SUCCESS;
- cvar_t *var;
- diskfile_backend *b = (diskfile_backend *)backend;
-
- CVAR_LIST_FOREACH(&b->var_list, var) {
- char *normalized = NULL;
+ iter->backend->free(iter->backend);
+ git__free(iter);
+}
- ret = cvar_normalize_name(var, &normalized);
- if (ret < GIT_SUCCESS)
- return ret;
+static int config_iterator_next(
+ git_config_entry **entry,
+ git_config_iterator *iter)
+{
+ git_config_file_iter *it = (git_config_file_iter *) iter;
+ diskfile_header *h = (diskfile_header *) it->parent.backend;
+ git_strmap *values = h->values->values;
+ int err = 0;
+ cvar_t * var;
+
+ if (it->next_var == NULL) {
+ err = git_strmap_next((void**) &var, &(it->iter), values);
+ } else {
+ var = it->next_var;
+ }
- ret = fn(normalized, var->value, data);
- free(normalized);
- if (ret)
- break;
+ if (err < 0) {
+ it->next_var = NULL;
+ return err;
}
- return ret;
+ *entry = var->entry;
+ it->next_var = CVAR_LIST_NEXT(var);
+
+ return 0;
}
-static int config_set(git_config_file *cfg, const char *name, const char *value)
+static int config_iterator_new(
+ git_config_iterator **iter,
+ struct git_config_backend* backend)
+{
+ diskfile_header *h;
+ git_config_file_iter *it;
+ git_config_backend *snapshot;
+ diskfile_backend *b = (diskfile_backend *) backend;
+ int error;
+
+ if ((error = config_snapshot(&snapshot, backend)) < 0)
+ return error;
+
+ if ((error = snapshot->open(snapshot, b->level)) < 0)
+ return error;
+
+ it = git__calloc(1, sizeof(git_config_file_iter));
+ GITERR_CHECK_ALLOC(it);
+
+ h = (diskfile_header *)snapshot;
+
+ /* strmap_begin() is currently a macro returning 0 */
+ GIT_UNUSED(h);
+
+ it->parent.backend = snapshot;
+ it->iter = git_strmap_begin(h->values);
+ it->next_var = NULL;
+
+ it->parent.next = config_iterator_next;
+ it->parent.free = config_iterator_free;
+ *iter = (git_config_iterator *) it;
+
+ return 0;
+}
+
+static int config_set(git_config_backend *cfg, const char *name, const char *value)
{
- cvar_t *var = NULL;
- cvar_t *existing = NULL;
- int error = GIT_SUCCESS;
- const char *last_dot;
diskfile_backend *b = (diskfile_backend *)cfg;
+ refcounted_strmap *map;
+ git_strmap *values;
+ char *key, *esc_value = NULL;
+ khiter_t pos;
+ int rval, ret;
+
+ if ((rval = git_config__normalize_name(name, &key)) < 0)
+ return rval;
+
+ map = refcounted_strmap_take(&b->header);
+ values = map->values;
/*
- * If it already exists, we just need to update its value.
+ * Try to find it in the existing values and update it if it
+ * only has one value.
*/
- existing = cvar_list_find(&b->var_list, name);
- if (existing != NULL) {
- char *tmp = value ? git__strdup(value) : NULL;
- if (tmp == NULL && value != NULL)
- return GIT_ENOMEM;
+ pos = git_strmap_lookup_index(values, key);
+ if (git_strmap_valid_index(values, pos)) {
+ cvar_t *existing = git_strmap_value_at(values, pos);
+
+ if (existing->next != NULL) {
+ giterr_set(GITERR_CONFIG, "Multivar incompatible with simple set");
+ ret = -1;
+ goto out;
+ }
+
+ /* don't update if old and new values already match */
+ if ((!existing->entry->value && !value) ||
+ (existing->entry->value && value &&
+ !strcmp(existing->entry->value, value))) {
+ ret = 0;
+ goto out;
+ }
+ }
- free(existing->value);
- existing->value = tmp;
+ /* No early returns due to sanity checks, let's write it out and refresh */
- return config_write(b, existing);
+ if (value) {
+ esc_value = escape_value(value);
+ GITERR_CHECK_ALLOC(esc_value);
}
- /*
- * Otherwise, create it and stick it at the end of the queue. If
- * value is NULL, we return an error, because you can't delete a
- * variable that doesn't exist.
- */
+ if ((ret = config_write(b, key, NULL, esc_value)) < 0)
+ goto out;
+
+ ret = config_refresh(cfg);
+
+out:
+ refcounted_strmap_free(map);
+ git__free(esc_value);
+ git__free(key);
+ return ret;
+}
+
+/*
+ * Internal function that actually gets the value in string form
+ */
+static int config_get(git_config_backend *cfg, const char *key, const git_config_entry **out)
+{
+ diskfile_header *h = (diskfile_header *)cfg;
+ refcounted_strmap *map;
+ git_strmap *values;
+ khiter_t pos;
+ cvar_t *var;
+ int error;
+
+ if (!h->readonly && ((error = config_refresh(cfg)) < 0))
+ return error;
+
+ map = refcounted_strmap_take(h);
+ values = map->values;
- if (value == NULL)
- return git__throw(GIT_ENOTFOUND, "Can't delete non-exitent variable");
+ pos = git_strmap_lookup_index(values, key);
- last_dot = strrchr(name, '.');
- if (last_dot == NULL) {
- return git__throw(GIT_EINVALIDTYPE, "Variables without section aren't allowed");
+ /* no error message; the config system will write one */
+ if (!git_strmap_valid_index(values, pos)) {
+ refcounted_strmap_free(map);
+ return GIT_ENOTFOUND;
}
- var = git__malloc(sizeof(cvar_t));
- if (var == NULL)
- return GIT_ENOMEM;
+ var = git_strmap_value_at(values, pos);
+ while (var->next)
+ var = var->next;
- memset(var, 0x0, sizeof(cvar_t));
+ refcounted_strmap_free(map);
+ *out = var->entry;
+ return 0;
+}
- var->section = interiorize_section(name);
- if (var->section == NULL) {
- error = GIT_ENOMEM;
- goto out;
+static int config_set_multivar(
+ git_config_backend *cfg, const char *name, const char *regexp, const char *value)
+{
+ diskfile_backend *b = (diskfile_backend *)cfg;
+ refcounted_strmap *map;
+ git_strmap *values;
+ char *key;
+ regex_t preg;
+ int result;
+ khiter_t pos;
+
+ assert(regexp);
+
+ if ((result = git_config__normalize_name(name, &key)) < 0)
+ return result;
+
+ map = refcounted_strmap_take(&b->header);
+ values = b->header.values->values;
+
+ pos = git_strmap_lookup_index(values, key);
+ if (!git_strmap_valid_index(values, pos)) {
+ /* If we don't have it, behave like a normal set */
+ result = config_set(cfg, name, value);
+ refcounted_strmap_free(map);
+ git__free(key);
+ return result;
}
- var->name = git__strdup(last_dot + 1);
- if (var->name == NULL) {
- error = GIT_ENOMEM;
+ result = regcomp(&preg, regexp, REG_EXTENDED);
+ if (result < 0) {
+ giterr_set_regex(&preg, result);
+ result = -1;
goto out;
}
- var->value = value ? git__strdup(value) : NULL;
- if (var->value == NULL && value != NULL) {
- error = GIT_ENOMEM;
+ /* If we do have it, set call config_write() and reload */
+ if ((result = config_write(b, key, &preg, value)) < 0)
goto out;
- }
- CVAR_LIST_APPEND(&b->var_list, var);
- error = config_write(b, var);
+ result = config_refresh(cfg);
- out:
- if (error < GIT_SUCCESS)
- cvar_free(var);
+out:
+ refcounted_strmap_free(map);
+ git__free(key);
+ regfree(&preg);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to set config value");
+ return result;
}
-/*
- * Internal function that actually gets the value in string form
- */
-static int config_get(git_config_file *cfg, const char *name, const char **out)
+static int config_delete(git_config_backend *cfg, const char *name)
{
cvar_t *var;
- int error = GIT_SUCCESS;
diskfile_backend *b = (diskfile_backend *)cfg;
+ refcounted_strmap *map; git_strmap *values;
+ char *key;
+ int result;
+ khiter_t pos;
- var = cvar_list_find(&b->var_list, name);
+ if ((result = git_config__normalize_name(name, &key)) < 0)
+ return result;
- if (var == NULL)
- return git__throw(GIT_ENOTFOUND, "Variable '%s' not found", name);
+ map = refcounted_strmap_take(&b->header);
+ values = b->header.values->values;
+
+ pos = git_strmap_lookup_index(values, key);
+ git__free(key);
+
+ if (!git_strmap_valid_index(values, pos)) {
+ refcounted_strmap_free(map);
+ giterr_set(GITERR_CONFIG, "Could not find key '%s' to delete", name);
+ return GIT_ENOTFOUND;
+ }
+
+ var = git_strmap_value_at(values, pos);
+ refcounted_strmap_free(map);
+
+ if (var->next != NULL) {
+ giterr_set(GITERR_CONFIG, "Cannot delete multivar with a single delete");
+ return -1;
+ }
+
+ if ((result = config_write(b, var->entry->name, NULL, NULL)) < 0)
+ return result;
+
+ return config_refresh(cfg);
+}
+
+static int config_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp)
+{
+ diskfile_backend *b = (diskfile_backend *)cfg;
+ refcounted_strmap *map;
+ git_strmap *values;
+ char *key;
+ regex_t preg;
+ int result;
+ khiter_t pos;
+
+ if ((result = git_config__normalize_name(name, &key)) < 0)
+ return result;
+
+ map = refcounted_strmap_take(&b->header);
+ values = b->header.values->values;
+
+ pos = git_strmap_lookup_index(values, key);
+
+ if (!git_strmap_valid_index(values, pos)) {
+ refcounted_strmap_free(map);
+ git__free(key);
+ giterr_set(GITERR_CONFIG, "Could not find key '%s' to delete", name);
+ return GIT_ENOTFOUND;
+ }
+
+ refcounted_strmap_free(map);
+
+ result = regcomp(&preg, regexp, REG_EXTENDED);
+ if (result < 0) {
+ giterr_set_regex(&preg, result);
+ result = -1;
+ goto out;
+ }
+
+ if ((result = config_write(b, key, &preg, NULL)) < 0)
+ goto out;
+
+ result = config_refresh(cfg);
+
+out:
+ git__free(key);
+ regfree(&preg);
+ return result;
+}
- *out = var->value;
+static int config_snapshot(git_config_backend **out, git_config_backend *in)
+{
+ diskfile_backend *b = (diskfile_backend *) in;
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to get config value for %s", name);
+ return git_config_file__snapshot(out, b);
}
-int git_config_file__ondisk(git_config_file **out, const char *path)
+int git_config_file__ondisk(git_config_backend **out, const char *path)
{
diskfile_backend *backend;
- backend = git__malloc(sizeof(diskfile_backend));
- if (backend == NULL)
- return GIT_ENOMEM;
+ backend = git__calloc(1, sizeof(diskfile_backend));
+ GITERR_CHECK_ALLOC(backend);
- memset(backend, 0x0, sizeof(diskfile_backend));
+ backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION;
+ git_mutex_init(&backend->header.values_mutex);
backend->file_path = git__strdup(path);
- if (backend->file_path == NULL) {
- free(backend);
- return GIT_ENOMEM;
- }
+ GITERR_CHECK_ALLOC(backend->file_path);
+
+ backend->header.parent.open = config_open;
+ backend->header.parent.get = config_get;
+ backend->header.parent.set = config_set;
+ backend->header.parent.set_multivar = config_set_multivar;
+ backend->header.parent.del = config_delete;
+ backend->header.parent.del_multivar = config_delete_multivar;
+ backend->header.parent.iterator = config_iterator_new;
+ backend->header.parent.refresh = config_refresh;
+ backend->header.parent.snapshot = config_snapshot;
+ backend->header.parent.free = backend_free;
+
+ *out = (git_config_backend *)backend;
+
+ return 0;
+}
+
+static int config_set_readonly(git_config_backend *cfg, const char *name, const char *value)
+{
+ GIT_UNUSED(cfg);
+ GIT_UNUSED(name);
+ GIT_UNUSED(value);
+
+ return config_error_readonly();
+}
- backend->parent.open = config_open;
- backend->parent.get = config_get;
- backend->parent.set = config_set;
- backend->parent.foreach = file_foreach;
- backend->parent.free = backend_free;
+static int config_set_multivar_readonly(
+ git_config_backend *cfg, const char *name, const char *regexp, const char *value)
+{
+ GIT_UNUSED(cfg);
+ GIT_UNUSED(name);
+ GIT_UNUSED(regexp);
+ GIT_UNUSED(value);
+
+ return config_error_readonly();
+}
+
+static int config_delete_multivar_readonly(git_config_backend *cfg, const char *name, const char *regexp)
+{
+ GIT_UNUSED(cfg);
+ GIT_UNUSED(name);
+ GIT_UNUSED(regexp);
+
+ return config_error_readonly();
+}
+
+static int config_delete_readonly(git_config_backend *cfg, const char *name)
+{
+ GIT_UNUSED(cfg);
+ GIT_UNUSED(name);
- *out = (git_config_file *)backend;
+ return config_error_readonly();
+}
+
+static int config_refresh_readonly(git_config_backend *cfg)
+{
+ GIT_UNUSED(cfg);
- return GIT_SUCCESS;
+ return config_error_readonly();
}
-static int cfg_getchar_raw(diskfile_backend *cfg)
+static void backend_readonly_free(git_config_backend *_backend)
+{
+ diskfile_backend *backend = (diskfile_backend *)_backend;
+
+ if (backend == NULL)
+ return;
+
+ refcounted_strmap_free(backend->header.values);
+ git_mutex_free(&backend->header.values_mutex);
+ git__free(backend);
+}
+
+static int config_readonly_open(git_config_backend *cfg, git_config_level_t level)
+{
+ diskfile_readonly_backend *b = (diskfile_readonly_backend *) cfg;
+ diskfile_backend *src = b->snapshot_from;
+ refcounted_strmap *src_map;
+
+ /* We're just copying data, don't care about the level */
+ GIT_UNUSED(level);
+
+ src_map = refcounted_strmap_take(&src->header);
+ b->header.values = src_map;
+
+ return 0;
+}
+
+int git_config_file__snapshot(git_config_backend **out, diskfile_backend *in)
+{
+ diskfile_readonly_backend *backend;
+
+ backend = git__calloc(1, sizeof(diskfile_readonly_backend));
+ GITERR_CHECK_ALLOC(backend);
+
+ backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION;
+ git_mutex_init(&backend->header.values_mutex);
+
+ backend->snapshot_from = in;
+
+ backend->header.readonly = 1;
+ backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION;
+ backend->header.parent.open = config_readonly_open;
+ backend->header.parent.get = config_get;
+ backend->header.parent.set = config_set_readonly;
+ backend->header.parent.set_multivar = config_set_multivar_readonly;
+ backend->header.parent.del = config_delete_readonly;
+ backend->header.parent.del_multivar = config_delete_multivar_readonly;
+ backend->header.parent.iterator = config_iterator_new;
+ backend->header.parent.refresh = config_refresh_readonly;
+ backend->header.parent.free = backend_readonly_free;
+
+ *out = (git_config_backend *)backend;
+
+ return 0;
+}
+
+static int reader_getchar_raw(struct reader *reader)
{
int c;
- c = *cfg->reader.read_ptr++;
+ c = *reader->read_ptr++;
/*
Win 32 line breaks: if we find a \r\n sequence,
return only the \n as a newline
*/
- if (c == '\r' && *cfg->reader.read_ptr == '\n') {
- cfg->reader.read_ptr++;
+ if (c == '\r' && *reader->read_ptr == '\n') {
+ reader->read_ptr++;
c = '\n';
}
if (c == '\n')
- cfg->reader.line_number++;
+ reader->line_number++;
if (c == 0) {
- cfg->reader.eof = 1;
+ reader->eof = 1;
c = '\n';
}
@@ -478,20 +836,23 @@ static int cfg_getchar_raw(diskfile_backend *cfg)
#define SKIP_WHITESPACE (1 << 1)
#define SKIP_COMMENTS (1 << 2)
-static int cfg_getchar(diskfile_backend *cfg_file, int flags)
+static int reader_getchar(struct reader *reader, int flags)
{
const int skip_whitespace = (flags & SKIP_WHITESPACE);
const int skip_comments = (flags & SKIP_COMMENTS);
int c;
- assert(cfg_file->reader.read_ptr);
+ assert(reader->read_ptr);
- do c = cfg_getchar_raw(cfg_file);
- while (skip_whitespace && isspace(c));
+ do {
+ c = reader_getchar_raw(reader);
+ } while (skip_whitespace && git__isspace(c) &&
+ !reader->eof);
if (skip_comments && (c == '#' || c == ';')) {
- do c = cfg_getchar_raw(cfg_file);
- while (c != '\n');
+ do {
+ c = reader_getchar_raw(reader);
+ } while (c != '\n');
}
return c;
@@ -500,23 +861,23 @@ static int cfg_getchar(diskfile_backend *cfg_file, int flags)
/*
* Read the next char, but don't move the reading pointer.
*/
-static int cfg_peek(diskfile_backend *cfg, int flags)
+static int reader_peek(struct reader *reader, int flags)
{
void *old_read_ptr;
int old_lineno, old_eof;
int ret;
- assert(cfg->reader.read_ptr);
+ assert(reader->read_ptr);
- old_read_ptr = cfg->reader.read_ptr;
- old_lineno = cfg->reader.line_number;
- old_eof = cfg->reader.eof;
+ old_read_ptr = reader->read_ptr;
+ old_lineno = reader->line_number;
+ old_eof = reader->eof;
- ret = cfg_getchar(cfg, flags);
+ ret = reader_getchar(reader, flags);
- cfg->reader.read_ptr = old_read_ptr;
- cfg->reader.line_number = old_lineno;
- cfg->reader.eof = old_eof;
+ reader->read_ptr = old_read_ptr;
+ reader->line_number = old_lineno;
+ reader->eof = old_eof;
return ret;
}
@@ -524,21 +885,23 @@ static int cfg_peek(diskfile_backend *cfg, int flags)
/*
* Read and consume a line, returning it in newly-allocated memory.
*/
-static char *cfg_readline(diskfile_backend *cfg)
+static char *reader_readline(struct reader *reader, bool skip_whitespace)
{
char *line = NULL;
char *line_src, *line_end;
- int line_len;
+ size_t line_len;
- line_src = cfg->reader.read_ptr;
+ line_src = reader->read_ptr;
- /* Skip empty empty lines */
- while (isspace(*line_src))
- ++line_src;
+ if (skip_whitespace) {
+ /* Skip empty empty lines */
+ while (git__isspace(*line_src))
+ ++line_src;
+ }
- line_end = strchr(line_src, '\n');
+ line_end = strchr(line_src, '\n');
- /* no newline at EOF */
+ /* no newline at EOF */
if (line_end == NULL)
line_end = strchr(line_src, 0);
@@ -550,19 +913,17 @@ static char *cfg_readline(diskfile_backend *cfg)
memcpy(line, line_src, line_len);
- line[line_len] = '\0';
-
- while (--line_len >= 0 && isspace(line[line_len]))
- line[line_len] = '\0';
+ do line[line_len] = '\0';
+ while (line_len-- > 0 && git__isspace(line[line_len]));
if (*line_end == '\n')
line_end++;
if (*line_end == '\0')
- cfg->reader.eof = 1;
+ reader->eof = 1;
- cfg->reader.line_number++;
- cfg->reader.read_ptr = line_end;
+ reader->line_number++;
+ reader->read_ptr = line_end;
return line;
}
@@ -570,11 +931,11 @@ static char *cfg_readline(diskfile_backend *cfg)
/*
* Consume a line, without storing it anywhere
*/
-void cfg_consume_line(diskfile_backend *cfg)
+static void reader_consume_line(struct reader *reader)
{
char *line_start, *line_end;
- line_start = cfg->reader.read_ptr;
+ line_start = reader->read_ptr;
line_end = strchr(line_start, '\n');
/* No newline at EOF */
if(line_end == NULL){
@@ -585,10 +946,10 @@ void cfg_consume_line(diskfile_backend *cfg)
line_end++;
if (*line_end == '\0')
- cfg->reader.eof = 1;
+ reader->eof = 1;
- cfg->reader.line_number++;
- cfg->reader.read_ptr = line_end;
+ reader->line_number++;
+ reader->read_ptr = line_end;
}
GIT_INLINE(int) config_keychar(int c)
@@ -596,13 +957,11 @@ GIT_INLINE(int) config_keychar(int c)
return isalnum(c) || c == '-';
}
-static int parse_section_header_ext(const char *line, const char *base_name, char **section_name)
+static int parse_section_header_ext(struct reader *reader, const char *line, const char *base_name, char **section_name)
{
- int buf_len, total_len, pos, rpos;
- int c, ret;
- char *subsection, *first_quote, *last_quote;
- int error = GIT_SUCCESS;
- int quote_marks;
+ int c, rpos;
+ char *first_quote, *last_quote;
+ git_buf buf = GIT_BUF_INIT;
/*
* base_name is what came before the space. We should be at the
* first quotation mark, except for now, line isn't being kept in
@@ -612,159 +971,141 @@ static int parse_section_header_ext(const char *line, const char *base_name, cha
first_quote = strchr(line, '"');
last_quote = strrchr(line, '"');
- if (last_quote - first_quote == 0)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse ext header. There is no final quotation mark");
-
- buf_len = last_quote - first_quote + 2;
+ if (last_quote - first_quote == 0) {
+ set_parse_error(reader, 0, "Missing closing quotation mark in section header");
+ return -1;
+ }
- subsection = git__malloc(buf_len + 2);
- if (subsection == NULL)
- return GIT_ENOMEM;
+ git_buf_grow(&buf, strlen(base_name) + last_quote - first_quote + 2);
+ git_buf_printf(&buf, "%s.", base_name);
- pos = 0;
rpos = 0;
- quote_marks = 0;
line = first_quote;
- c = line[rpos++];
+ c = line[++rpos];
/*
* At the end of each iteration, whatever is stored in c will be
* added to the string. In case of error, jump to out
*/
do {
- if (quote_marks == 2) {
- error = git__throw(GIT_EOBJCORRUPTED, "Falied to parse ext header. Text after closing quote");
- goto out;
-
- }
switch (c) {
+ case 0:
+ set_parse_error(reader, 0, "Unexpected end-of-line in section header");
+ git_buf_free(&buf);
+ return -1;
+
case '"':
- ++quote_marks;
- break;
+ goto end_parse;
+
case '\\':
- c = line[rpos++];
- switch (c) {
- case '"':
- case '\\':
- break;
- default:
- error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse ext header. Unsupported escape char \\%c", c);
- goto out;
+ c = line[++rpos];
+
+ if (c == 0) {
+ set_parse_error(reader, rpos, "Unexpected end-of-line in section header");
+ git_buf_free(&buf);
+ return -1;
}
- break;
+
default:
break;
}
- subsection[pos++] = (char) c;
- } while ((c = line[rpos++]) != ']');
-
- subsection[pos] = '\0';
-
- total_len = strlen(base_name) + strlen(subsection) + 2;
- *section_name = git__malloc(total_len);
- if (*section_name == NULL) {
- error = GIT_ENOMEM;
- goto out;
- }
+ git_buf_putc(&buf, (char)c);
+ c = line[++rpos];
+ } while (line + rpos < last_quote);
- ret = snprintf(*section_name, total_len, "%s %s", base_name, subsection);
- if (ret >= total_len) {
- /* If this fails, we've checked the length wrong */
- error = git__throw(GIT_ERROR, "Failed to parse ext header. Wrong total length calculation");
- goto out;
- } else if (ret < 0) {
- error = git__throw(GIT_EOSERR, "Failed to parse ext header. OS error: %s", strerror(errno));
- goto out;
+end_parse:
+ if (line[rpos] != '"' || line[rpos + 1] != ']') {
+ set_parse_error(reader, rpos, "Unexpected text after closing quotes");
+ git_buf_free(&buf);
+ return -1;
}
- git__strntolower(*section_name, strchr(*section_name, ' ') - *section_name);
-
- out:
- free(subsection);
-
- return error;
+ *section_name = git_buf_detach(&buf);
+ return 0;
}
-static int parse_section_header(diskfile_backend *cfg, char **section_out)
+static int parse_section_header(struct reader *reader, char **section_out)
{
char *name, *name_end;
int name_length, c, pos;
- int error = GIT_SUCCESS;
+ int result;
char *line;
- line = cfg_readline(cfg);
+ line = reader_readline(reader, true);
if (line == NULL)
- return GIT_ENOMEM;
+ return -1;
/* find the end of the variable's name */
- name_end = strchr(line, ']');
- if (name_end == NULL)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Can't find header name end");
+ name_end = strrchr(line, ']');
+ if (name_end == NULL) {
+ git__free(line);
+ set_parse_error(reader, 0, "Missing ']' in section header");
+ return -1;
+ }
name = (char *)git__malloc((size_t)(name_end - line) + 1);
- if (name == NULL)
- return GIT_ENOMEM;
+ GITERR_CHECK_ALLOC(name);
name_length = 0;
pos = 0;
/* Make sure we were given a section header */
c = line[pos++];
- if (c != '[') {
- error = git__throw(GIT_ERROR, "Failed to parse header. Didn't get section header. This is a bug");
- goto error;
- }
+ assert(c == '[');
c = line[pos++];
do {
- if (isspace(c)){
+ if (git__isspace(c)){
name[name_length] = '\0';
- error = parse_section_header_ext(line, name, section_out);
- free(line);
- free(name);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse header");
+ result = parse_section_header_ext(reader, line, name, section_out);
+ git__free(line);
+ git__free(name);
+ return result;
}
if (!config_keychar(c) && c != '.') {
- error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Wrong format on header");
- goto error;
+ set_parse_error(reader, pos, "Unexpected character in header");
+ goto fail_parse;
}
name[name_length++] = (char) tolower(c);
} while ((c = line[pos++]) != ']');
- if (line[pos - 1] != ']')
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Config file ended unexpectedly");
+ if (line[pos - 1] != ']') {
+ set_parse_error(reader, pos, "Unexpected end of file");
+ goto fail_parse;
+ }
+
+ git__free(line);
name[name_length] = 0;
- free(line);
- git__strtolower(name);
*section_out = name;
- return GIT_SUCCESS;
-error:
- free(line);
- free(name);
- return error;
+ return 0;
+
+fail_parse:
+ git__free(line);
+ git__free(name);
+ return -1;
}
-static int skip_bom(diskfile_backend *cfg)
+static int skip_bom(struct reader *reader)
{
- static const char *utf8_bom = "\xef\xbb\xbf";
+ git_bom_t bom;
+ int bom_offset = git_buf_text_detect_bom(&bom,
+ &reader->buffer, reader->read_ptr - reader->buffer.ptr);
- if (memcmp(cfg->reader.read_ptr, utf8_bom, sizeof(utf8_bom)) == 0)
- cfg->reader.read_ptr += sizeof(utf8_bom);
+ if (bom == GIT_BOM_UTF8)
+ reader->read_ptr += bom_offset;
- /* TODO: the reference implementation does pretty stupid
- shit with the BoM
- */
+ /* TODO: reference implementation is pretty stupid with BoM */
- return GIT_SUCCESS;
+ return 0;
}
/*
@@ -806,9 +1147,9 @@ static int skip_bom(diskfile_backend *cfg)
boolean_false = "no" | "0" | "false" | "off"
*/
-static void strip_comments(char *line)
+static int strip_comments(char *line, int in_quotes)
{
- int quote_count = 0;
+ int quote_count = in_quotes;
char *ptr;
for (ptr = line; *ptr; ++ptr) {
@@ -821,134 +1162,246 @@ static void strip_comments(char *line)
}
}
- if (isspace(ptr[-1])) {
- /* TODO skip whitespace */
+ /* skip any space at the end */
+ if (ptr > line && git__isspace(ptr[-1])) {
+ ptr--;
}
+ ptr[0] = '\0';
+
+ return quote_count;
+}
+
+static int included_path(git_buf *out, const char *dir, const char *path)
+{
+ /* From the user's home */
+ if (path[0] == '~' && path[1] == '/')
+ return git_sysdir_find_global_file(out, &path[1]);
+
+ return git_path_join_unrooted(out, path, dir, NULL);
}
-static int config_parse(diskfile_backend *cfg_file)
+static int config_parse(git_strmap *values, diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth)
{
- int error = GIT_SUCCESS, c;
+ int c;
char *current_section = NULL;
char *var_name;
char *var_value;
cvar_t *var;
+ git_buf buf = GIT_BUF_INIT;
+ int result = 0;
+ uint32_t reader_idx;
+
+ if (depth >= MAX_INCLUDE_DEPTH) {
+ giterr_set(GITERR_CONFIG, "Maximum config include depth reached");
+ return -1;
+ }
+ reader_idx = git_array_size(cfg_file->readers) - 1;
/* Initialize the reading position */
- cfg_file->reader.read_ptr = cfg_file->reader.buffer.data;
- cfg_file->reader.eof = 0;
+ reader->read_ptr = reader->buffer.ptr;
+ reader->eof = 0;
/* If the file is empty, there's nothing for us to do */
- if (*cfg_file->reader.read_ptr == '\0')
- return GIT_SUCCESS;
+ if (*reader->read_ptr == '\0')
+ return 0;
- skip_bom(cfg_file);
+ skip_bom(reader);
- while (error == GIT_SUCCESS && !cfg_file->reader.eof) {
+ while (result == 0 && !reader->eof) {
- c = cfg_peek(cfg_file, SKIP_WHITESPACE);
+ c = reader_peek(reader, SKIP_WHITESPACE);
switch (c) {
- case '\0': /* We've arrived at the end of the file */
+ case '\n': /* EOF when peeking, set EOF in the reader to exit the loop */
+ reader->eof = 1;
break;
case '[': /* section header, new section begins */
- free(current_section);
+ git__free(current_section);
current_section = NULL;
- error = parse_section_header(cfg_file, &current_section);
+ result = parse_section_header(reader, &current_section);
break;
case ';':
case '#':
- cfg_consume_line(cfg_file);
+ reader_consume_line(reader);
break;
default: /* assume variable declaration */
- error = parse_variable(cfg_file, &var_name, &var_value);
-
- if (error < GIT_SUCCESS)
+ result = parse_variable(reader, &var_name, &var_value);
+ if (result < 0)
break;
- var = malloc(sizeof(cvar_t));
- if (var == NULL) {
- error = GIT_ENOMEM;
- break;
+ var = git__malloc(sizeof(cvar_t));
+ GITERR_CHECK_ALLOC(var);
+ memset(var, 0x0, sizeof(cvar_t));
+ var->entry = git__malloc(sizeof(git_config_entry));
+ GITERR_CHECK_ALLOC(var->entry);
+ memset(var->entry, 0x0, sizeof(git_config_entry));
+
+ git__strtolower(var_name);
+ git_buf_printf(&buf, "%s.%s", current_section, var_name);
+ git__free(var_name);
+
+ if (git_buf_oom(&buf)) {
+ git__free(var_value);
+ return -1;
}
- memset(var, 0x0, sizeof(cvar_t));
+ var->entry->name = git_buf_detach(&buf);
+ var->entry->value = var_value;
+ var->entry->level = level;
+ var->included = !!depth;
+
- var->section = git__strdup(current_section);
- if (var->section == NULL) {
- error = GIT_ENOMEM;
- free(var);
+ if ((result = append_entry(values, var)) < 0)
break;
- }
+ else
+ result = 0;
+
+ /* Add or append the new config option */
+ if (!git__strcmp(var->entry->name, "include.path")) {
+ struct reader *r;
+ git_buf path = GIT_BUF_INIT;
+ char *dir;
+ uint32_t index;
+
+ r = git_array_alloc(cfg_file->readers);
+ /* The reader may have been reallocated */
+ reader = git_array_get(cfg_file->readers, reader_idx);
+ memset(r, 0, sizeof(struct reader));
+ if ((result = git_path_dirname_r(&path, reader->file_path)) < 0)
+ break;
+
+ /* We need to know out index in the array, as the next config_parse call may realloc */
+ index = git_array_size(cfg_file->readers) - 1;
+ dir = git_buf_detach(&path);
+ result = included_path(&path, dir, var->entry->value);
+ git__free(dir);
+
+ if (result < 0)
+ break;
- var->name = var_name;
- var->value = var_value;
- git__strtolower(var->name);
+ r->file_path = git_buf_detach(&path);
+ git_buf_init(&r->buffer, 0);
+ if ((result = git_futils_readbuffer_updated(&r->buffer, r->file_path, &r->file_mtime,
+ &r->file_size, NULL)) < 0)
+ break;
+
+ result = config_parse(values, cfg_file, r, level, depth+1);
+ r = git_array_get(cfg_file->readers, index);
+ git_buf_free(&r->buffer);
- CVAR_LIST_APPEND(&cfg_file->var_list, var);
+ if (result < 0)
+ break;
+ }
break;
}
}
- free(current_section);
+ git__free(current_section);
+ return result;
+}
+
+static int write_section(git_filebuf *file, const char *key)
+{
+ int result;
+ const char *dot;
+ git_buf buf = GIT_BUF_INIT;
+
+ /* All of this just for [section "subsection"] */
+ dot = strchr(key, '.');
+ git_buf_putc(&buf, '[');
+ if (dot == NULL) {
+ git_buf_puts(&buf, key);
+ } else {
+ char *escaped;
+ git_buf_put(&buf, key, dot - key);
+ escaped = escape_value(dot + 1);
+ GITERR_CHECK_ALLOC(escaped);
+ git_buf_printf(&buf, " \"%s\"", escaped);
+ git__free(escaped);
+ }
+ git_buf_puts(&buf, "]\n");
+
+ if (git_buf_oom(&buf))
+ return -1;
+
+ result = git_filebuf_write(file, git_buf_cstr(&buf), buf.size);
+ git_buf_free(&buf);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse config");
+ return result;
}
-static int write_section(git_filebuf *file, cvar_t *var)
+static const char *quotes_for_value(const char *value)
{
- int error;
+ const char *ptr;
- error = git_filebuf_printf(file, "[%s]\n", var->section);
- if (error < GIT_SUCCESS)
- return error;
+ if (value[0] == ' ' || value[0] == '\0')
+ return "\"";
- error = git_filebuf_printf(file, " %s = %s\n", var->name, var->value);
- return error;
+ for (ptr = value; *ptr; ++ptr) {
+ if (*ptr == ';' || *ptr == '#')
+ return "\"";
+ }
+
+ if (ptr[-1] == ' ')
+ return "\"";
+
+ return "";
}
/*
* This is pretty much the parsing, except we write out anything we don't have
*/
-static int config_write(diskfile_backend *cfg, cvar_t *var)
+static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char* value)
{
- int error = GIT_SUCCESS, c;
- int section_matches = 0, last_section_matched = 0;
- char *current_section = NULL;
- char *var_name, *var_value, *data_start;
- git_filebuf file;
- const char *pre_end = NULL, *post_start = NULL;
+ int result, c;
+ int section_matches = 0, last_section_matched = 0, preg_replaced = 0, write_trailer = 0;
+ const char *pre_end = NULL, *post_start = NULL, *data_start, *write_start;
+ char *current_section = NULL, *section, *name, *ldot;
+ git_filebuf file = GIT_FILEBUF_INIT;
+ struct reader *reader = git_array_get(cfg->readers, 0);
/* We need to read in our own config file */
- error = git_futils_readbuffer(&cfg->reader.buffer, cfg->file_path);
- if (error < GIT_SUCCESS) {
- return git__rethrow(error, "Failed to read existing config file %s", cfg->file_path);
- }
+ result = git_futils_readbuffer(&reader->buffer, cfg->file_path);
/* Initialise the reading position */
- cfg->reader.read_ptr = cfg->reader.buffer.data;
- cfg->reader.eof = 0;
- data_start = cfg->reader.read_ptr;
+ if (result == GIT_ENOTFOUND) {
+ reader->read_ptr = NULL;
+ reader->eof = 1;
+ data_start = NULL;
+ git_buf_clear(&reader->buffer);
+ } else if (result == 0) {
+ reader->read_ptr = reader->buffer.ptr;
+ reader->eof = 0;
+ data_start = reader->read_ptr;
+ } else {
+ return -1; /* OS error when reading the file */
+ }
+
+ write_start = data_start;
/* Lock the file */
- error = git_filebuf_open(&file, cfg->file_path, 0);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to lock config file");
+ if ((result = git_filebuf_open(
+ &file, cfg->file_path, 0, GIT_CONFIG_FILE_MODE)) < 0) {
+ git_buf_free(&reader->buffer);
+ return result;
+ }
- skip_bom(cfg);
+ skip_bom(reader);
+ ldot = strrchr(key, '.');
+ name = ldot + 1;
+ section = git__strndup(key, ldot - key);
- while (error == GIT_SUCCESS && !cfg->reader.eof) {
- c = cfg_peek(cfg, SKIP_WHITESPACE);
+ while (!reader->eof) {
+ c = reader_peek(reader, SKIP_WHITESPACE);
- switch (c) {
- case '\0': /* We've arrived at the end of the file */
+ if (c == '\0') { /* We've arrived at the end of the file */
break;
- case '[': /* section header, new section begins */
+ } else if (c == '[') { /* section header, new section begins */
/*
* We set both positions to the current one in case we
* need to add a variable to the end of a section. In that
@@ -956,23 +1409,23 @@ static int config_write(diskfile_backend *cfg, cvar_t *var)
* new section. If we actually want to replace it, the
* default case will take care of updating them.
*/
- pre_end = post_start = cfg->reader.read_ptr;
- free(current_section);
- error = parse_section_header(cfg, &current_section);
- if (error < GIT_SUCCESS)
- break;
+ pre_end = post_start = reader->read_ptr;
+
+ git__free(current_section);
+ current_section = NULL;
+ if (parse_section_header(reader, &current_section) < 0)
+ goto rewrite_fail;
/* Keep track of when it stops matching */
last_section_matched = section_matches;
- section_matches = !strcmp(current_section, var->section);
- break;
+ section_matches = !strcmp(current_section, section);
+ }
- case ';':
- case '#':
- cfg_consume_line(cfg);
- break;
+ else if (c == ';' || c == '#') {
+ reader_consume_line(reader);
+ }
- default:
+ else {
/*
* If the section doesn't match, but the last section did,
* it means we need to add a variable (so skip the line
@@ -985,59 +1438,60 @@ static int config_write(diskfile_backend *cfg, cvar_t *var)
*/
if (!section_matches) {
if (!last_section_matched) {
- cfg_consume_line(cfg);
- break;
+ reader_consume_line(reader);
+ continue;
}
} else {
- int cmp = -1;
+ int has_matched = 0;
+ char *var_name, *var_value;
- pre_end = cfg->reader.read_ptr;
- if ((error = parse_variable(cfg, &var_name, &var_value)) == GIT_SUCCESS)
- cmp = strcasecmp(var->name, var_name);
+ pre_end = reader->read_ptr;
+ if (parse_variable(reader, &var_name, &var_value) < 0)
+ goto rewrite_fail;
- free(var_name);
- free(var_value);
+ /* First try to match the name of the variable */
+ if (strcasecmp(name, var_name) == 0)
+ has_matched = 1;
- if (cmp != 0)
- break;
+ /* If the name matches, and we have a regex to match the
+ * value, try to match it */
+ if (has_matched && preg != NULL)
+ has_matched = (regexec(preg, var_value, 0, NULL, 0) == 0);
+
+ git__free(var_name);
+ git__free(var_value);
+
+ /* if there is no match, keep going */
+ if (!has_matched)
+ continue;
- post_start = cfg->reader.read_ptr;
+ post_start = reader->read_ptr;
}
- /*
- * We've found the variable we wanted to change, so
- * write anything up to it
- */
- error = git_filebuf_write(&file, data_start, pre_end - data_start);
- if (error < GIT_SUCCESS) {
- git__rethrow(error, "Failed to write the first part of the file");
- break;
+ /* We've found the variable we wanted to change, so
+ * write anything up to it */
+ git_filebuf_write(&file, write_start, pre_end - write_start);
+ preg_replaced = 1;
+
+ /* Then replace the variable. If the value is NULL, it
+ * means we want to delete it, so don't write anything. */
+ if (value != NULL) {
+ const char *q = quotes_for_value(value);
+ git_filebuf_printf(&file, "\t%s = %s%s%s\n", name, q, value, q);
}
/*
- * Then replace the variable. If the value is NULL, it
- * means we want to delete it, so pretend everything went
- * fine
+ * If we have a multivar, we should keep looking for entries,
+ * but only if we're in the right section. Otherwise we'll end up
+ * looping on the edge of a matching and a non-matching section.
*/
- if (var->value == NULL)
- error = GIT_SUCCESS;
- else
- error = git_filebuf_printf(&file, "\t%s = %s\n", var->name, var->value);
- if (error < GIT_SUCCESS) {
- git__rethrow(error, "Failed to overwrite the variable");
- break;
- }
-
- /* And then the write out rest of the file */
- error = git_filebuf_write(&file, post_start,
- cfg->reader.buffer.len - (post_start - data_start));
-
- if (error < GIT_SUCCESS) {
- git__rethrow(error, "Failed to write the rest of the file");
- break;
+ if (section_matches && preg != NULL) {
+ write_start = post_start;
+ continue;
}
- goto cleanup;
+ write_trailer = 1;
+ break; /* break from the loop */
}
}
@@ -1050,126 +1504,218 @@ static int config_write(diskfile_backend *cfg, cvar_t *var)
* 2) we didn't find a section for us so we need to create it
* ourselves.
*
- * Either way we need to write out the whole file.
+ * 3) we're setting a multivar with a regex, which means we
+ * continue to search for matching values
+ *
+ * In the last case, if we've already replaced a value, we
+ * want to write the rest of the file. Otherwise we need to write
+ * out the whole file and then the new variable.
*/
+ if (write_trailer) {
+ /* Write out rest of the file */
+ git_filebuf_write(&file, post_start, reader->buffer.size - (post_start - data_start));
+ } else {
+ if (preg_replaced) {
+ git_filebuf_printf(&file, "\n%s", write_start);
+ } else {
+ const char *q;
+
+ git_filebuf_write(&file, reader->buffer.ptr, reader->buffer.size);
+
+ /* And now if we just need to add a variable */
+ if (!section_matches && write_section(&file, section) < 0)
+ goto rewrite_fail;
+
+ /* Sanity check: if we are here, and value is NULL, that means that somebody
+ * touched the config file after our intial read. We should probably assert()
+ * this, but instead we'll handle it gracefully with an error. */
+ if (value == NULL) {
+ giterr_set(GITERR_CONFIG,
+ "race condition when writing a config file (a cvar has been removed)");
+ goto rewrite_fail;
+ }
+
+ /* If we are here, there is at least a section line */
+ if (reader->buffer.size > 0 && *(reader->buffer.ptr + reader->buffer.size - 1) != '\n')
+ git_filebuf_write(&file, "\n", 1);
- error = git_filebuf_write(&file, cfg->reader.buffer.data, cfg->reader.buffer.len);
- if (error < GIT_SUCCESS) {
- git__rethrow(error, "Failed to write original config content");
- goto cleanup;
+ q = quotes_for_value(value);
+ git_filebuf_printf(&file, "\t%s = %s%s%s\n", name, q, value, q);
+ }
}
- /* And now if we just need to add a variable */
- if (section_matches) {
- error = git_filebuf_printf(&file, "\t%s = %s\n", var->name, var->value);
- goto cleanup;
+ git__free(section);
+ git__free(current_section);
+
+ /* refresh stats - if this errors, then commit will error too */
+ (void)git_filebuf_stats(&reader->file_mtime, &reader->file_size, &file);
+
+ result = git_filebuf_commit(&file);
+ git_buf_free(&reader->buffer);
+
+ return result;
+
+rewrite_fail:
+ git__free(section);
+ git__free(current_section);
+
+ git_filebuf_cleanup(&file);
+ git_buf_free(&reader->buffer);
+ return -1;
+}
+
+static const char *escapes = "ntb\"\\";
+static const char *escaped = "\n\t\b\"\\";
+
+/* Escape the values to write them to the file */
+static char *escape_value(const char *ptr)
+{
+ git_buf buf = GIT_BUF_INIT;
+ size_t len;
+ const char *esc;
+
+ assert(ptr);
+
+ len = strlen(ptr);
+ if (!len)
+ return git__calloc(1, sizeof(char));
+
+ git_buf_grow(&buf, len);
+
+ while (*ptr != '\0') {
+ if ((esc = strchr(escaped, *ptr)) != NULL) {
+ git_buf_putc(&buf, '\\');
+ git_buf_putc(&buf, escapes[esc - escaped]);
+ } else {
+ git_buf_putc(&buf, *ptr);
+ }
+ ptr++;
}
- /* Or maybe we need to write out a whole section */
- error = write_section(&file, var);
- if (error < GIT_SUCCESS)
- git__rethrow(error, "Failed to write new section");
+ if (git_buf_oom(&buf)) {
+ git_buf_free(&buf);
+ return NULL;
+ }
- cleanup:
- free(current_section);
+ return git_buf_detach(&buf);
+}
- if (error < GIT_SUCCESS)
- git_filebuf_cleanup(&file);
- else
- error = git_filebuf_commit(&file);
+/* '\"' -> '"' etc */
+static char *fixup_line(const char *ptr, int quote_count)
+{
+ char *str = git__malloc(strlen(ptr) + 1);
+ char *out = str, *esc;
- git_futils_freebuffer(&cfg->reader.buffer);
- return error;
+ if (str == NULL)
+ return NULL;
+
+ while (*ptr != '\0') {
+ if (*ptr == '"') {
+ quote_count++;
+ } else if (*ptr != '\\') {
+ *out++ = *ptr;
+ } else {
+ /* backslash, check the next char */
+ ptr++;
+ /* if we're at the end, it's a multiline, so keep the backslash */
+ if (*ptr == '\0') {
+ *out++ = '\\';
+ goto out;
+ }
+ if ((esc = strchr(escapes, *ptr)) != NULL) {
+ *out++ = escaped[esc - escapes];
+ } else {
+ git__free(str);
+ giterr_set(GITERR_CONFIG, "Invalid escape at %s", ptr);
+ return NULL;
+ }
+ }
+ ptr++;
+ }
+
+out:
+ *out = '\0';
+
+ return str;
}
static int is_multiline_var(const char *str)
{
- char *end = strrchr(str, '\0') - 1;
-
- while (isspace(*end))
- --end;
+ int count = 0;
+ const char *end = str + strlen(str);
+ while (end > str && end[-1] == '\\') {
+ count++;
+ end--;
+ }
- return *end == '\\';
+ /* An odd number means last backslash wasn't escaped, so it's multiline */
+ return (end > str) && (count & 1);
}
-static int parse_multiline_variable(diskfile_backend *cfg, const char *first, char **out)
+static int parse_multiline_variable(struct reader *reader, git_buf *value, int in_quotes)
{
- char *line = NULL, *end;
- int error = GIT_SUCCESS, len, ret;
- char *buf;
+ char *line = NULL, *proc_line = NULL;
+ int quote_count;
/* Check that the next line exists */
- line = cfg_readline(cfg);
+ line = reader_readline(reader, false);
if (line == NULL)
- return GIT_ENOMEM;
+ return -1;
/* We've reached the end of the file, there is input missing */
if (line[0] == '\0') {
- error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse multiline var. File ended unexpectedly");
- goto out;
+ set_parse_error(reader, 0, "Unexpected end of file while parsing multine var");
+ git__free(line);
+ return -1;
}
- strip_comments(line);
+ quote_count = strip_comments(line, !!in_quotes);
/* If it was just a comment, pretend it didn't exist */
if (line[0] == '\0') {
- error = parse_multiline_variable(cfg, first, out);
- goto out;
+ git__free(line);
+ return parse_multiline_variable(reader, value, quote_count);
+ /* TODO: unbounded recursion. This **could** be exploitable */
}
- /* Find the continuation character '\' and strip the whitespace */
- end = strrchr(first, '\\');
- while (isspace(end[-1]))
- --end;
-
- *end = '\0'; /* Terminate the string here */
+ /* Drop the continuation character '\': to closely follow the UNIX
+ * standard, this character **has** to be last one in the buf, with
+ * no whitespace after it */
+ assert(is_multiline_var(value->ptr));
+ git_buf_shorten(value, 1);
- len = strlen(first) + strlen(line) + 2;
- buf = git__malloc(len);
- if (buf == NULL) {
- error = GIT_ENOMEM;
- goto out;
- }
-
- ret = snprintf(buf, len, "%s %s", first, line);
- if (ret < 0) {
- error = git__throw(GIT_EOSERR, "Failed to parse multiline var. Failed to put together two lines. OS err: %s", strerror(errno));
- free(buf);
- goto out;
+ proc_line = fixup_line(line, in_quotes);
+ if (proc_line == NULL) {
+ git__free(line);
+ return -1;
}
+ /* add this line to the multiline var */
+ git_buf_puts(value, proc_line);
+ git__free(line);
+ git__free(proc_line);
/*
- * If we need to continue reading the next line, pretend
- * everything we've read up to now was in one line and call
- * ourselves.
+ * If we need to continue reading the next line, let's just
+ * keep putting stuff in the buffer
*/
- if (is_multiline_var(buf)) {
- char *final_val;
- error = parse_multiline_variable(cfg, buf, &final_val);
- free(buf);
- buf = final_val;
- }
+ if (is_multiline_var(value->ptr))
+ return parse_multiline_variable(reader, value, quote_count);
- *out = buf;
-
- out:
- free(line);
- return error;
+ return 0;
}
-static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value)
+static int parse_variable(struct reader *reader, char **var_name, char **var_value)
{
- char *tmp;
- int error = GIT_SUCCESS;
const char *var_end = NULL;
const char *value_start = NULL;
char *line;
+ int quote_count;
- line = cfg_readline(cfg);
+ line = reader_readline(reader, true);
if (line == NULL)
- return GIT_ENOMEM;
+ return -1;
- strip_comments(line);
+ quote_count = strip_comments(line, 0);
var_end = strchr(line, '=');
@@ -1178,51 +1724,47 @@ static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_val
else
value_start = var_end + 1;
- if (isspace(var_end[-1])) {
- do var_end--;
- while (isspace(var_end[0]));
- }
+ do var_end--;
+ while (var_end>line && git__isspace(*var_end));
- tmp = git__strndup(line, var_end - line + 1);
- if (tmp == NULL) {
- error = GIT_ENOMEM;
- goto out;
- }
+ *var_name = git__strndup(line, var_end - line + 1);
+ GITERR_CHECK_ALLOC(*var_name);
- *var_name = tmp;
+ /* If there is no value, boolean true is assumed */
+ *var_value = NULL;
/*
* Now, let's try to parse the value
*/
if (value_start != NULL) {
-
- while (isspace(value_start[0]))
+ while (git__isspace(value_start[0]))
value_start++;
- if (value_start[0] == '\0')
- goto out;
-
if (is_multiline_var(value_start)) {
- error = parse_multiline_variable(cfg, value_start, var_value);
- if (error < GIT_SUCCESS)
- free(*var_name);
- goto out;
- }
+ git_buf multi_value = GIT_BUF_INIT;
+ char *proc_line = fixup_line(value_start, 0);
+ GITERR_CHECK_ALLOC(proc_line);
+ git_buf_puts(&multi_value, proc_line);
+ git__free(proc_line);
+ if (parse_multiline_variable(reader, &multi_value, quote_count) < 0 || git_buf_oom(&multi_value)) {
+ git__free(*var_name);
+ git__free(line);
+ git_buf_free(&multi_value);
+ return -1;
+ }
- tmp = strdup(value_start);
- if (tmp == NULL) {
- free(*var_name);
- error = GIT_ENOMEM;
- goto out;
- }
+ *var_value = git_buf_detach(&multi_value);
- *var_value = tmp;
- } else {
- /* If there is no value, boolean true is assumed */
- *var_value = NULL;
+ }
+ 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);
+ }
}
- out:
- free(line);
- return error;
+ git__free(line);
+ return 0;
}
diff --git a/src/config_file.h b/src/config_file.h
new file mode 100644
index 000000000..fcccbd5cc
--- /dev/null
+++ b/src/config_file.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_config_file_h__
+#define INCLUDE_config_file_h__
+
+#include "git2/config.h"
+
+GIT_INLINE(int) git_config_file_open(git_config_backend *cfg, unsigned int level)
+{
+ return cfg->open(cfg, level);
+}
+
+GIT_INLINE(void) git_config_file_free(git_config_backend *cfg)
+{
+ if (cfg)
+ cfg->free(cfg);
+}
+
+GIT_INLINE(int) git_config_file_get_string(
+ const git_config_entry **out, git_config_backend *cfg, const char *name)
+{
+ return cfg->get(cfg, name, out);
+}
+
+GIT_INLINE(int) git_config_file_set_string(
+ git_config_backend *cfg, const char *name, const char *value)
+{
+ return cfg->set(cfg, name, value);
+}
+
+GIT_INLINE(int) git_config_file_delete(
+ git_config_backend *cfg, const char *name)
+{
+ return cfg->del(cfg, name);
+}
+
+GIT_INLINE(int) git_config_file_foreach(
+ git_config_backend *cfg,
+ int (*fn)(const git_config_entry *entry, void *data),
+ void *data)
+{
+ return git_config_backend_foreach_match(cfg, NULL, fn, data);
+}
+
+GIT_INLINE(int) git_config_file_foreach_match(
+ git_config_backend *cfg,
+ const char *regexp,
+ int (*fn)(const git_config_entry *entry, void *data),
+ void *data)
+{
+ return git_config_backend_foreach_match(cfg, regexp, fn, data);
+}
+
+extern int git_config_file_normalize_section(char *start, char *end);
+
+#endif
+
diff --git a/src/crlf.c b/src/crlf.c
new file mode 100644
index 000000000..821e04eb2
--- /dev/null
+++ b/src/crlf.c
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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/attr.h"
+#include "git2/blob.h"
+#include "git2/index.h"
+#include "git2/sys/filter.h"
+
+#include "common.h"
+#include "fileops.h"
+#include "hash.h"
+#include "filter.h"
+#include "buf_text.h"
+#include "repository.h"
+
+struct crlf_attrs {
+ int crlf_action;
+ int eol;
+ int auto_crlf;
+ int safe_crlf;
+};
+
+struct crlf_filter {
+ git_filter f;
+};
+
+static int check_crlf(const char *value)
+{
+ if (GIT_ATTR_TRUE(value))
+ return GIT_CRLF_TEXT;
+
+ if (GIT_ATTR_FALSE(value))
+ return GIT_CRLF_BINARY;
+
+ if (GIT_ATTR_UNSPECIFIED(value))
+ return GIT_CRLF_GUESS;
+
+ if (strcmp(value, "input") == 0)
+ return GIT_CRLF_INPUT;
+
+ if (strcmp(value, "auto") == 0)
+ return GIT_CRLF_AUTO;
+
+ return GIT_CRLF_GUESS;
+}
+
+static int check_eol(const char *value)
+{
+ if (GIT_ATTR_UNSPECIFIED(value))
+ return GIT_EOL_UNSET;
+
+ if (strcmp(value, "lf") == 0)
+ return GIT_EOL_LF;
+
+ if (strcmp(value, "crlf") == 0)
+ return GIT_EOL_CRLF;
+
+ return GIT_EOL_UNSET;
+}
+
+static int crlf_input_action(struct crlf_attrs *ca)
+{
+ if (ca->crlf_action == GIT_CRLF_BINARY)
+ return GIT_CRLF_BINARY;
+
+ if (ca->eol == GIT_EOL_LF)
+ return GIT_CRLF_INPUT;
+
+ if (ca->eol == GIT_EOL_CRLF)
+ return GIT_CRLF_CRLF;
+
+ return ca->crlf_action;
+}
+
+static int has_cr_in_index(const git_filter_source *src)
+{
+ git_repository *repo = git_filter_source_repo(src);
+ const char *path = git_filter_source_path(src);
+ git_index *index;
+ const git_index_entry *entry;
+ git_blob *blob;
+ const void *blobcontent;
+ git_off_t blobsize;
+ bool found_cr;
+
+ if (!path)
+ return false;
+
+ if (git_repository_index__weakptr(&index, repo) < 0) {
+ giterr_clear();
+ return false;
+ }
+
+ if (!(entry = git_index_get_bypath(index, path, 0)) &&
+ !(entry = git_index_get_bypath(index, path, 1)))
+ return false;
+
+ if (!S_ISREG(entry->mode)) /* don't crlf filter non-blobs */
+ return true;
+
+ if (git_blob_lookup(&blob, repo, &entry->id) < 0)
+ return false;
+
+ blobcontent = git_blob_rawcontent(blob);
+ blobsize = git_blob_rawsize(blob);
+ if (!git__is_sizet(blobsize))
+ blobsize = (size_t)-1;
+
+ found_cr = (blobcontent != NULL &&
+ blobsize > 0 &&
+ memchr(blobcontent, '\r', (size_t)blobsize) != NULL);
+
+ git_blob_free(blob);
+ return found_cr;
+}
+
+static int crlf_apply_to_odb(
+ struct crlf_attrs *ca,
+ git_buf *to,
+ const git_buf *from,
+ const git_filter_source *src)
+{
+ /* Empty file? Nothing to do */
+ if (!git_buf_len(from))
+ return 0;
+
+ /* Heuristics to see if we can skip the conversion.
+ * Straight from Core Git.
+ */
+ if (ca->crlf_action == GIT_CRLF_AUTO || ca->crlf_action == GIT_CRLF_GUESS) {
+ git_buf_text_stats stats;
+
+ /* Check heuristics for binary vs text - returns true if binary */
+ if (git_buf_text_gather_stats(&stats, from, false))
+ return GIT_PASSTHROUGH;
+
+ /* If there are no CR characters to filter out, then just pass */
+ if (!stats.cr)
+ return GIT_PASSTHROUGH;
+
+ /* If safecrlf is enabled, sanity-check the result. */
+ if (stats.cr != stats.crlf || stats.lf != stats.crlf) {
+ switch (ca->safe_crlf) {
+ case GIT_SAFE_CRLF_FAIL:
+ giterr_set(
+ GITERR_FILTER, "LF would be replaced by CRLF in '%s'",
+ git_filter_source_path(src));
+ return -1;
+ case GIT_SAFE_CRLF_WARN:
+ /* TODO: issue warning when warning API is available */;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /*
+ * We're currently not going to even try to convert stuff
+ * that has bare CR characters. Does anybody do that crazy
+ * stuff?
+ */
+ if (stats.cr != stats.crlf)
+ return GIT_PASSTHROUGH;
+
+ if (ca->crlf_action == GIT_CRLF_GUESS) {
+ /*
+ * If the file in the index has any CR in it, do not convert.
+ * This is the new safer autocrlf handling.
+ */
+ if (has_cr_in_index(src))
+ return GIT_PASSTHROUGH;
+ }
+
+ if (!stats.cr)
+ return GIT_PASSTHROUGH;
+ }
+
+ /* Actually drop the carriage returns */
+ return git_buf_text_crlf_to_lf(to, from);
+}
+
+static const char *line_ending(struct crlf_attrs *ca)
+{
+ switch (ca->crlf_action) {
+ case GIT_CRLF_BINARY:
+ case GIT_CRLF_INPUT:
+ return "\n";
+
+ case GIT_CRLF_CRLF:
+ return "\r\n";
+
+ case GIT_CRLF_AUTO:
+ case GIT_CRLF_TEXT:
+ case GIT_CRLF_GUESS:
+ break;
+
+ default:
+ goto line_ending_error;
+ }
+
+ switch (ca->eol) {
+ case GIT_EOL_UNSET:
+ return GIT_EOL_NATIVE == GIT_EOL_CRLF ? "\r\n" : "\n";
+
+ case GIT_EOL_CRLF:
+ return "\r\n";
+
+ case GIT_EOL_LF:
+ return "\n";
+
+ default:
+ goto line_ending_error;
+ }
+
+line_ending_error:
+ giterr_set(GITERR_INVALID, "Invalid input to line ending filter");
+ return NULL;
+}
+
+static int crlf_apply_to_workdir(
+ struct crlf_attrs *ca, git_buf *to, const git_buf *from)
+{
+ const char *workdir_ending = NULL;
+
+ /* Empty file? Nothing to do. */
+ if (git_buf_len(from) == 0)
+ return 0;
+
+ /* Don't filter binary files */
+ if (git_buf_text_is_binary(from))
+ return GIT_PASSTHROUGH;
+
+ /* Determine proper line ending */
+ workdir_ending = line_ending(ca);
+ if (!workdir_ending)
+ return -1;
+
+ /* only LF->CRLF conversion is supported, do nothing on LF platforms */
+ if (strcmp(workdir_ending, "\r\n") != 0)
+ return GIT_PASSTHROUGH;
+
+ return git_buf_text_lf_to_crlf(to, from);
+}
+
+static int crlf_check(
+ git_filter *self,
+ void **payload, /* points to NULL ptr on entry, may be set */
+ const git_filter_source *src,
+ const char **attr_values)
+{
+ int error;
+ struct crlf_attrs ca;
+
+ GIT_UNUSED(self);
+
+ if (!attr_values) {
+ ca.crlf_action = GIT_CRLF_GUESS;
+ ca.eol = GIT_EOL_UNSET;
+ } else {
+ ca.crlf_action = check_crlf(attr_values[2]); /* text */
+ if (ca.crlf_action == GIT_CRLF_GUESS)
+ ca.crlf_action = check_crlf(attr_values[0]); /* clrf */
+ ca.eol = check_eol(attr_values[1]); /* eol */
+ }
+ ca.auto_crlf = GIT_AUTO_CRLF_DEFAULT;
+
+ /*
+ * Use the core Git logic to see if we should perform CRLF for this file
+ * based on its attributes & the value of `core.autocrlf`
+ */
+ ca.crlf_action = crlf_input_action(&ca);
+
+ if (ca.crlf_action == GIT_CRLF_BINARY)
+ return GIT_PASSTHROUGH;
+
+ if (ca.crlf_action == GIT_CRLF_GUESS ||
+ (ca.crlf_action == GIT_CRLF_AUTO &&
+ git_filter_source_mode(src) == GIT_FILTER_SMUDGE)) {
+
+ error = git_repository__cvar(
+ &ca.auto_crlf, git_filter_source_repo(src), GIT_CVAR_AUTO_CRLF);
+ if (error < 0)
+ return error;
+
+ if (ca.auto_crlf == GIT_AUTO_CRLF_FALSE)
+ return GIT_PASSTHROUGH;
+
+ if (ca.auto_crlf == GIT_AUTO_CRLF_INPUT &&
+ git_filter_source_mode(src) == GIT_FILTER_SMUDGE)
+ return GIT_PASSTHROUGH;
+ }
+
+ if (git_filter_source_mode(src) == GIT_FILTER_CLEAN) {
+ error = git_repository__cvar(
+ &ca.safe_crlf, git_filter_source_repo(src), GIT_CVAR_SAFE_CRLF);
+ if (error < 0)
+ return error;
+
+ /* downgrade FAIL to WARN if ALLOW_UNSAFE option is used */
+ if ((git_filter_source_options(src) & GIT_FILTER_OPT_ALLOW_UNSAFE) &&
+ ca.safe_crlf == GIT_SAFE_CRLF_FAIL)
+ ca.safe_crlf = GIT_SAFE_CRLF_WARN;
+ }
+
+ *payload = git__malloc(sizeof(ca));
+ GITERR_CHECK_ALLOC(*payload);
+ memcpy(*payload, &ca, sizeof(ca));
+
+ return 0;
+}
+
+static int crlf_apply(
+ git_filter *self,
+ void **payload, /* may be read and/or set */
+ git_buf *to,
+ const git_buf *from,
+ const git_filter_source *src)
+{
+ /* initialize payload in case `check` was bypassed */
+ if (!*payload) {
+ int error = crlf_check(self, payload, src, NULL);
+ if (error < 0 && error != GIT_PASSTHROUGH)
+ return error;
+ }
+
+ if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE)
+ return crlf_apply_to_workdir(*payload, to, from);
+ else
+ return crlf_apply_to_odb(*payload, to, from, src);
+}
+
+static void crlf_cleanup(
+ git_filter *self,
+ void *payload)
+{
+ GIT_UNUSED(self);
+ git__free(payload);
+}
+
+git_filter *git_crlf_filter_new(void)
+{
+ struct crlf_filter *f = git__calloc(1, sizeof(struct crlf_filter));
+
+ f->f.version = GIT_FILTER_VERSION;
+ f->f.attributes = "crlf eol text";
+ f->f.initialize = NULL;
+ f->f.shutdown = git_filter_free;
+ f->f.check = crlf_check;
+ f->f.apply = crlf_apply;
+ f->f.cleanup = crlf_cleanup;
+
+ return (git_filter *)f;
+}
diff --git a/src/date.c b/src/date.c
new file mode 100644
index 000000000..0e1b31aee
--- /dev/null
+++ b/src/date.c
@@ -0,0 +1,904 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+
+#include "common.h"
+
+#ifndef GIT_WIN32
+#include <sys/time.h>
+#endif
+
+#include "util.h"
+#include "cache.h"
+#include "posix.h"
+
+#include <ctype.h>
+#include <time.h>
+
+typedef enum {
+ DATE_NORMAL = 0,
+ DATE_RELATIVE,
+ DATE_SHORT,
+ DATE_LOCAL,
+ DATE_ISO8601,
+ DATE_RFC2822,
+ DATE_RAW
+} date_mode;
+
+/*
+ * This is like mktime, but without normalization of tm_wday and tm_yday.
+ */
+static git_time_t tm_to_time_t(const struct tm *tm)
+{
+ static const int mdays[] = {
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
+ };
+ int year = tm->tm_year - 70;
+ int month = tm->tm_mon;
+ int day = tm->tm_mday;
+
+ if (year < 0 || year > 129) /* algo only works for 1970-2099 */
+ return -1;
+ if (month < 0 || month > 11) /* array bounds */
+ return -1;
+ if (month < 2 || (year + 2) % 4)
+ day--;
+ if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0)
+ return -1;
+ return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL +
+ tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec;
+}
+
+static const char *month_names[] = {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"
+};
+
+static const char *weekday_names[] = {
+ "Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays"
+};
+
+
+
+/*
+ * Check these. And note how it doesn't do the summer-time conversion.
+ *
+ * In my world, it's always summer, and things are probably a bit off
+ * in other ways too.
+ */
+static const struct {
+ const char *name;
+ int offset;
+ int dst;
+} timezone_names[] = {
+ { "IDLW", -12, 0, }, /* International Date Line West */
+ { "NT", -11, 0, }, /* Nome */
+ { "CAT", -10, 0, }, /* Central Alaska */
+ { "HST", -10, 0, }, /* Hawaii Standard */
+ { "HDT", -10, 1, }, /* Hawaii Daylight */
+ { "YST", -9, 0, }, /* Yukon Standard */
+ { "YDT", -9, 1, }, /* Yukon Daylight */
+ { "PST", -8, 0, }, /* Pacific Standard */
+ { "PDT", -8, 1, }, /* Pacific Daylight */
+ { "MST", -7, 0, }, /* Mountain Standard */
+ { "MDT", -7, 1, }, /* Mountain Daylight */
+ { "CST", -6, 0, }, /* Central Standard */
+ { "CDT", -6, 1, }, /* Central Daylight */
+ { "EST", -5, 0, }, /* Eastern Standard */
+ { "EDT", -5, 1, }, /* Eastern Daylight */
+ { "AST", -3, 0, }, /* Atlantic Standard */
+ { "ADT", -3, 1, }, /* Atlantic Daylight */
+ { "WAT", -1, 0, }, /* West Africa */
+
+ { "GMT", 0, 0, }, /* Greenwich Mean */
+ { "UTC", 0, 0, }, /* Universal (Coordinated) */
+ { "Z", 0, 0, }, /* Zulu, alias for UTC */
+
+ { "WET", 0, 0, }, /* Western European */
+ { "BST", 0, 1, }, /* British Summer */
+ { "CET", +1, 0, }, /* Central European */
+ { "MET", +1, 0, }, /* Middle European */
+ { "MEWT", +1, 0, }, /* Middle European Winter */
+ { "MEST", +1, 1, }, /* Middle European Summer */
+ { "CEST", +1, 1, }, /* Central European Summer */
+ { "MESZ", +1, 1, }, /* Middle European Summer */
+ { "FWT", +1, 0, }, /* French Winter */
+ { "FST", +1, 1, }, /* French Summer */
+ { "EET", +2, 0, }, /* Eastern Europe */
+ { "EEST", +2, 1, }, /* Eastern European Daylight */
+ { "WAST", +7, 0, }, /* West Australian Standard */
+ { "WADT", +7, 1, }, /* West Australian Daylight */
+ { "CCT", +8, 0, }, /* China Coast */
+ { "JST", +9, 0, }, /* Japan Standard */
+ { "EAST", +10, 0, }, /* Eastern Australian Standard */
+ { "EADT", +10, 1, }, /* Eastern Australian Daylight */
+ { "GST", +10, 0, }, /* Guam Standard */
+ { "NZT", +12, 0, }, /* New Zealand */
+ { "NZST", +12, 0, }, /* New Zealand Standard */
+ { "NZDT", +12, 1, }, /* New Zealand Daylight */
+ { "IDLE", +12, 0, }, /* International Date Line East */
+};
+
+static size_t match_string(const char *date, const char *str)
+{
+ size_t i = 0;
+
+ for (i = 0; *date; date++, str++, i++) {
+ if (*date == *str)
+ continue;
+ if (toupper(*date) == toupper(*str))
+ continue;
+ if (!isalnum(*date))
+ break;
+ return 0;
+ }
+ return i;
+}
+
+static int skip_alpha(const char *date)
+{
+ int i = 0;
+ do {
+ i++;
+ } while (isalpha(date[i]));
+ return i;
+}
+
+/*
+* Parse month, weekday, or timezone name
+*/
+static size_t match_alpha(const char *date, struct tm *tm, int *offset)
+{
+ unsigned int i;
+
+ for (i = 0; i < 12; i++) {
+ size_t match = match_string(date, month_names[i]);
+ if (match >= 3) {
+ tm->tm_mon = i;
+ return match;
+ }
+ }
+
+ for (i = 0; i < 7; i++) {
+ size_t match = match_string(date, weekday_names[i]);
+ if (match >= 3) {
+ tm->tm_wday = i;
+ return match;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(timezone_names); i++) {
+ size_t match = match_string(date, timezone_names[i].name);
+ if (match >= 3 || match == strlen(timezone_names[i].name)) {
+ int off = timezone_names[i].offset;
+
+ /* This is bogus, but we like summer */
+ off += timezone_names[i].dst;
+
+ /* Only use the tz name offset if we don't have anything better */
+ if (*offset == -1)
+ *offset = 60*off;
+
+ return match;
+ }
+ }
+
+ if (match_string(date, "PM") == 2) {
+ tm->tm_hour = (tm->tm_hour % 12) + 12;
+ return 2;
+ }
+
+ if (match_string(date, "AM") == 2) {
+ tm->tm_hour = (tm->tm_hour % 12) + 0;
+ return 2;
+ }
+
+ /* BAD */
+ return skip_alpha(date);
+}
+
+static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, struct tm *tm)
+{
+ if (month > 0 && month < 13 && day > 0 && day < 32) {
+ struct tm check = *tm;
+ struct tm *r = (now_tm ? &check : tm);
+ time_t specified;
+
+ r->tm_mon = month - 1;
+ r->tm_mday = day;
+ if (year == -1) {
+ if (!now_tm)
+ return 1;
+ r->tm_year = now_tm->tm_year;
+ }
+ else if (year >= 1970 && year < 2100)
+ r->tm_year = year - 1900;
+ else if (year > 70 && year < 100)
+ r->tm_year = year;
+ else if (year < 38)
+ r->tm_year = year + 100;
+ else
+ return 0;
+ if (!now_tm)
+ return 1;
+
+ specified = tm_to_time_t(r);
+
+ /* Be it commit time or author time, it does not make
+ * sense to specify timestamp way into the future. Make
+ * sure it is not later than ten days from now...
+ */
+ if (now + 10*24*3600 < specified)
+ return 0;
+ tm->tm_mon = r->tm_mon;
+ tm->tm_mday = r->tm_mday;
+ if (year != -1)
+ tm->tm_year = r->tm_year;
+ return 1;
+ }
+ return 0;
+}
+
+static size_t match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm)
+{
+ time_t now;
+ struct tm now_tm;
+ struct tm *refuse_future;
+ long num2, num3;
+
+ num2 = strtol(end+1, &end, 10);
+ num3 = -1;
+ if (*end == c && isdigit(end[1]))
+ num3 = strtol(end+1, &end, 10);
+
+ /* Time? Date? */
+ switch (c) {
+ case ':':
+ if (num3 < 0)
+ num3 = 0;
+ if (num < 25 && num2 >= 0 && num2 < 60 && num3 >= 0 && num3 <= 60) {
+ tm->tm_hour = num;
+ tm->tm_min = num2;
+ tm->tm_sec = num3;
+ break;
+ }
+ return 0;
+
+ case '-':
+ case '/':
+ case '.':
+ now = time(NULL);
+ refuse_future = NULL;
+ if (p_gmtime_r(&now, &now_tm))
+ refuse_future = &now_tm;
+
+ if (num > 70) {
+ /* yyyy-mm-dd? */
+ if (is_date(num, num2, num3, refuse_future, now, tm))
+ break;
+ /* yyyy-dd-mm? */
+ if (is_date(num, num3, num2, refuse_future, now, tm))
+ break;
+ }
+ /* Our eastern European friends say dd.mm.yy[yy]
+ * is the norm there, so giving precedence to
+ * mm/dd/yy[yy] form only when separator is not '.'
+ */
+ if (c != '.' &&
+ is_date(num3, num, num2, refuse_future, now, tm))
+ break;
+ /* European dd.mm.yy[yy] or funny US dd/mm/yy[yy] */
+ if (is_date(num3, num2, num, refuse_future, now, tm))
+ break;
+ /* Funny European mm.dd.yy */
+ if (c == '.' &&
+ is_date(num3, num, num2, refuse_future, now, tm))
+ break;
+ return 0;
+ }
+ return end - date;
+}
+
+/*
+ * Have we filled in any part of the time/date yet?
+ * We just do a binary 'and' to see if the sign bit
+ * is set in all the values.
+ */
+static int nodate(struct tm *tm)
+{
+ return (tm->tm_year &
+ tm->tm_mon &
+ tm->tm_mday &
+ tm->tm_hour &
+ tm->tm_min &
+ tm->tm_sec) < 0;
+}
+
+/*
+ * We've seen a digit. Time? Year? Date?
+ */
+static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt)
+{
+ size_t n;
+ char *end;
+ unsigned long num;
+
+ num = strtoul(date, &end, 10);
+
+ /*
+ * Seconds since 1970? We trigger on that for any numbers with
+ * more than 8 digits. This is because we don't want to rule out
+ * numbers like 20070606 as a YYYYMMDD date.
+ */
+ if (num >= 100000000 && nodate(tm)) {
+ time_t time = num;
+ if (p_gmtime_r(&time, tm)) {
+ *tm_gmt = 1;
+ return end - date;
+ }
+ }
+
+ /*
+ * Check for special formats: num[-.:/]num[same]num
+ */
+ switch (*end) {
+ case ':':
+ case '.':
+ case '/':
+ case '-':
+ if (isdigit(end[1])) {
+ size_t match = match_multi_number(num, *end, date, end, tm);
+ if (match)
+ return match;
+ }
+ }
+
+ /*
+ * None of the special formats? Try to guess what
+ * the number meant. We use the number of digits
+ * to make a more educated guess..
+ */
+ n = 0;
+ do {
+ n++;
+ } while (isdigit(date[n]));
+
+ /* Four-digit year or a timezone? */
+ if (n == 4) {
+ if (num <= 1400 && *offset == -1) {
+ unsigned int minutes = num % 100;
+ unsigned int hours = num / 100;
+ *offset = hours*60 + minutes;
+ } else if (num > 1900 && num < 2100)
+ tm->tm_year = num - 1900;
+ return n;
+ }
+
+ /*
+ * Ignore lots of numerals. We took care of 4-digit years above.
+ * Days or months must be one or two digits.
+ */
+ if (n > 2)
+ return n;
+
+ /*
+ * NOTE! We will give precedence to day-of-month over month or
+ * year numbers in the 1-12 range. So 05 is always "mday 5",
+ * unless we already have a mday..
+ *
+ * IOW, 01 Apr 05 parses as "April 1st, 2005".
+ */
+ if (num > 0 && num < 32 && tm->tm_mday < 0) {
+ tm->tm_mday = num;
+ return n;
+ }
+
+ /* Two-digit year? */
+ if (n == 2 && tm->tm_year < 0) {
+ if (num < 10 && tm->tm_mday >= 0) {
+ tm->tm_year = num + 100;
+ return n;
+ }
+ if (num >= 70) {
+ tm->tm_year = num;
+ return n;
+ }
+ }
+
+ if (num > 0 && num < 13 && tm->tm_mon < 0)
+ tm->tm_mon = num-1;
+
+ return n;
+}
+
+static size_t match_tz(const char *date, int *offp)
+{
+ char *end;
+ int hour = strtoul(date + 1, &end, 10);
+ size_t n = end - (date + 1);
+ int min = 0;
+
+ if (n == 4) {
+ /* hhmm */
+ min = hour % 100;
+ hour = hour / 100;
+ } else if (n != 2) {
+ min = 99; /* random stuff */
+ } else if (*end == ':') {
+ /* hh:mm? */
+ min = strtoul(end + 1, &end, 10);
+ if (end - (date + 1) != 5)
+ min = 99; /* random stuff */
+ } /* otherwise we parsed "hh" */
+
+ /*
+ * Don't accept any random stuff. Even though some places have
+ * offset larger than 12 hours (e.g. Pacific/Kiritimati is at
+ * UTC+14), there is something wrong if hour part is much
+ * larger than that. We might also want to check that the
+ * minutes are divisible by 15 or something too. (Offset of
+ * Kathmandu, Nepal is UTC+5:45)
+ */
+ if (min < 60 && hour < 24) {
+ int offset = hour * 60 + min;
+ if (*date == '-')
+ offset = -offset;
+ *offp = offset;
+ }
+ return end - date;
+}
+
+/*
+ * Parse a string like "0 +0000" as ancient timestamp near epoch, but
+ * only when it appears not as part of any other string.
+ */
+static int match_object_header_date(const char *date, git_time_t *timestamp, int *offset)
+{
+ char *end;
+ unsigned long stamp;
+ int ofs;
+
+ if (*date < '0' || '9' <= *date)
+ return -1;
+ stamp = strtoul(date, &end, 10);
+ if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-'))
+ return -1;
+ date = end + 2;
+ ofs = strtol(date, &end, 10);
+ if ((*end != '\0' && (*end != '\n')) || end != date + 4)
+ return -1;
+ ofs = (ofs / 100) * 60 + (ofs % 100);
+ if (date[-1] == '-')
+ ofs = -ofs;
+ *timestamp = stamp;
+ *offset = ofs;
+ return 0;
+}
+
+/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822
+ (i.e. English) day/month names, and it doesn't work correctly with %z. */
+static int parse_date_basic(const char *date, git_time_t *timestamp, int *offset)
+{
+ struct tm tm;
+ int tm_gmt;
+ git_time_t dummy_timestamp;
+ int dummy_offset;
+
+ if (!timestamp)
+ timestamp = &dummy_timestamp;
+ if (!offset)
+ offset = &dummy_offset;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_year = -1;
+ tm.tm_mon = -1;
+ tm.tm_mday = -1;
+ tm.tm_isdst = -1;
+ tm.tm_hour = -1;
+ tm.tm_min = -1;
+ tm.tm_sec = -1;
+ *offset = -1;
+ tm_gmt = 0;
+
+ if (*date == '@' &&
+ !match_object_header_date(date + 1, timestamp, offset))
+ return 0; /* success */
+ for (;;) {
+ size_t match = 0;
+ unsigned char c = *date;
+
+ /* Stop at end of string or newline */
+ if (!c || c == '\n')
+ break;
+
+ if (isalpha(c))
+ match = match_alpha(date, &tm, offset);
+ else if (isdigit(c))
+ match = match_digit(date, &tm, offset, &tm_gmt);
+ else if ((c == '-' || c == '+') && isdigit(date[1]))
+ match = match_tz(date, offset);
+
+ if (!match) {
+ /* BAD */
+ match = 1;
+ }
+
+ date += match;
+ }
+
+ /* mktime uses local timezone */
+ *timestamp = tm_to_time_t(&tm);
+ if (*offset == -1)
+ *offset = (int)((time_t)*timestamp - mktime(&tm)) / 60;
+
+ if (*timestamp == (git_time_t)-1)
+ return -1;
+
+ if (!tm_gmt)
+ *timestamp -= *offset * 60;
+ return 0; /* success */
+}
+
+
+/*
+ * Relative time update (eg "2 days ago"). If we haven't set the time
+ * yet, we need to set it from current time.
+ */
+static git_time_t update_tm(struct tm *tm, struct tm *now, unsigned long sec)
+{
+ time_t n;
+
+ if (tm->tm_mday < 0)
+ tm->tm_mday = now->tm_mday;
+ if (tm->tm_mon < 0)
+ tm->tm_mon = now->tm_mon;
+ if (tm->tm_year < 0) {
+ tm->tm_year = now->tm_year;
+ if (tm->tm_mon > now->tm_mon)
+ tm->tm_year--;
+ }
+
+ n = mktime(tm) - sec;
+ p_localtime_r(&n, tm);
+ return n;
+}
+
+static void date_now(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ update_tm(tm, now, 0);
+}
+
+static void date_yesterday(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ update_tm(tm, now, 24*60*60);
+}
+
+static void date_time(struct tm *tm, struct tm *now, int hour)
+{
+ if (tm->tm_hour < hour)
+ date_yesterday(tm, now, NULL);
+ tm->tm_hour = hour;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+}
+
+static void date_midnight(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ date_time(tm, now, 0);
+}
+
+static void date_noon(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ date_time(tm, now, 12);
+}
+
+static void date_tea(struct tm *tm, struct tm *now, int *num)
+{
+ GIT_UNUSED(num);
+ date_time(tm, now, 17);
+}
+
+static void date_pm(struct tm *tm, struct tm *now, int *num)
+{
+ int hour, n = *num;
+ *num = 0;
+ GIT_UNUSED(now);
+
+ hour = tm->tm_hour;
+ if (n) {
+ hour = n;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+ }
+ tm->tm_hour = (hour % 12) + 12;
+}
+
+static void date_am(struct tm *tm, struct tm *now, int *num)
+{
+ int hour, n = *num;
+ *num = 0;
+ GIT_UNUSED(now);
+
+ hour = tm->tm_hour;
+ if (n) {
+ hour = n;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+ }
+ tm->tm_hour = (hour % 12);
+}
+
+static void date_never(struct tm *tm, struct tm *now, int *num)
+{
+ time_t n = 0;
+ GIT_UNUSED(now);
+ GIT_UNUSED(num);
+ p_localtime_r(&n, tm);
+}
+
+static const struct special {
+ const char *name;
+ void (*fn)(struct tm *, struct tm *, int *);
+} special[] = {
+ { "yesterday", date_yesterday },
+ { "noon", date_noon },
+ { "midnight", date_midnight },
+ { "tea", date_tea },
+ { "PM", date_pm },
+ { "AM", date_am },
+ { "never", date_never },
+ { "now", date_now },
+ { NULL }
+};
+
+static const char *number_name[] = {
+ "zero", "one", "two", "three", "four",
+ "five", "six", "seven", "eight", "nine", "ten",
+};
+
+static const struct typelen {
+ const char *type;
+ int length;
+} typelen[] = {
+ { "seconds", 1 },
+ { "minutes", 60 },
+ { "hours", 60*60 },
+ { "days", 24*60*60 },
+ { "weeks", 7*24*60*60 },
+ { NULL }
+};
+
+static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num, int *touched)
+{
+ const struct typelen *tl;
+ const struct special *s;
+ const char *end = date;
+ int i;
+
+ while (isalpha(*++end))
+ /* scan to non-alpha */;
+
+ for (i = 0; i < 12; i++) {
+ size_t match = match_string(date, month_names[i]);
+ if (match >= 3) {
+ tm->tm_mon = i;
+ *touched = 1;
+ return end;
+ }
+ }
+
+ for (s = special; s->name; s++) {
+ size_t len = strlen(s->name);
+ if (match_string(date, s->name) == len) {
+ s->fn(tm, now, num);
+ *touched = 1;
+ return end;
+ }
+ }
+
+ if (!*num) {
+ for (i = 1; i < 11; i++) {
+ size_t len = strlen(number_name[i]);
+ if (match_string(date, number_name[i]) == len) {
+ *num = i;
+ *touched = 1;
+ return end;
+ }
+ }
+ if (match_string(date, "last") == 4) {
+ *num = 1;
+ *touched = 1;
+ }
+ return end;
+ }
+
+ tl = typelen;
+ while (tl->type) {
+ size_t len = strlen(tl->type);
+ if (match_string(date, tl->type) >= len-1) {
+ update_tm(tm, now, tl->length * *num);
+ *num = 0;
+ *touched = 1;
+ return end;
+ }
+ tl++;
+ }
+
+ for (i = 0; i < 7; i++) {
+ size_t match = match_string(date, weekday_names[i]);
+ if (match >= 3) {
+ int diff, n = *num -1;
+ *num = 0;
+
+ diff = tm->tm_wday - i;
+ if (diff <= 0)
+ n++;
+ diff += 7*n;
+
+ update_tm(tm, now, diff * 24 * 60 * 60);
+ *touched = 1;
+ return end;
+ }
+ }
+
+ if (match_string(date, "months") >= 5) {
+ int n;
+ update_tm(tm, now, 0); /* fill in date fields if needed */
+ n = tm->tm_mon - *num;
+ *num = 0;
+ while (n < 0) {
+ n += 12;
+ tm->tm_year--;
+ }
+ tm->tm_mon = n;
+ *touched = 1;
+ return end;
+ }
+
+ if (match_string(date, "years") >= 4) {
+ update_tm(tm, now, 0); /* fill in date fields if needed */
+ tm->tm_year -= *num;
+ *num = 0;
+ *touched = 1;
+ return end;
+ }
+
+ return end;
+}
+
+static const char *approxidate_digit(const char *date, struct tm *tm, int *num)
+{
+ char *end;
+ unsigned long number = strtoul(date, &end, 10);
+
+ switch (*end) {
+ case ':':
+ case '.':
+ case '/':
+ case '-':
+ if (isdigit(end[1])) {
+ size_t match = match_multi_number(number, *end, date, end, tm);
+ if (match)
+ return date + match;
+ }
+ }
+
+ /* Accept zero-padding only for small numbers ("Dec 02", never "Dec 0002") */
+ if (date[0] != '0' || end - date <= 2)
+ *num = number;
+ return end;
+}
+
+/*
+ * Do we have a pending number at the end, or when
+ * we see a new one? Let's assume it's a month day,
+ * as in "Dec 6, 1992"
+ */
+static void pending_number(struct tm *tm, int *num)
+{
+ int number = *num;
+
+ if (number) {
+ *num = 0;
+ if (tm->tm_mday < 0 && number < 32)
+ tm->tm_mday = number;
+ else if (tm->tm_mon < 0 && number < 13)
+ tm->tm_mon = number-1;
+ else if (tm->tm_year < 0) {
+ if (number > 1969 && number < 2100)
+ tm->tm_year = number - 1900;
+ else if (number > 69 && number < 100)
+ tm->tm_year = number;
+ else if (number < 38)
+ tm->tm_year = 100 + number;
+ /* We mess up for number = 00 ? */
+ }
+ }
+}
+
+static git_time_t approxidate_str(const char *date,
+ time_t time_sec,
+ int *error_ret)
+{
+ int number = 0;
+ int touched = 0;
+ struct tm tm = {0}, now;
+
+ p_localtime_r(&time_sec, &tm);
+ now = tm;
+
+ tm.tm_year = -1;
+ tm.tm_mon = -1;
+ tm.tm_mday = -1;
+
+ for (;;) {
+ unsigned char c = *date;
+ if (!c)
+ break;
+ date++;
+ if (isdigit(c)) {
+ pending_number(&tm, &number);
+ date = approxidate_digit(date-1, &tm, &number);
+ touched = 1;
+ continue;
+ }
+ if (isalpha(c))
+ date = approxidate_alpha(date-1, &tm, &now, &number, &touched);
+ }
+ pending_number(&tm, &number);
+ if (!touched)
+ *error_ret = 1;
+ return update_tm(&tm, &now, 0);
+}
+
+int git__date_parse(git_time_t *out, const char *date)
+{
+ time_t time_sec;
+ git_time_t timestamp;
+ int offset, error_ret=0;
+
+ if (!parse_date_basic(date, &timestamp, &offset)) {
+ *out = timestamp;
+ return 0;
+ }
+
+ if (time(&time_sec) == -1)
+ return -1;
+
+ *out = approxidate_str(date, time_sec, &error_ret);
+ return error_ret;
+}
+
+int git__date_rfc2822_fmt(char *out, size_t len, const git_time *date)
+{
+ int written;
+ struct tm gmt;
+ time_t t;
+
+ assert(out && date);
+
+ t = (time_t) (date->time + date->offset * 60);
+
+ if (p_gmtime_r (&t, &gmt) == NULL)
+ return -1;
+
+ written = p_snprintf(out, len, "%.3s, %u %.3s %.4u %02u:%02u:%02u %+03d%02d",
+ weekday_names[gmt.tm_wday],
+ gmt.tm_mday,
+ month_names[gmt.tm_mon],
+ gmt.tm_year + 1900,
+ gmt.tm_hour, gmt.tm_min, gmt.tm_sec,
+ date->offset / 60, date->offset % 60);
+
+ if (written < 0 || (written > (int) len - 1))
+ return -1;
+
+ return 0;
+}
+
diff --git a/src/delta-apply.c b/src/delta-apply.c
index a6b711436..a39c7af5c 100644
--- a/src/delta-apply.c
+++ b/src/delta-apply.c
@@ -1,3 +1,9 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
#include "common.h"
#include "git2/odb.h"
#include "delta-apply.h"
@@ -5,7 +11,7 @@
/*
* This file was heavily cribbed from BinaryDelta.java in JGit, which
* itself was heavily cribbed from <code>patch-delta.c</code> in the
- * GIT project. The original delta patching code was written by
+ * GIT project. The original delta patching code was written by
* Nicolas Pitre <nico@cam.org>.
*/
@@ -30,6 +36,19 @@ static int hdr_sz(
return 0;
}
+int git__delta_read_header(
+ const unsigned char *delta,
+ size_t delta_len,
+ size_t *base_sz,
+ size_t *res_sz)
+{
+ const unsigned char *delta_end = delta + delta_len;
+ if ((hdr_sz(base_sz, &delta, delta_end) < 0) ||
+ (hdr_sz(res_sz, &delta, delta_end) < 0))
+ return -1;
+ return 0;
+}
+
int git__delta_apply(
git_rawobj *out,
const unsigned char *base,
@@ -45,14 +64,19 @@ int git__delta_apply(
* if not we would underflow while accessing data from the
* base object, resulting in data corruption or segfault.
*/
- if ((hdr_sz(&base_sz, &delta, delta_end) < 0) || (base_sz != base_len))
- return git__throw(GIT_ERROR, "Failed to apply delta. Base size does not match given data");
+ if ((hdr_sz(&base_sz, &delta, delta_end) < 0) || (base_sz != base_len)) {
+ giterr_set(GITERR_INVALID, "Failed to apply delta. Base size does not match given data");
+ return -1;
+ }
- if (hdr_sz(&res_sz, &delta, delta_end) < 0)
- return git__throw(GIT_ERROR, "Failed to apply delta. Base size does not match given data");
+ if (hdr_sz(&res_sz, &delta, delta_end) < 0) {
+ giterr_set(GITERR_INVALID, "Failed to apply delta. Base size does not match given data");
+ return -1;
+ }
+
+ res_dp = git__malloc(res_sz + 1);
+ GITERR_CHECK_ALLOC(res_dp);
- if ((res_dp = git__malloc(res_sz + 1)) == NULL)
- return GIT_ENOMEM;
res_dp[res_sz] = '\0';
out->data = res_dp;
out->len = res_sz;
@@ -64,15 +88,15 @@ int git__delta_apply(
*/
size_t off = 0, len = 0;
- if (cmd & 0x01) off = *delta++;
- if (cmd & 0x02) off |= *delta++ << 8;
+ if (cmd & 0x01) off = *delta++;
+ if (cmd & 0x02) off |= *delta++ << 8;
if (cmd & 0x04) off |= *delta++ << 16;
if (cmd & 0x08) off |= *delta++ << 24;
- if (cmd & 0x10) len = *delta++;
- if (cmd & 0x20) len |= *delta++ << 8;
+ if (cmd & 0x10) len = *delta++;
+ if (cmd & 0x20) len |= *delta++ << 8;
if (cmd & 0x40) len |= *delta++ << 16;
- if (!len) len = 0x10000;
+ if (!len) len = 0x10000;
if (base_len < off + len || res_sz < len)
goto fail;
@@ -87,7 +111,7 @@ int git__delta_apply(
if (delta_end - delta < cmd || res_sz < cmd)
goto fail;
memcpy(res_dp, delta, cmd);
- delta += cmd;
+ delta += cmd;
res_dp += cmd;
res_sz -= cmd;
@@ -100,10 +124,11 @@ int git__delta_apply(
if (delta != delta_end || res_sz)
goto fail;
- return GIT_SUCCESS;
+ return 0;
fail:
- free(out->data);
+ git__free(out->data);
out->data = NULL;
- return git__throw(GIT_ERROR, "Failed to apply delta");
+ giterr_set(GITERR_INVALID, "Failed to apply delta");
+ return -1;
}
diff --git a/src/delta-apply.h b/src/delta-apply.h
index 36c5cc60d..d7d99d04c 100644
--- a/src/delta-apply.h
+++ b/src/delta-apply.h
@@ -1,3 +1,9 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_delta_apply_h__
#define INCLUDE_delta_apply_h__
@@ -14,7 +20,7 @@
* @param delta the delta to execute copy/insert instructions from.
* @param delta_len total number of bytes in the delta.
* @return
- * - GIT_SUCCESS on a successful delta unpack.
+ * - 0 on a successful delta unpack.
* - GIT_ERROR if the delta is corrupt or doesn't match the base.
*/
extern int git__delta_apply(
@@ -24,4 +30,21 @@ extern int git__delta_apply(
const unsigned char *delta,
size_t delta_len);
+/**
+ * Read the header of a git binary delta.
+ *
+ * @param delta the delta to execute copy/insert instructions from.
+ * @param delta_len total number of bytes in the delta.
+ * @param base_sz pointer to store the base size field.
+ * @param res_sz pointer to store the result size field.
+ * @return
+ * - 0 on a successful decoding the header.
+ * - GIT_ERROR if the delta is corrupt.
+ */
+extern int git__delta_read_header(
+ const unsigned char *delta,
+ size_t delta_len,
+ size_t *base_sz,
+ size_t *res_sz);
+
#endif
diff --git a/src/delta.c b/src/delta.c
new file mode 100644
index 000000000..8375a2c4d
--- /dev/null
+++ b/src/delta.c
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "delta.h"
+
+/* maximum hash entry list for the same hash bucket */
+#define HASH_LIMIT 64
+
+#define RABIN_SHIFT 23
+#define RABIN_WINDOW 16
+
+static const unsigned int T[256] = {
+ 0x00000000, 0xab59b4d1, 0x56b369a2, 0xfdeadd73, 0x063f6795, 0xad66d344,
+ 0x508c0e37, 0xfbd5bae6, 0x0c7ecf2a, 0xa7277bfb, 0x5acda688, 0xf1941259,
+ 0x0a41a8bf, 0xa1181c6e, 0x5cf2c11d, 0xf7ab75cc, 0x18fd9e54, 0xb3a42a85,
+ 0x4e4ef7f6, 0xe5174327, 0x1ec2f9c1, 0xb59b4d10, 0x48719063, 0xe32824b2,
+ 0x1483517e, 0xbfdae5af, 0x423038dc, 0xe9698c0d, 0x12bc36eb, 0xb9e5823a,
+ 0x440f5f49, 0xef56eb98, 0x31fb3ca8, 0x9aa28879, 0x6748550a, 0xcc11e1db,
+ 0x37c45b3d, 0x9c9defec, 0x6177329f, 0xca2e864e, 0x3d85f382, 0x96dc4753,
+ 0x6b369a20, 0xc06f2ef1, 0x3bba9417, 0x90e320c6, 0x6d09fdb5, 0xc6504964,
+ 0x2906a2fc, 0x825f162d, 0x7fb5cb5e, 0xd4ec7f8f, 0x2f39c569, 0x846071b8,
+ 0x798aaccb, 0xd2d3181a, 0x25786dd6, 0x8e21d907, 0x73cb0474, 0xd892b0a5,
+ 0x23470a43, 0x881ebe92, 0x75f463e1, 0xdeadd730, 0x63f67950, 0xc8afcd81,
+ 0x354510f2, 0x9e1ca423, 0x65c91ec5, 0xce90aa14, 0x337a7767, 0x9823c3b6,
+ 0x6f88b67a, 0xc4d102ab, 0x393bdfd8, 0x92626b09, 0x69b7d1ef, 0xc2ee653e,
+ 0x3f04b84d, 0x945d0c9c, 0x7b0be704, 0xd05253d5, 0x2db88ea6, 0x86e13a77,
+ 0x7d348091, 0xd66d3440, 0x2b87e933, 0x80de5de2, 0x7775282e, 0xdc2c9cff,
+ 0x21c6418c, 0x8a9ff55d, 0x714a4fbb, 0xda13fb6a, 0x27f92619, 0x8ca092c8,
+ 0x520d45f8, 0xf954f129, 0x04be2c5a, 0xafe7988b, 0x5432226d, 0xff6b96bc,
+ 0x02814bcf, 0xa9d8ff1e, 0x5e738ad2, 0xf52a3e03, 0x08c0e370, 0xa39957a1,
+ 0x584ced47, 0xf3155996, 0x0eff84e5, 0xa5a63034, 0x4af0dbac, 0xe1a96f7d,
+ 0x1c43b20e, 0xb71a06df, 0x4ccfbc39, 0xe79608e8, 0x1a7cd59b, 0xb125614a,
+ 0x468e1486, 0xedd7a057, 0x103d7d24, 0xbb64c9f5, 0x40b17313, 0xebe8c7c2,
+ 0x16021ab1, 0xbd5bae60, 0x6cb54671, 0xc7ecf2a0, 0x3a062fd3, 0x915f9b02,
+ 0x6a8a21e4, 0xc1d39535, 0x3c394846, 0x9760fc97, 0x60cb895b, 0xcb923d8a,
+ 0x3678e0f9, 0x9d215428, 0x66f4eece, 0xcdad5a1f, 0x3047876c, 0x9b1e33bd,
+ 0x7448d825, 0xdf116cf4, 0x22fbb187, 0x89a20556, 0x7277bfb0, 0xd92e0b61,
+ 0x24c4d612, 0x8f9d62c3, 0x7836170f, 0xd36fa3de, 0x2e857ead, 0x85dcca7c,
+ 0x7e09709a, 0xd550c44b, 0x28ba1938, 0x83e3ade9, 0x5d4e7ad9, 0xf617ce08,
+ 0x0bfd137b, 0xa0a4a7aa, 0x5b711d4c, 0xf028a99d, 0x0dc274ee, 0xa69bc03f,
+ 0x5130b5f3, 0xfa690122, 0x0783dc51, 0xacda6880, 0x570fd266, 0xfc5666b7,
+ 0x01bcbbc4, 0xaae50f15, 0x45b3e48d, 0xeeea505c, 0x13008d2f, 0xb85939fe,
+ 0x438c8318, 0xe8d537c9, 0x153feaba, 0xbe665e6b, 0x49cd2ba7, 0xe2949f76,
+ 0x1f7e4205, 0xb427f6d4, 0x4ff24c32, 0xe4abf8e3, 0x19412590, 0xb2189141,
+ 0x0f433f21, 0xa41a8bf0, 0x59f05683, 0xf2a9e252, 0x097c58b4, 0xa225ec65,
+ 0x5fcf3116, 0xf49685c7, 0x033df00b, 0xa86444da, 0x558e99a9, 0xfed72d78,
+ 0x0502979e, 0xae5b234f, 0x53b1fe3c, 0xf8e84aed, 0x17bea175, 0xbce715a4,
+ 0x410dc8d7, 0xea547c06, 0x1181c6e0, 0xbad87231, 0x4732af42, 0xec6b1b93,
+ 0x1bc06e5f, 0xb099da8e, 0x4d7307fd, 0xe62ab32c, 0x1dff09ca, 0xb6a6bd1b,
+ 0x4b4c6068, 0xe015d4b9, 0x3eb80389, 0x95e1b758, 0x680b6a2b, 0xc352defa,
+ 0x3887641c, 0x93ded0cd, 0x6e340dbe, 0xc56db96f, 0x32c6cca3, 0x999f7872,
+ 0x6475a501, 0xcf2c11d0, 0x34f9ab36, 0x9fa01fe7, 0x624ac294, 0xc9137645,
+ 0x26459ddd, 0x8d1c290c, 0x70f6f47f, 0xdbaf40ae, 0x207afa48, 0x8b234e99,
+ 0x76c993ea, 0xdd90273b, 0x2a3b52f7, 0x8162e626, 0x7c883b55, 0xd7d18f84,
+ 0x2c043562, 0x875d81b3, 0x7ab75cc0, 0xd1eee811
+};
+
+static const unsigned int U[256] = {
+ 0x00000000, 0x7eb5200d, 0x5633f4cb, 0x2886d4c6, 0x073e5d47, 0x798b7d4a,
+ 0x510da98c, 0x2fb88981, 0x0e7cba8e, 0x70c99a83, 0x584f4e45, 0x26fa6e48,
+ 0x0942e7c9, 0x77f7c7c4, 0x5f711302, 0x21c4330f, 0x1cf9751c, 0x624c5511,
+ 0x4aca81d7, 0x347fa1da, 0x1bc7285b, 0x65720856, 0x4df4dc90, 0x3341fc9d,
+ 0x1285cf92, 0x6c30ef9f, 0x44b63b59, 0x3a031b54, 0x15bb92d5, 0x6b0eb2d8,
+ 0x4388661e, 0x3d3d4613, 0x39f2ea38, 0x4747ca35, 0x6fc11ef3, 0x11743efe,
+ 0x3eccb77f, 0x40799772, 0x68ff43b4, 0x164a63b9, 0x378e50b6, 0x493b70bb,
+ 0x61bda47d, 0x1f088470, 0x30b00df1, 0x4e052dfc, 0x6683f93a, 0x1836d937,
+ 0x250b9f24, 0x5bbebf29, 0x73386bef, 0x0d8d4be2, 0x2235c263, 0x5c80e26e,
+ 0x740636a8, 0x0ab316a5, 0x2b7725aa, 0x55c205a7, 0x7d44d161, 0x03f1f16c,
+ 0x2c4978ed, 0x52fc58e0, 0x7a7a8c26, 0x04cfac2b, 0x73e5d470, 0x0d50f47d,
+ 0x25d620bb, 0x5b6300b6, 0x74db8937, 0x0a6ea93a, 0x22e87dfc, 0x5c5d5df1,
+ 0x7d996efe, 0x032c4ef3, 0x2baa9a35, 0x551fba38, 0x7aa733b9, 0x041213b4,
+ 0x2c94c772, 0x5221e77f, 0x6f1ca16c, 0x11a98161, 0x392f55a7, 0x479a75aa,
+ 0x6822fc2b, 0x1697dc26, 0x3e1108e0, 0x40a428ed, 0x61601be2, 0x1fd53bef,
+ 0x3753ef29, 0x49e6cf24, 0x665e46a5, 0x18eb66a8, 0x306db26e, 0x4ed89263,
+ 0x4a173e48, 0x34a21e45, 0x1c24ca83, 0x6291ea8e, 0x4d29630f, 0x339c4302,
+ 0x1b1a97c4, 0x65afb7c9, 0x446b84c6, 0x3adea4cb, 0x1258700d, 0x6ced5000,
+ 0x4355d981, 0x3de0f98c, 0x15662d4a, 0x6bd30d47, 0x56ee4b54, 0x285b6b59,
+ 0x00ddbf9f, 0x7e689f92, 0x51d01613, 0x2f65361e, 0x07e3e2d8, 0x7956c2d5,
+ 0x5892f1da, 0x2627d1d7, 0x0ea10511, 0x7014251c, 0x5facac9d, 0x21198c90,
+ 0x099f5856, 0x772a785b, 0x4c921c31, 0x32273c3c, 0x1aa1e8fa, 0x6414c8f7,
+ 0x4bac4176, 0x3519617b, 0x1d9fb5bd, 0x632a95b0, 0x42eea6bf, 0x3c5b86b2,
+ 0x14dd5274, 0x6a687279, 0x45d0fbf8, 0x3b65dbf5, 0x13e30f33, 0x6d562f3e,
+ 0x506b692d, 0x2ede4920, 0x06589de6, 0x78edbdeb, 0x5755346a, 0x29e01467,
+ 0x0166c0a1, 0x7fd3e0ac, 0x5e17d3a3, 0x20a2f3ae, 0x08242768, 0x76910765,
+ 0x59298ee4, 0x279caee9, 0x0f1a7a2f, 0x71af5a22, 0x7560f609, 0x0bd5d604,
+ 0x235302c2, 0x5de622cf, 0x725eab4e, 0x0ceb8b43, 0x246d5f85, 0x5ad87f88,
+ 0x7b1c4c87, 0x05a96c8a, 0x2d2fb84c, 0x539a9841, 0x7c2211c0, 0x029731cd,
+ 0x2a11e50b, 0x54a4c506, 0x69998315, 0x172ca318, 0x3faa77de, 0x411f57d3,
+ 0x6ea7de52, 0x1012fe5f, 0x38942a99, 0x46210a94, 0x67e5399b, 0x19501996,
+ 0x31d6cd50, 0x4f63ed5d, 0x60db64dc, 0x1e6e44d1, 0x36e89017, 0x485db01a,
+ 0x3f77c841, 0x41c2e84c, 0x69443c8a, 0x17f11c87, 0x38499506, 0x46fcb50b,
+ 0x6e7a61cd, 0x10cf41c0, 0x310b72cf, 0x4fbe52c2, 0x67388604, 0x198da609,
+ 0x36352f88, 0x48800f85, 0x6006db43, 0x1eb3fb4e, 0x238ebd5d, 0x5d3b9d50,
+ 0x75bd4996, 0x0b08699b, 0x24b0e01a, 0x5a05c017, 0x728314d1, 0x0c3634dc,
+ 0x2df207d3, 0x534727de, 0x7bc1f318, 0x0574d315, 0x2acc5a94, 0x54797a99,
+ 0x7cffae5f, 0x024a8e52, 0x06852279, 0x78300274, 0x50b6d6b2, 0x2e03f6bf,
+ 0x01bb7f3e, 0x7f0e5f33, 0x57888bf5, 0x293dabf8, 0x08f998f7, 0x764cb8fa,
+ 0x5eca6c3c, 0x207f4c31, 0x0fc7c5b0, 0x7172e5bd, 0x59f4317b, 0x27411176,
+ 0x1a7c5765, 0x64c97768, 0x4c4fa3ae, 0x32fa83a3, 0x1d420a22, 0x63f72a2f,
+ 0x4b71fee9, 0x35c4dee4, 0x1400edeb, 0x6ab5cde6, 0x42331920, 0x3c86392d,
+ 0x133eb0ac, 0x6d8b90a1, 0x450d4467, 0x3bb8646a
+};
+
+struct index_entry {
+ const unsigned char *ptr;
+ unsigned int val;
+ struct index_entry *next;
+};
+
+struct git_delta_index {
+ unsigned long memsize;
+ const void *src_buf;
+ unsigned long src_size;
+ unsigned int hash_mask;
+ struct index_entry *hash[GIT_FLEX_ARRAY];
+};
+
+struct git_delta_index *
+git_delta_create_index(const void *buf, unsigned long bufsize)
+{
+ unsigned int i, hsize, hmask, entries, prev_val, *hash_count;
+ const unsigned char *data, *buffer = buf;
+ struct git_delta_index *index;
+ struct index_entry *entry, **hash;
+ void *mem;
+ unsigned long memsize;
+
+ if (!buf || !bufsize)
+ return NULL;
+
+ /* Determine index hash size. Note that indexing skips the
+ first byte to allow for optimizing the rabin polynomial
+ initialization in create_delta(). */
+ entries = (unsigned int)(bufsize - 1) / RABIN_WINDOW;
+ if (bufsize >= 0xffffffffUL) {
+ /*
+ * Current delta format can't encode offsets into
+ * reference buffer with more than 32 bits.
+ */
+ entries = 0xfffffffeU / RABIN_WINDOW;
+ }
+ hsize = entries / 4;
+ for (i = 4; i < 31 && (1u << i) < hsize; i++);
+ hsize = 1 << i;
+ hmask = hsize - 1;
+
+ /* allocate lookup index */
+ memsize = sizeof(*index) +
+ sizeof(*hash) * hsize +
+ sizeof(*entry) * entries;
+ mem = git__malloc(memsize);
+ if (!mem)
+ return NULL;
+ index = mem;
+ mem = index->hash;
+ hash = mem;
+ mem = hash + hsize;
+ entry = mem;
+
+ index->memsize = memsize;
+ index->src_buf = buf;
+ index->src_size = bufsize;
+ index->hash_mask = hmask;
+ memset(hash, 0, hsize * sizeof(*hash));
+
+ /* allocate an array to count hash entries */
+ hash_count = git__calloc(hsize, sizeof(*hash_count));
+ if (!hash_count) {
+ git__free(index);
+ return NULL;
+ }
+
+ /* then populate the index */
+ prev_val = ~0;
+ for (data = buffer + entries * RABIN_WINDOW - RABIN_WINDOW;
+ data >= buffer;
+ data -= RABIN_WINDOW) {
+ unsigned int val = 0;
+ for (i = 1; i <= RABIN_WINDOW; i++)
+ val = ((val << 8) | data[i]) ^ T[val >> RABIN_SHIFT];
+ if (val == prev_val) {
+ /* keep the lowest of consecutive identical blocks */
+ entry[-1].ptr = data + RABIN_WINDOW;
+ } else {
+ prev_val = val;
+ i = val & hmask;
+ entry->ptr = data + RABIN_WINDOW;
+ entry->val = val;
+ entry->next = hash[i];
+ hash[i] = entry++;
+ hash_count[i]++;
+ }
+ }
+
+ /*
+ * Determine a limit on the number of entries in the same hash
+ * bucket. This guard us against patological data sets causing
+ * really bad hash distribution with most entries in the same hash
+ * bucket that would bring us to O(m*n) computing costs (m and n
+ * corresponding to reference and target buffer sizes).
+ *
+ * Make sure none of the hash buckets has more entries than
+ * we're willing to test. Otherwise we cull the entry list
+ * uniformly to still preserve a good repartition across
+ * the reference buffer.
+ */
+ for (i = 0; i < hsize; i++) {
+ if (hash_count[i] < HASH_LIMIT)
+ continue;
+
+ entry = hash[i];
+ do {
+ struct index_entry *keep = entry;
+ int skip = hash_count[i] / HASH_LIMIT / 2;
+ do {
+ entry = entry->next;
+ } while(--skip && entry);
+ keep->next = entry;
+ } while (entry);
+ }
+ git__free(hash_count);
+
+ return index;
+}
+
+void git_delta_free_index(struct git_delta_index *index)
+{
+ git__free(index);
+}
+
+unsigned long git_delta_sizeof_index(struct git_delta_index *index)
+{
+ if (index)
+ return index->memsize;
+ else
+ return 0;
+}
+
+/*
+ * The maximum size for any opcode sequence, including the initial header
+ * plus rabin window plus biggest copy.
+ */
+#define MAX_OP_SIZE (5 + 5 + 1 + RABIN_WINDOW + 7)
+
+void *
+git_delta_create(
+ const struct git_delta_index *index,
+ const void *trg_buf,
+ unsigned long trg_size,
+ unsigned long *delta_size,
+ unsigned long max_size)
+{
+ unsigned int i, outpos, outsize, moff, msize, val;
+ int inscnt;
+ const unsigned char *ref_data, *ref_top, *data, *top;
+ unsigned char *out;
+
+ if (!trg_buf || !trg_size)
+ return NULL;
+
+ outpos = 0;
+ outsize = 8192;
+ if (max_size && outsize >= max_size)
+ outsize = (unsigned int)(max_size + MAX_OP_SIZE + 1);
+ out = git__malloc(outsize);
+ if (!out)
+ return NULL;
+
+ /* store reference buffer size */
+ i = index->src_size;
+ while (i >= 0x80) {
+ out[outpos++] = i | 0x80;
+ i >>= 7;
+ }
+ out[outpos++] = i;
+
+ /* store target buffer size */
+ i = trg_size;
+ while (i >= 0x80) {
+ out[outpos++] = i | 0x80;
+ i >>= 7;
+ }
+ out[outpos++] = i;
+
+ ref_data = index->src_buf;
+ ref_top = ref_data + index->src_size;
+ data = trg_buf;
+ top = (const unsigned char *) trg_buf + trg_size;
+
+ outpos++;
+ val = 0;
+ for (i = 0; i < RABIN_WINDOW && data < top; i++, data++) {
+ out[outpos++] = *data;
+ val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
+ }
+ inscnt = i;
+
+ moff = 0;
+ msize = 0;
+ while (data < top) {
+ if (msize < 4096) {
+ struct index_entry *entry;
+ val ^= U[data[-RABIN_WINDOW]];
+ val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
+ i = val & index->hash_mask;
+ for (entry = index->hash[i]; entry; entry = entry->next) {
+ const unsigned char *ref = entry->ptr;
+ const unsigned char *src = data;
+ unsigned int ref_size = (unsigned int)(ref_top - ref);
+ if (entry->val != val)
+ continue;
+ if (ref_size > (unsigned int)(top - src))
+ ref_size = (unsigned int)(top - src);
+ if (ref_size <= msize)
+ break;
+ while (ref_size-- && *src++ == *ref)
+ ref++;
+ if (msize < (unsigned int)(ref - entry->ptr)) {
+ /* this is our best match so far */
+ msize = (unsigned int)(ref - entry->ptr);
+ moff = (unsigned int)(entry->ptr - ref_data);
+ if (msize >= 4096) /* good enough */
+ break;
+ }
+ }
+ }
+
+ if (msize < 4) {
+ if (!inscnt)
+ outpos++;
+ out[outpos++] = *data++;
+ inscnt++;
+ if (inscnt == 0x7f) {
+ out[outpos - inscnt - 1] = inscnt;
+ inscnt = 0;
+ }
+ msize = 0;
+ } else {
+ unsigned int left;
+ unsigned char *op;
+
+ if (inscnt) {
+ while (moff && ref_data[moff-1] == data[-1]) {
+ /* we can match one byte back */
+ msize++;
+ moff--;
+ data--;
+ outpos--;
+ if (--inscnt)
+ continue;
+ outpos--; /* remove count slot */
+ inscnt--; /* make it -1 */
+ break;
+ }
+ out[outpos - inscnt - 1] = inscnt;
+ inscnt = 0;
+ }
+
+ /* A copy op is currently limited to 64KB (pack v2) */
+ left = (msize < 0x10000) ? 0 : (msize - 0x10000);
+ msize -= left;
+
+ op = out + outpos++;
+ i = 0x80;
+
+ if (moff & 0x000000ff)
+ out[outpos++] = moff >> 0, i |= 0x01;
+ if (moff & 0x0000ff00)
+ out[outpos++] = moff >> 8, i |= 0x02;
+ if (moff & 0x00ff0000)
+ out[outpos++] = moff >> 16, i |= 0x04;
+ if (moff & 0xff000000)
+ out[outpos++] = moff >> 24, i |= 0x08;
+
+ if (msize & 0x00ff)
+ out[outpos++] = msize >> 0, i |= 0x10;
+ if (msize & 0xff00)
+ out[outpos++] = msize >> 8, i |= 0x20;
+
+ *op = i;
+
+ data += msize;
+ moff += msize;
+ msize = left;
+
+ if (msize < 4096) {
+ int j;
+ val = 0;
+ for (j = -RABIN_WINDOW; j < 0; j++)
+ val = ((val << 8) | data[j])
+ ^ T[val >> RABIN_SHIFT];
+ }
+ }
+
+ if (outpos >= outsize - MAX_OP_SIZE) {
+ void *tmp = out;
+ outsize = outsize * 3 / 2;
+ if (max_size && outsize >= max_size)
+ outsize = max_size + MAX_OP_SIZE + 1;
+ if (max_size && outpos > max_size)
+ break;
+ out = git__realloc(out, outsize);
+ if (!out) {
+ git__free(tmp);
+ return NULL;
+ }
+ }
+ }
+
+ if (inscnt)
+ out[outpos - inscnt - 1] = inscnt;
+
+ if (max_size && outpos > max_size) {
+ git__free(out);
+ return NULL;
+ }
+
+ *delta_size = outpos;
+ return out;
+}
diff --git a/src/delta.h b/src/delta.h
new file mode 100644
index 000000000..4ca327992
--- /dev/null
+++ b/src/delta.h
@@ -0,0 +1,114 @@
+/*
+ * diff-delta code taken from git.git. See diff-delta.c for details.
+ *
+ */
+#ifndef INCLUDE_git_delta_h__
+#define INCLUDE_git_delta_h__
+
+#include "common.h"
+
+/* opaque object for delta index */
+struct git_delta_index;
+
+/*
+ * create_delta_index: compute index data from given buffer
+ *
+ * This returns a pointer to a struct delta_index that should be passed to
+ * subsequent create_delta() calls, or to free_delta_index(). A NULL pointer
+ * is returned on failure. The given buffer must not be freed nor altered
+ * before free_delta_index() is called. The returned pointer must be freed
+ * using free_delta_index().
+ */
+extern struct git_delta_index *
+git_delta_create_index(const void *buf, unsigned long bufsize);
+
+/*
+ * free_delta_index: free the index created by create_delta_index()
+ *
+ * Given pointer must be what create_delta_index() returned, or NULL.
+ */
+extern void git_delta_free_index(struct git_delta_index *index);
+
+/*
+ * sizeof_delta_index: returns memory usage of delta index
+ *
+ * Given pointer must be what create_delta_index() returned, or NULL.
+ */
+extern unsigned long git_delta_sizeof_index(struct git_delta_index *index);
+
+/*
+ * create_delta: create a delta from given index for the given buffer
+ *
+ * This function may be called multiple times with different buffers using
+ * the same delta_index pointer. If max_delta_size is non-zero and the
+ * resulting delta is to be larger than max_delta_size then NULL is returned.
+ * On success, a non-NULL pointer to the buffer with the delta data is
+ * returned and *delta_size is updated with its size. The returned buffer
+ * must be freed by the caller.
+ */
+extern void *git_delta_create(
+ const struct git_delta_index *index,
+ const void *buf,
+ unsigned long bufsize,
+ unsigned long *delta_size,
+ unsigned long max_delta_size);
+
+/*
+ * diff_delta: create a delta from source buffer to target buffer
+ *
+ * If max_delta_size is non-zero and the resulting delta is to be larger
+ * than max_delta_size then NULL is returned. On success, a non-NULL
+ * pointer to the buffer with the delta data is returned and *delta_size is
+ * updated with its size. The returned buffer must be freed by the caller.
+ */
+GIT_INLINE(void *) git_delta(
+ const void *src_buf, unsigned long src_bufsize,
+ const void *trg_buf, unsigned long trg_bufsize,
+ unsigned long *delta_size,
+ unsigned long max_delta_size)
+{
+ struct git_delta_index *index = git_delta_create_index(src_buf, src_bufsize);
+ if (index) {
+ void *delta = git_delta_create(
+ index, trg_buf, trg_bufsize, delta_size, max_delta_size);
+ git_delta_free_index(index);
+ return delta;
+ }
+ return NULL;
+}
+
+/*
+ * patch_delta: recreate target buffer given source buffer and delta data
+ *
+ * On success, a non-NULL pointer to the target buffer is returned and
+ * *trg_bufsize is updated with its size. On failure a NULL pointer is
+ * returned. The returned buffer must be freed by the caller.
+ */
+extern void *git_delta_patch(
+ const void *src_buf, unsigned long src_size,
+ const void *delta_buf, unsigned long delta_size,
+ unsigned long *dst_size);
+
+/* the smallest possible delta size is 4 bytes */
+#define GIT_DELTA_SIZE_MIN 4
+
+/*
+ * This must be called twice on the delta data buffer, first to get the
+ * expected source buffer size, and again to get the target buffer size.
+ */
+GIT_INLINE(unsigned long) git_delta_get_hdr_size(
+ const unsigned char **datap, const unsigned char *top)
+{
+ const unsigned char *data = *datap;
+ unsigned long cmd, size = 0;
+ int i = 0;
+ do {
+ cmd = *data++;
+ size |= (cmd & 0x7f) << i;
+ i += 7;
+ } while (cmd & 0x80 && data < top);
+ *datap = data;
+ return size;
+}
+
+#endif
diff --git a/src/diff.c b/src/diff.c
new file mode 100644
index 000000000..325c599e0
--- /dev/null
+++ b/src/diff.c
@@ -0,0 +1,1618 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "fileops.h"
+#include "config.h"
+#include "attr_file.h"
+#include "filter.h"
+#include "pathspec.h"
+#include "index.h"
+#include "odb.h"
+#include "submodule.h"
+
+#define DIFF_FLAG_IS_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) != 0)
+#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) == 0)
+#define DIFF_FLAG_SET(DIFF,FLAG,VAL) (DIFF)->opts.flags = \
+ (VAL) ? ((DIFF)->opts.flags | (FLAG)) : ((DIFF)->opts.flags & ~(VAL))
+
+static git_diff_delta *diff_delta__alloc(
+ git_diff *diff,
+ git_delta_t status,
+ const char *path)
+{
+ git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta));
+ if (!delta)
+ return NULL;
+
+ delta->old_file.path = git_pool_strdup(&diff->pool, path);
+ if (delta->old_file.path == NULL) {
+ git__free(delta);
+ return NULL;
+ }
+
+ delta->new_file.path = delta->old_file.path;
+
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
+ switch (status) {
+ case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break;
+ case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break;
+ default: break; /* leave other status values alone */
+ }
+ }
+ delta->status = status;
+
+ return delta;
+}
+
+static int diff_insert_delta(
+ git_diff *diff, git_diff_delta *delta, const char *matched_pathspec)
+{
+ int error = 0;
+
+ if (diff->opts.notify_cb) {
+ error = diff->opts.notify_cb(
+ diff, delta, matched_pathspec, diff->opts.notify_payload);
+
+ if (error) {
+ git__free(delta);
+
+ if (error > 0) /* positive value means to skip this delta */
+ return 0;
+ else /* negative value means to cancel diff */
+ return giterr_set_after_callback_function(error, "git_diff");
+ }
+ }
+
+ if ((error = git_vector_insert(&diff->deltas, delta)) < 0)
+ git__free(delta);
+
+ return error;
+}
+
+static int diff_delta__from_one(
+ git_diff *diff,
+ git_delta_t status,
+ const git_index_entry *entry)
+{
+ git_diff_delta *delta;
+ const char *matched_pathspec;
+
+ if ((entry->flags & GIT_IDXENTRY_VALID) != 0)
+ return 0;
+
+ if (status == GIT_DELTA_IGNORED &&
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED))
+ return 0;
+
+ if (status == GIT_DELTA_UNTRACKED &&
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED))
+ return 0;
+
+ if (!git_pathspec__match(
+ &diff->pathspec, entry->path,
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE),
+ &matched_pathspec, NULL))
+ return 0;
+
+ delta = diff_delta__alloc(diff, status, entry->path);
+ GITERR_CHECK_ALLOC(delta);
+
+ /* This fn is just for single-sided diffs */
+ assert(status != GIT_DELTA_MODIFIED);
+ delta->nfiles = 1;
+
+ if (delta->status == GIT_DELTA_DELETED) {
+ delta->old_file.mode = entry->mode;
+ delta->old_file.size = entry->file_size;
+ git_oid_cpy(&delta->old_file.id, &entry->id);
+ } else /* ADDED, IGNORED, UNTRACKED */ {
+ delta->new_file.mode = entry->mode;
+ delta->new_file.size = entry->file_size;
+ git_oid_cpy(&delta->new_file.id, &entry->id);
+ }
+
+ delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
+
+ if (delta->status == GIT_DELTA_DELETED ||
+ !git_oid_iszero(&delta->new_file.id))
+ delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
+
+ return diff_insert_delta(diff, delta, matched_pathspec);
+}
+
+static int diff_delta__from_two(
+ git_diff *diff,
+ git_delta_t status,
+ const git_index_entry *old_entry,
+ uint32_t old_mode,
+ const git_index_entry *new_entry,
+ uint32_t new_mode,
+ git_oid *new_oid,
+ const char *matched_pathspec)
+{
+ git_diff_delta *delta;
+ const char *canonical_path = old_entry->path;
+
+ if (status == GIT_DELTA_UNMODIFIED &&
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED))
+ return 0;
+
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
+ uint32_t temp_mode = old_mode;
+ const git_index_entry *temp_entry = old_entry;
+ old_entry = new_entry;
+ new_entry = temp_entry;
+ old_mode = new_mode;
+ new_mode = temp_mode;
+ }
+
+ delta = diff_delta__alloc(diff, status, canonical_path);
+ GITERR_CHECK_ALLOC(delta);
+ delta->nfiles = 2;
+
+ git_oid_cpy(&delta->old_file.id, &old_entry->id);
+ delta->old_file.size = old_entry->file_size;
+ delta->old_file.mode = old_mode;
+ delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
+
+ git_oid_cpy(&delta->new_file.id, &new_entry->id);
+ delta->new_file.size = new_entry->file_size;
+ delta->new_file.mode = new_mode;
+
+ if (new_oid) {
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE))
+ git_oid_cpy(&delta->old_file.id, new_oid);
+ else
+ git_oid_cpy(&delta->new_file.id, new_oid);
+ }
+
+ if (new_oid || !git_oid_iszero(&new_entry->id))
+ delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
+
+ return diff_insert_delta(diff, delta, matched_pathspec);
+}
+
+static git_diff_delta *diff_delta__last_for_item(
+ git_diff *diff,
+ const git_index_entry *item)
+{
+ git_diff_delta *delta = git_vector_last(&diff->deltas);
+ if (!delta)
+ return NULL;
+
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED:
+ case GIT_DELTA_DELETED:
+ if (git_oid__cmp(&delta->old_file.id, &item->id) == 0)
+ return delta;
+ break;
+ case GIT_DELTA_ADDED:
+ if (git_oid__cmp(&delta->new_file.id, &item->id) == 0)
+ return delta;
+ break;
+ case GIT_DELTA_UNTRACKED:
+ if (diff->strcomp(delta->new_file.path, item->path) == 0 &&
+ git_oid__cmp(&delta->new_file.id, &item->id) == 0)
+ return delta;
+ break;
+ case GIT_DELTA_MODIFIED:
+ if (git_oid__cmp(&delta->old_file.id, &item->id) == 0 ||
+ git_oid__cmp(&delta->new_file.id, &item->id) == 0)
+ return delta;
+ break;
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static char *diff_strdup_prefix(git_pool *pool, const char *prefix)
+{
+ size_t len = strlen(prefix);
+
+ /* append '/' at end if needed */
+ if (len > 0 && prefix[len - 1] != '/')
+ return git_pool_strcat(pool, prefix, "/");
+ else
+ return git_pool_strndup(pool, prefix, len + 1);
+}
+
+GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta)
+{
+ const char *str = delta->old_file.path;
+
+ if (!str ||
+ delta->status == GIT_DELTA_ADDED ||
+ delta->status == GIT_DELTA_RENAMED ||
+ delta->status == GIT_DELTA_COPIED)
+ str = delta->new_file.path;
+
+ return str;
+}
+
+const char *git_diff_delta__path(const git_diff_delta *delta)
+{
+ return diff_delta__path(delta);
+}
+
+int git_diff_delta__cmp(const void *a, const void *b)
+{
+ const git_diff_delta *da = a, *db = b;
+ int val = strcmp(diff_delta__path(da), diff_delta__path(db));
+ return val ? val : ((int)da->status - (int)db->status);
+}
+
+int git_diff_delta__casecmp(const void *a, const void *b)
+{
+ const git_diff_delta *da = a, *db = b;
+ int val = strcasecmp(diff_delta__path(da), diff_delta__path(db));
+ return val ? val : ((int)da->status - (int)db->status);
+}
+
+GIT_INLINE(const char *) diff_delta__i2w_path(const git_diff_delta *delta)
+{
+ return delta->old_file.path ?
+ delta->old_file.path : delta->new_file.path;
+}
+
+int git_diff_delta__i2w_cmp(const void *a, const void *b)
+{
+ const git_diff_delta *da = a, *db = b;
+ int val = strcmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db));
+ return val ? val : ((int)da->status - (int)db->status);
+}
+
+int git_diff_delta__i2w_casecmp(const void *a, const void *b)
+{
+ const git_diff_delta *da = a, *db = b;
+ int val = strcasecmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db));
+ 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 const char *diff_mnemonic_prefix(
+ git_iterator_type_t type, bool left_side)
+{
+ const char *pfx = "";
+
+ switch (type) {
+ case GIT_ITERATOR_TYPE_EMPTY: pfx = "c"; break;
+ case GIT_ITERATOR_TYPE_TREE: pfx = "c"; break;
+ case GIT_ITERATOR_TYPE_INDEX: pfx = "i"; break;
+ case GIT_ITERATOR_TYPE_WORKDIR: pfx = "w"; break;
+ case GIT_ITERATOR_TYPE_FS: pfx = left_side ? "1" : "2"; break;
+ default: break;
+ }
+
+ /* note: without a deeper look at pathspecs, there is no easy way
+ * to get the (o)bject / (w)ork tree mnemonics working...
+ */
+
+ return pfx;
+}
+
+static void diff_set_ignore_case(git_diff *diff, bool ignore_case)
+{
+ if (!ignore_case) {
+ diff->opts.flags &= ~GIT_DIFF_IGNORE_CASE;
+
+ diff->strcomp = git__strcmp;
+ diff->strncomp = git__strncmp;
+ diff->pfxcomp = git__prefixcmp;
+ diff->entrycomp = git_index_entry_cmp;
+
+ git_vector_set_cmp(&diff->deltas, git_diff_delta__cmp);
+ } else {
+ diff->opts.flags |= GIT_DIFF_IGNORE_CASE;
+
+ diff->strcomp = git__strcasecmp;
+ diff->strncomp = git__strncasecmp;
+ diff->pfxcomp = git__prefixcmp_icase;
+ diff->entrycomp = git_index_entry_icmp;
+
+ git_vector_set_cmp(&diff->deltas, git_diff_delta__casecmp);
+ }
+
+ git_vector_sort(&diff->deltas);
+}
+
+static git_diff *diff_list_alloc(
+ git_repository *repo,
+ git_iterator *old_iter,
+ git_iterator *new_iter)
+{
+ git_diff_options dflt = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = git__calloc(1, sizeof(git_diff));
+ if (!diff)
+ return NULL;
+
+ assert(repo && old_iter && new_iter);
+
+ GIT_REFCOUNT_INC(diff);
+ diff->repo = repo;
+ diff->old_src = old_iter->type;
+ diff->new_src = new_iter->type;
+ memcpy(&diff->opts, &dflt, sizeof(diff->opts));
+
+ if (git_vector_init(&diff->deltas, 0, git_diff_delta__cmp) < 0 ||
+ git_pool_init(&diff->pool, 1, 0) < 0) {
+ git_diff_free(diff);
+ return NULL;
+ }
+
+ /* Use case-insensitive compare if either iterator has
+ * the ignore_case bit set */
+ diff_set_ignore_case(
+ diff,
+ git_iterator_ignore_case(old_iter) ||
+ git_iterator_ignore_case(new_iter));
+
+ return diff;
+}
+
+static int diff_list_apply_options(
+ git_diff *diff,
+ const git_diff_options *opts)
+{
+ git_config *cfg = NULL;
+ git_repository *repo = diff->repo;
+ git_pool *pool = &diff->pool;
+ int val;
+
+ if (opts) {
+ /* copy user options (except case sensitivity info from iterators) */
+ bool icase = DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE);
+ memcpy(&diff->opts, opts, sizeof(diff->opts));
+ DIFF_FLAG_SET(diff, GIT_DIFF_IGNORE_CASE, icase);
+
+ /* initialize pathspec from options */
+ if (git_pathspec__vinit(&diff->pathspec, &opts->pathspec, pool) < 0)
+ return -1;
+ }
+
+ /* flag INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES))
+ diff->opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE;
+
+ /* flag INCLUDE_UNTRACKED_CONTENT implies INCLUDE_UNTRACKED */
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_SHOW_UNTRACKED_CONTENT))
+ diff->opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
+
+ /* load config values that affect diff behavior */
+ if ((val = git_repository_config_snapshot(&cfg, repo)) < 0)
+ return val;
+
+ if (!git_config__cvar(&val, cfg, GIT_CVAR_SYMLINKS) && val)
+ diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_HAS_SYMLINKS;
+
+ if (!git_config__cvar(&val, cfg, GIT_CVAR_IGNORESTAT) && val)
+ diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_IGNORE_STAT;
+
+ if ((diff->opts.flags & GIT_DIFF_IGNORE_FILEMODE) == 0 &&
+ !git_config__cvar(&val, cfg, GIT_CVAR_FILEMODE) && val)
+ diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_MODE_BITS;
+
+ if (!git_config__cvar(&val, cfg, GIT_CVAR_TRUSTCTIME) && val)
+ diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME;
+
+ /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */
+
+ /* Set GIT_DIFFCAPS_TRUST_NANOSECS on a platform basis */
+ diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_NANOSECS;
+
+ /* If not given explicit `opts`, check `diff.xyz` configs */
+ if (!opts) {
+ int context = git_config__get_int_force(cfg, "diff.context", 3);
+ diff->opts.context_lines = context >= 0 ? (uint16_t)context : 3;
+
+ /* add other defaults here */
+ }
+
+ /* Reverse src info if diff is reversed */
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
+ git_iterator_type_t tmp_src = diff->old_src;
+ diff->old_src = diff->new_src;
+ diff->new_src = tmp_src;
+ }
+
+ /* Unset UPDATE_INDEX unless diffing workdir and index */
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) &&
+ (!(diff->old_src == GIT_ITERATOR_TYPE_WORKDIR ||
+ diff->new_src == GIT_ITERATOR_TYPE_WORKDIR) ||
+ !(diff->old_src == GIT_ITERATOR_TYPE_INDEX ||
+ diff->new_src == GIT_ITERATOR_TYPE_INDEX)))
+ diff->opts.flags &= ~GIT_DIFF_UPDATE_INDEX;
+
+ /* if ignore_submodules not explicitly set, check diff config */
+ if (diff->opts.ignore_submodules <= 0) {
+ const git_config_entry *entry;
+ git_config__lookup_entry(&entry, cfg, "diff.ignoresubmodules", true);
+
+ if (entry && git_submodule_parse_ignore(
+ &diff->opts.ignore_submodules, entry->value) < 0)
+ giterr_clear();
+ }
+
+ /* if either prefix is not set, figure out appropriate value */
+ if (!diff->opts.old_prefix || !diff->opts.new_prefix) {
+ const char *use_old = DIFF_OLD_PREFIX_DEFAULT;
+ const char *use_new = DIFF_NEW_PREFIX_DEFAULT;
+
+ if (git_config__get_bool_force(cfg, "diff.noprefix", 0))
+ use_old = use_new = "";
+ else if (git_config__get_bool_force(cfg, "diff.mnemonicprefix", 0)) {
+ use_old = diff_mnemonic_prefix(diff->old_src, true);
+ use_new = diff_mnemonic_prefix(diff->new_src, false);
+ }
+
+ if (!diff->opts.old_prefix)
+ diff->opts.old_prefix = use_old;
+ if (!diff->opts.new_prefix)
+ diff->opts.new_prefix = use_new;
+ }
+
+ /* strdup prefix from pool so we're not dependent on external data */
+ diff->opts.old_prefix = diff_strdup_prefix(pool, diff->opts.old_prefix);
+ diff->opts.new_prefix = diff_strdup_prefix(pool, diff->opts.new_prefix);
+
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
+ const char *tmp_prefix = diff->opts.old_prefix;
+ diff->opts.old_prefix = diff->opts.new_prefix;
+ diff->opts.new_prefix = tmp_prefix;
+ }
+
+ git_config_free(cfg);
+
+ /* check strdup results for error */
+ return (!diff->opts.old_prefix || !diff->opts.new_prefix) ? -1 : 0;
+}
+
+static void diff_list_free(git_diff *diff)
+{
+ git_vector_free_deep(&diff->deltas);
+
+ git_pathspec__vfree(&diff->pathspec);
+ git_pool_clear(&diff->pool);
+
+ git__memzero(diff, sizeof(*diff));
+ git__free(diff);
+}
+
+void git_diff_free(git_diff *diff)
+{
+ if (!diff)
+ return;
+
+ GIT_REFCOUNT_DEC(diff, diff_list_free);
+}
+
+void git_diff_addref(git_diff *diff)
+{
+ GIT_REFCOUNT_INC(diff);
+}
+
+int git_diff__oid_for_file(
+ git_oid *out,
+ git_diff *diff,
+ const char *path,
+ uint16_t mode,
+ git_off_t size)
+{
+ git_index_entry entry;
+
+ memset(&entry, 0, sizeof(entry));
+ entry.mode = mode;
+ entry.file_size = size;
+ entry.path = (char *)path;
+
+ return git_diff__oid_for_entry(out, diff, &entry, NULL);
+}
+
+int git_diff__oid_for_entry(
+ git_oid *out,
+ git_diff *diff,
+ const git_index_entry *src,
+ const git_oid *update_match)
+{
+ int error = 0;
+ git_buf full_path = GIT_BUF_INIT;
+ git_index_entry entry = *src;
+ git_filter_list *fl = NULL;
+
+ memset(out, 0, sizeof(*out));
+
+ if (git_buf_joinpath(
+ &full_path, git_repository_workdir(diff->repo), entry.path) < 0)
+ return -1;
+
+ if (!entry.mode) {
+ struct stat st;
+
+ diff->perf.stat_calls++;
+
+ if (p_stat(full_path.ptr, &st) < 0) {
+ error = git_path_set_error(errno, entry.path, "stat");
+ git_buf_free(&full_path);
+ return error;
+ }
+
+ git_index_entry__init_from_stat(
+ &entry, &st, (diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) != 0);
+ }
+
+ /* calculate OID for file if possible */
+ if (S_ISGITLINK(entry.mode)) {
+ git_submodule *sm;
+
+ if (!git_submodule_lookup(&sm, diff->repo, entry.path)) {
+ const git_oid *sm_oid = git_submodule_wd_id(sm);
+ if (sm_oid)
+ git_oid_cpy(out, sm_oid);
+ git_submodule_free(sm);
+ } else {
+ /* if submodule lookup failed probably just in an intermediate
+ * state where some init hasn't happened, so ignore the error
+ */
+ giterr_clear();
+ }
+ } else if (S_ISLNK(entry.mode)) {
+ error = git_odb__hashlink(out, full_path.ptr);
+ diff->perf.oid_calculations++;
+ } else if (!git__is_sizet(entry.file_size)) {
+ giterr_set(GITERR_OS, "File size overflow (for 32-bits) on '%s'",
+ entry.path);
+ error = -1;
+ } else if (!(error = git_filter_list_load(
+ &fl, diff->repo, NULL, entry.path,
+ GIT_FILTER_TO_ODB, GIT_FILTER_OPT_ALLOW_UNSAFE)))
+ {
+ int fd = git_futils_open_ro(full_path.ptr);
+ if (fd < 0)
+ error = fd;
+ else {
+ error = git_odb__hashfd_filtered(
+ out, fd, (size_t)entry.file_size, GIT_OBJ_BLOB, fl);
+ p_close(fd);
+ diff->perf.oid_calculations++;
+ }
+
+ git_filter_list_free(fl);
+ }
+
+ /* update index for entry if requested */
+ if (!error && update_match && git_oid_equal(out, update_match)) {
+ git_index *idx;
+
+ if (!(error = git_repository_index(&idx, diff->repo))) {
+ memcpy(&entry.id, out, sizeof(entry.id));
+ error = git_index_add(idx, &entry);
+ git_index_free(idx);
+ }
+ }
+
+ git_buf_free(&full_path);
+ return error;
+}
+
+static bool diff_time_eq(
+ const git_index_time *a, const git_index_time *b, bool use_nanos)
+{
+ return a->seconds == b->seconds &&
+ (!use_nanos || a->nanoseconds == b->nanoseconds);
+}
+
+typedef struct {
+ git_repository *repo;
+ git_iterator *old_iter;
+ git_iterator *new_iter;
+ const git_index_entry *oitem;
+ const git_index_entry *nitem;
+} diff_in_progress;
+
+#define MODE_BITS_MASK 0000777
+
+static int maybe_modified_submodule(
+ git_delta_t *status,
+ git_oid *found_oid,
+ git_diff *diff,
+ diff_in_progress *info)
+{
+ int error = 0;
+ git_submodule *sub;
+ unsigned int sm_status = 0;
+ git_submodule_ignore_t ign = diff->opts.ignore_submodules;
+
+ *status = GIT_DELTA_UNMODIFIED;
+
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) ||
+ ign == GIT_SUBMODULE_IGNORE_ALL)
+ return 0;
+
+ if ((error = git_submodule_lookup(
+ &sub, diff->repo, info->nitem->path)) < 0) {
+
+ /* GIT_EEXISTS means dir with .git in it was found - ignore it */
+ if (error == GIT_EEXISTS) {
+ giterr_clear();
+ error = 0;
+ }
+ return error;
+ }
+
+ if (ign <= 0 && git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL)
+ /* ignore it */;
+ else if ((error = git_submodule__status(
+ &sm_status, NULL, NULL, found_oid, sub, ign)) < 0)
+ /* return error below */;
+
+ /* check IS_WD_UNMODIFIED because this case is only used
+ * when the new side of the diff is the working directory
+ */
+ else if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status))
+ *status = GIT_DELTA_MODIFIED;
+
+ /* now that we have a HEAD OID, check if HEAD moved */
+ else if ((sm_status & GIT_SUBMODULE_STATUS_IN_WD) != 0 &&
+ !git_oid_equal(&info->oitem->id, found_oid))
+ *status = GIT_DELTA_MODIFIED;
+
+ git_submodule_free(sub);
+ return error;
+}
+
+static int maybe_modified(
+ git_diff *diff,
+ diff_in_progress *info)
+{
+ git_oid noid;
+ git_delta_t status = GIT_DELTA_MODIFIED;
+ const git_index_entry *oitem = info->oitem;
+ const git_index_entry *nitem = info->nitem;
+ unsigned int omode = oitem->mode;
+ unsigned int nmode = nitem->mode;
+ bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_TYPE_WORKDIR);
+ bool modified_uncertain = false;
+ const char *matched_pathspec;
+ int error = 0;
+
+ if (!git_pathspec__match(
+ &diff->pathspec, oitem->path,
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE),
+ &matched_pathspec, NULL))
+ return 0;
+
+ memset(&noid, 0, sizeof(noid));
+
+ /* on platforms with no symlinks, preserve mode of existing symlinks */
+ if (S_ISLNK(omode) && S_ISREG(nmode) && new_is_workdir &&
+ !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))
+ nmode = omode;
+
+ /* on platforms with no execmode, just preserve old mode */
+ if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) &&
+ (nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) &&
+ new_is_workdir)
+ nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK);
+
+ /* support "assume unchanged" (poorly, b/c we still stat everything) */
+ if ((oitem->flags & GIT_IDXENTRY_VALID) != 0)
+ status = GIT_DELTA_UNMODIFIED;
+
+ /* support "skip worktree" index bit */
+ else if ((oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0)
+ status = GIT_DELTA_UNMODIFIED;
+
+ /* if basic type of file changed, then split into delete and add */
+ else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) {
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE))
+ status = GIT_DELTA_TYPECHANGE;
+ else {
+ if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem)))
+ error = diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem);
+ return error;
+ }
+ }
+
+ /* if oids and modes match (and are valid), then file is unmodified */
+ else if (git_oid_equal(&oitem->id, &nitem->id) &&
+ omode == nmode &&
+ !git_oid_iszero(&oitem->id))
+ status = GIT_DELTA_UNMODIFIED;
+
+ /* if we have an unknown OID and a workdir iterator, then check some
+ * circumstances that can accelerate things or need special handling
+ */
+ else if (git_oid_iszero(&nitem->id) && new_is_workdir) {
+ bool use_ctime = ((diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) != 0);
+ bool use_nanos = ((diff->diffcaps & GIT_DIFFCAPS_TRUST_NANOSECS) != 0);
+
+ status = GIT_DELTA_UNMODIFIED;
+
+ /* TODO: add check against index file st_mtime to avoid racy-git */
+
+ if (S_ISGITLINK(nmode)) {
+ if ((error = maybe_modified_submodule(&status, &noid, diff, info)) < 0)
+ return error;
+ }
+
+ /* if the stat data looks different, then mark modified - this just
+ * means that the OID will be recalculated below to confirm change
+ */
+ else if (omode != nmode || oitem->file_size != nitem->file_size) {
+ status = GIT_DELTA_MODIFIED;
+ modified_uncertain =
+ (oitem->file_size <= 0 && nitem->file_size > 0);
+ }
+ else if (!diff_time_eq(&oitem->mtime, &nitem->mtime, use_nanos) ||
+ (use_ctime &&
+ !diff_time_eq(&oitem->ctime, &nitem->ctime, use_nanos)) ||
+ oitem->ino != nitem->ino ||
+ oitem->uid != nitem->uid ||
+ oitem->gid != nitem->gid)
+ {
+ status = GIT_DELTA_MODIFIED;
+ modified_uncertain = true;
+ }
+ }
+
+ /* if mode is GITLINK and submodules are ignored, then skip */
+ else if (S_ISGITLINK(nmode) &&
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES))
+ status = GIT_DELTA_UNMODIFIED;
+
+ /* if we got here and decided that the files are modified, but we
+ * haven't calculated the OID of the new item, then calculate it now
+ */
+ if (modified_uncertain && git_oid_iszero(&nitem->id)) {
+ if (git_oid_iszero(&noid)) {
+ const git_oid *update_check =
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) ?
+ &oitem->id : NULL;
+
+ if ((error = git_diff__oid_for_entry(
+ &noid, diff, nitem, update_check)) < 0)
+ return error;
+ }
+
+ /* if oid matches, then mark unmodified (except submodules, where
+ * the filesystem content may be modified even if the oid still
+ * matches between the index and the workdir HEAD)
+ */
+ if (omode == nmode && !S_ISGITLINK(omode) &&
+ git_oid_equal(&oitem->id, &noid))
+ status = GIT_DELTA_UNMODIFIED;
+ }
+
+ return diff_delta__from_two(
+ diff, status, oitem, omode, nitem, nmode,
+ git_oid_iszero(&noid) ? NULL : &noid, matched_pathspec);
+}
+
+static bool entry_is_prefixed(
+ git_diff *diff,
+ const git_index_entry *item,
+ const git_index_entry *prefix_item)
+{
+ size_t pathlen;
+
+ if (!item || diff->pfxcomp(item->path, prefix_item->path) != 0)
+ return false;
+
+ pathlen = strlen(prefix_item->path);
+
+ return (prefix_item->path[pathlen - 1] == '/' ||
+ item->path[pathlen] == '\0' ||
+ item->path[pathlen] == '/');
+}
+
+static int handle_unmatched_new_item(
+ git_diff *diff, diff_in_progress *info)
+{
+ int error = 0;
+ const git_index_entry *nitem = info->nitem;
+ git_delta_t delta_type = GIT_DELTA_UNTRACKED;
+ bool contains_oitem;
+
+ /* check if this is a prefix of the other side */
+ contains_oitem = entry_is_prefixed(diff, info->oitem, nitem);
+
+ /* update delta_type if this item is ignored */
+ if (git_iterator_current_is_ignored(info->new_iter))
+ delta_type = GIT_DELTA_IGNORED;
+
+ if (nitem->mode == GIT_FILEMODE_TREE) {
+ bool recurse_into_dir = contains_oitem;
+
+ /* check if user requests recursion into this type of dir */
+ recurse_into_dir = contains_oitem ||
+ (delta_type == GIT_DELTA_UNTRACKED &&
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) ||
+ (delta_type == GIT_DELTA_IGNORED &&
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS));
+
+ /* do not advance into directories that contain a .git file */
+ if (recurse_into_dir && !contains_oitem) {
+ git_buf *full = NULL;
+ if (git_iterator_current_workdir_path(&full, info->new_iter) < 0)
+ return -1;
+ if (full && git_path_contains(full, DOT_GIT)) {
+ /* TODO: warning if not a valid git repository */
+ recurse_into_dir = false;
+ }
+ }
+
+ /* still have to look into untracked directories to match core git -
+ * with no untracked files, directory is treated as ignored
+ */
+ if (!recurse_into_dir &&
+ delta_type == GIT_DELTA_UNTRACKED &&
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS))
+ {
+ git_diff_delta *last;
+ git_iterator_status_t untracked_state;
+
+ /* attempt to insert record for this directory */
+ if ((error = diff_delta__from_one(diff, delta_type, nitem)) != 0)
+ return error;
+
+ /* if delta wasn't created (because of rules), just skip ahead */
+ last = diff_delta__last_for_item(diff, nitem);
+ if (!last)
+ return git_iterator_advance(&info->nitem, info->new_iter);
+
+ /* iterate into dir looking for an actual untracked file */
+ if ((error = git_iterator_advance_over_with_status(
+ &info->nitem, &untracked_state, info->new_iter)) < 0 &&
+ error != GIT_ITEROVER)
+ return error;
+
+ /* if we found nothing or just ignored items, update the record */
+ if (untracked_state == GIT_ITERATOR_STATUS_IGNORED ||
+ untracked_state == GIT_ITERATOR_STATUS_EMPTY) {
+ last->status = GIT_DELTA_IGNORED;
+
+ /* remove the record if we don't want ignored records */
+ if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) {
+ git_vector_pop(&diff->deltas);
+ git__free(last);
+ }
+ }
+
+ return 0;
+ }
+
+ /* try to advance into directory if necessary */
+ if (recurse_into_dir) {
+ error = git_iterator_advance_into(&info->nitem, info->new_iter);
+
+ /* if real error or no error, proceed with iteration */
+ if (error != GIT_ENOTFOUND)
+ return error;
+ giterr_clear();
+
+ /* if directory is empty, can't advance into it, so either skip
+ * it or ignore it
+ */
+ if (contains_oitem)
+ return git_iterator_advance(&info->nitem, info->new_iter);
+ delta_type = GIT_DELTA_IGNORED;
+ }
+ }
+
+ else if (delta_type == GIT_DELTA_IGNORED &&
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS) &&
+ git_iterator_current_tree_is_ignored(info->new_iter))
+ /* item contained in ignored directory, so skip over it */
+ return git_iterator_advance(&info->nitem, info->new_iter);
+
+ else if (info->new_iter->type != GIT_ITERATOR_TYPE_WORKDIR)
+ delta_type = GIT_DELTA_ADDED;
+
+ else if (nitem->mode == GIT_FILEMODE_COMMIT) {
+ /* ignore things that are not actual submodules */
+ if (git_submodule_lookup(NULL, info->repo, nitem->path) != 0) {
+ giterr_clear();
+ delta_type = GIT_DELTA_IGNORED;
+
+ /* if this contains a tracked item, treat as normal TREE */
+ if (contains_oitem) {
+ error = git_iterator_advance_into(&info->nitem, info->new_iter);
+ if (error != GIT_ENOTFOUND)
+ return error;
+
+ giterr_clear();
+ return git_iterator_advance(&info->nitem, info->new_iter);
+ }
+ }
+ }
+
+ /* Actually create the record for this item if necessary */
+ if ((error = diff_delta__from_one(diff, delta_type, nitem)) != 0)
+ return error;
+
+ /* If user requested TYPECHANGE records, then check for that instead of
+ * just generating an ADDED/UNTRACKED record
+ */
+ if (delta_type != GIT_DELTA_IGNORED &&
+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
+ contains_oitem)
+ {
+ /* this entry was prefixed with a tree - make TYPECHANGE */
+ git_diff_delta *last = diff_delta__last_for_item(diff, nitem);
+ if (last) {
+ last->status = GIT_DELTA_TYPECHANGE;
+ last->old_file.mode = GIT_FILEMODE_TREE;
+ }
+ }
+
+ return git_iterator_advance(&info->nitem, info->new_iter);
+}
+
+static int handle_unmatched_old_item(
+ git_diff *diff, diff_in_progress *info)
+{
+ int error = diff_delta__from_one(diff, GIT_DELTA_DELETED, info->oitem);
+ if (error != 0)
+ return error;
+
+ /* if we are generating TYPECHANGE records then check for that
+ * instead of just generating a DELETE record
+ */
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
+ entry_is_prefixed(diff, info->nitem, info->oitem))
+ {
+ /* this entry has become a tree! convert to TYPECHANGE */
+ git_diff_delta *last = diff_delta__last_for_item(diff, info->oitem);
+ if (last) {
+ last->status = GIT_DELTA_TYPECHANGE;
+ last->new_file.mode = GIT_FILEMODE_TREE;
+ }
+
+ /* If new_iter is a workdir iterator, then this situation
+ * will certainly be followed by a series of untracked items.
+ * Unless RECURSE_UNTRACKED_DIRS is set, skip over them...
+ */
+ if (S_ISDIR(info->nitem->mode) &&
+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS))
+ return git_iterator_advance(&info->nitem, info->new_iter);
+ }
+
+ return git_iterator_advance(&info->oitem, info->old_iter);
+}
+
+static int handle_matched_item(
+ git_diff *diff, diff_in_progress *info)
+{
+ int error = 0;
+
+ if ((error = maybe_modified(diff, info)) < 0)
+ return error;
+
+ if (!(error = git_iterator_advance(&info->oitem, info->old_iter)) ||
+ error == GIT_ITEROVER)
+ error = git_iterator_advance(&info->nitem, info->new_iter);
+
+ return error;
+}
+
+int git_diff__from_iterators(
+ git_diff **diff_ptr,
+ git_repository *repo,
+ git_iterator *old_iter,
+ git_iterator *new_iter,
+ const git_diff_options *opts)
+{
+ int error = 0;
+ diff_in_progress info;
+ git_diff *diff;
+
+ *diff_ptr = NULL;
+
+ diff = diff_list_alloc(repo, old_iter, new_iter);
+ GITERR_CHECK_ALLOC(diff);
+
+ info.repo = repo;
+ info.old_iter = old_iter;
+ info.new_iter = new_iter;
+
+ /* make iterators have matching icase behavior */
+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE)) {
+ if ((error = git_iterator_set_ignore_case(old_iter, true)) < 0 ||
+ (error = git_iterator_set_ignore_case(new_iter, true)) < 0)
+ goto cleanup;
+ }
+
+ /* finish initialization */
+ if ((error = diff_list_apply_options(diff, opts)) < 0)
+ goto cleanup;
+
+ if ((error = git_iterator_current(&info.oitem, old_iter)) < 0 &&
+ error != GIT_ITEROVER)
+ goto cleanup;
+ if ((error = git_iterator_current(&info.nitem, new_iter)) < 0 &&
+ error != GIT_ITEROVER)
+ goto cleanup;
+ error = 0;
+
+ /* run iterators building diffs */
+ while (!error && (info.oitem || info.nitem)) {
+ int cmp = info.oitem ?
+ (info.nitem ? diff->entrycomp(info.oitem, info.nitem) : -1) : 1;
+
+ /* create DELETED records for old items not matched in new */
+ if (cmp < 0)
+ error = handle_unmatched_old_item(diff, &info);
+
+ /* create ADDED, TRACKED, or IGNORED records for new items not
+ * matched in old (and/or descend into directories as needed)
+ */
+ else if (cmp > 0)
+ error = handle_unmatched_new_item(diff, &info);
+
+ /* otherwise item paths match, so create MODIFIED record
+ * (or ADDED and DELETED pair if type changed)
+ */
+ else
+ error = handle_matched_item(diff, &info);
+
+ /* because we are iterating over two lists, ignore ITEROVER */
+ if (error == GIT_ITEROVER)
+ error = 0;
+ }
+
+ diff->perf.stat_calls += old_iter->stat_calls + new_iter->stat_calls;
+
+cleanup:
+ if (!error)
+ *diff_ptr = diff;
+ else
+ git_diff_free(diff);
+
+ return error;
+}
+
+#define DIFF_FROM_ITERATORS(MAKE_FIRST, MAKE_SECOND) do { \
+ git_iterator *a = NULL, *b = NULL; \
+ char *pfx = opts ? git_pathspec_prefix(&opts->pathspec) : NULL; \
+ GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); \
+ if (!(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \
+ error = git_diff__from_iterators(diff, repo, a, b, opts); \
+ git__free(pfx); git_iterator_free(a); git_iterator_free(b); \
+} while (0)
+
+int git_diff_tree_to_tree(
+ git_diff **diff,
+ git_repository *repo,
+ git_tree *old_tree,
+ git_tree *new_tree,
+ const git_diff_options *opts)
+{
+ int error = 0;
+ git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ assert(diff && repo);
+
+ /* for tree to tree diff, be case sensitive even if the index is
+ * currently case insensitive, unless the user explicitly asked
+ * for case insensitivity
+ */
+ if (opts && (opts->flags & GIT_DIFF_IGNORE_CASE) != 0)
+ iflag = GIT_ITERATOR_IGNORE_CASE;
+
+ DIFF_FROM_ITERATORS(
+ git_iterator_for_tree(&a, old_tree, iflag, pfx, pfx),
+ git_iterator_for_tree(&b, new_tree, iflag, pfx, pfx)
+ );
+
+ return error;
+}
+
+static int diff_load_index(git_index **index, git_repository *repo)
+{
+ int error = git_repository_index__weakptr(index, repo);
+
+ /* reload the repository index when user did not pass one in */
+ if (!error && git_index_read(*index, false) < 0)
+ giterr_clear();
+
+ return error;
+}
+
+int git_diff_tree_to_index(
+ git_diff **diff,
+ git_repository *repo,
+ git_tree *old_tree,
+ git_index *index,
+ const git_diff_options *opts)
+{
+ int error = 0;
+ bool index_ignore_case = false;
+
+ assert(diff && repo);
+
+ if (!index && (error = diff_load_index(&index, repo)) < 0)
+ return error;
+
+ index_ignore_case = index->ignore_case;
+
+ DIFF_FROM_ITERATORS(
+ git_iterator_for_tree(
+ &a, old_tree, GIT_ITERATOR_DONT_IGNORE_CASE, pfx, pfx),
+ git_iterator_for_index(
+ &b, index, GIT_ITERATOR_DONT_IGNORE_CASE, pfx, pfx)
+ );
+
+ /* if index is in case-insensitive order, re-sort deltas to match */
+ if (!error && index_ignore_case)
+ diff_set_ignore_case(*diff, true);
+
+ return error;
+}
+
+int git_diff_index_to_workdir(
+ git_diff **diff,
+ git_repository *repo,
+ git_index *index,
+ const git_diff_options *opts)
+{
+ int error = 0;
+
+ assert(diff && repo);
+
+ if (!index && (error = diff_load_index(&index, repo)) < 0)
+ return error;
+
+ DIFF_FROM_ITERATORS(
+ git_iterator_for_index(&a, index, 0, pfx, pfx),
+ git_iterator_for_workdir(
+ &b, repo, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx)
+ );
+
+ if (!error && DIFF_FLAG_IS_SET(*diff, GIT_DIFF_UPDATE_INDEX))
+ error = git_index_write(index);
+
+ return error;
+}
+
+int git_diff_tree_to_workdir(
+ git_diff **diff,
+ git_repository *repo,
+ git_tree *old_tree,
+ const git_diff_options *opts)
+{
+ int error = 0;
+
+ assert(diff && repo);
+
+ DIFF_FROM_ITERATORS(
+ git_iterator_for_tree(&a, old_tree, 0, pfx, pfx),
+ git_iterator_for_workdir(
+ &b, repo, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx)
+ );
+
+ return error;
+}
+
+int git_diff_tree_to_workdir_with_index(
+ git_diff **diff,
+ git_repository *repo,
+ git_tree *old_tree,
+ const git_diff_options *opts)
+{
+ int error = 0;
+ git_diff *d1 = NULL, *d2 = NULL;
+ git_index *index = NULL;
+
+ assert(diff && repo);
+
+ if ((error = diff_load_index(&index, repo)) < 0)
+ return error;
+
+ if (!(error = git_diff_tree_to_index(&d1, repo, old_tree, index, opts)) &&
+ !(error = git_diff_index_to_workdir(&d2, repo, index, opts)))
+ error = git_diff_merge(d1, d2);
+
+ git_diff_free(d2);
+
+ if (error) {
+ git_diff_free(d1);
+ d1 = NULL;
+ }
+
+ *diff = d1;
+ return error;
+}
+
+size_t git_diff_num_deltas(const git_diff *diff)
+{
+ assert(diff);
+ return diff->deltas.length;
+}
+
+size_t git_diff_num_deltas_of_type(const git_diff *diff, git_delta_t type)
+{
+ size_t i, count = 0;
+ const git_diff_delta *delta;
+
+ assert(diff);
+
+ git_vector_foreach(&diff->deltas, i, delta) {
+ count += (delta->status == type);
+ }
+
+ return count;
+}
+
+const git_diff_delta *git_diff_get_delta(const git_diff *diff, size_t idx)
+{
+ assert(diff);
+ return git_vector_get(&diff->deltas, idx);
+}
+
+int git_diff_is_sorted_icase(const git_diff *diff)
+{
+ return (diff->opts.flags & GIT_DIFF_IGNORE_CASE) != 0;
+}
+
+int git_diff_get_perfdata(git_diff_perfdata *out, const git_diff *diff)
+{
+ assert(out);
+ GITERR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata");
+ out->stat_calls = diff->perf.stat_calls;
+ out->oid_calculations = diff->perf.oid_calculations;
+ return 0;
+}
+
+int git_diff__paired_foreach(
+ git_diff *head2idx,
+ git_diff *idx2wd,
+ int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload),
+ void *payload)
+{
+ int cmp, error = 0;
+ git_diff_delta *h2i, *i2w;
+ size_t i, j, i_max, j_max;
+ int (*strcomp)(const char *, const char *) = git__strcmp;
+ bool h2i_icase, i2w_icase, icase_mismatch;
+
+ i_max = head2idx ? head2idx->deltas.length : 0;
+ j_max = idx2wd ? idx2wd->deltas.length : 0;
+ if (!i_max && !j_max)
+ return 0;
+
+ /* At some point, tree-to-index diffs will probably never ignore case,
+ * even if that isn't true now. Index-to-workdir diffs may or may not
+ * ignore case, but the index filename for the idx2wd diff should
+ * still be using the canonical case-preserving name.
+ *
+ * Therefore the main thing we need to do here is make sure the diffs
+ * are traversed in a compatible order. To do this, we temporarily
+ * resort a mismatched diff to get the order correct.
+ *
+ * In order to traverse renames in the index->workdir, we need to
+ * ensure that we compare the index name on both sides, so we
+ * always sort by the old name in the i2w list.
+ */
+ h2i_icase = head2idx != NULL &&
+ (head2idx->opts.flags & GIT_DIFF_IGNORE_CASE) != 0;
+
+ i2w_icase = idx2wd != NULL &&
+ (idx2wd->opts.flags & GIT_DIFF_IGNORE_CASE) != 0;
+
+ icase_mismatch =
+ (head2idx != NULL && idx2wd != NULL && h2i_icase != i2w_icase);
+
+ if (icase_mismatch && h2i_icase) {
+ git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp);
+ git_vector_sort(&head2idx->deltas);
+ }
+
+ if (i2w_icase && !icase_mismatch) {
+ strcomp = git__strcasecmp;
+
+ git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_casecmp);
+ git_vector_sort(&idx2wd->deltas);
+ } else if (idx2wd != NULL) {
+ git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_cmp);
+ git_vector_sort(&idx2wd->deltas);
+ }
+
+ for (i = 0, j = 0; i < i_max || j < j_max; ) {
+ h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL;
+ i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL;
+
+ cmp = !i2w ? -1 : !h2i ? 1 :
+ strcomp(h2i->new_file.path, i2w->old_file.path);
+
+ if (cmp < 0) {
+ i++; i2w = NULL;
+ } else if (cmp > 0) {
+ j++; h2i = NULL;
+ } else {
+ i++; j++;
+ }
+
+ if ((error = cb(h2i, i2w, payload)) != 0) {
+ giterr_set_after_callback(error);
+ break;
+ }
+ }
+
+ /* restore case-insensitive delta sort */
+ if (icase_mismatch && h2i_icase) {
+ git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp);
+ git_vector_sort(&head2idx->deltas);
+ }
+
+ /* restore idx2wd sort by new path */
+ if (idx2wd != NULL) {
+ git_vector_set_cmp(&idx2wd->deltas,
+ i2w_icase ? git_diff_delta__casecmp : git_diff_delta__cmp);
+ git_vector_sort(&idx2wd->deltas);
+ }
+
+ return error;
+}
+
+int git_diff__commit(
+ git_diff **diff,
+ git_repository *repo,
+ const git_commit *commit,
+ const git_diff_options *opts)
+{
+ git_commit *parent = NULL;
+ git_diff *commit_diff = NULL;
+ git_tree *old_tree = NULL, *new_tree = NULL;
+ size_t parents;
+ int error = 0;
+
+ if ((parents = git_commit_parentcount(commit)) > 1) {
+ char commit_oidstr[GIT_OID_HEXSZ + 1];
+
+ error = -1;
+ giterr_set(GITERR_INVALID, "Commit %s is a merge commit",
+ git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit)));
+ goto on_error;
+ }
+
+ if (parents > 0)
+ if ((error = git_commit_parent(&parent, commit, 0)) < 0 ||
+ (error = git_commit_tree(&old_tree, parent)) < 0)
+ goto on_error;
+
+ if ((error = git_commit_tree(&new_tree, commit)) < 0 ||
+ (error = git_diff_tree_to_tree(&commit_diff, repo, old_tree, new_tree, opts)) < 0)
+ goto on_error;
+
+ *diff = commit_diff;
+
+on_error:
+ git_tree_free(new_tree);
+ git_tree_free(old_tree);
+ git_commit_free(parent);
+
+ return error;
+}
+
+int git_diff_format_email__append_header_tobuf(
+ git_buf *out,
+ const git_oid *id,
+ const git_signature *author,
+ const char *summary,
+ size_t patch_no,
+ size_t total_patches,
+ bool exclude_patchno_marker)
+{
+ char idstr[GIT_OID_HEXSZ + 1];
+ char date_str[GIT_DATE_RFC2822_SZ];
+ int error = 0;
+
+ git_oid_fmt(idstr, id);
+ idstr[GIT_OID_HEXSZ] = '\0';
+
+ if ((error = git__date_rfc2822_fmt(date_str, sizeof(date_str), &author->when)) < 0)
+ return error;
+
+ error = git_buf_printf(out,
+ "From %s Mon Sep 17 00:00:00 2001\n" \
+ "From: %s <%s>\n" \
+ "Date: %s\n" \
+ "Subject: ",
+ idstr,
+ author->name, author->email,
+ date_str);
+
+ if (error < 0)
+ return error;
+
+ if (!exclude_patchno_marker) {
+ if (total_patches == 1) {
+ error = git_buf_puts(out, "[PATCH] ");
+ } else {
+ error = git_buf_printf(out, "[PATCH %"PRIuZ"/%"PRIuZ"] ", patch_no, total_patches);
+ }
+
+ if (error < 0)
+ return error;
+ }
+
+ error = git_buf_printf(out, "%s\n\n", summary);
+
+ return error;
+}
+
+int git_diff_format_email__append_patches_tobuf(
+ git_buf *out,
+ git_diff *diff)
+{
+ size_t i, deltas;
+ int error = 0;
+
+ deltas = git_diff_num_deltas(diff);
+
+ for (i = 0; i < deltas; ++i) {
+ git_patch *patch = NULL;
+
+ if ((error = git_patch_from_diff(&patch, diff, i)) >= 0)
+ error = git_patch_to_buf(out, patch);
+
+ git_patch_free(patch);
+
+ if (error < 0)
+ break;
+ }
+
+ return error;
+}
+
+int git_diff_format_email(
+ git_buf *out,
+ git_diff *diff,
+ const git_diff_format_email_options *opts)
+{
+ git_diff_stats *stats = NULL;
+ char *summary = NULL, *loc = NULL;
+ bool ignore_marker;
+ unsigned int format_flags = 0;
+ int error;
+
+ assert(out && diff && opts);
+ assert(opts->summary && opts->id && opts->author);
+
+ GITERR_CHECK_VERSION(opts, GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION, "git_format_email_options");
+
+ if ((ignore_marker = opts->flags & GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) == false) {
+ if (opts->patch_no > opts->total_patches) {
+ giterr_set(GITERR_INVALID, "patch %"PRIuZ" out of range. max %"PRIuZ, opts->patch_no, opts->total_patches);
+ return -1;
+ }
+
+ if (opts->patch_no == 0) {
+ giterr_set(GITERR_INVALID, "invalid patch no %"PRIuZ". should be >0", opts->patch_no);
+ return -1;
+ }
+ }
+
+ /* the summary we receive may not be clean.
+ * it could potentially contain new line characters
+ * or not be set, sanitize, */
+ if ((loc = strpbrk(opts->summary, "\r\n")) != NULL) {
+ size_t offset = 0;
+
+ if ((offset = (loc - opts->summary)) == 0) {
+ giterr_set(GITERR_INVALID, "summary is empty");
+ error = -1;
+ }
+
+ summary = git__calloc(offset + 1, sizeof(char));
+ GITERR_CHECK_ALLOC(summary);
+ strncpy(summary, opts->summary, offset);
+ }
+
+ error = git_diff_format_email__append_header_tobuf(out,
+ opts->id, opts->author, summary == NULL ? opts->summary : summary,
+ opts->patch_no, opts->total_patches, ignore_marker);
+
+ if (error < 0)
+ goto on_error;
+
+ format_flags = GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_INCLUDE_SUMMARY;
+
+ if ((error = git_buf_puts(out, "---\n")) < 0 ||
+ (error = git_diff_get_stats(&stats, diff)) < 0 ||
+ (error = git_diff_stats_to_buf(out, stats, format_flags, 0)) < 0 ||
+ (error = git_buf_putc(out, '\n')) < 0 ||
+ (error = git_diff_format_email__append_patches_tobuf(out, diff)) < 0)
+ goto on_error;
+
+ error = git_buf_puts(out, "--\nlibgit2 " LIBGIT2_VERSION "\n\n");
+
+on_error:
+ git__free(summary);
+ git_diff_stats_free(stats);
+
+ return error;
+}
+
+int git_diff_commit_as_email(
+ git_buf *out,
+ git_repository *repo,
+ git_commit *commit,
+ size_t patch_no,
+ size_t total_patches,
+ git_diff_format_email_flags_t flags,
+ const git_diff_options *diff_opts)
+{
+ git_diff *diff = NULL;
+ git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
+ int error;
+
+ assert (out && repo && commit);
+
+ opts.flags = flags;
+ opts.patch_no = patch_no;
+ opts.total_patches = total_patches;
+ opts.id = git_commit_id(commit);
+ opts.summary = git_commit_summary(commit);
+ opts.author = git_commit_author(commit);
+
+ if ((error = git_diff__commit(&diff, repo, commit, diff_opts)) < 0)
+ return error;
+
+ error = git_diff_format_email(out, diff, &opts);
+
+ git_diff_free(diff);
+ return error;
+}
+
+int git_diff_init_options(git_diff_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_diff_options, GIT_DIFF_OPTIONS_INIT);
+ return 0;
+}
+
+int git_diff_find_init_options(
+ git_diff_find_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_diff_find_options, GIT_DIFF_FIND_OPTIONS_INIT);
+ return 0;
+}
+
+int git_diff_format_email_init_options(
+ git_diff_format_email_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_diff_format_email_options,
+ GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT);
+ return 0;
+}
diff --git a/src/diff.h b/src/diff.h
new file mode 100644
index 000000000..3305238d0
--- /dev/null
+++ b/src/diff.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_diff_h__
+#define INCLUDE_diff_h__
+
+#include "git2/diff.h"
+#include "git2/sys/diff.h"
+#include "git2/oid.h"
+
+#include <stdio.h>
+#include "vector.h"
+#include "buffer.h"
+#include "iterator.h"
+#include "repository.h"
+#include "pool.h"
+#include "odb.h"
+
+#define DIFF_OLD_PREFIX_DEFAULT "a/"
+#define DIFF_NEW_PREFIX_DEFAULT "b/"
+
+enum {
+ GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */
+ GIT_DIFFCAPS_IGNORE_STAT = (1 << 1), /* use stat? */
+ GIT_DIFFCAPS_TRUST_MODE_BITS = (1 << 2), /* use st_mode? */
+ GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */
+ GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */
+ GIT_DIFFCAPS_TRUST_NANOSECS = (1 << 5), /* use stat time nanoseconds */
+};
+
+#define DIFF_FLAGS_KNOWN_BINARY (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY)
+#define DIFF_FLAGS_NOT_BINARY (GIT_DIFF_FLAG_NOT_BINARY|GIT_DIFF_FLAG__NO_DATA)
+
+enum {
+ GIT_DIFF_FLAG__FREE_PATH = (1 << 7), /* `path` is allocated memory */
+ GIT_DIFF_FLAG__FREE_DATA = (1 << 8), /* internal file data is allocated */
+ GIT_DIFF_FLAG__UNMAP_DATA = (1 << 9), /* internal file data is mmap'ed */
+ GIT_DIFF_FLAG__NO_DATA = (1 << 10), /* file data should not be loaded */
+ GIT_DIFF_FLAG__FREE_BLOB = (1 << 11), /* release the blob when done */
+ GIT_DIFF_FLAG__LOADED = (1 << 12), /* file data has been loaded */
+
+ GIT_DIFF_FLAG__TO_DELETE = (1 << 16), /* delete entry during rename det. */
+ GIT_DIFF_FLAG__TO_SPLIT = (1 << 17), /* split entry during rename det. */
+ GIT_DIFF_FLAG__IS_RENAME_TARGET = (1 << 18),
+ GIT_DIFF_FLAG__IS_RENAME_SOURCE = (1 << 19),
+ GIT_DIFF_FLAG__HAS_SELF_SIMILARITY = (1 << 20),
+};
+
+#define GIT_DIFF_FLAG__CLEAR_INTERNAL(F) (F) = ((F) & 0x00FFFF)
+
+#define GIT_DIFF__VERBOSE (1 << 30)
+
+struct git_diff {
+ git_refcount rc;
+ git_repository *repo;
+ git_diff_options opts;
+ git_vector pathspec;
+ git_vector deltas; /* vector of git_diff_delta */
+ git_pool pool;
+ git_iterator_type_t old_src;
+ git_iterator_type_t new_src;
+ uint32_t diffcaps;
+ git_diff_perfdata perf;
+
+ int (*strcomp)(const char *, const char *);
+ int (*strncomp)(const char *, const char *, size_t);
+ int (*pfxcomp)(const char *str, const char *pfx);
+ int (*entrycomp)(const void *a, const void *b);
+};
+
+extern void git_diff__cleanup_modes(
+ uint32_t diffcaps, uint32_t *omode, uint32_t *nmode);
+
+extern void git_diff_addref(git_diff *diff);
+
+extern int git_diff_delta__cmp(const void *a, const void *b);
+extern int git_diff_delta__casecmp(const void *a, const void *b);
+
+extern const char *git_diff_delta__path(const git_diff_delta *delta);
+
+extern bool git_diff_delta__should_skip(
+ const git_diff_options *opts, const git_diff_delta *delta);
+
+extern int git_diff_delta__format_file_header(
+ git_buf *out,
+ const git_diff_delta *delta,
+ const char *oldpfx,
+ const char *newpfx,
+ int oid_strlen);
+
+extern int git_diff__oid_for_file(
+ git_oid *out, git_diff *, const char *, uint16_t, git_off_t);
+extern int git_diff__oid_for_entry(
+ git_oid *out, git_diff *, const git_index_entry *, const git_oid *update);
+
+extern int git_diff__from_iterators(
+ git_diff **diff_ptr,
+ git_repository *repo,
+ git_iterator *old_iter,
+ git_iterator *new_iter,
+ const git_diff_options *opts);
+
+extern int git_diff__paired_foreach(
+ git_diff *idx2head,
+ git_diff *wd2idx,
+ int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload),
+ void *payload);
+
+extern int git_diff_find_similar__hashsig_for_file(
+ void **out, const git_diff_file *f, const char *path, void *p);
+
+extern int git_diff_find_similar__hashsig_for_buf(
+ void **out, const git_diff_file *f, const char *buf, size_t len, void *p);
+
+extern void git_diff_find_similar__hashsig_free(void *sig, void *payload);
+
+extern int git_diff_find_similar__calc_similarity(
+ int *score, void *siga, void *sigb, void *payload);
+
+extern int git_diff__commit(
+ git_diff **diff, git_repository *repo, const git_commit *commit, const git_diff_options *opts);
+
+/*
+ * Sometimes a git_diff_file will have a zero size; this attempts to
+ * fill in the size without loading the blob if possible. If that is
+ * not possible, then it will return the git_odb_object that had to be
+ * loaded and the caller can use it or dispose of it as needed.
+ */
+GIT_INLINE(int) git_diff_file__resolve_zero_size(
+ git_diff_file *file, git_odb_object **odb_obj, git_repository *repo)
+{
+ int error;
+ git_odb *odb;
+ size_t len;
+ git_otype type;
+
+ if ((error = git_repository_odb(&odb, repo)) < 0)
+ return error;
+
+ error = git_odb__read_header_or_object(
+ odb_obj, &len, &type, odb, &file->id);
+
+ git_odb_free(odb);
+
+ if (!error)
+ file->size = (git_off_t)len;
+
+ return error;
+}
+
+#endif
+
diff --git a/src/diff_driver.c b/src/diff_driver.c
new file mode 100644
index 000000000..c3c5f365b
--- /dev/null
+++ b/src/diff_driver.c
@@ -0,0 +1,503 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+
+#include "git2/attr.h"
+
+#include "diff.h"
+#include "diff_patch.h"
+#include "diff_driver.h"
+#include "strmap.h"
+#include "map.h"
+#include "buf_text.h"
+#include "config.h"
+#include "repository.h"
+
+GIT__USE_STRMAP;
+
+typedef enum {
+ DIFF_DRIVER_AUTO = 0,
+ DIFF_DRIVER_BINARY = 1,
+ DIFF_DRIVER_TEXT = 2,
+ DIFF_DRIVER_PATTERNLIST = 3,
+} git_diff_driver_t;
+
+typedef struct {
+ regex_t re;
+ int flags;
+} git_diff_driver_pattern;
+
+enum {
+ REG_NEGATE = (1 << 15) /* get out of the way of existing flags */
+};
+
+/* data for finding function context for a given file type */
+struct git_diff_driver {
+ git_diff_driver_t type;
+ uint32_t binary_flags;
+ uint32_t other_flags;
+ git_array_t(git_diff_driver_pattern) fn_patterns;
+ regex_t word_pattern;
+ char name[GIT_FLEX_ARRAY];
+};
+
+#include "userdiff.h"
+
+struct git_diff_driver_registry {
+ git_strmap *drivers;
+};
+
+#define FORCE_DIFFABLE (GIT_DIFF_FORCE_TEXT | GIT_DIFF_FORCE_BINARY)
+
+static git_diff_driver global_drivers[3] = {
+ { DIFF_DRIVER_AUTO, 0, 0, },
+ { DIFF_DRIVER_BINARY, GIT_DIFF_FORCE_BINARY, 0 },
+ { DIFF_DRIVER_TEXT, GIT_DIFF_FORCE_TEXT, 0 },
+};
+
+git_diff_driver_registry *git_diff_driver_registry_new()
+{
+ git_diff_driver_registry *reg =
+ git__calloc(1, sizeof(git_diff_driver_registry));
+ if (!reg)
+ return NULL;
+
+ if (git_strmap_alloc(&reg->drivers) < 0) {
+ git_diff_driver_registry_free(reg);
+ return NULL;
+ }
+
+ return reg;
+}
+
+void git_diff_driver_registry_free(git_diff_driver_registry *reg)
+{
+ git_diff_driver *drv;
+
+ if (!reg)
+ return;
+
+ git_strmap_foreach_value(reg->drivers, drv, git_diff_driver_free(drv));
+ git_strmap_free(reg->drivers);
+ git__free(reg);
+}
+
+static int diff_driver_add_patterns(
+ git_diff_driver *drv, const char *regex_str, int regex_flags)
+{
+ int error = 0;
+ const char *scan, *end;
+ git_diff_driver_pattern *pat = NULL;
+ git_buf buf = GIT_BUF_INIT;
+
+ for (scan = regex_str; scan; scan = end) {
+ /* get pattern to fill in */
+ if ((pat = git_array_alloc(drv->fn_patterns)) == NULL) {
+ error = -1;
+ break;
+ }
+
+ pat->flags = regex_flags;
+ if (*scan == '!') {
+ pat->flags |= REG_NEGATE;
+ ++scan;
+ }
+
+ if ((end = strchr(scan, '\n')) != NULL) {
+ error = git_buf_set(&buf, scan, end - scan);
+ end++;
+ } else {
+ error = git_buf_sets(&buf, scan);
+ }
+ if (error < 0)
+ break;
+
+ if ((error = regcomp(&pat->re, buf.ptr, regex_flags)) < 0) {
+ /* if regex fails to compile, warn? fail? */
+ error = giterr_set_regex(&pat->re, error);
+ regfree(&pat->re);
+ break;
+ }
+ }
+
+ if (error && pat != NULL)
+ (void)git_array_pop(drv->fn_patterns); /* release last item */
+ git_buf_free(&buf);
+
+ return error;
+}
+
+static int diff_driver_xfuncname(const git_config_entry *entry, void *payload)
+{
+ return diff_driver_add_patterns(payload, entry->value, REG_EXTENDED);
+}
+
+static int diff_driver_funcname(const git_config_entry *entry, void *payload)
+{
+ return diff_driver_add_patterns(payload, entry->value, 0);
+}
+
+static git_diff_driver_registry *git_repository_driver_registry(
+ git_repository *repo)
+{
+ if (!repo->diff_drivers) {
+ git_diff_driver_registry *reg = git_diff_driver_registry_new();
+ reg = git__compare_and_swap(&repo->diff_drivers, NULL, reg);
+
+ if (reg != NULL) /* if we race, free losing allocation */
+ git_diff_driver_registry_free(reg);
+ }
+
+ if (!repo->diff_drivers)
+ giterr_set(GITERR_REPOSITORY, "Unable to create diff driver registry");
+
+ return repo->diff_drivers;
+}
+
+static int git_diff_driver_builtin(
+ git_diff_driver **out,
+ git_diff_driver_registry *reg,
+ const char *driver_name)
+{
+ int error = 0;
+ git_diff_driver_definition *ddef = NULL;
+ git_diff_driver *drv = NULL;
+ size_t namelen, idx;
+
+ for (idx = 0; idx < ARRAY_SIZE(builtin_defs); ++idx) {
+ if (!strcasecmp(driver_name, builtin_defs[idx].name)) {
+ ddef = &builtin_defs[idx];
+ break;
+ }
+ }
+ if (!ddef)
+ goto done;
+
+ namelen = strlen(ddef->name);
+
+ drv = git__calloc(1, sizeof(git_diff_driver) + namelen + 1);
+ GITERR_CHECK_ALLOC(drv);
+
+ drv->type = DIFF_DRIVER_PATTERNLIST;
+ memcpy(drv->name, ddef->name, namelen);
+
+ if (ddef->fns &&
+ (error = diff_driver_add_patterns(
+ drv, ddef->fns, ddef->flags | REG_EXTENDED)) < 0)
+ goto done;
+
+ if (ddef->words &&
+ (error = regcomp(
+ &drv->word_pattern, ddef->words, ddef->flags | REG_EXTENDED)))
+ {
+ error = giterr_set_regex(&drv->word_pattern, error);
+ goto done;
+ }
+
+ git_strmap_insert(reg->drivers, drv->name, drv, error);
+ if (error > 0)
+ error = 0;
+
+done:
+ if (error && drv)
+ git_diff_driver_free(drv);
+ else
+ *out = drv;
+
+ return error;
+}
+
+static int git_diff_driver_load(
+ git_diff_driver **out, git_repository *repo, const char *driver_name)
+{
+ int error = 0;
+ git_diff_driver_registry *reg;
+ git_diff_driver *drv = NULL;
+ size_t namelen = strlen(driver_name);
+ khiter_t pos;
+ git_config *cfg;
+ git_buf name = GIT_BUF_INIT;
+ const git_config_entry *ce;
+ bool found_driver = false;
+
+ if ((reg = git_repository_driver_registry(repo)) == NULL)
+ return -1;
+
+ pos = git_strmap_lookup_index(reg->drivers, driver_name);
+ if (git_strmap_valid_index(reg->drivers, pos)) {
+ *out = git_strmap_value_at(reg->drivers, pos);
+ return 0;
+ }
+
+ drv = git__calloc(1, sizeof(git_diff_driver) + namelen + 1);
+ GITERR_CHECK_ALLOC(drv);
+ drv->type = DIFF_DRIVER_AUTO;
+ memcpy(drv->name, driver_name, namelen);
+
+ /* if you can't read config for repo, just use default driver */
+ if (git_repository_config_snapshot(&cfg, repo) < 0) {
+ giterr_clear();
+ goto done;
+ }
+
+ if ((error = git_buf_printf(&name, "diff.%s.binary", driver_name)) < 0)
+ goto done;
+
+ switch (git_config__get_bool_force(cfg, name.ptr, -1)) {
+ case true:
+ /* if diff.<driver>.binary is true, just return the binary driver */
+ *out = &global_drivers[DIFF_DRIVER_BINARY];
+ goto done;
+ case false:
+ /* if diff.<driver>.binary is false, force binary checks off */
+ /* but still may have custom function context patterns, etc. */
+ drv->binary_flags = GIT_DIFF_FORCE_TEXT;
+ found_driver = true;
+ break;
+ default:
+ /* diff.<driver>.binary unspecified or "auto", so just continue */
+ break;
+ }
+
+ /* TODO: warn if diff.<name>.command or diff.<name>.textconv are set */
+
+ git_buf_truncate(&name, namelen + strlen("diff.."));
+ git_buf_put(&name, "xfuncname", strlen("xfuncname"));
+ if ((error = git_config_get_multivar_foreach(
+ cfg, name.ptr, NULL, diff_driver_xfuncname, drv)) < 0) {
+ if (error != GIT_ENOTFOUND)
+ goto done;
+ giterr_clear(); /* no diff.<driver>.xfuncname, so just continue */
+ }
+
+ git_buf_truncate(&name, namelen + strlen("diff.."));
+ git_buf_put(&name, "funcname", strlen("funcname"));
+ if ((error = git_config_get_multivar_foreach(
+ cfg, name.ptr, NULL, diff_driver_funcname, drv)) < 0) {
+ if (error != GIT_ENOTFOUND)
+ goto done;
+ giterr_clear(); /* no diff.<driver>.funcname, so just continue */
+ }
+
+ /* if we found any patterns, set driver type to use correct callback */
+ if (git_array_size(drv->fn_patterns) > 0) {
+ drv->type = DIFF_DRIVER_PATTERNLIST;
+ found_driver = true;
+ }
+
+ git_buf_truncate(&name, namelen + strlen("diff.."));
+ git_buf_put(&name, "wordregex", strlen("wordregex"));
+ if ((error = git_config__lookup_entry(&ce, cfg, name.ptr, false)) < 0)
+ goto done;
+ if (!ce || !ce->value)
+ /* no diff.<driver>.wordregex, so just continue */;
+ else if (!(error = regcomp(&drv->word_pattern, ce->value, REG_EXTENDED)))
+ found_driver = true;
+ else {
+ /* TODO: warn about bad regex instead of failure */
+ error = giterr_set_regex(&drv->word_pattern, error);
+ goto done;
+ }
+
+ /* TODO: look up diff.<driver>.algorithm to turn on minimal / patience
+ * diff in drv->other_flags
+ */
+
+ /* if no driver config found at all, fall back on AUTO driver */
+ if (!found_driver)
+ goto done;
+
+ /* store driver in registry */
+ git_strmap_insert(reg->drivers, drv->name, drv, error);
+ if (error < 0)
+ goto done;
+ error = 0;
+
+ *out = drv;
+
+done:
+ git_buf_free(&name);
+ git_config_free(cfg);
+
+ if (!*out) {
+ int error2 = git_diff_driver_builtin(out, reg, driver_name);
+ if (!error)
+ error = error2;
+ }
+
+ if (drv && drv != *out)
+ git_diff_driver_free(drv);
+
+ return error;
+}
+
+int git_diff_driver_lookup(
+ git_diff_driver **out, git_repository *repo, const char *path)
+{
+ int error = 0;
+ const char *value;
+
+ assert(out);
+ *out = NULL;
+
+ if (!repo || !path || !strlen(path))
+ /* just use the auto value */;
+ else if ((error = git_attr_get(&value, repo, 0, path, "diff")) < 0)
+ /* return error below */;
+ else if (GIT_ATTR_UNSPECIFIED(value))
+ /* just use the auto value */;
+ else if (GIT_ATTR_FALSE(value))
+ *out = &global_drivers[DIFF_DRIVER_BINARY];
+ else if (GIT_ATTR_TRUE(value))
+ *out = &global_drivers[DIFF_DRIVER_TEXT];
+
+ /* otherwise look for driver information in config and build driver */
+ else if ((error = git_diff_driver_load(out, repo, value)) < 0) {
+ if (error == GIT_ENOTFOUND) {
+ error = 0;
+ giterr_clear();
+ }
+ }
+
+ if (!*out)
+ *out = &global_drivers[DIFF_DRIVER_AUTO];
+
+ return error;
+}
+
+void git_diff_driver_free(git_diff_driver *driver)
+{
+ size_t i;
+
+ if (!driver)
+ return;
+
+ for (i = 0; i < git_array_size(driver->fn_patterns); ++i)
+ regfree(& git_array_get(driver->fn_patterns, i)->re);
+ git_array_clear(driver->fn_patterns);
+
+ regfree(&driver->word_pattern);
+
+ git__free(driver);
+}
+
+void git_diff_driver_update_options(
+ uint32_t *option_flags, git_diff_driver *driver)
+{
+ if ((*option_flags & FORCE_DIFFABLE) == 0)
+ *option_flags |= driver->binary_flags;
+
+ *option_flags |= driver->other_flags;
+}
+
+int git_diff_driver_content_is_binary(
+ git_diff_driver *driver, const char *content, size_t content_len)
+{
+ git_buf search;
+
+ search.ptr = (char *)content;
+ search.size = min(content_len, GIT_FILTER_BYTES_TO_CHECK_NUL);
+ search.asize = 0;
+
+ GIT_UNUSED(driver);
+
+ /* TODO: provide encoding / binary detection callbacks that can
+ * be UTF-8 aware, etc. For now, instead of trying to be smart,
+ * let's just use the simple NUL-byte detection that core git uses.
+ */
+
+ /* previously was: if (git_buf_text_is_binary(&search)) */
+ if (git_buf_text_contains_nul(&search))
+ return 1;
+
+ return 0;
+}
+
+static int diff_context_line__simple(
+ git_diff_driver *driver, git_buf *line)
+{
+ char firstch = line->ptr[0];
+ GIT_UNUSED(driver);
+ return (git__isalpha(firstch) || firstch == '_' || firstch == '$');
+}
+
+static int diff_context_line__pattern_match(
+ git_diff_driver *driver, git_buf *line)
+{
+ size_t i, maxi = git_array_size(driver->fn_patterns);
+ regmatch_t pmatch[2];
+
+ for (i = 0; i < maxi; ++i) {
+ git_diff_driver_pattern *pat = git_array_get(driver->fn_patterns, i);
+
+ if (!regexec(&pat->re, line->ptr, 2, pmatch, 0)) {
+ if (pat->flags & REG_NEGATE)
+ return false;
+
+ /* use pmatch data to trim line data */
+ i = (pmatch[1].rm_so >= 0) ? 1 : 0;
+ git_buf_consume(line, git_buf_cstr(line) + pmatch[i].rm_so);
+ git_buf_truncate(line, pmatch[i].rm_eo - pmatch[i].rm_so);
+ git_buf_rtrim(line);
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static long diff_context_find(
+ const char *line,
+ long line_len,
+ char *out,
+ long out_size,
+ void *payload)
+{
+ git_diff_find_context_payload *ctxt = payload;
+
+ if (git_buf_set(&ctxt->line, line, (size_t)line_len) < 0)
+ return -1;
+ git_buf_rtrim(&ctxt->line);
+
+ if (!ctxt->line.size)
+ return -1;
+
+ if (!ctxt->match_line || !ctxt->match_line(ctxt->driver, &ctxt->line))
+ return -1;
+
+ if (out_size > (long)ctxt->line.size)
+ out_size = (long)ctxt->line.size;
+ memcpy(out, ctxt->line.ptr, (size_t)out_size);
+
+ return out_size;
+}
+
+void git_diff_find_context_init(
+ git_diff_find_context_fn *findfn_out,
+ git_diff_find_context_payload *payload_out,
+ git_diff_driver *driver)
+{
+ *findfn_out = driver ? diff_context_find : NULL;
+
+ memset(payload_out, 0, sizeof(*payload_out));
+ if (driver) {
+ payload_out->driver = driver;
+ payload_out->match_line = (driver->type == DIFF_DRIVER_PATTERNLIST) ?
+ diff_context_line__pattern_match : diff_context_line__simple;
+ git_buf_init(&payload_out->line, 0);
+ }
+}
+
+void git_diff_find_context_clear(git_diff_find_context_payload *payload)
+{
+ if (payload) {
+ git_buf_free(&payload->line);
+ payload->driver = NULL;
+ }
+}
+
diff --git a/src/diff_driver.h b/src/diff_driver.h
new file mode 100644
index 000000000..0706dcfc5
--- /dev/null
+++ b/src/diff_driver.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_diff_driver_h__
+#define INCLUDE_diff_driver_h__
+
+#include "common.h"
+#include "buffer.h"
+
+typedef struct git_diff_driver_registry git_diff_driver_registry;
+
+git_diff_driver_registry *git_diff_driver_registry_new(void);
+void git_diff_driver_registry_free(git_diff_driver_registry *);
+
+typedef struct git_diff_driver git_diff_driver;
+
+int git_diff_driver_lookup(git_diff_driver **, git_repository *, const char *);
+void git_diff_driver_free(git_diff_driver *);
+
+/* diff option flags to force off and on for this driver */
+void git_diff_driver_update_options(uint32_t *option_flags, git_diff_driver *);
+
+/* returns -1 meaning "unknown", 0 meaning not binary, 1 meaning binary */
+int git_diff_driver_content_is_binary(
+ git_diff_driver *, const char *content, size_t content_len);
+
+typedef long (*git_diff_find_context_fn)(
+ const char *, long, char *, long, void *);
+
+typedef int (*git_diff_find_context_line)(
+ git_diff_driver *, git_buf *);
+
+typedef struct {
+ git_diff_driver *driver;
+ git_diff_find_context_line match_line;
+ git_buf line;
+} git_diff_find_context_payload;
+
+void git_diff_find_context_init(
+ git_diff_find_context_fn *findfn_out,
+ git_diff_find_context_payload *payload_out,
+ git_diff_driver *driver);
+
+void git_diff_find_context_clear(git_diff_find_context_payload *);
+
+#endif
diff --git a/src/diff_file.c b/src/diff_file.c
new file mode 100644
index 000000000..f2a1d5099
--- /dev/null
+++ b/src/diff_file.c
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+#include "git2/blob.h"
+#include "git2/submodule.h"
+#include "diff.h"
+#include "diff_file.h"
+#include "odb.h"
+#include "fileops.h"
+#include "filter.h"
+
+#define DIFF_MAX_FILESIZE 0x20000000
+
+static bool diff_file_content_binary_by_size(git_diff_file_content *fc)
+{
+ /* if we have diff opts, check max_size vs file size */
+ if ((fc->file->flags & DIFF_FLAGS_KNOWN_BINARY) == 0 &&
+ fc->opts_max_size > 0 &&
+ fc->file->size > fc->opts_max_size)
+ fc->file->flags |= GIT_DIFF_FLAG_BINARY;
+
+ return ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0);
+}
+
+static void diff_file_content_binary_by_content(git_diff_file_content *fc)
+{
+ if ((fc->file->flags & DIFF_FLAGS_KNOWN_BINARY) != 0)
+ return;
+
+ switch (git_diff_driver_content_is_binary(
+ fc->driver, fc->map.data, fc->map.len)) {
+ case 0: fc->file->flags |= GIT_DIFF_FLAG_NOT_BINARY; break;
+ case 1: fc->file->flags |= GIT_DIFF_FLAG_BINARY; break;
+ default: break;
+ }
+}
+
+static int diff_file_content_init_common(
+ git_diff_file_content *fc, const git_diff_options *opts)
+{
+ fc->opts_flags = opts ? opts->flags : GIT_DIFF_NORMAL;
+
+ if (opts && opts->max_size >= 0)
+ fc->opts_max_size = opts->max_size ?
+ opts->max_size : DIFF_MAX_FILESIZE;
+
+ if (fc->src == GIT_ITERATOR_TYPE_EMPTY)
+ fc->src = GIT_ITERATOR_TYPE_TREE;
+
+ if (!fc->driver &&
+ git_diff_driver_lookup(&fc->driver, fc->repo, fc->file->path) < 0)
+ return -1;
+
+ /* give driver a chance to modify options */
+ git_diff_driver_update_options(&fc->opts_flags, fc->driver);
+
+ /* make sure file is conceivable mmap-able */
+ if ((git_off_t)((size_t)fc->file->size) != fc->file->size)
+ fc->file->flags |= GIT_DIFF_FLAG_BINARY;
+ /* check if user is forcing text diff the file */
+ else if (fc->opts_flags & GIT_DIFF_FORCE_TEXT) {
+ fc->file->flags &= ~GIT_DIFF_FLAG_BINARY;
+ fc->file->flags |= GIT_DIFF_FLAG_NOT_BINARY;
+ }
+ /* check if user is forcing binary diff the file */
+ else if (fc->opts_flags & GIT_DIFF_FORCE_BINARY) {
+ fc->file->flags &= ~GIT_DIFF_FLAG_NOT_BINARY;
+ fc->file->flags |= GIT_DIFF_FLAG_BINARY;
+ }
+
+ diff_file_content_binary_by_size(fc);
+
+ if ((fc->flags & GIT_DIFF_FLAG__NO_DATA) != 0) {
+ fc->flags |= GIT_DIFF_FLAG__LOADED;
+ fc->map.len = 0;
+ fc->map.data = "";
+ }
+
+ if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0)
+ diff_file_content_binary_by_content(fc);
+
+ return 0;
+}
+
+int git_diff_file_content__init_from_diff(
+ git_diff_file_content *fc,
+ git_diff *diff,
+ size_t delta_index,
+ bool use_old)
+{
+ git_diff_delta *delta = git_vector_get(&diff->deltas, delta_index);
+ bool has_data = true;
+
+ memset(fc, 0, sizeof(*fc));
+ fc->repo = diff->repo;
+ fc->file = use_old ? &delta->old_file : &delta->new_file;
+ fc->src = use_old ? diff->old_src : diff->new_src;
+
+ if (git_diff_driver_lookup(&fc->driver, fc->repo, fc->file->path) < 0)
+ return -1;
+
+ switch (delta->status) {
+ case GIT_DELTA_ADDED:
+ has_data = !use_old; break;
+ case GIT_DELTA_DELETED:
+ has_data = use_old; break;
+ case GIT_DELTA_UNTRACKED:
+ has_data = !use_old &&
+ (diff->opts.flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) != 0;
+ break;
+ case GIT_DELTA_MODIFIED:
+ case GIT_DELTA_COPIED:
+ case GIT_DELTA_RENAMED:
+ break;
+ default:
+ has_data = false;
+ break;
+ }
+
+ if (!has_data)
+ fc->flags |= GIT_DIFF_FLAG__NO_DATA;
+
+ return diff_file_content_init_common(fc, &diff->opts);
+}
+
+int git_diff_file_content__init_from_src(
+ git_diff_file_content *fc,
+ git_repository *repo,
+ const git_diff_options *opts,
+ const git_diff_file_content_src *src,
+ git_diff_file *as_file)
+{
+ memset(fc, 0, sizeof(*fc));
+ fc->repo = repo;
+ fc->file = as_file;
+ fc->blob = src->blob;
+
+ if (!src->blob && !src->buf) {
+ fc->flags |= GIT_DIFF_FLAG__NO_DATA;
+ } else {
+ fc->flags |= GIT_DIFF_FLAG__LOADED;
+ fc->file->flags |= GIT_DIFF_FLAG_VALID_ID;
+ fc->file->mode = GIT_FILEMODE_BLOB;
+
+ if (src->blob) {
+ fc->file->size = git_blob_rawsize(src->blob);
+ git_oid_cpy(&fc->file->id, git_blob_id(src->blob));
+
+ fc->map.len = (size_t)fc->file->size;
+ fc->map.data = (char *)git_blob_rawcontent(src->blob);
+ } else {
+ fc->file->size = src->buflen;
+ git_odb_hash(&fc->file->id, src->buf, src->buflen, GIT_OBJ_BLOB);
+
+ fc->map.len = src->buflen;
+ fc->map.data = (char *)src->buf;
+ }
+ }
+
+ return diff_file_content_init_common(fc, opts);
+}
+
+static int diff_file_content_commit_to_str(
+ git_diff_file_content *fc, bool check_status)
+{
+ char oid[GIT_OID_HEXSZ+1];
+ git_buf content = GIT_BUF_INIT;
+ const char *status = "";
+
+ if (check_status) {
+ int error = 0;
+ git_submodule *sm = NULL;
+ unsigned int sm_status = 0;
+ const git_oid *sm_head;
+
+ if ((error = git_submodule_lookup(&sm, fc->repo, fc->file->path)) < 0) {
+ /* GIT_EEXISTS means a "submodule" that has not been git added */
+ if (error == GIT_EEXISTS) {
+ giterr_clear();
+ error = 0;
+ }
+ return error;
+ }
+
+ if ((error = git_submodule_status(&sm_status, sm)) < 0) {
+ git_submodule_free(sm);
+ return error;
+ }
+
+ /* update OID if we didn't have it previously */
+ if ((fc->file->flags & GIT_DIFF_FLAG_VALID_ID) == 0 &&
+ ((sm_head = git_submodule_wd_id(sm)) != NULL ||
+ (sm_head = git_submodule_head_id(sm)) != NULL))
+ {
+ git_oid_cpy(&fc->file->id, sm_head);
+ fc->file->flags |= GIT_DIFF_FLAG_VALID_ID;
+ }
+
+ if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status))
+ status = "-dirty";
+
+ git_submodule_free(sm);
+ }
+
+ git_oid_tostr(oid, sizeof(oid), &fc->file->id);
+ if (git_buf_printf(&content, "Subproject commit %s%s\n", oid, status) < 0)
+ return -1;
+
+ fc->map.len = git_buf_len(&content);
+ fc->map.data = git_buf_detach(&content);
+ fc->flags |= GIT_DIFF_FLAG__FREE_DATA;
+
+ return 0;
+}
+
+static int diff_file_content_load_blob(git_diff_file_content *fc)
+{
+ int error = 0;
+ git_odb_object *odb_obj = NULL;
+
+ if (git_oid_iszero(&fc->file->id))
+ return 0;
+
+ if (fc->file->mode == GIT_FILEMODE_COMMIT)
+ return diff_file_content_commit_to_str(fc, false);
+
+ /* if we don't know size, try to peek at object header first */
+ if (!fc->file->size) {
+ if ((error = git_diff_file__resolve_zero_size(
+ fc->file, &odb_obj, fc->repo)) < 0)
+ return error;
+ }
+
+ if (diff_file_content_binary_by_size(fc))
+ return 0;
+
+ if (odb_obj != NULL) {
+ error = git_object__from_odb_object(
+ (git_object **)&fc->blob, fc->repo, odb_obj, GIT_OBJ_BLOB);
+ git_odb_object_free(odb_obj);
+ } else {
+ error = git_blob_lookup(
+ (git_blob **)&fc->blob, fc->repo, &fc->file->id);
+ }
+
+ if (!error) {
+ fc->flags |= GIT_DIFF_FLAG__FREE_BLOB;
+ fc->map.data = (void *)git_blob_rawcontent(fc->blob);
+ fc->map.len = (size_t)git_blob_rawsize(fc->blob);
+ }
+
+ return error;
+}
+
+static int diff_file_content_load_workdir_symlink(
+ git_diff_file_content *fc, git_buf *path)
+{
+ ssize_t alloc_len, read_len;
+
+ /* link path on disk could be UTF-16, so prepare a buffer that is
+ * big enough to handle some UTF-8 data expansion
+ */
+ alloc_len = (ssize_t)(fc->file->size * 2) + 1;
+
+ fc->map.data = git__calloc(alloc_len, sizeof(char));
+ GITERR_CHECK_ALLOC(fc->map.data);
+
+ fc->flags |= GIT_DIFF_FLAG__FREE_DATA;
+
+ read_len = p_readlink(git_buf_cstr(path), fc->map.data, alloc_len);
+ if (read_len < 0) {
+ giterr_set(GITERR_OS, "Failed to read symlink '%s'", fc->file->path);
+ return -1;
+ }
+
+ fc->map.len = read_len;
+ return 0;
+}
+
+static int diff_file_content_load_workdir_file(
+ git_diff_file_content *fc, git_buf *path)
+{
+ int error = 0;
+ git_filter_list *fl = NULL;
+ git_file fd = git_futils_open_ro(git_buf_cstr(path));
+ git_buf raw = GIT_BUF_INIT;
+
+ if (fd < 0)
+ return fd;
+
+ if (!fc->file->size &&
+ !(fc->file->size = git_futils_filesize(fd)))
+ goto cleanup;
+
+ if (diff_file_content_binary_by_size(fc))
+ goto cleanup;
+
+ if ((error = git_filter_list_load(
+ &fl, fc->repo, NULL, fc->file->path,
+ GIT_FILTER_TO_ODB, GIT_FILTER_OPT_ALLOW_UNSAFE)) < 0)
+ goto cleanup;
+
+ /* if there are no filters, try to mmap the file */
+ if (fl == NULL) {
+ if (!(error = git_futils_mmap_ro(
+ &fc->map, fd, 0, (size_t)fc->file->size))) {
+ fc->flags |= GIT_DIFF_FLAG__UNMAP_DATA;
+ goto cleanup;
+ }
+
+ /* if mmap failed, fall through to try readbuffer below */
+ giterr_clear();
+ }
+
+ if (!(error = git_futils_readbuffer_fd(&raw, fd, (size_t)fc->file->size))) {
+ git_buf out = GIT_BUF_INIT;
+
+ error = git_filter_list_apply_to_data(&out, fl, &raw);
+
+ if (out.ptr != raw.ptr)
+ git_buf_free(&raw);
+
+ if (!error) {
+ fc->map.len = out.size;
+ fc->map.data = out.ptr;
+ fc->flags |= GIT_DIFF_FLAG__FREE_DATA;
+ }
+ }
+
+cleanup:
+ git_filter_list_free(fl);
+ p_close(fd);
+
+ return error;
+}
+
+static int diff_file_content_load_workdir(git_diff_file_content *fc)
+{
+ int error = 0;
+ git_buf path = GIT_BUF_INIT;
+
+ if (fc->file->mode == GIT_FILEMODE_COMMIT)
+ return diff_file_content_commit_to_str(fc, true);
+
+ if (fc->file->mode == GIT_FILEMODE_TREE)
+ return 0;
+
+ if (git_buf_joinpath(
+ &path, git_repository_workdir(fc->repo), fc->file->path) < 0)
+ return -1;
+
+ if (S_ISLNK(fc->file->mode))
+ error = diff_file_content_load_workdir_symlink(fc, &path);
+ else
+ error = diff_file_content_load_workdir_file(fc, &path);
+
+ /* once data is loaded, update OID if we didn't have it previously */
+ if (!error && (fc->file->flags & GIT_DIFF_FLAG_VALID_ID) == 0) {
+ error = git_odb_hash(
+ &fc->file->id, fc->map.data, fc->map.len, GIT_OBJ_BLOB);
+ fc->file->flags |= GIT_DIFF_FLAG_VALID_ID;
+ }
+
+ git_buf_free(&path);
+ return error;
+}
+
+int git_diff_file_content__load(git_diff_file_content *fc)
+{
+ int error = 0;
+
+ if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0)
+ return 0;
+
+ if ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ return 0;
+
+ if (fc->src == GIT_ITERATOR_TYPE_WORKDIR)
+ error = diff_file_content_load_workdir(fc);
+ else
+ error = diff_file_content_load_blob(fc);
+ if (error)
+ return error;
+
+ fc->flags |= GIT_DIFF_FLAG__LOADED;
+
+ diff_file_content_binary_by_content(fc);
+
+ return 0;
+}
+
+void git_diff_file_content__unload(git_diff_file_content *fc)
+{
+ if ((fc->flags & GIT_DIFF_FLAG__LOADED) == 0)
+ return;
+
+ if (fc->flags & GIT_DIFF_FLAG__FREE_DATA) {
+ git__free(fc->map.data);
+ fc->map.data = "";
+ fc->map.len = 0;
+ fc->flags &= ~GIT_DIFF_FLAG__FREE_DATA;
+ }
+ else if (fc->flags & GIT_DIFF_FLAG__UNMAP_DATA) {
+ git_futils_mmap_free(&fc->map);
+ fc->map.data = "";
+ fc->map.len = 0;
+ fc->flags &= ~GIT_DIFF_FLAG__UNMAP_DATA;
+ }
+
+ if (fc->flags & GIT_DIFF_FLAG__FREE_BLOB) {
+ git_blob_free((git_blob *)fc->blob);
+ fc->blob = NULL;
+ fc->flags &= ~GIT_DIFF_FLAG__FREE_BLOB;
+ }
+
+ fc->flags &= ~GIT_DIFF_FLAG__LOADED;
+}
+
+void git_diff_file_content__clear(git_diff_file_content *fc)
+{
+ git_diff_file_content__unload(fc);
+
+ /* for now, nothing else to do */
+}
diff --git a/src/diff_file.h b/src/diff_file.h
new file mode 100644
index 000000000..4d290ad43
--- /dev/null
+++ b/src/diff_file.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_diff_file_h__
+#define INCLUDE_diff_file_h__
+
+#include "common.h"
+#include "diff.h"
+#include "diff_driver.h"
+#include "map.h"
+
+/* expanded information for one side of a delta */
+typedef struct {
+ git_repository *repo;
+ git_diff_file *file;
+ git_diff_driver *driver;
+ uint32_t flags;
+ uint32_t opts_flags;
+ git_off_t opts_max_size;
+ git_iterator_type_t src;
+ const git_blob *blob;
+ git_map map;
+} git_diff_file_content;
+
+extern int git_diff_file_content__init_from_diff(
+ git_diff_file_content *fc,
+ git_diff *diff,
+ size_t delta_index,
+ bool use_old);
+
+typedef struct {
+ const git_blob *blob;
+ const void *buf;
+ size_t buflen;
+ const char *as_path;
+} git_diff_file_content_src;
+
+#define GIT_DIFF_FILE_CONTENT_SRC__BLOB(BLOB,PATH) { (BLOB),NULL,0,(PATH) }
+#define GIT_DIFF_FILE_CONTENT_SRC__BUF(BUF,LEN,PATH) { NULL,(BUF),(LEN),(PATH) }
+
+extern int git_diff_file_content__init_from_src(
+ git_diff_file_content *fc,
+ git_repository *repo,
+ const git_diff_options *opts,
+ const git_diff_file_content_src *src,
+ git_diff_file *as_file);
+
+/* this loads the blob/file-on-disk as needed */
+extern int git_diff_file_content__load(git_diff_file_content *fc);
+
+/* this releases the blob/file-in-memory */
+extern void git_diff_file_content__unload(git_diff_file_content *fc);
+
+/* this unloads and also releases any other resources */
+extern void git_diff_file_content__clear(git_diff_file_content *fc);
+
+#endif
diff --git a/src/diff_patch.c b/src/diff_patch.c
new file mode 100644
index 000000000..38d5f4257
--- /dev/null
+++ b/src/diff_patch.c
@@ -0,0 +1,936 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+#include "git2/blob.h"
+#include "diff.h"
+#include "diff_file.h"
+#include "diff_driver.h"
+#include "diff_patch.h"
+#include "diff_xdiff.h"
+#include "fileops.h"
+
+/* cached information about a hunk in a diff */
+typedef struct diff_patch_hunk diff_patch_hunk;
+struct diff_patch_hunk {
+ git_diff_hunk hunk;
+ size_t line_start;
+ size_t line_count;
+};
+
+struct git_patch {
+ git_refcount rc;
+ git_diff *diff; /* for refcount purposes, maybe NULL for blob diffs */
+ git_diff_delta *delta;
+ size_t delta_index;
+ git_diff_file_content ofile;
+ git_diff_file_content nfile;
+ uint32_t flags;
+ git_array_t(diff_patch_hunk) hunks;
+ git_array_t(git_diff_line) lines;
+ size_t content_size, context_size, header_size;
+ git_pool flattened;
+};
+
+enum {
+ GIT_DIFF_PATCH_ALLOCATED = (1 << 0),
+ GIT_DIFF_PATCH_INITIALIZED = (1 << 1),
+ GIT_DIFF_PATCH_LOADED = (1 << 2),
+ GIT_DIFF_PATCH_DIFFABLE = (1 << 3),
+ GIT_DIFF_PATCH_DIFFED = (1 << 4),
+ GIT_DIFF_PATCH_FLATTENED = (1 << 5),
+};
+
+static void diff_output_init(
+ git_diff_output*, const git_diff_options*,
+ git_diff_file_cb, git_diff_hunk_cb, git_diff_line_cb, void*);
+
+static void diff_output_to_patch(git_diff_output *, git_patch *);
+
+static void diff_patch_update_binary(git_patch *patch)
+{
+ if ((patch->delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0)
+ return;
+
+ if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 ||
+ (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ patch->delta->flags |= GIT_DIFF_FLAG_BINARY;
+
+ else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 &&
+ (patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0)
+ patch->delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
+}
+
+static void diff_patch_init_common(git_patch *patch)
+{
+ diff_patch_update_binary(patch);
+
+ if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ patch->flags |= GIT_DIFF_PATCH_LOADED; /* LOADED but not DIFFABLE */
+
+ patch->flags |= GIT_DIFF_PATCH_INITIALIZED;
+
+ if (patch->diff)
+ git_diff_addref(patch->diff);
+}
+
+static int diff_patch_init_from_diff(
+ git_patch *patch, git_diff *diff, size_t delta_index)
+{
+ int error = 0;
+
+ memset(patch, 0, sizeof(*patch));
+ patch->diff = diff;
+ patch->delta = git_vector_get(&diff->deltas, delta_index);
+ patch->delta_index = delta_index;
+
+ if ((error = git_diff_file_content__init_from_diff(
+ &patch->ofile, diff, delta_index, true)) < 0 ||
+ (error = git_diff_file_content__init_from_diff(
+ &patch->nfile, diff, delta_index, false)) < 0)
+ return error;
+
+ diff_patch_init_common(patch);
+
+ return 0;
+}
+
+static int diff_patch_alloc_from_diff(
+ git_patch **out, git_diff *diff, size_t delta_index)
+{
+ int error;
+ git_patch *patch = git__calloc(1, sizeof(git_patch));
+ GITERR_CHECK_ALLOC(patch);
+
+ if (!(error = diff_patch_init_from_diff(patch, diff, delta_index))) {
+ patch->flags |= GIT_DIFF_PATCH_ALLOCATED;
+ GIT_REFCOUNT_INC(patch);
+ } else {
+ git__free(patch);
+ patch = NULL;
+ }
+
+ *out = patch;
+ return error;
+}
+
+static int diff_patch_load(git_patch *patch, git_diff_output *output)
+{
+ int error = 0;
+ bool incomplete_data;
+
+ if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0)
+ return 0;
+
+ /* if no hunk and data callbacks and user doesn't care if data looks
+ * binary, then there is no need to actually load the data
+ */
+ if ((patch->ofile.opts_flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0 &&
+ output && !output->hunk_cb && !output->data_cb)
+ return 0;
+
+ incomplete_data =
+ (((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
+ (patch->ofile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0) &&
+ ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
+ (patch->nfile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0));
+
+ /* always try to load workdir content first because filtering may
+ * need 2x data size and this minimizes peak memory footprint
+ */
+ if (patch->ofile.src == GIT_ITERATOR_TYPE_WORKDIR) {
+ if ((error = git_diff_file_content__load(&patch->ofile)) < 0 ||
+ (patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ goto cleanup;
+ }
+ if (patch->nfile.src == GIT_ITERATOR_TYPE_WORKDIR) {
+ if ((error = git_diff_file_content__load(&patch->nfile)) < 0 ||
+ (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ goto cleanup;
+ }
+
+ /* once workdir has been tried, load other data as needed */
+ if (patch->ofile.src != GIT_ITERATOR_TYPE_WORKDIR) {
+ if ((error = git_diff_file_content__load(&patch->ofile)) < 0 ||
+ (patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ goto cleanup;
+ }
+ if (patch->nfile.src != GIT_ITERATOR_TYPE_WORKDIR) {
+ if ((error = git_diff_file_content__load(&patch->nfile)) < 0 ||
+ (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ goto cleanup;
+ }
+
+ /* if previously missing an oid, and now that we have it the two sides
+ * are the same (and not submodules), update MODIFIED -> UNMODIFIED
+ */
+ if (incomplete_data &&
+ patch->ofile.file->mode == patch->nfile.file->mode &&
+ patch->ofile.file->mode != GIT_FILEMODE_COMMIT &&
+ git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) &&
+ patch->delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */
+ patch->delta->status = GIT_DELTA_UNMODIFIED;
+
+cleanup:
+ diff_patch_update_binary(patch);
+
+ if (!error) {
+ /* patch is diffable only for non-binary, modified files where
+ * at least one side has data and the data actually changed
+ */
+ if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) == 0 &&
+ patch->delta->status != GIT_DELTA_UNMODIFIED &&
+ (patch->ofile.map.len || patch->nfile.map.len) &&
+ (patch->ofile.map.len != patch->nfile.map.len ||
+ !git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id)))
+ patch->flags |= GIT_DIFF_PATCH_DIFFABLE;
+
+ patch->flags |= GIT_DIFF_PATCH_LOADED;
+ }
+
+ return error;
+}
+
+static int diff_patch_invoke_file_callback(
+ git_patch *patch, git_diff_output *output)
+{
+ float progress = patch->diff ?
+ ((float)patch->delta_index / patch->diff->deltas.length) : 1.0f;
+
+ if (!output->file_cb)
+ return 0;
+
+ return giterr_set_after_callback_function(
+ output->file_cb(patch->delta, progress, output->payload),
+ "git_patch");
+}
+
+static int diff_patch_generate(git_patch *patch, git_diff_output *output)
+{
+ int error = 0;
+
+ if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0)
+ return 0;
+
+ /* if we are not looking at the hunks and lines, don't do the diff */
+ if (!output->hunk_cb && !output->data_cb)
+ return 0;
+
+ if ((patch->flags & GIT_DIFF_PATCH_LOADED) == 0 &&
+ (error = diff_patch_load(patch, output)) < 0)
+ return error;
+
+ if ((patch->flags & GIT_DIFF_PATCH_DIFFABLE) == 0)
+ return 0;
+
+ if (output->diff_cb != NULL &&
+ (error = output->diff_cb(output, patch)) < 0)
+ patch->flags |= GIT_DIFF_PATCH_DIFFED;
+
+ return error;
+}
+
+static void diff_patch_free(git_patch *patch)
+{
+ git_diff_file_content__clear(&patch->ofile);
+ git_diff_file_content__clear(&patch->nfile);
+
+ git_array_clear(patch->lines);
+ git_array_clear(patch->hunks);
+
+ git_diff_free(patch->diff); /* decrements refcount */
+ patch->diff = NULL;
+
+ git_pool_clear(&patch->flattened);
+
+ if (patch->flags & GIT_DIFF_PATCH_ALLOCATED)
+ git__free(patch);
+}
+
+static int diff_required(git_diff *diff, const char *action)
+{
+ if (diff)
+ return 0;
+ giterr_set(GITERR_INVALID, "Must provide valid diff to %s", action);
+ return -1;
+}
+
+int git_diff_foreach(
+ git_diff *diff,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb data_cb,
+ void *payload)
+{
+ int error = 0;
+ git_xdiff_output xo;
+ size_t idx;
+ git_patch patch;
+
+ if ((error = diff_required(diff, "git_diff_foreach")) < 0)
+ return error;
+
+ memset(&xo, 0, sizeof(xo));
+ diff_output_init(
+ &xo.output, &diff->opts, file_cb, hunk_cb, data_cb, payload);
+ git_xdiff_init(&xo, &diff->opts);
+
+ git_vector_foreach(&diff->deltas, idx, patch.delta) {
+
+ /* check flags against patch status */
+ if (git_diff_delta__should_skip(&diff->opts, patch.delta))
+ continue;
+
+ if ((error = diff_patch_init_from_diff(&patch, diff, idx)) < 0)
+ break;
+
+ if (!(error = diff_patch_invoke_file_callback(&patch, &xo.output)))
+ error = diff_patch_generate(&patch, &xo.output);
+
+ git_patch_free(&patch);
+
+ if (error)
+ break;
+ }
+
+ return error;
+}
+
+typedef struct {
+ git_patch patch;
+ git_diff_delta delta;
+ char paths[GIT_FLEX_ARRAY];
+} diff_patch_with_delta;
+
+static int diff_single_generate(diff_patch_with_delta *pd, git_xdiff_output *xo)
+{
+ int error = 0;
+ git_patch *patch = &pd->patch;
+ bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
+ bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
+
+ pd->delta.status = has_new ?
+ (has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
+ (has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);
+
+ if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id))
+ pd->delta.status = GIT_DELTA_UNMODIFIED;
+
+ patch->delta = &pd->delta;
+
+ diff_patch_init_common(patch);
+
+ if (pd->delta.status == GIT_DELTA_UNMODIFIED &&
+ !(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED))
+ return error;
+
+ error = diff_patch_invoke_file_callback(patch, (git_diff_output *)xo);
+
+ if (!error)
+ error = diff_patch_generate(patch, (git_diff_output *)xo);
+
+ return error;
+}
+
+static int diff_patch_from_sources(
+ diff_patch_with_delta *pd,
+ git_xdiff_output *xo,
+ git_diff_file_content_src *oldsrc,
+ git_diff_file_content_src *newsrc,
+ const git_diff_options *opts)
+{
+ int error = 0;
+ git_repository *repo =
+ oldsrc->blob ? git_blob_owner(oldsrc->blob) :
+ newsrc->blob ? git_blob_owner(newsrc->blob) : NULL;
+ git_diff_file *lfile = &pd->delta.old_file, *rfile = &pd->delta.new_file;
+ git_diff_file_content *ldata = &pd->patch.ofile, *rdata = &pd->patch.nfile;
+
+ GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options");
+
+ if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
+ void *tmp = lfile; lfile = rfile; rfile = tmp;
+ tmp = ldata; ldata = rdata; rdata = tmp;
+ }
+
+ pd->patch.delta = &pd->delta;
+
+ if (!oldsrc->as_path) {
+ if (newsrc->as_path)
+ oldsrc->as_path = newsrc->as_path;
+ else
+ oldsrc->as_path = newsrc->as_path = "file";
+ }
+ else if (!newsrc->as_path)
+ newsrc->as_path = oldsrc->as_path;
+
+ lfile->path = oldsrc->as_path;
+ rfile->path = newsrc->as_path;
+
+ if ((error = git_diff_file_content__init_from_src(
+ ldata, repo, opts, oldsrc, lfile)) < 0 ||
+ (error = git_diff_file_content__init_from_src(
+ rdata, repo, opts, newsrc, rfile)) < 0)
+ return error;
+
+ return diff_single_generate(pd, xo);
+}
+
+static int diff_patch_with_delta_alloc(
+ diff_patch_with_delta **out,
+ const char **old_path,
+ const char **new_path)
+{
+ diff_patch_with_delta *pd;
+ size_t old_len = *old_path ? strlen(*old_path) : 0;
+ size_t new_len = *new_path ? strlen(*new_path) : 0;
+
+ *out = pd = git__calloc(1, sizeof(*pd) + old_len + new_len + 2);
+ GITERR_CHECK_ALLOC(pd);
+
+ pd->patch.flags = GIT_DIFF_PATCH_ALLOCATED;
+
+ if (*old_path) {
+ memcpy(&pd->paths[0], *old_path, old_len);
+ *old_path = &pd->paths[0];
+ } else if (*new_path)
+ *old_path = &pd->paths[old_len + 1];
+
+ if (*new_path) {
+ memcpy(&pd->paths[old_len + 1], *new_path, new_len);
+ *new_path = &pd->paths[old_len + 1];
+ } else if (*old_path)
+ *new_path = &pd->paths[0];
+
+ return 0;
+}
+
+static int diff_from_sources(
+ git_diff_file_content_src *oldsrc,
+ git_diff_file_content_src *newsrc,
+ const git_diff_options *opts,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb data_cb,
+ void *payload)
+{
+ int error = 0;
+ diff_patch_with_delta pd;
+ git_xdiff_output xo;
+
+ memset(&xo, 0, sizeof(xo));
+ diff_output_init(
+ &xo.output, opts, file_cb, hunk_cb, data_cb, payload);
+ git_xdiff_init(&xo, opts);
+
+ memset(&pd, 0, sizeof(pd));
+
+ error = diff_patch_from_sources(&pd, &xo, oldsrc, newsrc, opts);
+
+ git_patch_free(&pd.patch);
+
+ return error;
+}
+
+static int patch_from_sources(
+ git_patch **out,
+ git_diff_file_content_src *oldsrc,
+ git_diff_file_content_src *newsrc,
+ const git_diff_options *opts)
+{
+ int error = 0;
+ diff_patch_with_delta *pd;
+ git_xdiff_output xo;
+
+ assert(out);
+ *out = NULL;
+
+ if ((error = diff_patch_with_delta_alloc(
+ &pd, &oldsrc->as_path, &newsrc->as_path)) < 0)
+ return error;
+
+ memset(&xo, 0, sizeof(xo));
+ diff_output_to_patch(&xo.output, &pd->patch);
+ git_xdiff_init(&xo, opts);
+
+ if (!(error = diff_patch_from_sources(pd, &xo, oldsrc, newsrc, opts)))
+ *out = (git_patch *)pd;
+ else
+ git_patch_free((git_patch *)pd);
+
+ return error;
+}
+
+int git_diff_blobs(
+ const git_blob *old_blob,
+ const char *old_path,
+ const git_blob *new_blob,
+ const char *new_path,
+ const git_diff_options *opts,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb data_cb,
+ void *payload)
+{
+ git_diff_file_content_src osrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
+ git_diff_file_content_src nsrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path);
+ return diff_from_sources(
+ &osrc, &nsrc, opts, file_cb, hunk_cb, data_cb, payload);
+}
+
+int git_patch_from_blobs(
+ git_patch **out,
+ const git_blob *old_blob,
+ const char *old_path,
+ const git_blob *new_blob,
+ const char *new_path,
+ const git_diff_options *opts)
+{
+ git_diff_file_content_src osrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
+ git_diff_file_content_src nsrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path);
+ return patch_from_sources(out, &osrc, &nsrc, opts);
+}
+
+int git_diff_blob_to_buffer(
+ const git_blob *old_blob,
+ const char *old_path,
+ const char *buf,
+ size_t buflen,
+ const char *buf_path,
+ const git_diff_options *opts,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb data_cb,
+ void *payload)
+{
+ git_diff_file_content_src osrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
+ git_diff_file_content_src nsrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path);
+ return diff_from_sources(
+ &osrc, &nsrc, opts, file_cb, hunk_cb, data_cb, payload);
+}
+
+int git_patch_from_blob_and_buffer(
+ git_patch **out,
+ const git_blob *old_blob,
+ const char *old_path,
+ const char *buf,
+ size_t buflen,
+ const char *buf_path,
+ const git_diff_options *opts)
+{
+ git_diff_file_content_src osrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
+ git_diff_file_content_src nsrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path);
+ return patch_from_sources(out, &osrc, &nsrc, opts);
+}
+
+int git_diff_buffers(
+ const void *old_buf,
+ size_t old_len,
+ const char *old_path,
+ const void *new_buf,
+ size_t new_len,
+ const char *new_path,
+ const git_diff_options *opts,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb data_cb,
+ void *payload)
+{
+ git_diff_file_content_src osrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path);
+ git_diff_file_content_src nsrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path);
+ return diff_from_sources(
+ &osrc, &nsrc, opts, file_cb, hunk_cb, data_cb, payload);
+}
+
+int git_patch_from_buffers(
+ git_patch **out,
+ const void *old_buf,
+ size_t old_len,
+ const char *old_path,
+ const char *new_buf,
+ size_t new_len,
+ const char *new_path,
+ const git_diff_options *opts)
+{
+ git_diff_file_content_src osrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path);
+ git_diff_file_content_src nsrc =
+ GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path);
+ return patch_from_sources(out, &osrc, &nsrc, opts);
+}
+
+int git_patch_from_diff(
+ git_patch **patch_ptr, git_diff *diff, size_t idx)
+{
+ int error = 0;
+ git_xdiff_output xo;
+ git_diff_delta *delta = NULL;
+ git_patch *patch = NULL;
+
+ if (patch_ptr) *patch_ptr = NULL;
+
+ if (diff_required(diff, "git_patch_from_diff") < 0)
+ return -1;
+
+ delta = git_vector_get(&diff->deltas, idx);
+ if (!delta) {
+ giterr_set(GITERR_INVALID, "Index out of range for delta in diff");
+ return GIT_ENOTFOUND;
+ }
+
+ if (git_diff_delta__should_skip(&diff->opts, delta))
+ return 0;
+
+ /* don't load the patch data unless we need it for binary check */
+ if (!patch_ptr &&
+ ((delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0 ||
+ (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0))
+ return 0;
+
+ if ((error = diff_patch_alloc_from_diff(&patch, diff, idx)) < 0)
+ return error;
+
+ memset(&xo, 0, sizeof(xo));
+ diff_output_to_patch(&xo.output, patch);
+ git_xdiff_init(&xo, &diff->opts);
+
+ error = diff_patch_invoke_file_callback(patch, &xo.output);
+
+ if (!error)
+ error = diff_patch_generate(patch, &xo.output);
+
+ if (!error) {
+ /* TODO: if cumulative diff size is < 0.5 total size, flatten patch */
+ /* TODO: and unload the file content */
+ }
+
+ if (error || !patch_ptr)
+ git_patch_free(patch);
+ else
+ *patch_ptr = patch;
+
+ return error;
+}
+
+void git_patch_free(git_patch *patch)
+{
+ if (patch)
+ GIT_REFCOUNT_DEC(patch, diff_patch_free);
+}
+
+const git_diff_delta *git_patch_get_delta(const git_patch *patch)
+{
+ assert(patch);
+ return patch->delta;
+}
+
+size_t git_patch_num_hunks(const git_patch *patch)
+{
+ assert(patch);
+ return git_array_size(patch->hunks);
+}
+
+int git_patch_line_stats(
+ size_t *total_ctxt,
+ size_t *total_adds,
+ size_t *total_dels,
+ const git_patch *patch)
+{
+ size_t totals[3], idx;
+
+ memset(totals, 0, sizeof(totals));
+
+ for (idx = 0; idx < git_array_size(patch->lines); ++idx) {
+ git_diff_line *line = git_array_get(patch->lines, idx);
+ if (!line)
+ continue;
+
+ switch (line->origin) {
+ case GIT_DIFF_LINE_CONTEXT: totals[0]++; break;
+ case GIT_DIFF_LINE_ADDITION: totals[1]++; break;
+ case GIT_DIFF_LINE_DELETION: totals[2]++; break;
+ default:
+ /* diff --stat and --numstat don't count EOFNL marks because
+ * they will always be paired with a ADDITION or DELETION line.
+ */
+ break;
+ }
+ }
+
+ if (total_ctxt)
+ *total_ctxt = totals[0];
+ if (total_adds)
+ *total_adds = totals[1];
+ if (total_dels)
+ *total_dels = totals[2];
+
+ return 0;
+}
+
+static int diff_error_outofrange(const char *thing)
+{
+ giterr_set(GITERR_INVALID, "Diff patch %s index out of range", thing);
+ return GIT_ENOTFOUND;
+}
+
+int git_patch_get_hunk(
+ const git_diff_hunk **out,
+ size_t *lines_in_hunk,
+ git_patch *patch,
+ size_t hunk_idx)
+{
+ diff_patch_hunk *hunk;
+ assert(patch);
+
+ hunk = git_array_get(patch->hunks, hunk_idx);
+
+ if (!hunk) {
+ if (out) *out = NULL;
+ if (lines_in_hunk) *lines_in_hunk = 0;
+ return diff_error_outofrange("hunk");
+ }
+
+ if (out) *out = &hunk->hunk;
+ if (lines_in_hunk) *lines_in_hunk = hunk->line_count;
+ return 0;
+}
+
+int git_patch_num_lines_in_hunk(const git_patch *patch, size_t hunk_idx)
+{
+ diff_patch_hunk *hunk;
+ assert(patch);
+
+ if (!(hunk = git_array_get(patch->hunks, hunk_idx)))
+ return diff_error_outofrange("hunk");
+ return (int)hunk->line_count;
+}
+
+int git_patch_get_line_in_hunk(
+ const git_diff_line **out,
+ git_patch *patch,
+ size_t hunk_idx,
+ size_t line_of_hunk)
+{
+ diff_patch_hunk *hunk;
+ git_diff_line *line;
+
+ assert(patch);
+
+ if (!(hunk = git_array_get(patch->hunks, hunk_idx))) {
+ if (out) *out = NULL;
+ return diff_error_outofrange("hunk");
+ }
+
+ if (line_of_hunk >= hunk->line_count ||
+ !(line = git_array_get(
+ patch->lines, hunk->line_start + line_of_hunk))) {
+ if (out) *out = NULL;
+ return diff_error_outofrange("line");
+ }
+
+ if (out) *out = line;
+ return 0;
+}
+
+size_t git_patch_size(
+ git_patch *patch,
+ int include_context,
+ int include_hunk_headers,
+ int include_file_headers)
+{
+ size_t out;
+
+ assert(patch);
+
+ out = patch->content_size;
+
+ if (!include_context)
+ out -= patch->context_size;
+
+ if (include_hunk_headers)
+ out += patch->header_size;
+
+ if (include_file_headers) {
+ git_buf file_header = GIT_BUF_INIT;
+
+ if (git_diff_delta__format_file_header(
+ &file_header, patch->delta, NULL, NULL, 0) < 0)
+ giterr_clear();
+ else
+ out += git_buf_len(&file_header);
+
+ git_buf_free(&file_header);
+ }
+
+ return out;
+}
+
+git_diff *git_patch__diff(git_patch *patch)
+{
+ return patch->diff;
+}
+
+git_diff_driver *git_patch__driver(git_patch *patch)
+{
+ /* ofile driver is representative for whole patch */
+ return patch->ofile.driver;
+}
+
+void git_patch__old_data(
+ char **ptr, size_t *len, git_patch *patch)
+{
+ *ptr = patch->ofile.map.data;
+ *len = patch->ofile.map.len;
+}
+
+void git_patch__new_data(
+ char **ptr, size_t *len, git_patch *patch)
+{
+ *ptr = patch->nfile.map.data;
+ *len = patch->nfile.map.len;
+}
+
+int git_patch__invoke_callbacks(
+ git_patch *patch,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb line_cb,
+ void *payload)
+{
+ int error = 0;
+ uint32_t i, j;
+
+ if (file_cb)
+ error = file_cb(patch->delta, 0, payload);
+
+ if (!hunk_cb && !line_cb)
+ return error;
+
+ for (i = 0; !error && i < git_array_size(patch->hunks); ++i) {
+ diff_patch_hunk *h = git_array_get(patch->hunks, i);
+
+ error = hunk_cb(patch->delta, &h->hunk, payload);
+
+ if (!line_cb)
+ continue;
+
+ for (j = 0; !error && j < h->line_count; ++j) {
+ git_diff_line *l =
+ git_array_get(patch->lines, h->line_start + j);
+
+ error = line_cb(patch->delta, &h->hunk, l, payload);
+ }
+ }
+
+ return error;
+}
+
+
+static int diff_patch_file_cb(
+ const git_diff_delta *delta,
+ float progress,
+ void *payload)
+{
+ GIT_UNUSED(delta); GIT_UNUSED(progress); GIT_UNUSED(payload);
+ return 0;
+}
+
+static int diff_patch_hunk_cb(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk_,
+ void *payload)
+{
+ git_patch *patch = payload;
+ diff_patch_hunk *hunk;
+
+ GIT_UNUSED(delta);
+
+ hunk = git_array_alloc(patch->hunks);
+ GITERR_CHECK_ALLOC(hunk);
+
+ memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk));
+
+ patch->header_size += hunk_->header_len;
+
+ hunk->line_start = git_array_size(patch->lines);
+ hunk->line_count = 0;
+
+ return 0;
+}
+
+static int diff_patch_line_cb(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk_,
+ const git_diff_line *line_,
+ void *payload)
+{
+ git_patch *patch = payload;
+ diff_patch_hunk *hunk;
+ git_diff_line *line;
+
+ GIT_UNUSED(delta);
+ GIT_UNUSED(hunk_);
+
+ hunk = git_array_last(patch->hunks);
+ assert(hunk); /* programmer error if no hunk is available */
+
+ line = git_array_alloc(patch->lines);
+ GITERR_CHECK_ALLOC(line);
+
+ memcpy(line, line_, sizeof(*line));
+
+ /* do some bookkeeping so we can provide old/new line numbers */
+
+ patch->content_size += line->content_len;
+
+ if (line->origin == GIT_DIFF_LINE_ADDITION ||
+ line->origin == GIT_DIFF_LINE_DELETION)
+ patch->content_size += 1;
+ else if (line->origin == GIT_DIFF_LINE_CONTEXT) {
+ patch->content_size += 1;
+ patch->context_size += line->content_len + 1;
+ } else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL)
+ patch->context_size += line->content_len;
+
+ hunk->line_count++;
+
+ return 0;
+}
+
+static void diff_output_init(
+ git_diff_output *out,
+ const git_diff_options *opts,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb data_cb,
+ void *payload)
+{
+ GIT_UNUSED(opts);
+
+ memset(out, 0, sizeof(*out));
+
+ out->file_cb = file_cb;
+ out->hunk_cb = hunk_cb;
+ out->data_cb = data_cb;
+ out->payload = payload;
+}
+
+static void diff_output_to_patch(git_diff_output *out, git_patch *patch)
+{
+ diff_output_init(
+ out, NULL,
+ diff_patch_file_cb, diff_patch_hunk_cb, diff_patch_line_cb, patch);
+}
diff --git a/src/diff_patch.h b/src/diff_patch.h
new file mode 100644
index 000000000..df2ba4c31
--- /dev/null
+++ b/src/diff_patch.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_diff_patch_h__
+#define INCLUDE_diff_patch_h__
+
+#include "common.h"
+#include "diff.h"
+#include "diff_file.h"
+#include "array.h"
+#include "git2/patch.h"
+
+extern git_diff *git_patch__diff(git_patch *);
+
+extern git_diff_driver *git_patch__driver(git_patch *);
+
+extern void git_patch__old_data(char **, size_t *, git_patch *);
+extern void git_patch__new_data(char **, size_t *, git_patch *);
+
+extern int git_patch__invoke_callbacks(
+ git_patch *patch,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb line_cb,
+ void *payload);
+
+typedef struct git_diff_output git_diff_output;
+struct git_diff_output {
+ /* these callbacks are issued with the diff data */
+ git_diff_file_cb file_cb;
+ git_diff_hunk_cb hunk_cb;
+ git_diff_line_cb data_cb;
+ void *payload;
+
+ /* this records the actual error in cases where it may be obscured */
+ int error;
+
+ /* this callback is used to do the diff and drive the other callbacks.
+ * see diff_xdiff.h for how to use this in practice for now.
+ */
+ int (*diff_cb)(git_diff_output *output, git_patch *patch);
+};
+
+#endif
diff --git a/src/diff_print.c b/src/diff_print.c
new file mode 100644
index 000000000..bb925ef98
--- /dev/null
+++ b/src/diff_print.c
@@ -0,0 +1,632 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "diff_patch.h"
+#include "fileops.h"
+#include "zstream.h"
+#include "blob.h"
+#include "delta.h"
+#include "git2/sys/diff.h"
+
+typedef struct {
+ git_diff *diff;
+ git_diff_format_t format;
+ git_diff_line_cb print_cb;
+ void *payload;
+ git_buf *buf;
+ uint32_t flags;
+ int oid_strlen;
+ git_diff_line line;
+} diff_print_info;
+
+static int diff_print_info_init(
+ diff_print_info *pi,
+ git_buf *out,
+ git_diff *diff,
+ git_diff_format_t format,
+ git_diff_line_cb cb,
+ void *payload)
+{
+ pi->diff = diff;
+ pi->format = format;
+ pi->print_cb = cb;
+ pi->payload = payload;
+ pi->buf = out;
+
+ if (diff)
+ pi->flags = diff->opts.flags;
+ else
+ pi->flags = 0;
+
+ if (diff && diff->opts.id_abbrev != 0)
+ pi->oid_strlen = diff->opts.id_abbrev;
+ else if (!diff || !diff->repo)
+ pi->oid_strlen = GIT_ABBREV_DEFAULT;
+ else if (git_repository__cvar(
+ &pi->oid_strlen, diff->repo, GIT_CVAR_ABBREV) < 0)
+ return -1;
+
+ pi->oid_strlen += 1; /* for NUL byte */
+
+ if (pi->oid_strlen < 2)
+ pi->oid_strlen = 2;
+ else if (pi->oid_strlen > GIT_OID_HEXSZ + 1)
+ pi->oid_strlen = GIT_OID_HEXSZ + 1;
+
+ memset(&pi->line, 0, sizeof(pi->line));
+ pi->line.old_lineno = -1;
+ pi->line.new_lineno = -1;
+ pi->line.num_lines = 1;
+
+ return 0;
+}
+
+static char diff_pick_suffix(int mode)
+{
+ if (S_ISDIR(mode))
+ return '/';
+ else if (GIT_PERMS_IS_EXEC(mode)) /* -V536 */
+ /* in git, modes are very regular, so we must have 0100755 mode */
+ return '*';
+ else
+ return ' ';
+}
+
+char git_diff_status_char(git_delta_t status)
+{
+ char code;
+
+ switch (status) {
+ case GIT_DELTA_ADDED: code = 'A'; break;
+ case GIT_DELTA_DELETED: code = 'D'; break;
+ case GIT_DELTA_MODIFIED: code = 'M'; break;
+ case GIT_DELTA_RENAMED: code = 'R'; break;
+ case GIT_DELTA_COPIED: code = 'C'; break;
+ case GIT_DELTA_IGNORED: code = 'I'; break;
+ case GIT_DELTA_UNTRACKED: code = '?'; break;
+ default: code = ' '; break;
+ }
+
+ return code;
+}
+
+static int diff_print_one_name_only(
+ const git_diff_delta *delta, float progress, void *data)
+{
+ diff_print_info *pi = data;
+ git_buf *out = pi->buf;
+
+ GIT_UNUSED(progress);
+
+ if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 &&
+ delta->status == GIT_DELTA_UNMODIFIED)
+ return 0;
+
+ git_buf_clear(out);
+ git_buf_puts(out, delta->new_file.path);
+ git_buf_putc(out, '\n');
+ if (git_buf_oom(out))
+ return -1;
+
+ pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
+ pi->line.content = git_buf_cstr(out);
+ pi->line.content_len = git_buf_len(out);
+
+ return pi->print_cb(delta, NULL, &pi->line, pi->payload);
+}
+
+static int diff_print_one_name_status(
+ const git_diff_delta *delta, float progress, void *data)
+{
+ diff_print_info *pi = data;
+ git_buf *out = pi->buf;
+ char old_suffix, new_suffix, code = git_diff_status_char(delta->status);
+ int (*strcomp)(const char *, const char *) =
+ pi->diff ? pi->diff->strcomp : git__strcmp;
+
+ GIT_UNUSED(progress);
+
+ if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ')
+ return 0;
+
+ old_suffix = diff_pick_suffix(delta->old_file.mode);
+ new_suffix = diff_pick_suffix(delta->new_file.mode);
+
+ git_buf_clear(out);
+
+ if (delta->old_file.path != delta->new_file.path &&
+ strcomp(delta->old_file.path,delta->new_file.path) != 0)
+ git_buf_printf(out, "%c\t%s%c %s%c\n", code,
+ delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
+ else if (delta->old_file.mode != delta->new_file.mode &&
+ delta->old_file.mode != 0 && delta->new_file.mode != 0)
+ git_buf_printf(out, "%c\t%s%c %s%c\n", code,
+ delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
+ else if (old_suffix != ' ')
+ git_buf_printf(out, "%c\t%s%c\n", code, delta->old_file.path, old_suffix);
+ else
+ git_buf_printf(out, "%c\t%s\n", code, delta->old_file.path);
+ if (git_buf_oom(out))
+ return -1;
+
+ pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
+ pi->line.content = git_buf_cstr(out);
+ pi->line.content_len = git_buf_len(out);
+
+ return pi->print_cb(delta, NULL, &pi->line, pi->payload);
+}
+
+static int diff_print_one_raw(
+ const git_diff_delta *delta, float progress, void *data)
+{
+ diff_print_info *pi = data;
+ git_buf *out = pi->buf;
+ char code = git_diff_status_char(delta->status);
+ char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
+
+ GIT_UNUSED(progress);
+
+ if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ')
+ return 0;
+
+ git_buf_clear(out);
+
+ git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.id);
+ git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.id);
+
+ git_buf_printf(
+ out, (pi->oid_strlen <= GIT_OID_HEXSZ) ?
+ ":%06o %06o %s... %s... %c" : ":%06o %06o %s %s %c",
+ delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code);
+
+ if (delta->similarity > 0)
+ git_buf_printf(out, "%03u", delta->similarity);
+
+ if (delta->old_file.path != delta->new_file.path)
+ git_buf_printf(
+ out, "\t%s %s\n", delta->old_file.path, delta->new_file.path);
+ else
+ git_buf_printf(
+ out, "\t%s\n", delta->old_file.path ?
+ delta->old_file.path : delta->new_file.path);
+
+ if (git_buf_oom(out))
+ return -1;
+
+ pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
+ pi->line.content = git_buf_cstr(out);
+ pi->line.content_len = git_buf_len(out);
+
+ return pi->print_cb(delta, NULL, &pi->line, pi->payload);
+}
+
+static int diff_print_oid_range(
+ git_buf *out, const git_diff_delta *delta, int oid_strlen)
+{
+ char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
+
+ git_oid_tostr(start_oid, oid_strlen, &delta->old_file.id);
+ git_oid_tostr(end_oid, oid_strlen, &delta->new_file.id);
+
+ /* TODO: Match git diff more closely */
+ if (delta->old_file.mode == delta->new_file.mode) {
+ git_buf_printf(out, "index %s..%s %o\n",
+ start_oid, end_oid, delta->old_file.mode);
+ } else {
+ if (delta->old_file.mode == 0) {
+ git_buf_printf(out, "new file mode %o\n", delta->new_file.mode);
+ } else if (delta->new_file.mode == 0) {
+ git_buf_printf(out, "deleted file mode %o\n", delta->old_file.mode);
+ } else {
+ git_buf_printf(out, "old mode %o\n", delta->old_file.mode);
+ git_buf_printf(out, "new mode %o\n", delta->new_file.mode);
+ }
+ git_buf_printf(out, "index %s..%s\n", start_oid, end_oid);
+ }
+
+ return git_buf_oom(out) ? -1 : 0;
+}
+
+static int diff_delta_format_with_paths(
+ git_buf *out,
+ const git_diff_delta *delta,
+ const char *oldpfx,
+ const char *newpfx,
+ const char *template)
+{
+ const char *oldpath = delta->old_file.path;
+ const char *newpath = delta->new_file.path;
+
+ if (git_oid_iszero(&delta->old_file.id)) {
+ oldpfx = "";
+ oldpath = "/dev/null";
+ }
+ if (git_oid_iszero(&delta->new_file.id)) {
+ newpfx = "";
+ newpath = "/dev/null";
+ }
+
+ return git_buf_printf(out, template, oldpfx, oldpath, newpfx, newpath);
+}
+
+int git_diff_delta__format_file_header(
+ git_buf *out,
+ const git_diff_delta *delta,
+ const char *oldpfx,
+ const char *newpfx,
+ int oid_strlen)
+{
+ if (!oldpfx)
+ oldpfx = DIFF_OLD_PREFIX_DEFAULT;
+ if (!newpfx)
+ newpfx = DIFF_NEW_PREFIX_DEFAULT;
+ if (!oid_strlen)
+ oid_strlen = GIT_ABBREV_DEFAULT + 1;
+
+ git_buf_clear(out);
+
+ git_buf_printf(out, "diff --git %s%s %s%s\n",
+ oldpfx, delta->old_file.path, newpfx, delta->new_file.path);
+
+ GITERR_CHECK_ERROR(diff_print_oid_range(out, delta, oid_strlen));
+
+ if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
+ diff_delta_format_with_paths(
+ out, delta, oldpfx, newpfx, "--- %s%s\n+++ %s%s\n");
+
+ return git_buf_oom(out) ? -1 : 0;
+}
+
+static int print_binary_hunk(diff_print_info *pi, git_blob *old, git_blob *new)
+{
+ git_buf deflate = GIT_BUF_INIT, delta = GIT_BUF_INIT, *out = NULL;
+ const void *old_data, *new_data;
+ git_off_t old_data_len, new_data_len;
+ unsigned long delta_data_len, inflated_len;
+ const char *out_type = "literal";
+ char *scan, *end;
+ int error;
+
+ old_data = old ? git_blob_rawcontent(old) : NULL;
+ new_data = new ? git_blob_rawcontent(new) : NULL;
+
+ old_data_len = old ? git_blob_rawsize(old) : 0;
+ new_data_len = new ? git_blob_rawsize(new) : 0;
+
+ /* The git_delta function accepts unsigned long only */
+ if (!git__is_ulong(old_data_len) || !git__is_ulong(new_data_len))
+ return GIT_EBUFS;
+
+ out = &deflate;
+ inflated_len = (unsigned long)new_data_len;
+
+ if ((error = git_zstream_deflatebuf(
+ out, new_data, (size_t)new_data_len)) < 0)
+ goto done;
+
+ /* The git_delta function accepts unsigned long only */
+ if (!git__is_ulong((git_off_t)deflate.size)) {
+ error = GIT_EBUFS;
+ goto done;
+ }
+
+ if (old && new) {
+ void *delta_data = git_delta(
+ old_data, (unsigned long)old_data_len,
+ new_data, (unsigned long)new_data_len,
+ &delta_data_len, (unsigned long)deflate.size);
+
+ if (delta_data) {
+ error = git_zstream_deflatebuf(
+ &delta, delta_data, (size_t)delta_data_len);
+
+ git__free(delta_data);
+
+ if (error < 0)
+ goto done;
+
+ if (delta.size < deflate.size) {
+ out = &delta;
+ out_type = "delta";
+ inflated_len = delta_data_len;
+ }
+ }
+ }
+
+ git_buf_printf(pi->buf, "%s %lu\n", out_type, inflated_len);
+ pi->line.num_lines++;
+
+ for (scan = out->ptr, end = out->ptr + out->size; scan < end; ) {
+ size_t chunk_len = end - scan;
+ if (chunk_len > 52)
+ chunk_len = 52;
+
+ if (chunk_len <= 26)
+ git_buf_putc(pi->buf, (char)chunk_len + 'A' - 1);
+ else
+ git_buf_putc(pi->buf, (char)chunk_len - 26 + 'a' - 1);
+
+ git_buf_put_base85(pi->buf, scan, chunk_len);
+ git_buf_putc(pi->buf, '\n');
+
+ if (git_buf_oom(pi->buf)) {
+ error = -1;
+ goto done;
+ }
+
+ scan += chunk_len;
+ pi->line.num_lines++;
+ }
+
+done:
+ git_buf_free(&deflate);
+ git_buf_free(&delta);
+
+ return error;
+}
+
+/* git diff --binary 8d7523f~2 8d7523f~1 */
+static int diff_print_patch_file_binary(
+ diff_print_info *pi, const git_diff_delta *delta,
+ const char *oldpfx, const char *newpfx)
+{
+ git_blob *old = NULL, *new = NULL;
+ const git_oid *old_id, *new_id;
+ int error;
+ size_t pre_binary_size;
+
+ if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0)
+ goto noshow;
+
+ pre_binary_size = pi->buf->size;
+ git_buf_printf(pi->buf, "GIT binary patch\n");
+ pi->line.num_lines++;
+
+ old_id = (delta->status != GIT_DELTA_ADDED) ? &delta->old_file.id : NULL;
+ new_id = (delta->status != GIT_DELTA_DELETED) ? &delta->new_file.id : NULL;
+
+ if (old_id && (error = git_blob_lookup(&old, pi->diff->repo, old_id)) < 0)
+ goto done;
+ if (new_id && (error = git_blob_lookup(&new, pi->diff->repo,new_id)) < 0)
+ goto done;
+
+ if ((error = print_binary_hunk(pi, old, new)) < 0 ||
+ (error = git_buf_putc(pi->buf, '\n')) < 0 ||
+ (error = print_binary_hunk(pi, new, old)) < 0)
+ {
+ if (error == GIT_EBUFS) {
+ giterr_clear();
+ git_buf_truncate(pi->buf, pre_binary_size);
+ goto noshow;
+ }
+ }
+
+ pi->line.num_lines++;
+
+done:
+ git_blob_free(old);
+ git_blob_free(new);
+
+ return error;
+
+noshow:
+ pi->line.num_lines = 1;
+ return diff_delta_format_with_paths(
+ pi->buf, delta, oldpfx, newpfx,
+ "Binary files %s%s and %s%s differ\n");
+}
+
+static int diff_print_patch_file(
+ const git_diff_delta *delta, float progress, void *data)
+{
+ int error;
+ diff_print_info *pi = data;
+ const char *oldpfx =
+ pi->diff ? pi->diff->opts.old_prefix : DIFF_OLD_PREFIX_DEFAULT;
+ const char *newpfx =
+ pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT;
+
+ bool binary = !!(delta->flags & GIT_DIFF_FLAG_BINARY);
+ bool show_binary = !!(pi->flags & GIT_DIFF_SHOW_BINARY);
+ int oid_strlen = binary && show_binary ?
+ GIT_OID_HEXSZ + 1 : pi->oid_strlen;
+
+ GIT_UNUSED(progress);
+
+ if (S_ISDIR(delta->new_file.mode) ||
+ delta->status == GIT_DELTA_UNMODIFIED ||
+ delta->status == GIT_DELTA_IGNORED ||
+ (delta->status == GIT_DELTA_UNTRACKED &&
+ (pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0))
+ return 0;
+
+ if ((error = git_diff_delta__format_file_header(
+ pi->buf, delta, oldpfx, newpfx, oid_strlen)) < 0)
+ return error;
+
+ pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
+ pi->line.content = git_buf_cstr(pi->buf);
+ pi->line.content_len = git_buf_len(pi->buf);
+
+ if ((error = pi->print_cb(delta, NULL, &pi->line, pi->payload)) != 0)
+ return error;
+
+ if (!binary)
+ return 0;
+
+ git_buf_clear(pi->buf);
+
+ if ((error = diff_print_patch_file_binary(pi, delta, oldpfx, newpfx)) < 0)
+ return error;
+
+ pi->line.origin = GIT_DIFF_LINE_BINARY;
+ pi->line.content = git_buf_cstr(pi->buf);
+ pi->line.content_len = git_buf_len(pi->buf);
+
+ return pi->print_cb(delta, NULL, &pi->line, pi->payload);
+}
+
+static int diff_print_patch_hunk(
+ const git_diff_delta *d,
+ const git_diff_hunk *h,
+ void *data)
+{
+ diff_print_info *pi = data;
+
+ if (S_ISDIR(d->new_file.mode))
+ return 0;
+
+ pi->line.origin = GIT_DIFF_LINE_HUNK_HDR;
+ pi->line.content = h->header;
+ pi->line.content_len = h->header_len;
+
+ return pi->print_cb(d, h, &pi->line, pi->payload);
+}
+
+static int diff_print_patch_line(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk,
+ const git_diff_line *line,
+ void *data)
+{
+ diff_print_info *pi = data;
+
+ if (S_ISDIR(delta->new_file.mode))
+ return 0;
+
+ return pi->print_cb(delta, hunk, line, pi->payload);
+}
+
+/* print a git_diff to an output callback */
+int git_diff_print(
+ git_diff *diff,
+ git_diff_format_t format,
+ git_diff_line_cb print_cb,
+ void *payload)
+{
+ int error;
+ git_buf buf = GIT_BUF_INIT;
+ diff_print_info pi;
+ git_diff_file_cb print_file = NULL;
+ git_diff_hunk_cb print_hunk = NULL;
+ git_diff_line_cb print_line = NULL;
+
+ switch (format) {
+ case GIT_DIFF_FORMAT_PATCH:
+ print_file = diff_print_patch_file;
+ print_hunk = diff_print_patch_hunk;
+ print_line = diff_print_patch_line;
+ break;
+ case GIT_DIFF_FORMAT_PATCH_HEADER:
+ print_file = diff_print_patch_file;
+ break;
+ case GIT_DIFF_FORMAT_RAW:
+ print_file = diff_print_one_raw;
+ break;
+ case GIT_DIFF_FORMAT_NAME_ONLY:
+ print_file = diff_print_one_name_only;
+ break;
+ case GIT_DIFF_FORMAT_NAME_STATUS:
+ print_file = diff_print_one_name_status;
+ break;
+ default:
+ giterr_set(GITERR_INVALID, "Unknown diff output format (%d)", format);
+ return -1;
+ }
+
+ if (!(error = diff_print_info_init(
+ &pi, &buf, diff, format, print_cb, payload)))
+ {
+ error = git_diff_foreach(
+ diff, print_file, print_hunk, print_line, &pi);
+
+ if (error) /* make sure error message is set */
+ giterr_set_after_callback_function(error, "git_diff_print");
+ }
+
+ git_buf_free(&buf);
+
+ return error;
+}
+
+/* print a git_patch to an output callback */
+int git_patch_print(
+ git_patch *patch,
+ git_diff_line_cb print_cb,
+ void *payload)
+{
+ int error;
+ git_buf temp = GIT_BUF_INIT;
+ diff_print_info pi;
+
+ assert(patch && print_cb);
+
+ if (!(error = diff_print_info_init(
+ &pi, &temp, git_patch__diff(patch),
+ GIT_DIFF_FORMAT_PATCH, print_cb, payload)))
+ {
+ error = git_patch__invoke_callbacks(
+ patch, diff_print_patch_file, diff_print_patch_hunk,
+ diff_print_patch_line, &pi);
+
+ if (error) /* make sure error message is set */
+ giterr_set_after_callback_function(error, "git_patch_print");
+ }
+
+ git_buf_free(&temp);
+
+ return error;
+}
+
+int git_diff_print_callback__to_buf(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk,
+ const git_diff_line *line,
+ void *payload)
+{
+ git_buf *output = payload;
+ GIT_UNUSED(delta); GIT_UNUSED(hunk);
+
+ if (!output) {
+ giterr_set(GITERR_INVALID, "Buffer pointer must be provided");
+ return -1;
+ }
+
+ if (line->origin == GIT_DIFF_LINE_ADDITION ||
+ line->origin == GIT_DIFF_LINE_DELETION ||
+ line->origin == GIT_DIFF_LINE_CONTEXT)
+ git_buf_putc(output, line->origin);
+
+ return git_buf_put(output, line->content, line->content_len);
+}
+
+int git_diff_print_callback__to_file_handle(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk,
+ const git_diff_line *line,
+ void *payload)
+{
+ FILE *fp = payload ? payload : stdout;
+
+ GIT_UNUSED(delta); GIT_UNUSED(hunk);
+
+ if (line->origin == GIT_DIFF_LINE_CONTEXT ||
+ line->origin == GIT_DIFF_LINE_ADDITION ||
+ line->origin == GIT_DIFF_LINE_DELETION)
+ fputc(line->origin, fp);
+ fwrite(line->content, 1, line->content_len, fp);
+ return 0;
+}
+
+/* print a git_patch to a git_buf */
+int git_patch_to_buf(git_buf *out, git_patch *patch)
+{
+ assert(out && patch);
+ git_buf_sanitize(out);
+ return git_patch_print(patch, git_diff_print_callback__to_buf, out);
+}
diff --git a/src/diff_stats.c b/src/diff_stats.c
new file mode 100644
index 000000000..42ccbfb87
--- /dev/null
+++ b/src/diff_stats.c
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "vector.h"
+#include "diff.h"
+#include "diff_patch.h"
+
+#define DIFF_RENAME_FILE_SEPARATOR " => "
+#define STATS_FULL_MIN_SCALE 7
+
+typedef struct {
+ size_t insertions;
+ size_t deletions;
+} diff_file_stats;
+
+struct git_diff_stats {
+ git_diff *diff;
+ diff_file_stats *filestats;
+
+ size_t files_changed;
+ size_t insertions;
+ size_t deletions;
+ size_t renames;
+
+ size_t max_name;
+ size_t max_filestat;
+ int max_digits;
+};
+
+static int digits_for_value(size_t val)
+{
+ int count = 1;
+ size_t placevalue = 10;
+
+ while (val >= placevalue) {
+ ++count;
+ placevalue *= 10;
+ }
+
+ return count;
+}
+
+int git_diff_file_stats__full_to_buf(
+ git_buf *out,
+ const git_diff_delta *delta,
+ const diff_file_stats *filestat,
+ const git_diff_stats *stats,
+ size_t width)
+{
+ const char *old_path = NULL, *new_path = NULL;
+ size_t padding, old_size, new_size;
+
+ old_path = delta->old_file.path;
+ new_path = delta->new_file.path;
+ old_size = delta->old_file.size;
+ new_size = delta->new_file.size;
+
+ if (git_buf_printf(out, " %s", old_path) < 0)
+ goto on_error;
+
+ if (strcmp(old_path, new_path) != 0) {
+ padding = stats->max_name - strlen(old_path) - strlen(new_path);
+
+ if (git_buf_printf(out, DIFF_RENAME_FILE_SEPARATOR "%s", new_path) < 0)
+ goto on_error;
+ } else {
+ padding = stats->max_name - strlen(old_path);
+
+ if (stats->renames > 0)
+ padding += strlen(DIFF_RENAME_FILE_SEPARATOR);
+ }
+
+ if (git_buf_putcn(out, ' ', padding) < 0 ||
+ git_buf_puts(out, " | ") < 0)
+ goto on_error;
+
+ if (delta->flags & GIT_DIFF_FLAG_BINARY) {
+ if (git_buf_printf(out,
+ "Bin %" PRIuZ " -> %" PRIuZ " bytes", old_size, new_size) < 0)
+ goto on_error;
+ }
+ else {
+ if (git_buf_printf(out,
+ "%*" PRIuZ, stats->max_digits,
+ filestat->insertions + filestat->deletions) < 0)
+ goto on_error;
+
+ if (filestat->insertions || filestat->deletions) {
+ if (git_buf_putc(out, ' ') < 0)
+ goto on_error;
+
+ if (!width) {
+ if (git_buf_putcn(out, '+', filestat->insertions) < 0 ||
+ git_buf_putcn(out, '-', filestat->deletions) < 0)
+ goto on_error;
+ } else {
+ size_t total = filestat->insertions + filestat->deletions;
+ size_t full = (total * width + stats->max_filestat / 2) /
+ stats->max_filestat;
+ size_t plus = full * filestat->insertions / total;
+ size_t minus = full - plus;
+
+ if (git_buf_putcn(out, '+', max(plus, 1)) < 0 ||
+ git_buf_putcn(out, '-', max(minus, 1)) < 0)
+ goto on_error;
+ }
+ }
+ }
+
+ git_buf_putc(out, '\n');
+
+on_error:
+ return (git_buf_oom(out) ? -1 : 0);
+}
+
+int git_diff_file_stats__number_to_buf(
+ git_buf *out,
+ const git_diff_delta *delta,
+ const diff_file_stats *filestats)
+{
+ int error;
+ const char *path = delta->new_file.path;
+
+ if (delta->flags & GIT_DIFF_FLAG_BINARY)
+ error = git_buf_printf(out, "%-8c" "%-8c" "%s\n", '-', '-', path);
+ else
+ error = git_buf_printf(out, "%-8" PRIuZ "%-8" PRIuZ "%s\n",
+ filestats->insertions, filestats->deletions, path);
+
+ return error;
+}
+
+int git_diff_file_stats__summary_to_buf(
+ git_buf *out,
+ const git_diff_delta *delta)
+{
+ if (delta->old_file.mode != delta->new_file.mode) {
+ if (delta->old_file.mode == 0) {
+ git_buf_printf(out, " create mode %06o %s\n",
+ delta->new_file.mode, delta->new_file.path);
+ }
+ else if (delta->new_file.mode == 0) {
+ git_buf_printf(out, " delete mode %06o %s\n",
+ delta->old_file.mode, delta->old_file.path);
+ }
+ else {
+ git_buf_printf(out, " mode change %06o => %06o %s\n",
+ delta->old_file.mode, delta->new_file.mode, delta->new_file.path);
+ }
+ }
+
+ return 0;
+}
+
+int git_diff_get_stats(
+ git_diff_stats **out,
+ git_diff *diff)
+{
+ size_t i, deltas;
+ size_t total_insertions = 0, total_deletions = 0;
+ git_diff_stats *stats = NULL;
+ int error = 0;
+
+ assert(out && diff);
+
+ stats = git__calloc(1, sizeof(git_diff_stats));
+ GITERR_CHECK_ALLOC(stats);
+
+ deltas = git_diff_num_deltas(diff);
+
+ stats->filestats = git__calloc(deltas, sizeof(diff_file_stats));
+ if (!stats->filestats) {
+ git__free(stats);
+ return -1;
+ }
+
+ stats->diff = diff;
+ GIT_REFCOUNT_INC(diff);
+
+ for (i = 0; i < deltas && !error; ++i) {
+ git_patch *patch = NULL;
+ size_t add = 0, remove = 0, namelen;
+ const git_diff_delta *delta;
+
+ if ((error = git_patch_from_diff(&patch, diff, i)) < 0)
+ break;
+
+ /* keep a count of renames because it will affect formatting */
+ delta = git_patch_get_delta(patch);
+
+ namelen = strlen(delta->new_file.path);
+ if (strcmp(delta->old_file.path, delta->new_file.path) != 0) {
+ namelen += strlen(delta->old_file.path);
+ stats->renames++;
+ }
+
+ /* and, of course, count the line stats */
+ error = git_patch_line_stats(NULL, &add, &remove, patch);
+
+ git_patch_free(patch);
+
+ stats->filestats[i].insertions = add;
+ stats->filestats[i].deletions = remove;
+
+ total_insertions += add;
+ total_deletions += remove;
+
+ if (stats->max_name < namelen)
+ stats->max_name = namelen;
+ if (stats->max_filestat < add + remove)
+ stats->max_filestat = add + remove;
+ }
+
+ stats->files_changed = deltas;
+ stats->insertions = total_insertions;
+ stats->deletions = total_deletions;
+ stats->max_digits = digits_for_value(stats->max_filestat + 1);
+
+ if (error < 0) {
+ git_diff_stats_free(stats);
+ stats = NULL;
+ }
+
+ *out = stats;
+ return error;
+}
+
+size_t git_diff_stats_files_changed(
+ const git_diff_stats *stats)
+{
+ assert(stats);
+
+ return stats->files_changed;
+}
+
+size_t git_diff_stats_insertions(
+ const git_diff_stats *stats)
+{
+ assert(stats);
+
+ return stats->insertions;
+}
+
+size_t git_diff_stats_deletions(
+ const git_diff_stats *stats)
+{
+ assert(stats);
+
+ return stats->deletions;
+}
+
+int git_diff_stats_to_buf(
+ git_buf *out,
+ const git_diff_stats *stats,
+ git_diff_stats_format_t format,
+ size_t width)
+{
+ int error = 0;
+ size_t i;
+ const git_diff_delta *delta;
+
+ assert(out && stats);
+
+ if (format & GIT_DIFF_STATS_NUMBER) {
+ for (i = 0; i < stats->files_changed; ++i) {
+ if ((delta = git_diff_get_delta(stats->diff, i)) == NULL)
+ continue;
+
+ error = git_diff_file_stats__number_to_buf(
+ out, delta, &stats->filestats[i]);
+ if (error < 0)
+ return error;
+ }
+ }
+
+ if (format & GIT_DIFF_STATS_FULL) {
+ if (width > 0) {
+ if (width > stats->max_name + stats->max_digits + 5)
+ width -= (stats->max_name + stats->max_digits + 5);
+ if (width < STATS_FULL_MIN_SCALE)
+ width = STATS_FULL_MIN_SCALE;
+ }
+ if (width > stats->max_filestat)
+ width = 0;
+
+ for (i = 0; i < stats->files_changed; ++i) {
+ if ((delta = git_diff_get_delta(stats->diff, i)) == NULL)
+ continue;
+
+ error = git_diff_file_stats__full_to_buf(
+ out, delta, &stats->filestats[i], stats, width);
+ if (error < 0)
+ return error;
+ }
+ }
+
+ if (format & GIT_DIFF_STATS_FULL || format & GIT_DIFF_STATS_SHORT) {
+ error = git_buf_printf(
+ out, " %" PRIuZ " file%s changed, %" PRIuZ
+ " insertion%s(+), %" PRIuZ " deletion%s(-)\n",
+ stats->files_changed, stats->files_changed != 1 ? "s" : "",
+ stats->insertions, stats->insertions != 1 ? "s" : "",
+ stats->deletions, stats->deletions != 1 ? "s" : "");
+
+ if (error < 0)
+ return error;
+ }
+
+ if (format & GIT_DIFF_STATS_INCLUDE_SUMMARY) {
+ for (i = 0; i < stats->files_changed; ++i) {
+ if ((delta = git_diff_get_delta(stats->diff, i)) == NULL)
+ continue;
+
+ error = git_diff_file_stats__summary_to_buf(out, delta);
+ if (error < 0)
+ return error;
+ }
+ }
+
+ return error;
+}
+
+void git_diff_stats_free(git_diff_stats *stats)
+{
+ if (stats == NULL)
+ return;
+
+ git_diff_free(stats->diff); /* bumped refcount in constructor */
+ git__free(stats->filestats);
+ git__free(stats);
+}
+
diff --git a/src/diff_tform.c b/src/diff_tform.c
new file mode 100644
index 000000000..a2dab0ae2
--- /dev/null
+++ b/src/diff_tform.c
@@ -0,0 +1,1126 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+
+#include "git2/config.h"
+#include "git2/blob.h"
+
+#include "diff.h"
+#include "hashsig.h"
+#include "path.h"
+#include "fileops.h"
+#include "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));
+ GIT_DIFF_FLAG__CLEAR_INTERNAL(delta->flags);
+
+ if (d->old_file.path != NULL) {
+ 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 && d->new_file.path != NULL) {
+ 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.id, &a->old_file.id);
+ 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 git_diff_delta *diff_delta__merge_like_cgit_reversed(
+ const git_diff_delta *a,
+ const git_diff_delta *b,
+ git_pool *pool)
+{
+ git_diff_delta *dup;
+
+ /* reversed version of above logic */
+
+ if (a->status == GIT_DELTA_UNMODIFIED)
+ return diff_delta__dup(b, pool);
+
+ if ((dup = diff_delta__dup(a, pool)) == NULL)
+ return NULL;
+
+ if (b->status == GIT_DELTA_UNMODIFIED || b->status == GIT_DELTA_UNTRACKED)
+ return dup;
+
+ if (dup->status == GIT_DELTA_DELETED) {
+ if (b->status == GIT_DELTA_ADDED)
+ dup->status = GIT_DELTA_UNMODIFIED;
+ } else {
+ dup->status = b->status;
+ }
+
+ git_oid_cpy(&dup->old_file.id, &b->old_file.id);
+ dup->old_file.mode = b->old_file.mode;
+ dup->old_file.size = b->old_file.size;
+ dup->old_file.flags = b->old_file.flags;
+
+ return dup;
+}
+
+int git_diff_merge(git_diff *onto, const git_diff *from)
+{
+ int error = 0;
+ git_pool onto_pool;
+ git_vector onto_new;
+ git_diff_delta *delta;
+ bool ignore_case, reversed;
+ unsigned int i, j;
+
+ assert(onto && from);
+
+ if (!from->deltas.length)
+ return 0;
+
+ ignore_case = ((onto->opts.flags & GIT_DIFF_IGNORE_CASE) != 0);
+ reversed = ((onto->opts.flags & GIT_DIFF_REVERSE) != 0);
+
+ if (ignore_case != ((from->opts.flags & GIT_DIFF_IGNORE_CASE) != 0) ||
+ reversed != ((from->opts.flags & GIT_DIFF_REVERSE) != 0)) {
+ giterr_set(GITERR_INVALID,
+ "Attempt to merge diffs created with conflicting options");
+ return -1;
+ }
+
+ if (git_vector_init(
+ &onto_new, onto->deltas.length, git_diff_delta__cmp) < 0 ||
+ git_pool_init(&onto_pool, 1, 0) < 0)
+ return -1;
+
+ for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) {
+ git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i);
+ const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j);
+ int cmp = !f ? -1 : !o ? 1 :
+ STRCMP_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 = reversed ?
+ diff_delta__merge_like_cgit_reversed(o, f, &onto_pool) :
+ 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);
+
+ if ((onto->opts.flags & GIT_DIFF_REVERSE) != 0)
+ onto->old_src = from->old_src;
+ else
+ 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_free_deep(&onto_new);
+ git_pool_clear(&onto_pool);
+
+ return error;
+}
+
+int git_diff_find_similar__hashsig_for_file(
+ void **out, const git_diff_file *f, const char *path, void *p)
+{
+ git_hashsig_option_t opt = (git_hashsig_option_t)(intptr_t)p;
+ int error = 0;
+
+ GIT_UNUSED(f);
+ error = git_hashsig_create_fromfile((git_hashsig **)out, path, opt);
+
+ if (error == GIT_EBUFS) {
+ error = 0;
+ giterr_clear();
+ }
+
+ return error;
+}
+
+int git_diff_find_similar__hashsig_for_buf(
+ void **out, const git_diff_file *f, const char *buf, size_t len, void *p)
+{
+ git_hashsig_option_t opt = (git_hashsig_option_t)(intptr_t)p;
+ int error = 0;
+
+ GIT_UNUSED(f);
+ error = git_hashsig_create((git_hashsig **)out, buf, len, opt);
+
+ if (error == GIT_EBUFS) {
+ error = 0;
+ giterr_clear();
+ }
+
+ return error;
+}
+
+void git_diff_find_similar__hashsig_free(void *sig, void *payload)
+{
+ GIT_UNUSED(payload);
+ git_hashsig_free(sig);
+}
+
+int git_diff_find_similar__calc_similarity(
+ int *score, void *siga, void *sigb, void *payload)
+{
+ GIT_UNUSED(payload);
+ *score = git_hashsig_compare(siga, sigb);
+ return 0;
+}
+
+#define DEFAULT_THRESHOLD 50
+#define DEFAULT_BREAK_REWRITE_THRESHOLD 60
+#define DEFAULT_RENAME_LIMIT 200
+
+static int normalize_find_opts(
+ git_diff *diff,
+ git_diff_find_options *opts,
+ const git_diff_find_options *given)
+{
+ git_config *cfg = NULL;
+
+ GITERR_CHECK_VERSION(given, GIT_DIFF_FIND_OPTIONS_VERSION, "git_diff_find_options");
+
+ if (diff->repo != NULL &&
+ git_repository_config__weakptr(&cfg, diff->repo) < 0)
+ return -1;
+
+ if (given)
+ memcpy(opts, given, sizeof(*opts));
+
+ if (!given ||
+ (given->flags & GIT_DIFF_FIND_ALL) == GIT_DIFF_FIND_BY_CONFIG)
+ {
+ const char *rule =
+ git_config__get_string_force(cfg, "diff.renames", "true");
+ int boolval;
+
+ if (!git__parse_bool(&boolval, rule) && !boolval)
+ /* don't set FIND_RENAMES if bool value is false */;
+ else if (!strcasecmp(rule, "copies") || !strcasecmp(rule, "copy"))
+ opts->flags |= GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES;
+ else
+ opts->flags |= GIT_DIFF_FIND_RENAMES;
+ }
+
+ /* some flags imply others */
+
+ if (opts->flags & GIT_DIFF_FIND_EXACT_MATCH_ONLY) {
+ /* if we are only looking for exact matches, then don't turn
+ * MODIFIED items into ADD/DELETE pairs because it's too picky
+ */
+ opts->flags &= ~(GIT_DIFF_FIND_REWRITES | GIT_DIFF_BREAK_REWRITES);
+
+ /* similarly, don't look for self-rewrites to split */
+ opts->flags &= ~GIT_DIFF_FIND_RENAMES_FROM_REWRITES;
+ }
+
+ 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;
+
+ if (opts->flags & GIT_DIFF_BREAK_REWRITES)
+ opts->flags |= GIT_DIFF_FIND_REWRITES;
+
+#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->rename_limit) {
+ opts->rename_limit = git_config__get_int_force(
+ cfg, "diff.renamelimit", DEFAULT_RENAME_LIMIT);
+
+ if (opts->rename_limit <= 0)
+ opts->rename_limit = DEFAULT_RENAME_LIMIT;
+ }
+
+ /* assign the internal metric with whitespace flag as payload */
+ if (!opts->metric) {
+ opts->metric = git__malloc(sizeof(git_diff_similarity_metric));
+ GITERR_CHECK_ALLOC(opts->metric);
+
+ opts->metric->file_signature = git_diff_find_similar__hashsig_for_file;
+ opts->metric->buffer_signature = git_diff_find_similar__hashsig_for_buf;
+ opts->metric->free_signature = git_diff_find_similar__hashsig_free;
+ opts->metric->similarity = git_diff_find_similar__calc_similarity;
+
+ if (opts->flags & GIT_DIFF_FIND_IGNORE_WHITESPACE)
+ opts->metric->payload = (void *)GIT_HASHSIG_IGNORE_WHITESPACE;
+ else if (opts->flags & GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE)
+ opts->metric->payload = (void *)GIT_HASHSIG_NORMAL;
+ else
+ opts->metric->payload = (void *)GIT_HASHSIG_SMART_WHITESPACE;
+ }
+
+ return 0;
+}
+
+static int insert_delete_side_of_split(
+ git_diff *diff, git_vector *onto, const git_diff_delta *delta)
+{
+ /* make new record for DELETED side of split */
+ git_diff_delta *deleted = diff_delta__dup(delta, &diff->pool);
+ GITERR_CHECK_ALLOC(deleted);
+
+ deleted->status = GIT_DELTA_DELETED;
+ deleted->nfiles = 1;
+ memset(&deleted->new_file, 0, sizeof(deleted->new_file));
+ deleted->new_file.path = deleted->old_file.path;
+ deleted->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
+
+ return git_vector_insert(onto, deleted);
+}
+
+static int apply_splits_and_deletes(
+ git_diff *diff, size_t expected_size, bool actually_split)
+{
+ git_vector onto = GIT_VECTOR_INIT;
+ size_t i;
+ git_diff_delta *delta;
+
+ if (git_vector_init(&onto, expected_size, git_diff_delta__cmp) < 0)
+ return -1;
+
+ /* build new delta list without TO_DELETE and splitting TO_SPLIT */
+ git_vector_foreach(&diff->deltas, i, delta) {
+ if ((delta->flags & GIT_DIFF_FLAG__TO_DELETE) != 0)
+ continue;
+
+ if ((delta->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0 && actually_split) {
+ delta->similarity = 0;
+
+ if (insert_delete_side_of_split(diff, &onto, delta) < 0)
+ goto on_error;
+
+ if (diff->new_src == GIT_ITERATOR_TYPE_WORKDIR)
+ delta->status = GIT_DELTA_UNTRACKED;
+ else
+ delta->status = GIT_DELTA_ADDED;
+ delta->nfiles = 1;
+ memset(&delta->old_file, 0, sizeof(delta->old_file));
+ delta->old_file.path = delta->new_file.path;
+ delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
+ }
+
+ /* clean up delta before inserting into new list */
+ GIT_DIFF_FLAG__CLEAR_INTERNAL(delta->flags);
+
+ if (delta->status != GIT_DELTA_COPIED &&
+ delta->status != GIT_DELTA_RENAMED &&
+ (delta->status != GIT_DELTA_MODIFIED || actually_split))
+ delta->similarity = 0;
+
+ /* insert into new list */
+ if (git_vector_insert(&onto, delta) < 0)
+ goto on_error;
+ }
+
+ /* cannot return an error past this point */
+
+ /* free deltas from old list that didn't make it to the new one */
+ git_vector_foreach(&diff->deltas, i, delta) {
+ if ((delta->flags & GIT_DIFF_FLAG__TO_DELETE) != 0)
+ git__free(delta);
+ }
+
+ /* swap new delta list into place */
+ git_vector_swap(&diff->deltas, &onto);
+ git_vector_free(&onto);
+ git_vector_sort(&diff->deltas);
+
+ return 0;
+
+on_error:
+ git_vector_free_deep(&onto);
+
+ return -1;
+}
+
+GIT_INLINE(git_diff_file *) similarity_get_file(git_diff *diff, size_t idx)
+{
+ git_diff_delta *delta = git_vector_get(&diff->deltas, idx / 2);
+ return (idx & 1) ? &delta->new_file : &delta->old_file;
+}
+
+typedef struct {
+ size_t idx;
+ git_iterator_type_t src;
+ git_repository *repo;
+ git_diff_file *file;
+ git_buf data;
+ git_odb_object *odb_obj;
+ git_blob *blob;
+} similarity_info;
+
+static int similarity_init(
+ similarity_info *info, git_diff *diff, size_t file_idx)
+{
+ info->idx = file_idx;
+ info->src = (file_idx & 1) ? diff->new_src : diff->old_src;
+ info->repo = diff->repo;
+ info->file = similarity_get_file(diff, file_idx);
+ info->odb_obj = NULL;
+ info->blob = NULL;
+ git_buf_init(&info->data, 0);
+
+ if (info->file->size > 0 || info->src == GIT_ITERATOR_TYPE_WORKDIR)
+ return 0;
+
+ return git_diff_file__resolve_zero_size(
+ info->file, &info->odb_obj, info->repo);
+}
+
+static int similarity_sig(
+ similarity_info *info,
+ const git_diff_find_options *opts,
+ void **cache)
+{
+ int error = 0;
+ git_diff_file *file = info->file;
+
+ if (info->src == GIT_ITERATOR_TYPE_WORKDIR) {
+ if ((error = git_buf_joinpath(
+ &info->data, git_repository_workdir(info->repo), file->path)) < 0)
+ return error;
+
+ /* if path is not a regular file, just skip this item */
+ if (!git_path_isfile(info->data.ptr))
+ return 0;
+
+ /* TODO: apply wd-to-odb filters to file data if necessary */
+
+ error = opts->metric->file_signature(
+ &cache[info->idx], info->file,
+ info->data.ptr, opts->metric->payload);
+ } else {
+ /* if we didn't initially know the size, we might have an odb_obj
+ * around from earlier, so convert that, otherwise load the blob now
+ */
+ if (info->odb_obj != NULL)
+ error = git_object__from_odb_object(
+ (git_object **)&info->blob, info->repo,
+ info->odb_obj, GIT_OBJ_BLOB);
+ else
+ error = git_blob_lookup(&info->blob, info->repo, &file->id);
+
+ if (error < 0) {
+ /* if lookup fails, just skip this item in similarity calc */
+ giterr_clear();
+ } else {
+ size_t sz;
+
+ /* index size may not be actual blob size if filtered */
+ if (file->size != git_blob_rawsize(info->blob))
+ file->size = git_blob_rawsize(info->blob);
+
+ sz = (size_t)(git__is_sizet(file->size) ? file->size : -1);
+
+ error = opts->metric->buffer_signature(
+ &cache[info->idx], info->file,
+ git_blob_rawcontent(info->blob), sz, opts->metric->payload);
+ }
+ }
+
+ return error;
+}
+
+static void similarity_unload(similarity_info *info)
+{
+ if (info->odb_obj)
+ git_odb_object_free(info->odb_obj);
+
+ if (info->blob)
+ git_blob_free(info->blob);
+ else
+ git_buf_free(&info->data);
+}
+
+#define FLAG_SET(opts,flag_name) (((opts)->flags & flag_name) != 0)
+
+/* - score < 0 means files cannot be compared
+ * - score >= 100 means files are exact match
+ * - score == 0 means files are completely different
+ */
+static int similarity_measure(
+ int *score,
+ git_diff *diff,
+ const git_diff_find_options *opts,
+ void **cache,
+ size_t a_idx,
+ size_t b_idx)
+{
+ git_diff_file *a_file = similarity_get_file(diff, a_idx);
+ git_diff_file *b_file = similarity_get_file(diff, b_idx);
+ bool exact_match = FLAG_SET(opts, GIT_DIFF_FIND_EXACT_MATCH_ONLY);
+ int error = 0;
+ similarity_info a_info, b_info;
+
+ *score = -1;
+
+ /* don't try to compare files of different types */
+ if (GIT_MODE_TYPE(a_file->mode) != GIT_MODE_TYPE(b_file->mode))
+ return 0;
+
+ /* if exact match is requested, force calculation of missing OIDs now */
+ if (exact_match) {
+ if (git_oid_iszero(&a_file->id) &&
+ diff->old_src == GIT_ITERATOR_TYPE_WORKDIR &&
+ !git_diff__oid_for_file(&a_file->id,
+ diff, a_file->path, a_file->mode, a_file->size))
+ a_file->flags |= GIT_DIFF_FLAG_VALID_ID;
+
+ if (git_oid_iszero(&b_file->id) &&
+ diff->new_src == GIT_ITERATOR_TYPE_WORKDIR &&
+ !git_diff__oid_for_file(&b_file->id,
+ diff, b_file->path, b_file->mode, b_file->size))
+ b_file->flags |= GIT_DIFF_FLAG_VALID_ID;
+ }
+
+ /* check OID match as a quick test */
+ if (git_oid__cmp(&a_file->id, &b_file->id) == 0) {
+ *score = 100;
+ return 0;
+ }
+
+ /* don't calculate signatures if we are doing exact match */
+ if (exact_match) {
+ *score = 0;
+ return 0;
+ }
+
+ memset(&a_info, 0, sizeof(a_info));
+ memset(&b_info, 0, sizeof(b_info));
+
+ /* set up similarity data (will try to update missing file sizes) */
+ if (!cache[a_idx] && (error = similarity_init(&a_info, diff, a_idx)) < 0)
+ return error;
+ if (!cache[b_idx] && (error = similarity_init(&b_info, diff, b_idx)) < 0)
+ goto cleanup;
+
+ /* check if file sizes are nowhere near each other */
+ if (a_file->size > 127 &&
+ b_file->size > 127 &&
+ (a_file->size > (b_file->size << 3) ||
+ b_file->size > (a_file->size << 3)))
+ goto cleanup;
+
+ /* update signature cache if needed */
+ if (!cache[a_idx]) {
+ if ((error = similarity_sig(&a_info, opts, cache)) < 0)
+ goto cleanup;
+ }
+ if (!cache[b_idx]) {
+ if ((error = similarity_sig(&b_info, opts, cache)) < 0)
+ goto cleanup;
+ }
+
+ /* calculate similarity provided that the metric choose to process
+ * both the a and b files (some may not if file is too big, etc).
+ */
+ if (cache[a_idx] && cache[b_idx])
+ error = opts->metric->similarity(
+ score, cache[a_idx], cache[b_idx], opts->metric->payload);
+
+cleanup:
+ similarity_unload(&a_info);
+ similarity_unload(&b_info);
+
+ return error;
+}
+
+static int calc_self_similarity(
+ git_diff *diff,
+ const git_diff_find_options *opts,
+ size_t delta_idx,
+ void **cache)
+{
+ int error, similarity = -1;
+ git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx);
+
+ if ((delta->flags & GIT_DIFF_FLAG__HAS_SELF_SIMILARITY) != 0)
+ return 0;
+
+ error = similarity_measure(
+ &similarity, diff, opts, cache, 2 * delta_idx, 2 * delta_idx + 1);
+ if (error < 0)
+ return error;
+
+ if (similarity >= 0) {
+ delta->similarity = (uint16_t)similarity;
+ delta->flags |= GIT_DIFF_FLAG__HAS_SELF_SIMILARITY;
+ }
+
+ return 0;
+}
+
+static bool is_rename_target(
+ git_diff *diff,
+ const git_diff_find_options *opts,
+ size_t delta_idx,
+ void **cache)
+{
+ git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx);
+
+ /* skip things that aren't plain blobs */
+ if (!GIT_MODE_ISBLOB(delta->new_file.mode))
+ return false;
+
+ /* only consider ADDED, RENAMED, COPIED, and split MODIFIED as
+ * targets; maybe include UNTRACKED and IGNORED if requested.
+ */
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED:
+ case GIT_DELTA_DELETED:
+ return false;
+
+ case GIT_DELTA_MODIFIED:
+ if (!FLAG_SET(opts, GIT_DIFF_FIND_REWRITES) &&
+ !FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES))
+ return false;
+
+ if (calc_self_similarity(diff, opts, delta_idx, cache) < 0)
+ return false;
+
+ if (FLAG_SET(opts, GIT_DIFF_BREAK_REWRITES) &&
+ delta->similarity < opts->break_rewrite_threshold) {
+ delta->flags |= GIT_DIFF_FLAG__TO_SPLIT;
+ break;
+ }
+ if (FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) &&
+ delta->similarity < opts->rename_from_rewrite_threshold)
+ break;
+
+ return false;
+
+ case GIT_DELTA_UNTRACKED:
+ if (!FLAG_SET(opts, GIT_DIFF_FIND_FOR_UNTRACKED))
+ return false;
+ break;
+
+ case GIT_DELTA_IGNORED:
+ return false;
+
+ default: /* all other status values should be checked */
+ break;
+ }
+
+ delta->flags |= GIT_DIFF_FLAG__IS_RENAME_TARGET;
+ return true;
+}
+
+static bool is_rename_source(
+ git_diff *diff,
+ const git_diff_find_options *opts,
+ size_t delta_idx,
+ void **cache)
+{
+ git_diff_delta *delta = GIT_VECTOR_GET(&diff->deltas, delta_idx);
+
+ /* skip things that aren't blobs */
+ if (!GIT_MODE_ISBLOB(delta->old_file.mode))
+ return false;
+
+ switch (delta->status) {
+ case GIT_DELTA_ADDED:
+ case GIT_DELTA_UNTRACKED:
+ case GIT_DELTA_IGNORED:
+ return false;
+
+ case GIT_DELTA_DELETED:
+ case GIT_DELTA_TYPECHANGE:
+ break;
+
+ case GIT_DELTA_UNMODIFIED:
+ if (!FLAG_SET(opts, GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED))
+ return false;
+ if (FLAG_SET(opts, GIT_DIFF_FIND_REMOVE_UNMODIFIED))
+ delta->flags |= GIT_DIFF_FLAG__TO_DELETE;
+ break;
+
+ default: /* MODIFIED, RENAMED, COPIED */
+ /* if we're finding copies, this could be a source */
+ if (FLAG_SET(opts, GIT_DIFF_FIND_COPIES))
+ break;
+
+ /* otherwise, this is only a source if we can split it */
+ if (!FLAG_SET(opts, GIT_DIFF_FIND_REWRITES) &&
+ !FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES))
+ return false;
+
+ if (calc_self_similarity(diff, opts, delta_idx, cache) < 0)
+ return false;
+
+ if (FLAG_SET(opts, GIT_DIFF_BREAK_REWRITES) &&
+ delta->similarity < opts->break_rewrite_threshold) {
+ delta->flags |= GIT_DIFF_FLAG__TO_SPLIT;
+ break;
+ }
+
+ if (FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) &&
+ delta->similarity < opts->rename_from_rewrite_threshold)
+ break;
+
+ return false;
+ }
+
+ delta->flags |= GIT_DIFF_FLAG__IS_RENAME_SOURCE;
+ return true;
+}
+
+GIT_INLINE(bool) delta_is_split(git_diff_delta *delta)
+{
+ return (delta->status == GIT_DELTA_TYPECHANGE ||
+ (delta->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0);
+}
+
+GIT_INLINE(bool) delta_is_new_only(git_diff_delta *delta)
+{
+ return (delta->status == GIT_DELTA_ADDED ||
+ delta->status == GIT_DELTA_UNTRACKED ||
+ delta->status == GIT_DELTA_IGNORED);
+}
+
+GIT_INLINE(void) delta_make_rename(
+ git_diff_delta *to, const git_diff_delta *from, uint16_t similarity)
+{
+ to->status = GIT_DELTA_RENAMED;
+ to->similarity = similarity;
+ to->nfiles = 2;
+ memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));
+ to->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
+}
+
+typedef struct {
+ size_t idx;
+ uint16_t similarity;
+} diff_find_match;
+
+int git_diff_find_similar(
+ git_diff *diff,
+ const git_diff_find_options *given_opts)
+{
+ size_t s, t;
+ int error = 0, result;
+ uint16_t similarity;
+ git_diff_delta *src, *tgt;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ size_t num_deltas, num_srcs = 0, num_tgts = 0;
+ size_t tried_srcs = 0, tried_tgts = 0;
+ size_t num_rewrites = 0, num_updates = 0, num_bumped = 0;
+ void **sigcache = NULL; /* cache of similarity metric file signatures */
+ diff_find_match *tgt2src = NULL;
+ diff_find_match *src2tgt = NULL;
+ diff_find_match *tgt2src_copy = NULL;
+ diff_find_match *best_match;
+ git_diff_file swap;
+
+ if ((error = normalize_find_opts(diff, &opts, given_opts)) < 0)
+ return error;
+
+ num_deltas = diff->deltas.length;
+
+ /* TODO: maybe abort if deltas.length > rename_limit ??? */
+ if (!git__is_uint32(num_deltas))
+ goto cleanup;
+
+ /* No flags set; nothing to do */
+ if ((opts.flags & GIT_DIFF_FIND_ALL) == 0)
+ goto cleanup;
+
+ sigcache = git__calloc(num_deltas * 2, sizeof(void *));
+ GITERR_CHECK_ALLOC(sigcache);
+
+ /* Label rename sources and targets
+ *
+ * This will also set self-similarity scores for MODIFIED files and
+ * mark them for splitting if break-rewrites is enabled
+ */
+ git_vector_foreach(&diff->deltas, t, tgt) {
+ if (is_rename_source(diff, &opts, t, sigcache))
+ ++num_srcs;
+
+ if (is_rename_target(diff, &opts, t, sigcache))
+ ++num_tgts;
+
+ if ((tgt->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0)
+ num_rewrites++;
+ }
+
+ /* if there are no candidate srcs or tgts, we're done */
+ if (!num_srcs || !num_tgts)
+ goto cleanup;
+
+ src2tgt = git__calloc(num_deltas, sizeof(diff_find_match));
+ GITERR_CHECK_ALLOC(src2tgt);
+ tgt2src = git__calloc(num_deltas, sizeof(diff_find_match));
+ GITERR_CHECK_ALLOC(tgt2src);
+
+ if (FLAG_SET(&opts, GIT_DIFF_FIND_COPIES)) {
+ tgt2src_copy = git__calloc(num_deltas, sizeof(diff_find_match));
+ GITERR_CHECK_ALLOC(tgt2src_copy);
+ }
+
+ /*
+ * Find best-fit matches for rename / copy candidates
+ */
+
+find_best_matches:
+ tried_tgts = num_bumped = 0;
+
+ git_vector_foreach(&diff->deltas, t, tgt) {
+ /* skip things that are not rename targets */
+ if ((tgt->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0)
+ continue;
+
+ tried_srcs = 0;
+
+ git_vector_foreach(&diff->deltas, s, src) {
+ /* skip things that are not rename sources */
+ if ((src->flags & GIT_DIFF_FLAG__IS_RENAME_SOURCE) == 0)
+ continue;
+
+ /* calculate similarity for this pair and find best match */
+ if (s == t)
+ result = -1; /* don't measure self-similarity here */
+ else if ((error = similarity_measure(
+ &result, diff, &opts, sigcache, 2 * s, 2 * t + 1)) < 0)
+ goto cleanup;
+
+ if (result < 0)
+ continue;
+ similarity = (uint16_t)result;
+
+ /* is this a better rename? */
+ if (tgt2src[t].similarity < similarity &&
+ src2tgt[s].similarity < similarity)
+ {
+ /* eject old mapping */
+ if (src2tgt[s].similarity > 0) {
+ tgt2src[src2tgt[s].idx].similarity = 0;
+ num_bumped++;
+ }
+ if (tgt2src[t].similarity > 0) {
+ src2tgt[tgt2src[t].idx].similarity = 0;
+ num_bumped++;
+ }
+
+ /* write new mapping */
+ tgt2src[t].idx = s;
+ tgt2src[t].similarity = similarity;
+ src2tgt[s].idx = t;
+ src2tgt[s].similarity = similarity;
+ }
+
+ /* keep best absolute match for copies */
+ if (tgt2src_copy != NULL &&
+ tgt2src_copy[t].similarity < similarity)
+ {
+ tgt2src_copy[t].idx = s;
+ tgt2src_copy[t].similarity = similarity;
+ }
+
+ if (++tried_srcs >= num_srcs)
+ break;
+
+ /* cap on maximum targets we'll examine (per "tgt" file) */
+ if (tried_srcs > opts.rename_limit)
+ break;
+ }
+
+ if (++tried_tgts >= num_tgts)
+ break;
+ }
+
+ if (num_bumped > 0) /* try again if we bumped some items */
+ goto find_best_matches;
+
+ /*
+ * Rewrite the diffs with renames / copies
+ */
+
+ tried_tgts = 0;
+
+ git_vector_foreach(&diff->deltas, t, tgt) {
+ /* skip things that are not rename targets */
+ if ((tgt->flags & GIT_DIFF_FLAG__IS_RENAME_TARGET) == 0)
+ continue;
+
+ /* check if this delta was the target of a similarity */
+ if (tgt2src[t].similarity)
+ best_match = &tgt2src[t];
+ else if (tgt2src_copy && tgt2src_copy[t].similarity)
+ best_match = &tgt2src_copy[t];
+ else
+ continue;
+
+ s = best_match->idx;
+ src = GIT_VECTOR_GET(&diff->deltas, s);
+
+ /* possible scenarios:
+ * 1. from DELETE to ADD/UNTRACK/IGNORE = RENAME
+ * 2. from DELETE to SPLIT/TYPECHANGE = RENAME + DELETE
+ * 3. from SPLIT/TYPECHANGE to ADD/UNTRACK/IGNORE = ADD + RENAME
+ * 4. from SPLIT/TYPECHANGE to SPLIT/TYPECHANGE = RENAME + SPLIT
+ * 5. from OTHER to ADD/UNTRACK/IGNORE = OTHER + COPY
+ */
+
+ if (src->status == GIT_DELTA_DELETED) {
+
+ if (delta_is_new_only(tgt)) {
+
+ if (best_match->similarity < opts.rename_threshold)
+ continue;
+
+ delta_make_rename(tgt, src, best_match->similarity);
+
+ src->flags |= GIT_DIFF_FLAG__TO_DELETE;
+ num_rewrites++;
+ } else {
+ assert(delta_is_split(tgt));
+
+ if (best_match->similarity < opts.rename_from_rewrite_threshold)
+ continue;
+
+ memcpy(&swap, &tgt->old_file, sizeof(swap));
+
+ delta_make_rename(tgt, src, best_match->similarity);
+ num_rewrites--;
+
+ assert(src->status == GIT_DELTA_DELETED);
+ memcpy(&src->old_file, &swap, sizeof(src->old_file));
+ memset(&src->new_file, 0, sizeof(src->new_file));
+ src->new_file.path = src->old_file.path;
+ src->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
+
+ num_updates++;
+
+ if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) {
+ /* what used to be at src t is now at src s */
+ tgt2src[src2tgt[t].idx].idx = s;
+ }
+ }
+ }
+
+ else if (delta_is_split(src)) {
+
+ if (delta_is_new_only(tgt)) {
+
+ if (best_match->similarity < opts.rename_threshold)
+ continue;
+
+ delta_make_rename(tgt, src, best_match->similarity);
+
+ src->status = (diff->new_src == GIT_ITERATOR_TYPE_WORKDIR) ?
+ GIT_DELTA_UNTRACKED : GIT_DELTA_ADDED;
+ src->nfiles = 1;
+ memset(&src->old_file, 0, sizeof(src->old_file));
+ src->old_file.path = src->new_file.path;
+ src->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
+
+ src->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
+ num_rewrites--;
+
+ num_updates++;
+ } else {
+ assert(delta_is_split(src));
+
+ if (best_match->similarity < opts.rename_from_rewrite_threshold)
+ continue;
+
+ memcpy(&swap, &tgt->old_file, sizeof(swap));
+
+ delta_make_rename(tgt, src, best_match->similarity);
+ num_rewrites--;
+ num_updates++;
+
+ memcpy(&src->old_file, &swap, sizeof(src->old_file));
+
+ /* if we've just swapped the new element into the correct
+ * place, clear the SPLIT flag
+ */
+ if (tgt2src[s].idx == t &&
+ tgt2src[s].similarity >
+ opts.rename_from_rewrite_threshold) {
+ src->status = GIT_DELTA_RENAMED;
+ src->similarity = tgt2src[s].similarity;
+ tgt2src[s].similarity = 0;
+ src->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
+ num_rewrites--;
+ }
+ /* otherwise, if we just overwrote a source, update mapping */
+ else if (src2tgt[t].similarity > 0 && src2tgt[t].idx > t) {
+ /* what used to be at src t is now at src s */
+ tgt2src[src2tgt[t].idx].idx = s;
+ }
+
+ num_updates++;
+ }
+ }
+
+ else if (FLAG_SET(&opts, GIT_DIFF_FIND_COPIES)) {
+ if (tgt2src_copy[t].similarity < opts.copy_threshold)
+ continue;
+
+ /* always use best possible source for copy */
+ best_match = &tgt2src_copy[t];
+ src = GIT_VECTOR_GET(&diff->deltas, best_match->idx);
+
+ if (delta_is_split(tgt)) {
+ error = insert_delete_side_of_split(diff, &diff->deltas, tgt);
+ if (error < 0)
+ goto cleanup;
+ num_rewrites--;
+ }
+
+ if (!delta_is_split(tgt) && !delta_is_new_only(tgt))
+ continue;
+
+ tgt->status = GIT_DELTA_COPIED;
+ tgt->similarity = best_match->similarity;
+ tgt->nfiles = 2;
+ memcpy(&tgt->old_file, &src->old_file, sizeof(tgt->old_file));
+ tgt->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;
+
+ num_updates++;
+ }
+ }
+
+ /*
+ * Actually split and delete entries as needed
+ */
+
+ if (num_rewrites > 0 || num_updates > 0)
+ error = apply_splits_and_deletes(
+ diff, diff->deltas.length - num_rewrites,
+ FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES) &&
+ !FLAG_SET(&opts, GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY));
+
+cleanup:
+ git__free(tgt2src);
+ git__free(src2tgt);
+ git__free(tgt2src_copy);
+
+ if (sigcache) {
+ for (t = 0; t < num_deltas * 2; ++t) {
+ if (sigcache[t] != NULL)
+ opts.metric->free_signature(sigcache[t], opts.metric->payload);
+ }
+ git__free(sigcache);
+ }
+
+ if (!given_opts || !given_opts->metric)
+ git__free(opts.metric);
+
+ return error;
+}
+
+#undef FLAG_SET
diff --git a/src/diff_xdiff.c b/src/diff_xdiff.c
new file mode 100644
index 000000000..e5984f1c9
--- /dev/null
+++ b/src/diff_xdiff.c
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "diff_driver.h"
+#include "diff_patch.h"
+#include "diff_xdiff.h"
+
+static int git_xdiff_scan_int(const char **str, int *value)
+{
+ const char *scan = *str;
+ int v = 0, digits = 0;
+ /* find next digit */
+ for (scan = *str; *scan && !git__isdigit(*scan); scan++);
+ /* parse next number */
+ for (; git__isdigit(*scan); scan++, digits++)
+ v = (v * 10) + (*scan - '0');
+ *str = scan;
+ *value = v;
+ return (digits > 0) ? 0 : -1;
+}
+
+static int git_xdiff_parse_hunk(git_diff_hunk *hunk, const char *header)
+{
+ /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */
+ if (*header != '@')
+ goto fail;
+ if (git_xdiff_scan_int(&header, &hunk->old_start) < 0)
+ goto fail;
+ if (*header == ',') {
+ if (git_xdiff_scan_int(&header, &hunk->old_lines) < 0)
+ goto fail;
+ } else
+ hunk->old_lines = 1;
+ if (git_xdiff_scan_int(&header, &hunk->new_start) < 0)
+ goto fail;
+ if (*header == ',') {
+ if (git_xdiff_scan_int(&header, &hunk->new_lines) < 0)
+ goto fail;
+ } else
+ hunk->new_lines = 1;
+ if (hunk->old_start < 0 || hunk->new_start < 0)
+ goto fail;
+
+ return 0;
+
+fail:
+ giterr_set(GITERR_INVALID, "Malformed hunk header from xdiff");
+ return -1;
+}
+
+typedef struct {
+ git_xdiff_output *xo;
+ git_patch *patch;
+ git_diff_hunk hunk;
+ int old_lineno, new_lineno;
+ mmfile_t xd_old_data, xd_new_data;
+} git_xdiff_info;
+
+static int diff_update_lines(
+ git_xdiff_info *info,
+ git_diff_line *line,
+ const char *content,
+ size_t content_len)
+{
+ const char *scan = content, *scan_end = content + content_len;
+
+ for (line->num_lines = 0; scan < scan_end; ++scan)
+ if (*scan == '\n')
+ ++line->num_lines;
+
+ line->content = content;
+ line->content_len = content_len;
+
+ /* expect " "/"-"/"+", then data */
+ switch (line->origin) {
+ case GIT_DIFF_LINE_ADDITION:
+ case GIT_DIFF_LINE_DEL_EOFNL:
+ line->old_lineno = -1;
+ line->new_lineno = info->new_lineno;
+ info->new_lineno += (int)line->num_lines;
+ break;
+ case GIT_DIFF_LINE_DELETION:
+ case GIT_DIFF_LINE_ADD_EOFNL:
+ line->old_lineno = info->old_lineno;
+ line->new_lineno = -1;
+ info->old_lineno += (int)line->num_lines;
+ break;
+ case GIT_DIFF_LINE_CONTEXT:
+ case GIT_DIFF_LINE_CONTEXT_EOFNL:
+ line->old_lineno = info->old_lineno;
+ line->new_lineno = info->new_lineno;
+ info->old_lineno += (int)line->num_lines;
+ info->new_lineno += (int)line->num_lines;
+ break;
+ default:
+ giterr_set(GITERR_INVALID, "Unknown diff line origin %02x",
+ (unsigned int)line->origin);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len)
+{
+ git_xdiff_info *info = priv;
+ git_patch *patch = info->patch;
+ const git_diff_delta *delta = git_patch_get_delta(patch);
+ git_diff_output *output = &info->xo->output;
+ git_diff_line line;
+
+ if (len == 1) {
+ output->error = git_xdiff_parse_hunk(&info->hunk, bufs[0].ptr);
+ if (output->error < 0)
+ return output->error;
+
+ info->hunk.header_len = bufs[0].size;
+ if (info->hunk.header_len >= sizeof(info->hunk.header))
+ info->hunk.header_len = sizeof(info->hunk.header) - 1;
+ memcpy(info->hunk.header, bufs[0].ptr, info->hunk.header_len);
+ info->hunk.header[info->hunk.header_len] = '\0';
+
+ if (output->hunk_cb != NULL &&
+ (output->error = output->hunk_cb(
+ delta, &info->hunk, output->payload)))
+ return output->error;
+
+ info->old_lineno = info->hunk.old_start;
+ info->new_lineno = info->hunk.new_start;
+ }
+
+ if (len == 2 || len == 3) {
+ /* expect " "/"-"/"+", then data */
+ line.origin =
+ (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION :
+ (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION :
+ GIT_DIFF_LINE_CONTEXT;
+
+ if (line.origin == GIT_DIFF_LINE_ADDITION)
+ line.content_offset = bufs[1].ptr - info->xd_new_data.ptr;
+ else if (line.origin == GIT_DIFF_LINE_DELETION)
+ line.content_offset = bufs[1].ptr - info->xd_old_data.ptr;
+ else
+ line.content_offset = -1;
+
+ output->error = diff_update_lines(
+ info, &line, bufs[1].ptr, bufs[1].size);
+
+ if (!output->error && output->data_cb != NULL)
+ output->error = output->data_cb(
+ delta, &info->hunk, &line, output->payload);
+ }
+
+ if (len == 3 && !output->error) {
+ /* If we have a '+' and a third buf, then we have added a line
+ * without a newline and the old code had one, so DEL_EOFNL.
+ * If we have a '-' and a third buf, then we have removed a line
+ * with out a newline but added a blank line, so ADD_EOFNL.
+ */
+ line.origin =
+ (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_DEL_EOFNL :
+ (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL :
+ GIT_DIFF_LINE_CONTEXT_EOFNL;
+
+ line.content_offset = -1;
+
+ output->error = diff_update_lines(
+ info, &line, bufs[2].ptr, bufs[2].size);
+
+ if (!output->error && output->data_cb != NULL)
+ output->error = output->data_cb(
+ delta, &info->hunk, &line, output->payload);
+ }
+
+ return output->error;
+}
+
+static int git_xdiff(git_diff_output *output, git_patch *patch)
+{
+ git_xdiff_output *xo = (git_xdiff_output *)output;
+ git_xdiff_info info;
+ git_diff_find_context_payload findctxt;
+
+ memset(&info, 0, sizeof(info));
+ info.patch = patch;
+ info.xo = xo;
+
+ xo->callback.priv = &info;
+
+ git_diff_find_context_init(
+ &xo->config.find_func, &findctxt, git_patch__driver(patch));
+ xo->config.find_func_priv = &findctxt;
+
+ if (xo->config.find_func != NULL)
+ xo->config.flags |= XDL_EMIT_FUNCNAMES;
+ else
+ xo->config.flags &= ~XDL_EMIT_FUNCNAMES;
+
+ /* TODO: check ofile.opts_flags to see if driver-specific per-file
+ * updates are needed to xo->params.flags
+ */
+
+ git_patch__old_data(&info.xd_old_data.ptr, &info.xd_old_data.size, patch);
+ git_patch__new_data(&info.xd_new_data.ptr, &info.xd_new_data.size, patch);
+
+ xdl_diff(&info.xd_old_data, &info.xd_new_data,
+ &xo->params, &xo->config, &xo->callback);
+
+ git_diff_find_context_clear(&findctxt);
+
+ return xo->output.error;
+}
+
+void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts)
+{
+ uint32_t flags = opts ? opts->flags : 0;
+
+ xo->output.diff_cb = git_xdiff;
+
+ xo->config.ctxlen = opts ? opts->context_lines : 3;
+ xo->config.interhunkctxlen = opts ? opts->interhunk_lines : 0;
+
+ if (flags & GIT_DIFF_IGNORE_WHITESPACE)
+ xo->params.flags |= XDF_WHITESPACE_FLAGS;
+ if (flags & GIT_DIFF_IGNORE_WHITESPACE_CHANGE)
+ xo->params.flags |= XDF_IGNORE_WHITESPACE_CHANGE;
+ if (flags & GIT_DIFF_IGNORE_WHITESPACE_EOL)
+ xo->params.flags |= XDF_IGNORE_WHITESPACE_AT_EOL;
+
+ if (flags & GIT_DIFF_PATIENCE)
+ xo->params.flags |= XDF_PATIENCE_DIFF;
+ if (flags & GIT_DIFF_MINIMAL)
+ xo->params.flags |= XDF_NEED_MINIMAL;
+
+ xo->callback.outf = git_xdiff_cb;
+}
diff --git a/src/diff_xdiff.h b/src/diff_xdiff.h
new file mode 100644
index 000000000..c547b00cf
--- /dev/null
+++ b/src/diff_xdiff.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_diff_xdiff_h__
+#define INCLUDE_diff_xdiff_h__
+
+#include "diff.h"
+#include "diff_patch.h"
+#include "xdiff/xdiff.h"
+
+/* A git_xdiff_output is a git_diff_output with extra fields necessary
+ * to use libxdiff. Calling git_xdiff_init() will set the diff_cb field
+ * of the output to use xdiff to generate the diffs.
+ */
+typedef struct {
+ git_diff_output output;
+
+ xdemitconf_t config;
+ xpparam_t params;
+ xdemitcb_t callback;
+} git_xdiff_output;
+
+void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts);
+
+#endif
diff --git a/src/dir.h b/src/dir.h
deleted file mode 100644
index c01c3fae7..000000000
--- a/src/dir.h
+++ /dev/null
@@ -1,41 +0,0 @@
-#ifndef INCLUDE_dir_h__
-#define INCLUDE_dir_h__
-
-#include "common.h"
-
-#ifndef GIT_WIN32
-# include <dirent.h>
-#endif
-
-#ifdef GIT_WIN32
-
-struct git__dirent {
- int d_ino;
- char d_name[261];
-};
-
-typedef struct {
- HANDLE h;
- WIN32_FIND_DATA f;
- struct git__dirent entry;
- char *dir;
- int first;
-} git__DIR;
-
-extern git__DIR *git__opendir(const char *);
-extern struct git__dirent *git__readdir(git__DIR *);
-extern void git__rewinddir(git__DIR *);
-extern int git__closedir(git__DIR *);
-
-# ifndef GIT__WIN32_NO_WRAP_DIR
-# define dirent git__dirent
-# define DIR git__DIR
-# define opendir git__opendir
-# define readdir git__readdir
-# define rewinddir git__rewinddir
-# define closedir git__closedir
-# endif
-
-#endif
-
-#endif /* INCLUDE_dir_h__ */
diff --git a/src/errors.c b/src/errors.c
index e9022c3d1..393a7875f 100644
--- a/src/errors.c
+++ b/src/errors.c
@@ -1,120 +1,174 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
-#include "git2/thread-utils.h" /* for GIT_TLS */
-#include "thread-utils.h" /* for GIT_TLS */
-
-#include <stdarg.h>
-
-static GIT_TLS char g_last_error[1024];
-
-static struct {
- int num;
- const char *str;
-} error_codes[] = {
- {GIT_ERROR, "Unspecified error"},
- {GIT_ENOTOID, "Input was not a properly formatted Git object id."},
- {GIT_ENOTFOUND, "Object does not exist in the scope searched."},
- {GIT_ENOMEM, "Not enough space available."},
- {GIT_EOSERR, "Consult the OS error information."},
- {GIT_EOBJTYPE, "The specified object is of invalid type"},
- {GIT_EOBJCORRUPTED, "The specified object has its data corrupted"},
- {GIT_ENOTAREPO, "The specified repository is invalid"},
- {GIT_EINVALIDTYPE, "The object or config variable type is invalid or doesn't match"},
- {GIT_EMISSINGOBJDATA, "The object cannot be written that because it's missing internal data"},
- {GIT_EPACKCORRUPTED, "The packfile for the ODB is corrupted"},
- {GIT_EFLOCKFAIL, "Failed to adquire or release a file lock"},
- {GIT_EZLIB, "The Z library failed to inflate/deflate an object's data"},
- {GIT_EBUSY, "The queried object is currently busy"},
- {GIT_EINVALIDPATH, "The path is invalid"},
- {GIT_EBAREINDEX, "The index file is not backed up by an existing repository"},
- {GIT_EINVALIDREFNAME, "The name of the reference is not valid"},
- {GIT_EREFCORRUPTED, "The specified reference has its data corrupted"},
- {GIT_ETOONESTEDSYMREF, "The specified symbolic reference is too deeply nested"},
- {GIT_EPACKEDREFSCORRUPTED, "The pack-refs file is either corrupted of its format is not currently supported"},
- {GIT_EINVALIDPATH, "The path is invalid" },
- {GIT_EREVWALKOVER, "The revision walker is empty; there are no more commits left to iterate"},
- {GIT_EINVALIDREFSTATE, "The state of the reference is not valid"},
- {GIT_ENOTIMPLEMENTED, "This feature has not been implemented yet"},
- {GIT_EEXISTS, "A reference with this name already exists"},
- {GIT_EOVERFLOW, "The given integer literal is too large to be parsed"},
- {GIT_ENOTNUM, "The given literal is not a valid number"},
- {GIT_EAMBIGUOUSOIDPREFIX, "The given oid prefix is ambiguous"},
+#include "global.h"
+#include "posix.h"
+#include "buffer.h"
+
+/********************************************
+ * New error handling
+ ********************************************/
+
+static git_error g_git_oom_error = {
+ "Out of memory",
+ GITERR_NOMEMORY
};
-const char *git_strerror(int num)
+static void set_error(int error_class, char *string)
+{
+ git_error *error = &GIT_GLOBAL->error_t;
+
+ if (error->message != string)
+ git__free(error->message);
+
+ error->message = string;
+ error->klass = error_class;
+
+ GIT_GLOBAL->last_error = error;
+}
+
+void giterr_set_oom(void)
+{
+ GIT_GLOBAL->last_error = &g_git_oom_error;
+}
+
+void giterr_set(int error_class, const char *string, ...)
+{
+ git_buf buf = GIT_BUF_INIT;
+ va_list arglist;
+#ifdef GIT_WIN32
+ DWORD win32_error_code = (error_class == GITERR_OS) ? GetLastError() : 0;
+#endif
+ int error_code = (error_class == GITERR_OS) ? errno : 0;
+
+ va_start(arglist, string);
+ git_buf_vprintf(&buf, string, arglist);
+ va_end(arglist);
+
+ if (error_class == GITERR_OS) {
+#ifdef GIT_WIN32
+ char * win32_error = git_win32_get_error_message(win32_error_code);
+ if (win32_error) {
+ git_buf_PUTS(&buf, ": ");
+ git_buf_puts(&buf, win32_error);
+ git__free(win32_error);
+
+ 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))
+ set_error(error_class, git_buf_detach(&buf));
+}
+
+void giterr_set_str(int error_class, const char *string)
{
- size_t i;
+ char *message;
- if (num == GIT_EOSERR)
- return strerror(errno);
- for (i = 0; i < ARRAY_SIZE(error_codes); i++)
- if (num == error_codes[i].num)
- return error_codes[i].str;
+ assert(string);
- return "Unknown error";
+ message = git__strdup(string);
+
+ if (message)
+ set_error(error_class, message);
}
-int git__rethrow(int error, const char *msg, ...)
+int giterr_set_regex(const regex_t *regex, int error_code)
{
- char new_error[1024];
- char *old_error = NULL;
+ char error_buf[1024];
+
+ assert(error_code);
- va_list va;
+ regerror(error_code, regex, error_buf, sizeof(error_buf));
+ giterr_set_str(GITERR_REGEX, error_buf);
- va_start(va, msg);
- vsnprintf(new_error, sizeof(new_error), msg, va);
- va_end(va);
+ if (error_code == REG_NOMATCH)
+ return GIT_ENOTFOUND;
- old_error = strdup(g_last_error);
- snprintf(g_last_error, sizeof(g_last_error), "%s \n - %s", new_error, old_error);
- free(old_error);
+ return GIT_EINVALIDSPEC;
+}
- return error;
+void giterr_clear(void)
+{
+ if (GIT_GLOBAL->last_error != NULL) {
+ set_error(0, NULL);
+ GIT_GLOBAL->last_error = NULL;
+ }
+
+ errno = 0;
+#ifdef GIT_WIN32
+ SetLastError(0);
+#endif
}
-int git__throw(int error, const char *msg, ...)
+int giterr_detach(git_error *cpy)
{
- va_list va;
+ git_error *error = GIT_GLOBAL->last_error;
+
+ assert(cpy);
+
+ if (!error)
+ return -1;
+
+ cpy->message = error->message;
+ cpy->klass = error->klass;
+
+ error->message = NULL;
+ giterr_clear();
- va_start(va, msg);
- vsnprintf(g_last_error, sizeof(g_last_error), msg, va);
- va_end(va);
+ return 0;
+}
+
+const git_error *giterr_last(void)
+{
+ return GIT_GLOBAL->last_error;
+}
- return error;
+int giterr_capture(git_error_state *state, int error_code)
+{
+ state->error_code = error_code;
+ if (error_code)
+ giterr_detach(&state->error_msg);
+ return error_code;
}
-const char *git_lasterror(void)
+int giterr_restore(git_error_state *state)
{
- if (!g_last_error[0])
- return NULL;
+ if (state && state->error_code && state->error_msg.message)
+ set_error(state->error_msg.klass, state->error_msg.message);
+ else
+ giterr_clear();
+
+ return state ? state->error_code : 0;
+}
- return g_last_error;
+int giterr_system_last(void)
+{
+#ifdef GIT_WIN32
+ return GetLastError();
+#else
+ return errno;
+#endif
}
-void git_clearerror(void)
+void giterr_system_set(int code)
{
- g_last_error[0] = '\0';
+#ifdef GIT_WIN32
+ SetLastError(code);
+#else
+ errno = code;
+#endif
}
diff --git a/src/fetch.c b/src/fetch.c
new file mode 100644
index 000000000..9ff95d935
--- /dev/null
+++ b/src/fetch.c
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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/oid.h"
+#include "git2/refs.h"
+#include "git2/revwalk.h"
+#include "git2/transport.h"
+
+#include "common.h"
+#include "remote.h"
+#include "refspec.h"
+#include "pack.h"
+#include "fetch.h"
+#include "netops.h"
+#include "repository.h"
+#include "refs.h"
+
+static int maybe_want(git_remote *remote, git_remote_head *head, git_odb *odb, git_refspec *tagspec)
+{
+ int match = 0;
+
+ if (!git_reference_is_valid_name(head->name))
+ return 0;
+
+ if (remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_ALL) {
+ /*
+ * If tagopt is --tags, then we only use the default
+ * tags refspec and ignore the remote's
+ */
+ if (git_refspec_src_matches(tagspec, head->name))
+ match = 1;
+ else
+ return 0;
+ } else if (git_remote__matching_refspec(remote, head->name))
+ match = 1;
+
+ if (!match)
+ return 0;
+
+ /* If we have the object, mark it so we don't ask for it */
+ if (git_odb_exists(odb, &head->oid)) {
+ head->local = 1;
+ }
+ else
+ remote->need_pack = 1;
+
+ return git_vector_insert(&remote->refs, head);
+}
+
+static int filter_wants(git_remote *remote)
+{
+ git_remote_head **heads;
+ git_refspec tagspec, head;
+ int error = 0;
+ git_odb *odb;
+ size_t i, heads_len;
+
+ git_vector_clear(&remote->refs);
+ if ((error = git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true)) < 0)
+ return error;
+
+ /*
+ * The fetch refspec can be NULL, and what this means is that the
+ * user didn't specify one. This is fine, as it means that we're
+ * not interested in any particular branch but just the remote's
+ * HEAD, which will be stored in FETCH_HEAD after the fetch.
+ */
+ if (remote->active_refspecs.length == 0) {
+ if ((error = git_refspec__parse(&head, "HEAD", true)) < 0)
+ goto cleanup;
+
+ error = git_refspec__dwim_one(&remote->active_refspecs, &head, &remote->refs);
+ git_refspec__free(&head);
+
+ if (error < 0)
+ goto cleanup;
+ }
+
+ if (git_repository_odb__weakptr(&odb, remote->repo) < 0)
+ goto cleanup;
+
+ if (git_remote_ls((const git_remote_head ***)&heads, &heads_len, remote) < 0)
+ goto cleanup;
+
+ for (i = 0; i < heads_len; i++) {
+ if ((error = maybe_want(remote, heads[i], odb, &tagspec)) < 0)
+ break;
+ }
+
+cleanup:
+ git_refspec__free(&tagspec);
+
+ return error;
+}
+
+/*
+ * 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
+ * traversing until we're done
+ */
+int git_fetch_negotiate(git_remote *remote)
+{
+ git_transport *t = remote->transport;
+
+ remote->need_pack = 0;
+
+ if (filter_wants(remote) < 0) {
+ giterr_set(GITERR_NET, "Failed to filter the reference list for wants");
+ return -1;
+ }
+
+ /* Don't try to negotiate when we don't want anything */
+ if (!remote->need_pack)
+ return 0;
+
+ /*
+ * Now we have everything set up so we can start tell the
+ * server what we want and what we have.
+ */
+ return t->negotiate_fetch(t,
+ remote->repo,
+ (const git_remote_head * const *)remote->refs.contents,
+ remote->refs.length);
+}
+
+int git_fetch_download_pack(git_remote *remote)
+{
+ git_transport *t = remote->transport;
+
+ if (!remote->need_pack)
+ return 0;
+
+ return t->download_pack(t, remote->repo, &remote->stats,
+ remote->callbacks.transfer_progress, remote->callbacks.payload);
+}
diff --git a/src/fetch.h b/src/fetch.h
new file mode 100644
index 000000000..f66e44663
--- /dev/null
+++ b/src/fetch.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_fetch_h__
+#define INCLUDE_fetch_h__
+
+#include "netops.h"
+
+int git_fetch_negotiate(git_remote *remote);
+
+int git_fetch_download_pack(git_remote *remote);
+
+int git_fetch__download_pack(
+ git_transport *t,
+ git_repository *repo,
+ git_transfer_progress *stats,
+ git_transfer_progress_cb 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..a95ea4ca4
--- /dev/null
+++ b/src/fetchhead.c
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2/types.h"
+#include "git2/oid.h"
+
+#include "fetchhead.h"
+#include "common.h"
+#include "buffer.h"
+#include "fileops.h"
+#include "filebuf.h"
+#include "refs.h"
+#include "repository.h"
+
+int git_fetchhead_ref_cmp(const void *a, const void *b)
+{
+ const git_fetchhead_ref *one = (const git_fetchhead_ref *)a;
+ const git_fetchhead_ref *two = (const git_fetchhead_ref *)b;
+
+ if (one->is_merge && !two->is_merge)
+ return -1;
+ if (two->is_merge && !one->is_merge)
+ return 1;
+
+ if (one->ref_name && two->ref_name)
+ return strcmp(one->ref_name, two->ref_name);
+ else if (one->ref_name)
+ return -1;
+ else if (two->ref_name)
+ return 1;
+
+ return 0;
+}
+
+int git_fetchhead_ref_create(
+ git_fetchhead_ref **out,
+ git_oid *oid,
+ unsigned int is_merge,
+ const char *ref_name,
+ const char *remote_url)
+{
+ git_fetchhead_ref *fetchhead_ref;
+
+ assert(out && oid);
+
+ *out = NULL;
+
+ fetchhead_ref = git__malloc(sizeof(git_fetchhead_ref));
+ GITERR_CHECK_ALLOC(fetchhead_ref);
+
+ memset(fetchhead_ref, 0x0, sizeof(git_fetchhead_ref));
+
+ git_oid_cpy(&fetchhead_ref->oid, oid);
+ fetchhead_ref->is_merge = is_merge;
+
+ if (ref_name)
+ fetchhead_ref->ref_name = git__strdup(ref_name);
+
+ if (remote_url)
+ fetchhead_ref->remote_url = git__strdup(remote_url);
+
+ *out = fetchhead_ref;
+
+ return 0;
+}
+
+static int fetchhead_ref_write(
+ git_filebuf *file,
+ git_fetchhead_ref *fetchhead_ref)
+{
+ char oid[GIT_OID_HEXSZ + 1];
+ const char *type, *name;
+ int head = 0;
+
+ 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 if (!git__strcmp(fetchhead_ref->ref_name, GIT_HEAD_FILE)) {
+ head = 1;
+ } else {
+ type = "";
+ name = fetchhead_ref->ref_name;
+ }
+
+ if (head)
+ return git_filebuf_printf(file, "%s\t\t%s\n", oid, fetchhead_ref->remote_url);
+
+ 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, GIT_REFS_FILE_MODE) < 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);
+}
+
+static int fetchhead_ref_parse(
+ git_oid *oid,
+ unsigned int *is_merge,
+ git_buf *ref_name,
+ const char **remote_url,
+ char *line,
+ size_t line_num)
+{
+ char *oid_str, *is_merge_str, *desc, *name = NULL;
+ const char *type = NULL;
+ int error = 0;
+
+ *remote_url = NULL;
+
+ if (!*line) {
+ giterr_set(GITERR_FETCHHEAD,
+ "Empty line in FETCH_HEAD line %d", line_num);
+ return -1;
+ }
+
+ /* Compat with old git clients that wrote FETCH_HEAD like a loose ref. */
+ if ((oid_str = git__strsep(&line, "\t")) == NULL) {
+ oid_str = line;
+ line += strlen(line);
+
+ *is_merge = 1;
+ }
+
+ if (strlen(oid_str) != GIT_OID_HEXSZ) {
+ giterr_set(GITERR_FETCHHEAD,
+ "Invalid object ID in FETCH_HEAD line %d", line_num);
+ return -1;
+ }
+
+ if (git_oid_fromstr(oid, oid_str) < 0) {
+ const git_error *oid_err = giterr_last();
+ const char *err_msg = oid_err ? oid_err->message : "Invalid object ID";
+
+ giterr_set(GITERR_FETCHHEAD, "%s in FETCH_HEAD line %d",
+ err_msg, line_num);
+ return -1;
+ }
+
+ /* Parse new data from newer git clients */
+ if (*line) {
+ if ((is_merge_str = git__strsep(&line, "\t")) == NULL) {
+ giterr_set(GITERR_FETCHHEAD,
+ "Invalid description data in FETCH_HEAD line %d", line_num);
+ return -1;
+ }
+
+ if (*is_merge_str == '\0')
+ *is_merge = 1;
+ else if (strcmp(is_merge_str, "not-for-merge") == 0)
+ *is_merge = 0;
+ else {
+ giterr_set(GITERR_FETCHHEAD,
+ "Invalid for-merge entry in FETCH_HEAD line %d", line_num);
+ return -1;
+ }
+
+ if ((desc = line) == NULL) {
+ giterr_set(GITERR_FETCHHEAD,
+ "Invalid description in FETCH_HEAD line %d", line_num);
+ return -1;
+ }
+
+ if (git__prefixcmp(desc, "branch '") == 0) {
+ type = GIT_REFS_HEADS_DIR;
+ name = desc + 8;
+ } else if (git__prefixcmp(desc, "tag '") == 0) {
+ type = GIT_REFS_TAGS_DIR;
+ name = desc + 5;
+ } else if (git__prefixcmp(desc, "'") == 0)
+ name = desc + 1;
+
+ if (name) {
+ if ((desc = strstr(name, "' ")) == NULL ||
+ git__prefixcmp(desc, "' of ") != 0) {
+ giterr_set(GITERR_FETCHHEAD,
+ "Invalid description in FETCH_HEAD line %d", line_num);
+ return -1;
+ }
+
+ *desc = '\0';
+ desc += 5;
+ }
+
+ *remote_url = desc;
+ }
+
+ git_buf_clear(ref_name);
+
+ if (type)
+ git_buf_join(ref_name, '/', type, name);
+ else if(name)
+ git_buf_puts(ref_name, name);
+
+ return error;
+}
+
+int git_repository_fetchhead_foreach(git_repository *repo,
+ git_repository_fetchhead_foreach_cb cb,
+ void *payload)
+{
+ git_buf path = GIT_BUF_INIT, file = GIT_BUF_INIT, name = GIT_BUF_INIT;
+ const char *ref_name;
+ git_oid oid;
+ const char *remote_url;
+ unsigned int is_merge = 0;
+ char *buffer, *line;
+ size_t line_num = 0;
+ int error = 0;
+
+ assert(repo && cb);
+
+ if (git_buf_joinpath(&path, repo->path_repository, GIT_FETCH_HEAD_FILE) < 0)
+ return -1;
+
+ if ((error = git_futils_readbuffer(&file, git_buf_cstr(&path))) < 0)
+ goto done;
+
+ buffer = file.ptr;
+
+ while ((line = git__strsep(&buffer, "\n")) != NULL) {
+ ++line_num;
+
+ if ((error = fetchhead_ref_parse(
+ &oid, &is_merge, &name, &remote_url, line, line_num)) < 0)
+ goto done;
+
+ if (git_buf_len(&name) > 0)
+ ref_name = git_buf_cstr(&name);
+ else
+ ref_name = NULL;
+
+ error = cb(ref_name, remote_url, &oid, is_merge, payload);
+ if (error) {
+ giterr_set_after_callback(error);
+ goto done;
+ }
+ }
+
+ if (*buffer) {
+ giterr_set(GITERR_FETCHHEAD, "No EOL at line %d", line_num+1);
+ error = -1;
+ goto done;
+ }
+
+done:
+ git_buf_free(&file);
+ git_buf_free(&path);
+ git_buf_free(&name);
+
+ return error;
+}
+
+void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref)
+{
+ if (fetchhead_ref == NULL)
+ return;
+
+ git__free(fetchhead_ref->remote_url);
+ git__free(fetchhead_ref->ref_name);
+ git__free(fetchhead_ref);
+}
+
diff --git a/src/fetchhead.h b/src/fetchhead.h
new file mode 100644
index 000000000..74fce049b
--- /dev/null
+++ b/src/fetchhead.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_fetchhead_h__
+#define INCLUDE_fetchhead_h__
+
+#include "vector.h"
+
+struct git_fetchhead_ref {
+ git_oid oid;
+ unsigned int is_merge;
+ char *ref_name;
+ char *remote_url;
+};
+
+typedef struct git_fetchhead_ref git_fetchhead_ref;
+
+int git_fetchhead_ref_create(
+ git_fetchhead_ref **fetchhead_ref_out,
+ git_oid *oid,
+ unsigned int is_merge,
+ const char *ref_name,
+ const char *remote_url);
+
+int git_fetchhead_ref_cmp(const void *a, const void *b);
+
+int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs);
+
+void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref);
+
+#endif
diff --git a/src/filebuf.c b/src/filebuf.c
index 6d398a7db..d23bcc11c 100644
--- a/src/filebuf.c
+++ b/src/filebuf.c
@@ -1,93 +1,130 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
*/
-#include <stdarg.h>
-
#include "common.h"
#include "filebuf.h"
#include "fileops.h"
static const size_t WRITE_BUFFER_SIZE = (4096 * 2);
-static int lock_file(git_filebuf *file, int flags)
+enum buferr_t {
+ BUFERR_OK = 0,
+ BUFERR_WRITE,
+ BUFERR_ZLIB,
+ BUFERR_MEM
+};
+
+#define ENSURE_BUF_OK(buf) if ((buf)->last_error != BUFERR_OK) { return -1; }
+
+static int verify_last_error(git_filebuf *file)
+{
+ switch (file->last_error) {
+ case BUFERR_WRITE:
+ giterr_set(GITERR_OS, "Failed to write out file");
+ return -1;
+
+ case BUFERR_MEM:
+ giterr_set_oom();
+ return -1;
+
+ case BUFERR_ZLIB:
+ giterr_set(GITERR_ZLIB,
+ "Buffer error when writing out ZLib data");
+ return -1;
+
+ default:
+ return 0;
+ }
+}
+
+static int lock_file(git_filebuf *file, int flags, mode_t mode)
{
- if (git_futils_exists(file->path_lock) == 0) {
+ if (git_path_exists(file->path_lock) == true) {
if (flags & GIT_FILEBUF_FORCE)
p_unlink(file->path_lock);
- else
- return git__throw(GIT_EOSERR, "Failed to lock file");
+ else {
+ giterr_clear(); /* actual OS error code just confuses */
+ giterr_set(GITERR_OS,
+ "Failed to lock file '%s' for writing", file->path_lock);
+ return GIT_ELOCKED;
+ }
}
/* create path to the file buffer is required */
if (flags & GIT_FILEBUF_FORCE) {
- file->fd = git_futils_creat_locked_withpath(file->path_lock, 0644);
+ /* XXX: Should dirmode here be configurable? Or is 0777 always fine? */
+ file->fd = git_futils_creat_locked_withpath(file->path_lock, 0777, mode);
} else {
- file->fd = git_futils_creat_locked(file->path_lock, 0644);
+ file->fd = git_futils_creat_locked(file->path_lock, mode);
}
if (file->fd < 0)
- return git__throw(GIT_EOSERR, "Failed to create lock");
+ return file->fd;
+
+ file->fd_is_open = true;
- if ((flags & GIT_FILEBUF_APPEND) && git_futils_exists(file->path_original) == 0) {
+ if ((flags & GIT_FILEBUF_APPEND) && git_path_exists(file->path_original) == true) {
git_file source;
char buffer[2048];
- size_t read_bytes;
+ ssize_t read_bytes;
source = p_open(file->path_original, O_RDONLY);
- if (source < 0)
- return git__throw(GIT_EOSERR, "Failed to lock file. Could not open %s", file->path_original);
+ if (source < 0) {
+ giterr_set(GITERR_OS,
+ "Failed to open file '%s' for reading",
+ file->path_original);
+ return -1;
+ }
- while ((read_bytes = p_read(source, buffer, 2048)) > 0) {
+ while ((read_bytes = p_read(source, buffer, sizeof(buffer))) > 0) {
p_write(file->fd, buffer, read_bytes);
- if (file->digest)
- git_hash_update(file->digest, buffer, read_bytes);
+ if (file->compute_digest)
+ git_hash_update(&file->digest, buffer, read_bytes);
}
p_close(source);
+
+ if (read_bytes < 0) {
+ giterr_set(GITERR_OS, "Failed to read file '%s'", file->path_original);
+ return -1;
+ }
}
- return GIT_SUCCESS;
+ return 0;
}
void git_filebuf_cleanup(git_filebuf *file)
{
- if (file->fd >= 0)
+ if (file->fd_is_open && file->fd >= 0)
p_close(file->fd);
- if (file->fd >= 0 && file->path_lock && git_futils_exists(file->path_lock) == GIT_SUCCESS)
+ 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);
- free(file->buffer);
- free(file->z_buf);
+ /* use the presence of z_buf to decide if we need to deflateEnd */
+ if (file->z_buf) {
+ git__free(file->z_buf);
+ deflateEnd(&file->zs);
+ }
- deflateEnd(&file->zs);
+ if (file->path_original)
+ git__free(file->path_original);
+ if (file->path_lock)
+ git__free(file->path_lock);
- free(file->path_original);
- free(file->path_lock);
+ memset(file, 0x0, sizeof(git_filebuf));
+ file->fd = -1;
}
GIT_INLINE(int) flush_buffer(git_filebuf *file)
@@ -97,96 +134,110 @@ GIT_INLINE(int) flush_buffer(git_filebuf *file)
return result;
}
-static int write_normal(git_filebuf *file, void *source, size_t len)
+int git_filebuf_flush(git_filebuf *file)
{
- int result = 0;
+ return flush_buffer(file);
+}
+static int write_normal(git_filebuf *file, void *source, size_t len)
+{
if (len > 0) {
- result = p_write(file->fd, (void *)source, len);
- if (file->digest)
- git_hash_update(file->digest, source, len);
+ if (p_write(file->fd, (void *)source, len) < 0) {
+ file->last_error = BUFERR_WRITE;
+ return -1;
+ }
+
+ if (file->compute_digest)
+ git_hash_update(&file->digest, source, len);
}
- return result;
+ return 0;
}
static int write_deflate(git_filebuf *file, void *source, size_t len)
{
- int result = Z_OK;
z_stream *zs = &file->zs;
if (len > 0 || file->flush_mode == Z_FINISH) {
- zs->next_in = (void *)source;
- zs->avail_in = len;
+ zs->next_in = source;
+ zs->avail_in = (uInt)len;
do {
- int have;
+ size_t have;
zs->next_out = file->z_buf;
- zs->avail_out = file->buf_size;
+ zs->avail_out = (uInt)file->buf_size;
- result = deflate(zs, file->flush_mode);
- assert(result != Z_STREAM_ERROR);
+ if (deflate(zs, file->flush_mode) == Z_STREAM_ERROR) {
+ file->last_error = BUFERR_ZLIB;
+ return -1;
+ }
- have = file->buf_size - zs->avail_out;
+ have = file->buf_size - (size_t)zs->avail_out;
- if (p_write(file->fd, file->z_buf, have) < GIT_SUCCESS)
- return git__throw(GIT_EOSERR, "Failed to write to file");
+ if (p_write(file->fd, file->z_buf, have) < 0) {
+ file->last_error = BUFERR_WRITE;
+ return -1;
+ }
- } while (zs->avail_out == 0);
+ } while (zs->avail_out == 0);
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 GIT_SUCCESS;
+ return 0;
}
-int git_filebuf_open(git_filebuf *file, const char *path, int flags)
+int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode)
{
- int error;
+ int compression, error = -1;
size_t path_len;
- assert(file && path);
+ /* opening an already open buffer is a programming error;
+ * assert that this never happens instead of returning
+ * an error code */
+ assert(file && path && file->buffer == NULL);
memset(file, 0x0, sizeof(git_filebuf));
+ if (flags & GIT_FILEBUF_DO_NOT_BUFFER)
+ file->do_not_buffer = true;
+
file->buf_size = WRITE_BUFFER_SIZE;
file->buf_pos = 0;
file->fd = -1;
+ file->last_error = BUFERR_OK;
/* Allocate the main cache buffer */
- file->buffer = git__malloc(file->buf_size);
- if (file->buffer == NULL){
- error = GIT_ENOMEM;
- goto cleanup;
+ if (!file->do_not_buffer) {
+ file->buffer = git__malloc(file->buf_size);
+ GITERR_CHECK_ALLOC(file->buffer);
}
/* If we are hashing on-write, allocate a new hash context */
if (flags & GIT_FILEBUF_HASH_CONTENTS) {
- if ((file->digest = git_hash_new_ctx()) == NULL) {
- error = GIT_ENOMEM;
+ file->compute_digest = 1;
+
+ if (git_hash_ctx_init(&file->digest) < 0)
goto cleanup;
- }
}
- /* If we are deflating on-write, */
- if (flags & GIT_FILEBUF_DEFLATE_CONTENTS) {
+ compression = flags >> GIT_FILEBUF_DEFLATE_SHIFT;
+ /* If we are deflating on-write, */
+ if (compression != 0) {
/* Initialize the ZLib stream */
- if (deflateInit(&file->zs, Z_BEST_SPEED) != Z_OK) {
- error = git__throw(GIT_EZLIB, "Failed to initialize zlib");
+ if (deflateInit(&file->zs, compression) != Z_OK) {
+ giterr_set(GITERR_ZLIB, "Failed to initialize zlib");
goto cleanup;
}
/* Allocate the Zlib cache buffer */
file->z_buf = git__malloc(file->buf_size);
- if (file->z_buf == NULL){
- error = GIT_ENOMEM;
- goto cleanup;
- }
+ GITERR_CHECK_ALLOC(file->z_buf);
/* Never flush */
file->flush_mode = Z_NO_FLUSH;
@@ -197,102 +248,105 @@ int git_filebuf_open(git_filebuf *file, const char *path, int flags)
/* If we are writing to a temp file */
if (flags & GIT_FILEBUF_TEMPORARY) {
- char tmp_path[GIT_PATH_MAX];
+ git_buf tmp_path = GIT_BUF_INIT;
/* Open the file as temporary for locking */
- file->fd = git_futils_mktmp(tmp_path, path);
+ file->fd = git_futils_mktmp(&tmp_path, path, mode);
+
if (file->fd < 0) {
- error = GIT_EOSERR;
+ git_buf_free(&tmp_path);
goto cleanup;
}
+ file->fd_is_open = true;
/* No original path */
file->path_original = NULL;
- file->path_lock = git__strdup(tmp_path);
-
- if (file->path_lock == NULL) {
- error = GIT_ENOMEM;
- goto cleanup;
- }
+ file->path_lock = git_buf_detach(&tmp_path);
+ GITERR_CHECK_ALLOC(file->path_lock);
} else {
path_len = strlen(path);
/* Save the original path of the file */
file->path_original = git__strdup(path);
- if (file->path_original == NULL) {
- error = GIT_ENOMEM;
- goto cleanup;
- }
+ GITERR_CHECK_ALLOC(file->path_original);
/* create the locking path by appending ".lock" to the original */
file->path_lock = git__malloc(path_len + GIT_FILELOCK_EXTLENGTH);
- if (file->path_lock == NULL) {
- error = GIT_ENOMEM;
- goto cleanup;
- }
+ GITERR_CHECK_ALLOC(file->path_lock);
memcpy(file->path_lock, file->path_original, path_len);
memcpy(file->path_lock + path_len, GIT_FILELOCK_EXTENSION, GIT_FILELOCK_EXTLENGTH);
/* open the file for locking */
- if ((error = lock_file(file, flags)) < GIT_SUCCESS)
+ if ((error = lock_file(file, flags, mode)) < 0)
goto cleanup;
}
- return GIT_SUCCESS;
+ return 0;
cleanup:
git_filebuf_cleanup(file);
- return git__rethrow(error, "Failed to open file buffer for '%s'", path);
+ return error;
}
int git_filebuf_hash(git_oid *oid, git_filebuf *file)
{
- int error;
+ assert(oid && file && file->compute_digest);
- assert(oid && file && file->digest);
+ flush_buffer(file);
- if ((error = flush_buffer(file)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to get hash for 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 GIT_SUCCESS;
+ return 0;
}
int git_filebuf_commit_at(git_filebuf *file, const char *path)
{
- free(file->path_original);
+ git__free(file->path_original);
file->path_original = git__strdup(path);
- if (file->path_original == NULL)
- return GIT_ENOMEM;
+ GITERR_CHECK_ALLOC(file->path_original);
return git_filebuf_commit(file);
}
int git_filebuf_commit(git_filebuf *file)
{
- int error;
-
/* temporary files cannot be committed */
assert(file && file->path_original);
file->flush_mode = Z_FINISH;
- if ((error = flush_buffer(file)) < GIT_SUCCESS)
- goto cleanup;
+ flush_buffer(file);
+
+ if (verify_last_error(file) < 0)
+ goto on_error;
+
+ file->fd_is_open = false;
+
+ if (p_close(file->fd) < 0) {
+ giterr_set(GITERR_OS, "Failed to close file at '%s'", file->path_lock);
+ goto on_error;
+ }
- p_close(file->fd);
file->fd = -1;
- error = git_futils_mv_atomic(file->path_lock, file->path_original);
+ p_unlink(file->path_original);
+
+ if (p_rename(file->path_lock, file->path_original) < 0) {
+ giterr_set(GITERR_OS, "Failed to rename lockfile to '%s'", file->path_original);
+ goto on_error;
+ }
-cleanup:
git_filebuf_cleanup(file);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to commit locked file from buffer");
- return GIT_SUCCESS;
+ return 0;
+
+on_error:
+ git_filebuf_cleanup(file);
+ return -1;
}
GIT_INLINE(void) add_to_cache(git_filebuf *file, const void *buf, size_t len)
@@ -303,22 +357,25 @@ GIT_INLINE(void) add_to_cache(git_filebuf *file, const void *buf, size_t len)
int git_filebuf_write(git_filebuf *file, const void *buff, size_t len)
{
- int error;
const unsigned char *buf = buff;
+ ENSURE_BUF_OK(file);
+
+ if (file->do_not_buffer)
+ return file->write(file, (void *)buff, len);
+
for (;;) {
size_t space_left = file->buf_size - file->buf_pos;
/* cache if it's small */
if (space_left > len) {
add_to_cache(file, buf, len);
- return GIT_SUCCESS;
+ return 0;
}
add_to_cache(file, buf, space_left);
-
- if ((error = flush_buffer(file)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to write to buffer");
+ if (flush_buffer(file) < 0)
+ return -1;
len -= space_left;
buf += space_left;
@@ -327,32 +384,37 @@ int git_filebuf_write(git_filebuf *file, const void *buff, size_t len)
int git_filebuf_reserve(git_filebuf *file, void **buffer, size_t len)
{
- int error;
size_t space_left = file->buf_size - file->buf_pos;
*buffer = NULL;
- if (len > file->buf_size)
- return GIT_ENOMEM;
+ ENSURE_BUF_OK(file);
+
+ if (len > file->buf_size) {
+ file->last_error = BUFERR_MEM;
+ return -1;
+ }
if (space_left <= len) {
- if ((error = flush_buffer(file)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to reserve buffer");
+ if (flush_buffer(file) < 0)
+ return -1;
}
*buffer = (file->buffer + file->buf_pos);
file->buf_pos += len;
- return GIT_SUCCESS;
+ return 0;
}
int git_filebuf_printf(git_filebuf *file, const char *format, ...)
{
va_list arglist;
size_t space_left;
- int len, error;
+ int len, res;
char *tmp_buffer;
+ ENSURE_BUF_OK(file);
+
space_left = file->buf_size - file->buf_pos;
do {
@@ -360,37 +422,65 @@ int git_filebuf_printf(git_filebuf *file, const char *format, ...)
len = p_vsnprintf((char *)file->buffer + file->buf_pos, space_left, format, arglist);
va_end(arglist);
- if (len < 0)
- return git__throw(GIT_EOSERR, "Failed to format string");
+ if (len < 0) {
+ file->last_error = BUFERR_MEM;
+ return -1;
+ }
if ((size_t)len + 1 <= space_left) {
file->buf_pos += len;
- return GIT_SUCCESS;
+ return 0;
}
- if ((error = flush_buffer(file)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to output to buffer");
+ if (flush_buffer(file) < 0)
+ return -1;
space_left = file->buf_size - file->buf_pos;
} while ((size_t)len + 1 <= space_left);
tmp_buffer = git__malloc(len + 1);
- if (!tmp_buffer)
- return GIT_ENOMEM;
+ if (!tmp_buffer) {
+ file->last_error = BUFERR_MEM;
+ return -1;
+ }
va_start(arglist, format);
len = p_vsnprintf(tmp_buffer, len + 1, format, arglist);
va_end(arglist);
if (len < 0) {
- free(tmp_buffer);
- return git__throw(GIT_EOSERR, "Failed to format string");
+ git__free(tmp_buffer);
+ file->last_error = BUFERR_MEM;
+ return -1;
}
- error = git_filebuf_write(file, tmp_buffer, len);
- free(tmp_buffer);
+ res = git_filebuf_write(file, tmp_buffer, len);
+ git__free(tmp_buffer);
- return error;
+ 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 9154cabcd..044af5405 100644
--- a/src/filebuf.h
+++ b/src/filebuf.h
@@ -1,9 +1,15 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
#ifndef INCLUDE_filebuf_h__
#define INCLUDE_filebuf_h__
#include "fileops.h"
#include "hash.h"
-#include "git2/zlib.h"
+#include <zlib.h>
#ifdef GIT_THREADS
# define GIT_FILEBUF_THREADS
@@ -13,7 +19,8 @@
#define GIT_FILEBUF_APPEND (1 << 2)
#define GIT_FILEBUF_FORCE (1 << 3)
#define GIT_FILEBUF_TEMPORARY (1 << 4)
-#define GIT_FILEBUF_DEFLATE_CONTENTS (1 << 5)
+#define GIT_FILEBUF_DO_NOT_BUFFER (1 << 5)
+#define GIT_FILEBUF_DEFLATE_SHIFT (6)
#define GIT_FILELOCK_EXTENSION ".lock\0"
#define GIT_FILELOCK_EXTLENGTH 6
@@ -24,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;
@@ -34,18 +42,47 @@ struct git_filebuf {
size_t buf_size, buf_pos;
git_file fd;
+ bool fd_is_open;
+ bool do_not_buffer;
+ int last_error;
};
typedef struct git_filebuf git_filebuf;
+#define GIT_FILEBUF_INIT {0}
+
+/*
+ * The git_filebuf object lifecycle is:
+ * - Allocate git_filebuf, preferably using GIT_FILEBUF_INIT.
+ *
+ * - Call git_filebuf_open() to initialize the filebuf for use.
+ *
+ * - Make as many calls to git_filebuf_write(), git_filebuf_printf(),
+ * git_filebuf_reserve() as you like. The error codes for these
+ * functions don't need to be checked. They are stored internally
+ * by the file buffer.
+ *
+ * - While you are writing, you may call git_filebuf_hash() to get
+ * the hash of all you have written so far. This function will
+ * fail if any of the previous writes to the buffer failed.
+ *
+ * - To close the git_filebuf, you may call git_filebuf_commit() or
+ * git_filebuf_commit_at() to save the file, or
+ * git_filebuf_cleanup() to abandon the file. All of these will
+ * free the git_filebuf object. Likewise, all of these will fail
+ * if any of the previous writes to the buffer failed, and set
+ * an error code accordingly.
+ */
int git_filebuf_write(git_filebuf *lock, const void *buff, size_t len);
int git_filebuf_reserve(git_filebuf *file, void **buff, size_t len);
int git_filebuf_printf(git_filebuf *file, const char *format, ...) GIT_FORMAT_PRINTF(2, 3);
-int git_filebuf_open(git_filebuf *lock, const char *path, int flags);
+int git_filebuf_open(git_filebuf *lock, const char *path, int flags, mode_t mode);
int git_filebuf_commit(git_filebuf *lock);
int git_filebuf_commit_at(git_filebuf *lock, const char *path);
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 c7fddc623..bebbae4f9 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -1,225 +1,252 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "fileops.h"
+#include "global.h"
#include <ctype.h>
+#if GIT_WIN32
+#include "win32/findfile.h"
+#endif
-int git_futils_mv_atomic(const char *from, const char *to)
+int git_futils_mkpath2file(const char *file_path, const mode_t mode)
{
-#ifdef GIT_WIN32
- /*
- * Win32 POSIX compilance my ass. If the destination
- * file exists, the `rename` call fails. This is as
- * close as it gets with the Win32 API.
- */
- return MoveFileEx(from, to, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? GIT_SUCCESS : GIT_EOSERR;
-#else
- /* Don't even try this on Win32 */
- if (!link(from, to)) {
- p_unlink(from);
- return GIT_SUCCESS;
- }
-
- if (!rename(from, to))
- return GIT_SUCCESS;
-
- return GIT_ERROR;
-#endif
+ return git_futils_mkdir(
+ file_path, NULL, mode,
+ GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR);
}
-int git_futils_mkpath2file(const char *file_path)
+int git_futils_mktmp(git_buf *path_out, const char *filename, mode_t mode)
{
- const int mode = 0755; /* or 0777 ? */
- int error = GIT_SUCCESS;
- char target_folder_path[GIT_PATH_MAX];
+ int fd;
+ mode_t mask;
+
+ p_umask(mask = p_umask(0));
- error = git_path_dirname_r(target_folder_path, sizeof(target_folder_path), file_path);
- if (error < GIT_SUCCESS)
- return git__throw(GIT_EINVALIDPATH, "Failed to recursively build `%s` tree structure. Unable to parse parent folder name", file_path);
+ git_buf_sets(path_out, filename);
+ git_buf_puts(path_out, "_git2_XXXXXX");
- /* Does the containing folder exist? */
- if (git_futils_isdir(target_folder_path)) {
- git_path_join(target_folder_path, target_folder_path, ""); /* Ensure there's a trailing slash */
+ if (git_buf_oom(path_out))
+ return -1;
- /* Let's create the tree structure */
- error = git_futils_mkdir_r(target_folder_path, mode);
- if (error < GIT_SUCCESS)
- return error; /* The callee already takes care of setting the correct error message. */
+ if ((fd = p_mkstemp(path_out->ptr)) < 0) {
+ giterr_set(GITERR_OS,
+ "Failed to create temporary file '%s'", path_out->ptr);
+ return -1;
}
- return GIT_SUCCESS;
+ if (p_chmod(path_out->ptr, (mode & ~mask))) {
+ giterr_set(GITERR_OS,
+ "Failed to set permissions on file '%s'", path_out->ptr);
+ return -1;
+ }
+
+ return fd;
}
-int git_futils_mktmp(char *path_out, const char *filename)
+int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode)
{
int fd;
- strcpy(path_out, filename);
- strcat(path_out, "_git2_XXXXXX");
-
-#if defined(_MSC_VER)
- /* FIXME: there may be race conditions when multi-threading
- * with the library */
- if (_mktemp_s(path_out, GIT_PATH_MAX) != 0)
- return git__throw(GIT_EOSERR, "Failed to make temporary file %s", path_out);
+ if (git_futils_mkpath2file(path, dirmode) < 0)
+ return -1;
- fd = p_creat(path_out, 0744);
-#else
- fd = mkstemp(path_out);
-#endif
+ fd = p_creat(path, mode);
+ if (fd < 0) {
+ giterr_set(GITERR_OS, "Failed to create file '%s'", path);
+ return -1;
+ }
- return fd >= 0 ? fd : git__throw(GIT_EOSERR, "Failed to create temporary file %s", path_out);
+ return fd;
}
-int git_futils_creat_withpath(const char *path, int mode)
+int git_futils_creat_locked(const char *path, const mode_t mode)
{
- if (git_futils_mkpath2file(path) < GIT_SUCCESS)
- return git__throw(GIT_EOSERR, "Failed to create file %s", path);
+ int fd = p_open(path, O_WRONLY | O_CREAT | O_TRUNC |
+ O_EXCL | O_BINARY | O_CLOEXEC, mode);
- return p_creat(path, mode);
-}
+ if (fd < 0) {
+ giterr_set(GITERR_OS, "Failed to create locked file '%s'", path);
+ return errno == EEXIST ? GIT_ELOCKED : -1;
+ }
-int git_futils_creat_locked(const char *path, int mode)
-{
- int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode);
- return fd >= 0 ? fd : git__throw(GIT_EOSERR, "Failed to create locked file. Could not open %s", path);
+ return fd;
}
-int git_futils_creat_locked_withpath(const char *path, int mode)
+int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode)
{
- if (git_futils_mkpath2file(path) < GIT_SUCCESS)
- return git__throw(GIT_EOSERR, "Failed to create locked file %s", path);
+ if (git_futils_mkpath2file(path, dirmode) < 0)
+ return -1;
return git_futils_creat_locked(path, mode);
}
-int git_futils_isdir(const char *path)
+int git_futils_open_ro(const char *path)
{
-#ifdef GIT_WIN32
- DWORD attr = GetFileAttributes(path);
- if (attr == INVALID_FILE_ATTRIBUTES)
- return GIT_ERROR;
+ int fd = p_open(path, O_RDONLY);
+ if (fd < 0)
+ return git_path_set_error(errno, path, "open");
+ return fd;
+}
- return (attr & FILE_ATTRIBUTE_DIRECTORY) ? GIT_SUCCESS : GIT_ERROR;
+git_off_t git_futils_filesize(git_file fd)
+{
+ struct stat sb;
-#else
- struct stat st;
- if (p_stat(path, &st) < GIT_SUCCESS)
- return GIT_ERROR;
+ if (p_fstat(fd, &sb)) {
+ giterr_set(GITERR_OS, "Failed to stat file descriptor");
+ return -1;
+ }
- return S_ISDIR(st.st_mode) ? GIT_SUCCESS : GIT_ERROR;
-#endif
+ return sb.st_size;
}
-int git_futils_isfile(const char *path)
+mode_t git_futils_canonical_mode(mode_t raw_mode)
{
- struct stat st;
- int stat_error;
+ if (S_ISREG(raw_mode))
+ return S_IFREG | GIT_PERMS_CANONICAL(raw_mode);
+ else if (S_ISLNK(raw_mode))
+ return S_IFLNK;
+ else if (S_ISGITLINK(raw_mode))
+ return S_IFGITLINK;
+ else if (S_ISDIR(raw_mode))
+ return S_IFDIR;
+ else
+ return 0;
+}
- assert(path);
- stat_error = p_stat(path, &st);
+int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len)
+{
+ ssize_t read_size = 0;
- if (stat_error < GIT_SUCCESS)
- return -1;
+ git_buf_clear(buf);
- if (!S_ISREG(st.st_mode))
+ if (git_buf_grow(buf, len + 1) < 0)
return -1;
- return 0;
-}
+ /* p_read loops internally to read len bytes */
+ read_size = p_read(fd, buf->ptr, len);
-int git_futils_exists(const char *path)
-{
- assert(path);
- return access(path, F_OK);
-}
+ if (read_size != (ssize_t)len) {
+ giterr_set(GITERR_OS, "Failed to read descriptor");
+ git_buf_free(buf);
+ return -1;
+ }
-git_off_t git_futils_filesize(git_file fd)
-{
- struct stat sb;
- if (p_fstat(fd, &sb))
- return GIT_ERROR;
+ buf->ptr[read_size] = '\0';
+ buf->size = read_size;
- return sb.st_size;
+ return 0;
}
-int git_futils_readbuffer_updated(git_fbuffer *obj, const char *path, time_t *mtime, int *updated)
+int git_futils_readbuffer_updated(
+ git_buf *buf, const char *path, time_t *mtime, size_t *size, int *updated)
{
git_file fd;
- size_t len;
struct stat st;
- unsigned char *buff;
+ bool changed = false;
- assert(obj && path && *path);
+ assert(buf && path && *path);
if (updated != NULL)
*updated = 0;
if (p_stat(path, &st) < 0)
- return git__throw(GIT_ENOTFOUND, "Failed to stat file %s", path);
+ return git_path_set_error(errno, path, "stat");
+
+
+ if (S_ISDIR(st.st_mode)) {
+ giterr_set(GITERR_INVALID, "requested file is a directory");
+ return GIT_ENOTFOUND;
+ }
- if (S_ISDIR(st.st_mode))
- return git__throw(GIT_ERROR, "Can't read a dir into a buffer");
+ if (!git__is_sizet(st.st_size+1)) {
+ giterr_set(GITERR_OS, "Invalid regular file stat for '%s'", path);
+ return -1;
+ }
/*
- * 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)
- return GIT_SUCCESS;
+ 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) {
+ return 0;
+ }
if (mtime != NULL)
*mtime = st.st_mtime;
- if (!git__is_sizet(st.st_size+1))
- return git__throw(GIT_ERROR, "Failed to read file `%s`. An error occured while calculating its size", path);
-
- len = (size_t) st.st_size;
+ if (size != NULL)
+ *size = (size_t)st.st_size;
- if ((fd = p_open(path, O_RDONLY)) < 0)
- return git__throw(GIT_EOSERR, "Failed to open %s for reading", path);
-
- if ((buff = git__malloc(len + 1)) == NULL) {
- p_close(fd);
- return GIT_ENOMEM;
- }
+ if ((fd = git_futils_open_ro(path)) < 0)
+ return fd;
- if (p_read(fd, buff, len) < 0) {
+ if (git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size) < 0) {
p_close(fd);
- free(buff);
- return git__throw(GIT_ERROR, "Failed to read file `%s`", path);
+ return -1;
}
- buff[len] = '\0';
p_close(fd);
- if (mtime != NULL)
- *mtime = st.st_mtime;
if (updated != NULL)
*updated = 1;
- obj->data = buff;
- obj->len = len;
-
- return GIT_SUCCESS;
+ return 0;
}
-int git_futils_readbuffer(git_fbuffer *obj, const char *path)
+int git_futils_readbuffer(git_buf *buf, const char *path)
{
- return git_futils_readbuffer_updated(obj, path, NULL, NULL);
+ return git_futils_readbuffer_updated(buf, path, NULL, NULL, NULL);
}
-void git_futils_freebuffer(git_fbuffer *obj)
+int git_futils_writebuffer(
+ const git_buf *buf, const char *path, int flags, mode_t mode)
{
- assert(obj);
- free(obj->data);
- obj->data = NULL;
-}
+ int fd, error = 0;
+ if (flags <= 0)
+ flags = O_CREAT | O_TRUNC | O_WRONLY;
+ if (!mode)
+ mode = GIT_FILEMODE_BLOB;
-int git_futils_mv_withpath(const char *from, const char *to)
+ if ((fd = p_open(path, flags, mode)) < 0) {
+ giterr_set(GITERR_OS, "Could not open '%s' for writing", path);
+ return fd;
+ }
+
+ if ((error = p_write(fd, git_buf_cstr(buf), git_buf_len(buf))) < 0) {
+ giterr_set(GITERR_OS, "Could not write to '%s'", path);
+ (void)p_close(fd);
+ return error;
+ }
+
+ if ((error = p_close(fd)) < 0)
+ giterr_set(GITERR_OS, "Error while closing '%s'", path);
+
+ return error;
+}
+
+int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode)
{
- if (git_futils_mkpath2file(to) < GIT_SUCCESS)
- return GIT_EOSERR; /* The callee already takes care of setting the correct error message. */
+ if (git_futils_mkpath2file(to, dirmode) < 0)
+ return -1;
+
+ if (p_rename(from, to) < 0) {
+ giterr_set(GITERR_OS, "Failed to rename '%s' to '%s'", from, to);
+ return -1;
+ }
- return git_futils_mv_atomic(from, to); /* The callee already takes care of setting the correct error message. */
+ return 0;
}
int git_futils_mmap_ro(git_map *out, git_file fd, git_off_t begin, size_t len)
@@ -227,158 +254,594 @@ int git_futils_mmap_ro(git_map *out, git_file fd, git_off_t begin, size_t len)
return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin);
}
+int git_futils_mmap_ro_file(git_map *out, const char *path)
+{
+ git_file fd = git_futils_open_ro(path);
+ git_off_t len;
+ int result;
+
+ if (fd < 0)
+ return fd;
+
+ len = git_futils_filesize(fd);
+ if (!git__is_sizet(len)) {
+ giterr_set(GITERR_OS, "File `%s` too large to mmap", path);
+ return -1;
+ }
+
+ result = git_futils_mmap_ro(out, fd, 0, (size_t)len);
+ p_close(fd);
+ return result;
+}
+
void git_futils_mmap_free(git_map *out)
{
p_munmap(out);
}
-/* Taken from git.git */
-GIT_INLINE(int) is_dot_or_dotdot(const char *name)
+int git_futils_mkdir(
+ const char *path,
+ const char *base,
+ mode_t mode,
+ uint32_t flags)
+{
+ int error = -1;
+ git_buf make_path = GIT_BUF_INIT;
+ ssize_t root = 0, min_root_len;
+ char lastch = '/', *tail;
+ struct stat st;
+
+ /* build path and find "root" where we should start calling mkdir */
+ if (git_path_join_unrooted(&make_path, path, base, &root) < 0)
+ return -1;
+
+ if (make_path.size == 0) {
+ giterr_set(GITERR_OS, "Attempt to create empty path");
+ goto done;
+ }
+
+ /* remove trailing slashes on path */
+ while (make_path.ptr[make_path.size - 1] == '/') {
+ make_path.size--;
+ make_path.ptr[make_path.size] = '\0';
+ }
+
+ /* if we are not supposed to made the last element, truncate it */
+ if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) {
+ git_buf_rtruncate_at_char(&make_path, '/');
+ flags |= GIT_MKDIR_SKIP_LAST;
+ }
+ if ((flags & GIT_MKDIR_SKIP_LAST) != 0)
+ git_buf_rtruncate_at_char(&make_path, '/');
+
+ /* if nothing left after truncation, then we're done! */
+ if (!make_path.size) {
+ error = 0;
+ goto done;
+ }
+
+ /* if we are not supposed to make the whole path, reset root */
+ if ((flags & GIT_MKDIR_PATH) == 0)
+ root = git_buf_rfind(&make_path, '/');
+
+ /* advance root past drive name or network mount prefix */
+ min_root_len = git_path_root(make_path.ptr);
+ if (root < min_root_len)
+ root = min_root_len;
+ while (root >= 0 && make_path.ptr[root] == '/')
+ ++root;
+
+ /* clip root to make_path length */
+ if (root > (ssize_t)make_path.size)
+ root = (ssize_t)make_path.size; /* i.e. NUL byte of string */
+ if (root < 0)
+ root = 0;
+
+ /* walk down tail of path making each directory */
+ for (tail = &make_path.ptr[root]; *tail; *tail = lastch) {
+
+ /* advance tail to include next path component */
+ while (*tail == '/')
+ tail++;
+ while (*tail && *tail != '/')
+ tail++;
+
+ /* truncate path at next component */
+ lastch = *tail;
+ *tail = '\0';
+ st.st_mode = 0;
+
+ /* make directory */
+ if (p_mkdir(make_path.ptr, mode) < 0) {
+ int tmp_errno = giterr_system_last();
+
+ /* ignore error if directory already exists */
+ if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) {
+ giterr_system_set(tmp_errno);
+ giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path.ptr);
+ goto done;
+ }
+
+ /* with exclusive create, existing dir is an error */
+ if ((flags & GIT_MKDIR_EXCL) != 0) {
+ giterr_set(GITERR_OS, "Directory already exists '%s'", make_path.ptr);
+ error = GIT_EEXISTS;
+ goto done;
+ }
+ }
+
+ /* chmod if requested and necessary */
+ if (((flags & GIT_MKDIR_CHMOD_PATH) != 0 ||
+ (lastch == '\0' && (flags & GIT_MKDIR_CHMOD) != 0)) &&
+ st.st_mode != mode &&
+ (error = p_chmod(make_path.ptr, mode)) < 0) {
+ giterr_set(GITERR_OS, "Failed to set permissions on '%s'", make_path.ptr);
+ goto done;
+ }
+ }
+
+ error = 0;
+
+ /* check that full path really is a directory if requested & needed */
+ if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 &&
+ lastch != '\0' &&
+ (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode))) {
+ giterr_set(GITERR_OS, "Path is not a directory '%s'", make_path.ptr);
+ error = GIT_ENOTFOUND;
+ }
+
+done:
+ git_buf_free(&make_path);
+ return error;
+}
+
+int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode)
{
- return (name[0] == '.' &&
- (name[1] == '\0' ||
- (name[1] == '.' && name[2] == '\0')));
+ return git_futils_mkdir(path, base, mode, GIT_MKDIR_PATH);
}
-int git_futils_direach(
- char *path,
- size_t path_sz,
- int (*fn)(void *, char *),
- void *arg)
+typedef struct {
+ const char *base;
+ size_t baselen;
+ uint32_t flags;
+ int depth;
+} futils__rmdir_data;
+
+#define FUTILS_MAX_DEPTH 100
+
+static int futils__error_cannot_rmdir(const char *path, const char *filemsg)
{
- size_t wd_len = strlen(path);
- DIR *dir;
- struct dirent *de;
+ 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 (!wd_len || path_sz < wd_len + 2)
- return git__throw(GIT_EINVALIDARGS, "Failed to process `%s` tree structure. Path is either empty or buffer size is too short", path);
+ return -1;
+}
- while (path[wd_len - 1] == '/')
- wd_len--;
- path[wd_len++] = '/';
- path[wd_len] = '\0';
+static int futils__rm_first_parent(git_buf *path, const char *ceiling)
+{
+ int error = GIT_ENOTFOUND;
+ struct stat st;
- dir = opendir(path);
- if (!dir)
- return git__throw(GIT_EOSERR, "Failed to process `%s` tree structure. An error occured while opening the directory", path);
+ 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;
+ }
- while ((de = readdir(dir)) != NULL) {
- size_t de_len;
- int result;
+ if (error)
+ futils__error_cannot_rmdir(path->ptr, "cannot remove parent");
- if (is_dot_or_dotdot(de->d_name))
- continue;
+ return error;
+}
+
+static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path)
+{
+ int error = 0;
+ futils__rmdir_data *data = opaque;
+ struct stat st;
- de_len = strlen(de->d_name);
- if (path_sz < wd_len + de_len + 1) {
- closedir(dir);
- return git__throw(GIT_ERROR, "Failed to process `%s` tree structure. Buffer size is too short", path);
+ if (data->depth > FUTILS_MAX_DEPTH)
+ error = futils__error_cannot_rmdir(
+ path->ptr, "directory nesting too deep");
+
+ else if ((error = p_lstat_posixly(path->ptr, &st)) < 0) {
+ if (errno == ENOENT)
+ 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)
+ error = futils__rm_first_parent(path, data->base);
+ else
+ futils__error_cannot_rmdir(
+ path->ptr, "parent is not directory");
}
+ else
+ error = git_path_set_error(errno, path->ptr, "rmdir");
+ }
+
+ else if (S_ISDIR(st.st_mode)) {
+ data->depth++;
- strcpy(path + wd_len, de->d_name);
- result = fn(arg, path);
- if (result < GIT_SUCCESS) {
- closedir(dir);
- return result; /* The callee is reponsible for setting the correct error message */
+ error = git_path_direach(path, 0, futils__rmdir_recurs_foreach, data);
+
+ data->depth--;
+
+ if (error < 0)
+ return error;
+
+ if (data->depth == 0 && (data->flags & GIT_RMDIR_SKIP_ROOT) != 0)
+ return error;
+
+ if ((error = p_rmdir(path->ptr)) < 0) {
+ if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 &&
+ (errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY))
+ error = 0;
+ else
+ error = git_path_set_error(errno, path->ptr, "rmdir");
}
- if (result > 0) {
- closedir(dir);
- return result;
+ }
+
+ else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) {
+ if (p_unlink(path->ptr) < 0)
+ error = git_path_set_error(errno, path->ptr, "remove");
+ }
+
+ else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0)
+ error = futils__error_cannot_rmdir(path->ptr, "still present");
+
+ return error;
+}
+
+static int futils__rmdir_empty_parent(void *opaque, git_buf *path)
+{
+ futils__rmdir_data *data = opaque;
+ int error = 0;
+
+ if (git_buf_len(path) <= data->baselen)
+ error = GIT_ITEROVER;
+
+ else if (p_rmdir(git_buf_cstr(path)) < 0) {
+ int en = errno;
+
+ if (en == ENOENT || en == ENOTDIR) {
+ /* do nothing */
+ } else if (en == ENOTEMPTY || en == EEXIST || en == EBUSY) {
+ error = GIT_ITEROVER;
+ } else {
+ error = git_path_set_error(errno, git_buf_cstr(path), "rmdir");
}
}
- closedir(dir);
- return GIT_SUCCESS;
+ return error;
}
-int git_futils_mkdir_r(const char *path, int mode)
+int git_futils_rmdir_r(
+ const char *path, const char *base, uint32_t flags)
{
- int error, root_path_offset;
- char *pp, *sp;
- char *path_copy = git__strdup(path);
+ int error;
+ git_buf fullpath = GIT_BUF_INIT;
+ futils__rmdir_data data;
+
+ /* build path and find "root" where we should start calling mkdir */
+ if (git_path_join_unrooted(&fullpath, path, base, NULL) < 0)
+ return -1;
- if (path_copy == NULL)
- return GIT_ENOMEM;
+ memset(&data, 0, sizeof(data));
+ data.base = base ? base : "";
+ data.baselen = base ? strlen(base) : 0;
+ data.flags = flags;
- error = GIT_SUCCESS;
- pp = path_copy;
+ error = futils__rmdir_recurs_foreach(&data, &fullpath);
- root_path_offset = git_path_root(pp);
- if (root_path_offset > 0)
- pp += root_path_offset; /* On Windows, will skip the drive name (eg. C: or D:) */
+ /* 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);
- while (error == GIT_SUCCESS && (sp = strchr(pp, '/')) != NULL) {
- if (sp != pp && git_futils_isdir(path_copy) < GIT_SUCCESS) {
- *sp = 0;
- error = p_mkdir(path_copy, mode);
+ if (error == GIT_ITEROVER) {
+ giterr_clear();
+ error = 0;
+ }
- /* Do not choke while trying to recreate an existing directory */
- if (errno == EEXIST)
- error = GIT_SUCCESS;
+ git_buf_free(&fullpath);
- *sp = '/';
- }
+ return error;
+}
+
+int git_futils_fake_symlink(const char *old, const char *new)
+{
+ int retcode = GIT_ERROR;
+ int fd = git_futils_creat_withpath(new, 0755, 0644);
+ if (fd >= 0) {
+ retcode = p_write(fd, old, strlen(old));
+ p_close(fd);
+ }
+ return retcode;
+}
+
+static int cp_by_fd(int ifd, int ofd, bool close_fd_when_done)
+{
+ int error = 0;
+ char buffer[4096];
+ ssize_t len = 0;
+
+ while (!error && (len = p_read(ifd, buffer, sizeof(buffer))) > 0)
+ /* p_write() does not have the same semantics as write(). It loops
+ * internally and will return 0 when it has completed writing.
+ */
+ error = p_write(ofd, buffer, len);
+
+ if (len < 0) {
+ giterr_set(GITERR_OS, "Read error while copying file");
+ error = (int)len;
+ }
+
+ if (close_fd_when_done) {
+ p_close(ifd);
+ p_close(ofd);
+ }
+
+ return error;
+}
+
+int git_futils_cp(const char *from, const char *to, mode_t filemode)
+{
+ int ifd, ofd;
+
+ if ((ifd = git_futils_open_ro(from)) < 0)
+ return ifd;
- pp = sp + 1;
+ if ((ofd = p_open(to, O_WRONLY | O_CREAT | O_EXCL, filemode)) < 0) {
+ p_close(ifd);
+ return git_path_set_error(errno, to, "open for writing");
}
- if (*pp != '\0' && error == GIT_SUCCESS) {
- error = p_mkdir(path, mode);
- if (errno == EEXIST)
- error = GIT_SUCCESS;
+ return cp_by_fd(ifd, ofd, true);
+}
+
+static int cp_link(const char *from, const char *to, size_t link_size)
+{
+ int error = 0;
+ ssize_t read_len;
+ char *link_data = git__malloc(link_size + 1);
+ GITERR_CHECK_ALLOC(link_data);
+
+ read_len = p_readlink(from, link_data, link_size);
+ if (read_len != (ssize_t)link_size) {
+ giterr_set(GITERR_OS, "Failed to read symlink data for '%s'", from);
+ error = -1;
}
+ else {
+ link_data[read_len] = '\0';
- free(path_copy);
+ if (p_symlink(link_data, to) < 0) {
+ giterr_set(GITERR_OS, "Could not symlink '%s' as '%s'",
+ link_data, to);
+ error = -1;
+ }
+ }
+
+ git__free(link_data);
+ return error;
+}
+
+typedef struct {
+ const char *to_root;
+ git_buf to;
+ ssize_t from_prefix;
+ uint32_t flags;
+ uint32_t mkdir_flags;
+ mode_t dirmode;
+} cp_r_info;
- if (error < GIT_SUCCESS)
- return git__throw(error, "Failed to recursively create `%s` tree structure", path);
+#define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10)
- return GIT_SUCCESS;
+static int _cp_r_mkdir(cp_r_info *info, git_buf *from)
+{
+ int error = 0;
+
+ /* create root directory the first time we need to create a directory */
+ if ((info->flags & GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT) == 0) {
+ error = git_futils_mkdir(
+ info->to_root, NULL, info->dirmode,
+ (info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0);
+
+ info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT;
+ }
+
+ /* create directory with root as base to prevent excess chmods */
+ if (!error)
+ error = git_futils_mkdir(
+ from->ptr + info->from_prefix, info->to_root,
+ info->dirmode, info->mkdir_flags);
+
+ return error;
}
-static int _rmdir_recurs_foreach(void *opaque, char *path)
+static int _cp_r_callback(void *ref, git_buf *from)
{
- int error = GIT_SUCCESS;
- int force = *(int *)opaque;
+ int error = 0;
+ cp_r_info *info = ref;
+ struct stat from_st, to_st;
+ bool exists = false;
+
+ if ((info->flags & GIT_CPDIR_COPY_DOTFILES) == 0 &&
+ from->ptr[git_path_basename_offset(from)] == '.')
+ return 0;
+
+ if ((error = git_buf_joinpath(
+ &info->to, info->to_root, from->ptr + info->from_prefix)) < 0)
+ return error;
+
+ if (!(error = git_path_lstat(info->to.ptr, &to_st)))
+ exists = true;
+ else if (error != GIT_ENOTFOUND)
+ return error;
+ else {
+ giterr_clear();
+ error = 0;
+ }
+
+ if ((error = git_path_lstat(from->ptr, &from_st)) < 0)
+ return error;
+
+ if (S_ISDIR(from_st.st_mode)) {
+ mode_t oldmode = info->dirmode;
+
+ /* if we are not chmod'ing, then overwrite dirmode */
+ if ((info->flags & GIT_CPDIR_CHMOD_DIRS) == 0)
+ info->dirmode = from_st.st_mode;
+
+ /* make directory now if CREATE_EMPTY_DIRS is requested and needed */
+ if (!exists && (info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) != 0)
+ error = _cp_r_mkdir(info, from);
+
+ /* recurse onto target directory */
+ if (!error && (!exists || S_ISDIR(to_st.st_mode)))
+ error = git_path_direach(from, 0, _cp_r_callback, info);
+
+ if (oldmode != 0)
+ info->dirmode = oldmode;
- if (git_futils_isdir(path) == GIT_SUCCESS) {
- size_t root_size = strlen(path);
+ return error;
+ }
- if ((error = git_futils_direach(path, GIT_PATH_MAX, _rmdir_recurs_foreach, opaque)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to remove directory `%s`", path);
+ if (exists) {
+ if ((info->flags & GIT_CPDIR_OVERWRITE) == 0)
+ return 0;
- path[root_size] = '\0';
- return p_rmdir(path);
+ if (p_unlink(info->to.ptr) < 0) {
+ giterr_set(GITERR_OS, "Cannot overwrite existing file '%s'",
+ info->to.ptr);
+ return GIT_EEXISTS;
+ }
+ }
- } else if (force) {
- return p_unlink(path);
+ /* Done if this isn't a regular file or a symlink */
+ if (!S_ISREG(from_st.st_mode) &&
+ (!S_ISLNK(from_st.st_mode) ||
+ (info->flags & GIT_CPDIR_COPY_SYMLINKS) == 0))
+ return 0;
+
+ /* Make container directory on demand if needed */
+ if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 &&
+ (error = _cp_r_mkdir(info, from)) < 0)
+ return error;
+
+ /* make symlink or regular file */
+ if (info->flags & GIT_CPDIR_LINK_FILES) {
+ error = p_link(from->ptr, info->to.ptr);
+ } else if (S_ISLNK(from_st.st_mode)) {
+ error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size);
+ } else {
+ mode_t usemode = from_st.st_mode;
+
+ if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0)
+ usemode = GIT_PERMS_FOR_WRITE(usemode);
+
+ error = git_futils_cp(from->ptr, info->to.ptr, usemode);
}
- return git__rethrow(error, "Failed to remove directory. `%s` is not empty", path);
+ return error;
}
-int git_futils_rmdir_r(const char *path, int force)
+int git_futils_cp_r(
+ const char *from,
+ const char *to,
+ uint32_t flags,
+ mode_t dirmode)
{
- char p[GIT_PATH_MAX];
- strncpy(p, path, GIT_PATH_MAX);
- return _rmdir_recurs_foreach(&force, p);
+ int error;
+ git_buf path = GIT_BUF_INIT;
+ cp_r_info info;
+
+ if (git_buf_joinpath(&path, from, "") < 0) /* ensure trailing slash */
+ return -1;
+
+ memset(&info, 0, sizeof(info));
+ info.to_root = to;
+ info.flags = flags;
+ info.dirmode = dirmode;
+ info.from_prefix = path.size;
+ git_buf_init(&info.to, 0);
+
+ /* precalculate mkdir flags */
+ if ((flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0) {
+ /* if not creating empty dirs, then use mkdir to create the path on
+ * demand right before files are copied.
+ */
+ info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST;
+ if ((flags & GIT_CPDIR_CHMOD_DIRS) != 0)
+ info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH;
+ } else {
+ /* otherwise, we will do simple mkdir as directories are encountered */
+ info.mkdir_flags =
+ ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) ? GIT_MKDIR_CHMOD : 0;
+ }
+
+ error = _cp_r_callback(&info, &path);
+
+ git_buf_free(&path);
+ git_buf_free(&info.to);
+
+ return error;
}
-int git_futils_cmp_path(const char *name1, int len1, int isdir1,
- const char *name2, int len2, int isdir2)
+int git_futils_filestamp_check(
+ git_futils_filestamp *stamp, const char *path)
{
- int len = len1 < len2 ? len1 : len2;
- int cmp;
+ struct stat st;
- cmp = memcmp(name1, name2, len);
- if (cmp)
- return cmp;
- if (len1 < len2)
- return ((!isdir1 && !isdir2) ? -1 :
- (isdir1 ? '/' - name2[len1] : name2[len1] - '/'));
- if (len1 > len2)
- return ((!isdir1 && !isdir2) ? 1 :
- (isdir2 ? name1[len2] - '/' : '/' - name1[len2]));
- return 0;
+ /* 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));
}
+
+void git_futils_filestamp_set_from_stat(
+ git_futils_filestamp *stamp, struct stat *st)
+{
+ if (st) {
+ stamp->mtime = (git_time_t)st->st_mtime;
+ stamp->size = (git_off_t)st->st_size;
+ stamp->ino = (unsigned int)st->st_ino;
+ } else {
+ memset(stamp, 0, sizeof(*stamp));
+ }
+}
diff --git a/src/fileops.h b/src/fileops.h
index 84c35e41b..4f5700a99 100644
--- a/src/fileops.h
+++ b/src/fileops.h
@@ -1,14 +1,14 @@
/*
- * fileops.h - OS agnostic disk io operations
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * This header describes the strictly internal part of the api
+ * 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_fileops_h__
#define INCLUDE_fileops_h__
#include "common.h"
#include "map.h"
-#include "dir.h"
#include "posix.h"
#include "path.h"
@@ -17,16 +17,13 @@
*
* Read whole files into an in-memory buffer for processing
*/
-#define GIT_FBUFFER_INIT {NULL, 0}
+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, size_t *size, int *updated);
+extern int git_futils_readbuffer_fd(git_buf *obj, git_file fd, size_t len);
-typedef struct { /* file io buffer */
- void *data; /* data bytes */
- size_t len; /* data length */
-} git_fbuffer;
-
-extern int git_futils_readbuffer(git_fbuffer *obj, const char *path);
-extern int git_futils_readbuffer_updated(git_fbuffer *obj, const char *path, time_t *mtime, int *updated);
-extern void git_futils_freebuffer(git_fbuffer *obj);
+extern int git_futils_writebuffer(
+ const git_buf *buf, const char *path, int open_flags, mode_t mode);
/**
* File utils
@@ -34,77 +31,203 @@ extern void git_futils_freebuffer(git_fbuffer *obj);
* These are custom filesystem-related helper methods. They are
* rather high level, and wrap the underlying POSIX methods
*
- * All these methods return GIT_SUCCESS on success,
+ * All these methods return 0 on success,
* or an error code on failure and an error message is set.
*/
/**
- * Check if a file exists and can be accessed.
- */
-extern int git_futils_exists(const char *path);
-
-/**
* Create and open a file, while also
* creating all the folders in its path
*/
-extern int git_futils_creat_withpath(const char *path, int mode);
+extern int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode);
/**
* Create an open a process-locked file
*/
-extern int git_futils_creat_locked(const char *path, int mode);
+extern int git_futils_creat_locked(const char *path, const mode_t mode);
/**
* Create an open a process-locked file, while
* also creating all the folders in its path
*/
-extern int git_futils_creat_locked_withpath(const char *path, int mode);
+extern int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode);
/**
- * Check if the given path points to a directory
+ * Create a path recursively
+ *
+ * If a base parameter is being passed, it's expected to be valued with a
+ * path pointing to an already existing directory.
*/
-extern int git_futils_isdir(const char *path);
+extern int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode);
/**
- * Check if the given path points to a regular file
+ * Flags to pass to `git_futils_mkdir`.
+ *
+ * * GIT_MKDIR_EXCL is "exclusive" - i.e. generate an error if dir exists.
+ * * GIT_MKDIR_PATH says to make all components in the path.
+ * * GIT_MKDIR_CHMOD says to chmod the final directory entry after creation
+ * * GIT_MKDIR_CHMOD_PATH says to chmod each directory component in the path
+ * * GIT_MKDIR_SKIP_LAST says to leave off the last element of the path
+ * * GIT_MKDIR_SKIP_LAST2 says to leave off the last 2 elements of the path
+ * * GIT_MKDIR_VERIFY_DIR says confirm final item is a dir, not just EEXIST
+ *
+ * Note that the chmod options will be executed even if the directory already
+ * exists, unless GIT_MKDIR_EXCL is given.
*/
-extern int git_futils_isfile(const char *path);
+typedef enum {
+ GIT_MKDIR_EXCL = 1,
+ GIT_MKDIR_PATH = 2,
+ GIT_MKDIR_CHMOD = 4,
+ GIT_MKDIR_CHMOD_PATH = 8,
+ GIT_MKDIR_SKIP_LAST = 16,
+ GIT_MKDIR_SKIP_LAST2 = 32,
+ GIT_MKDIR_VERIFY_DIR = 64,
+} git_futils_mkdir_flags;
/**
- * Create a path recursively
+ * Create a directory or entire path.
+ *
+ * This makes a directory (and the entire path leading up to it if requested),
+ * and optionally chmods the directory immediately after (or each part of the
+ * path if requested).
+ *
+ * @param path The path to create.
+ * @param base Root for relative path. These directories will never be made.
+ * @param mode The mode to use for created directories.
+ * @param flags Combination of the mkdir flags above.
+ * @return 0 on success, else error code
*/
-extern int git_futils_mkdir_r(const char *path, int mode);
+extern int git_futils_mkdir(const char *path, const char *base, mode_t mode, uint32_t flags);
/**
* Create all the folders required to contain
* the full path of a file
*/
-extern int git_futils_mkpath2file(const char *path);
+extern int git_futils_mkpath2file(const char *path, const mode_t mode);
-extern int git_futils_rmdir_r(const char *path, int force);
+/**
+ * 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
+ * * GIT_RMDIR_SKIP_ROOT - don't remove root directory itself
+ */
+typedef enum {
+ 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_RMDIR_SKIP_ROOT = (1 << 4),
+} git_futils_rmdir_flags;
/**
- * Create and open a temporary file with a `_git2_` suffix
+ * Remove path and any files and directories beneath it.
+ *
+ * @param path Path to the top level directory to process.
+ * @param base Root for relative path.
+ * @param flags Combination of git_futils_rmdir_flags values
+ * @return 0 on success; -1 on error.
*/
-extern int git_futils_mktmp(char *path_out, const char *filename);
+extern int git_futils_rmdir_r(const char *path, const char *base, uint32_t flags);
/**
- * Atomically rename a file on the filesystem
+ * Create and open a temporary file with a `_git2_` suffix.
+ * Writes the filename into path_out.
+ * @return On success, an open file descriptor, else an error code < 0.
*/
-extern int git_futils_mv_atomic(const char *from, const char *to);
+extern int git_futils_mktmp(git_buf *path_out, const char *filename, mode_t mode);
/**
* Move a file on the filesystem, create the
* destination path if it doesn't exist
*/
-extern int git_futils_mv_withpath(const char *from, const char *to);
+extern int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode);
+/**
+ * Copy a file
+ *
+ * The filemode will be used for the newly created file.
+ */
+extern int git_futils_cp(
+ const char *from,
+ const char *to,
+ mode_t filemode);
+
+/**
+ * Flags that can be passed to `git_futils_cp_r`.
+ *
+ * - GIT_CPDIR_CREATE_EMPTY_DIRS: create directories even if there are no
+ * files under them (otherwise directories will only be created lazily
+ * when a file inside them is copied).
+ * - GIT_CPDIR_COPY_SYMLINKS: copy symlinks, otherwise they are ignored.
+ * - GIT_CPDIR_COPY_DOTFILES: copy files with leading '.', otherwise ignored.
+ * - GIT_CPDIR_OVERWRITE: overwrite pre-existing files with source content,
+ * otherwise they are silently skipped.
+ * - GIT_CPDIR_CHMOD_DIRS: explicitly chmod directories to `dirmode`
+ * - GIT_CPDIR_SIMPLE_TO_MODE: default tries to replicate the mode of the
+ * source file to the target; with this flag, always use 0666 (or 0777 if
+ * source has exec bits set) for target.
+ * - GIT_CPDIR_LINK_FILES will try to use hardlinks for the files
+ */
+typedef enum {
+ GIT_CPDIR_CREATE_EMPTY_DIRS = (1u << 0),
+ GIT_CPDIR_COPY_SYMLINKS = (1u << 1),
+ GIT_CPDIR_COPY_DOTFILES = (1u << 2),
+ GIT_CPDIR_OVERWRITE = (1u << 3),
+ GIT_CPDIR_CHMOD_DIRS = (1u << 4),
+ GIT_CPDIR_SIMPLE_TO_MODE = (1u << 5),
+ GIT_CPDIR_LINK_FILES = (1u << 6),
+} git_futils_cpdir_flags;
+
+/**
+ * Copy a directory tree.
+ *
+ * This copies directories and files from one root to another. You can
+ * pass a combinationof GIT_CPDIR flags as defined above.
+ *
+ * If you pass the CHMOD flag, then the dirmode will be applied to all
+ * directories that are created during the copy, overiding the natural
+ * permissions. If you do not pass the CHMOD flag, then the dirmode
+ * will actually be copied from the source files and the `dirmode` arg
+ * will be ignored.
+ */
+extern int git_futils_cp_r(
+ const char *from,
+ const char *to,
+ uint32_t flags,
+ mode_t dirmode);
+
+/**
+ * Open a file readonly and set error if needed.
+ */
+extern int git_futils_open_ro(const char *path);
/**
* Get the filesize in bytes of a file
*/
extern git_off_t git_futils_filesize(git_file fd);
+#define GIT_PERMS_IS_EXEC(MODE) (((MODE) & 0111) != 0)
+#define GIT_PERMS_CANONICAL(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0755 : 0644)
+#define GIT_PERMS_FOR_WRITE(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0777 : 0666)
+
+#define GIT_MODE_PERMS_MASK 0777
+#define GIT_MODE_TYPE_MASK 0170000
+#define GIT_MODE_TYPE(MODE) ((MODE) & GIT_MODE_TYPE_MASK)
+#define GIT_MODE_ISBLOB(MODE) (GIT_MODE_TYPE(MODE) == GIT_MODE_TYPE(GIT_FILEMODE_BLOB))
+
+/**
+ * Convert a mode_t from the OS to a legal git mode_t value.
+ */
+extern mode_t git_futils_canonical_mode(mode_t raw_mode);
+
+
/**
* Read-only map all or part of a file into memory.
* When possible this function should favor a virtual memory
@@ -115,10 +238,10 @@ extern git_off_t git_futils_filesize(git_file fd);
* @param out buffer to populate with the mapping information.
* @param fd open descriptor to configure the mapping from.
* @param begin first byte to map, this should be page aligned.
- * @param end number of bytes to map.
+ * @param len number of bytes to map.
* @return
- * - GIT_SUCCESS on success;
- * - GIT_EOSERR on an unspecified OS related error.
+ * - 0 on success;
+ * - -1 on error.
*/
extern int git_futils_mmap_ro(
git_map *out,
@@ -127,29 +250,79 @@ extern int git_futils_mmap_ro(
size_t len);
/**
+ * Read-only map an entire file.
+ *
+ * @param out buffer to populate with the mapping information.
+ * @param path path to file to be opened.
+ * @return
+ * - 0 on success;
+ * - GIT_ENOTFOUND if not found;
+ * - -1 on an unspecified OS related error.
+ */
+extern int git_futils_mmap_ro_file(
+ git_map *out,
+ const char *path);
+
+/**
* Release the memory associated with a previous memory mapping.
* @param map the mapping description previously configured.
*/
extern void git_futils_mmap_free(git_map *map);
/**
- * Walk each directory entry, except '.' and '..', calling fn(state).
+ * Create a "fake" symlink (text file containing the target path).
*
- * @param pathbuf buffer the function reads the initial directory
- * path from, and updates with each successive entry's name.
- * @param pathmax maximum allocation of pathbuf.
- * @param fn function to invoke with each entry. The first arg is
- * the input state and the second arg is pathbuf. The function
- * may modify the pathbuf, but only by appending new text.
- * @param state to pass to fn as the first arg.
+ * @param new symlink file to be created
+ * @param old original symlink target
+ * @return 0 on success, -1 on error
*/
-extern int git_futils_direach(
- char *pathbuf,
- size_t pathmax,
- int (*fn)(void *, char *),
- void *state);
+extern int git_futils_fake_symlink(const char *new, const char *old);
-extern int git_futils_cmp_path(const char *name1, int len1, int isdir1,
- const char *name2, int len2, int isdir2);
+/**
+ * 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,
+ * 1 if the file has been changed, or GIT_ENOTFOUND if the file doesn't
+ * exist. This will not call giterr_set, so you must set the error if you
+ * plan to return an error.
+ *
+ * @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, GIT_ENOTFOUND if cannot stat
+ */
+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);
+
+/**
+ * Set file stamp data from stat structure
+ */
+extern void git_futils_filestamp_set_from_stat(
+ git_futils_filestamp *stamp, struct stat *st);
#endif /* INCLUDE_fileops_h__ */
diff --git a/src/filter.c b/src/filter.c
new file mode 100644
index 000000000..b9e4f9ec8
--- /dev/null
+++ b/src/filter.c
@@ -0,0 +1,693 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "fileops.h"
+#include "hash.h"
+#include "filter.h"
+#include "repository.h"
+#include "global.h"
+#include "git2/sys/filter.h"
+#include "git2/config.h"
+#include "blob.h"
+#include "attr_file.h"
+#include "array.h"
+
+struct git_filter_source {
+ git_repository *repo;
+ const char *path;
+ git_oid oid; /* zero if unknown (which is likely) */
+ uint16_t filemode; /* zero if unknown */
+ git_filter_mode_t mode;
+ uint32_t options;
+};
+
+typedef struct {
+ git_filter *filter;
+ void *payload;
+} git_filter_entry;
+
+struct git_filter_list {
+ git_array_t(git_filter_entry) filters;
+ git_filter_source source;
+ char path[GIT_FLEX_ARRAY];
+};
+
+typedef struct {
+ const char *filter_name;
+ git_filter *filter;
+ int priority;
+ int initialized;
+ size_t nattrs, nmatches;
+ char *attrdata;
+ const char *attrs[GIT_FLEX_ARRAY];
+} git_filter_def;
+
+static int filter_def_priority_cmp(const void *a, const void *b)
+{
+ int pa = ((const git_filter_def *)a)->priority;
+ int pb = ((const git_filter_def *)b)->priority;
+ return (pa < pb) ? -1 : (pa > pb) ? 1 : 0;
+}
+
+struct filter_registry {
+ git_vector filters;
+};
+
+static struct filter_registry *git__filter_registry = NULL;
+
+static void filter_registry_shutdown(void)
+{
+ struct filter_registry *reg = NULL;
+ size_t pos;
+ git_filter_def *fdef;
+
+ if ((reg = git__swap(git__filter_registry, NULL)) == NULL)
+ return;
+
+ git_vector_foreach(&reg->filters, pos, fdef) {
+ if (fdef->filter && fdef->filter->shutdown) {
+ fdef->filter->shutdown(fdef->filter);
+ fdef->initialized = false;
+ }
+
+ git__free(fdef->attrdata);
+ git__free(fdef);
+ }
+
+ git_vector_free(&reg->filters);
+ git__free(reg);
+}
+
+static int filter_registry_initialize(void)
+{
+ int error = 0;
+ struct filter_registry *reg;
+
+ if (git__filter_registry)
+ return 0;
+
+ reg = git__calloc(1, sizeof(struct filter_registry));
+ GITERR_CHECK_ALLOC(reg);
+
+ if ((error = git_vector_init(
+ &reg->filters, 2, filter_def_priority_cmp)) < 0)
+ goto cleanup;
+
+ reg = git__compare_and_swap(&git__filter_registry, NULL, reg);
+ if (reg != NULL)
+ goto cleanup;
+
+ git__on_shutdown(filter_registry_shutdown);
+
+ /* try to register both default filters */
+ {
+ git_filter *crlf = git_crlf_filter_new();
+ git_filter *ident = git_ident_filter_new();
+
+ if (crlf && git_filter_register(
+ GIT_FILTER_CRLF, crlf, GIT_FILTER_CRLF_PRIORITY) < 0)
+ crlf = NULL;
+ if (ident && git_filter_register(
+ GIT_FILTER_IDENT, ident, GIT_FILTER_IDENT_PRIORITY) < 0)
+ ident = NULL;
+
+ if (!crlf || !ident)
+ return -1;
+ }
+
+ return 0;
+
+cleanup:
+ git_vector_free(&reg->filters);
+ git__free(reg);
+ return error;
+}
+
+static int filter_def_scan_attrs(
+ git_buf *attrs, size_t *nattr, size_t *nmatch, const char *attr_str)
+{
+ const char *start, *scan = attr_str;
+ int has_eq;
+
+ *nattr = *nmatch = 0;
+
+ if (!scan)
+ return 0;
+
+ while (*scan) {
+ while (git__isspace(*scan)) scan++;
+
+ for (start = scan, has_eq = 0; *scan && !git__isspace(*scan); ++scan) {
+ if (*scan == '=')
+ has_eq = 1;
+ }
+
+ if (scan > start) {
+ (*nattr)++;
+ if (has_eq || *start == '-' || *start == '+' || *start == '!')
+ (*nmatch)++;
+
+ if (has_eq)
+ git_buf_putc(attrs, '=');
+ git_buf_put(attrs, start, scan - start);
+ git_buf_putc(attrs, '\0');
+ }
+ }
+
+ return 0;
+}
+
+static void filter_def_set_attrs(git_filter_def *fdef)
+{
+ char *scan = fdef->attrdata;
+ size_t i;
+
+ for (i = 0; i < fdef->nattrs; ++i) {
+ const char *name, *value;
+
+ switch (*scan) {
+ case '=':
+ name = scan + 1;
+ for (scan++; *scan != '='; scan++) /* find '=' */;
+ *scan++ = '\0';
+ value = scan;
+ break;
+ case '-':
+ name = scan + 1; value = git_attr__false; break;
+ case '+':
+ name = scan + 1; value = git_attr__true; break;
+ case '!':
+ name = scan + 1; value = git_attr__unset; break;
+ default:
+ name = scan; value = NULL; break;
+ }
+
+ fdef->attrs[i] = name;
+ fdef->attrs[i + fdef->nattrs] = value;
+
+ scan += strlen(scan) + 1;
+ }
+}
+
+static int filter_def_name_key_check(const void *key, const void *fdef)
+{
+ const char *name =
+ fdef ? ((const git_filter_def *)fdef)->filter_name : NULL;
+ return name ? git__strcmp(key, name) : -1;
+}
+
+static int filter_def_filter_key_check(const void *key, const void *fdef)
+{
+ const void *filter = fdef ? ((const git_filter_def *)fdef)->filter : NULL;
+ return (key == filter) ? 0 : -1;
+}
+
+static int filter_registry_find(size_t *pos, const char *name)
+{
+ return git_vector_search2(
+ pos, &git__filter_registry->filters, filter_def_name_key_check, name);
+}
+
+static git_filter_def *filter_registry_lookup(size_t *pos, const char *name)
+{
+ git_filter_def *fdef = NULL;
+
+ if (!filter_registry_find(pos, name))
+ fdef = git_vector_get(&git__filter_registry->filters, *pos);
+
+ return fdef;
+}
+
+int git_filter_register(
+ const char *name, git_filter *filter, int priority)
+{
+ git_filter_def *fdef;
+ size_t nattr = 0, nmatch = 0;
+ git_buf attrs = GIT_BUF_INIT;
+
+ if (filter_registry_initialize() < 0)
+ return -1;
+
+ if (!filter_registry_find(NULL, name)) {
+ giterr_set(
+ GITERR_FILTER, "Attempt to reregister existing filter '%s'", name);
+ return GIT_EEXISTS;
+ }
+
+ if (filter_def_scan_attrs(&attrs, &nattr, &nmatch, filter->attributes) < 0)
+ return -1;
+
+ fdef = git__calloc(
+ sizeof(git_filter_def) + 2 * nattr * sizeof(char *), 1);
+ GITERR_CHECK_ALLOC(fdef);
+
+ fdef->filter_name = name;
+ fdef->filter = filter;
+ fdef->priority = priority;
+ fdef->nattrs = nattr;
+ fdef->nmatches = nmatch;
+ fdef->attrdata = git_buf_detach(&attrs);
+
+ filter_def_set_attrs(fdef);
+
+ if (git_vector_insert(&git__filter_registry->filters, fdef) < 0) {
+ git__free(fdef->attrdata);
+ git__free(fdef);
+ return -1;
+ }
+
+ git_vector_sort(&git__filter_registry->filters);
+ return 0;
+}
+
+int git_filter_unregister(const char *name)
+{
+ size_t pos;
+ git_filter_def *fdef;
+
+ /* cannot unregister default filters */
+ if (!strcmp(GIT_FILTER_CRLF, name) || !strcmp(GIT_FILTER_IDENT, name)) {
+ giterr_set(GITERR_FILTER, "Cannot unregister filter '%s'", name);
+ return -1;
+ }
+
+ if ((fdef = filter_registry_lookup(&pos, name)) == NULL) {
+ giterr_set(GITERR_FILTER, "Cannot find filter '%s' to unregister", name);
+ return GIT_ENOTFOUND;
+ }
+
+ (void)git_vector_remove(&git__filter_registry->filters, pos);
+
+ if (fdef->initialized && fdef->filter && fdef->filter->shutdown) {
+ fdef->filter->shutdown(fdef->filter);
+ fdef->initialized = false;
+ }
+
+ git__free(fdef->attrdata);
+ git__free(fdef);
+
+ return 0;
+}
+
+static int filter_initialize(git_filter_def *fdef)
+{
+ int error = 0;
+
+ if (!fdef->initialized &&
+ fdef->filter &&
+ fdef->filter->initialize &&
+ (error = fdef->filter->initialize(fdef->filter)) < 0)
+ {
+ /* auto-unregister if initialize fails */
+ git_filter_unregister(fdef->filter_name);
+ return error;
+ }
+
+ fdef->initialized = true;
+ return 0;
+}
+
+git_filter *git_filter_lookup(const char *name)
+{
+ size_t pos;
+ git_filter_def *fdef;
+
+ if (filter_registry_initialize() < 0)
+ return NULL;
+
+ if ((fdef = filter_registry_lookup(&pos, name)) == NULL)
+ return NULL;
+
+ if (!fdef->initialized && filter_initialize(fdef) < 0)
+ return NULL;
+
+ return fdef->filter;
+}
+
+void git_filter_free(git_filter *filter)
+{
+ git__free(filter);
+}
+
+git_repository *git_filter_source_repo(const git_filter_source *src)
+{
+ return src->repo;
+}
+
+const char *git_filter_source_path(const git_filter_source *src)
+{
+ return src->path;
+}
+
+uint16_t git_filter_source_filemode(const git_filter_source *src)
+{
+ return src->filemode;
+}
+
+const git_oid *git_filter_source_id(const git_filter_source *src)
+{
+ return git_oid_iszero(&src->oid) ? NULL : &src->oid;
+}
+
+git_filter_mode_t git_filter_source_mode(const git_filter_source *src)
+{
+ return src->mode;
+}
+
+uint32_t git_filter_source_options(const git_filter_source *src)
+{
+ return src->options;
+}
+
+static int filter_list_new(
+ git_filter_list **out, const git_filter_source *src)
+{
+ git_filter_list *fl = NULL;
+ size_t pathlen = src->path ? strlen(src->path) : 0;
+
+ fl = git__calloc(1, sizeof(git_filter_list) + pathlen + 1);
+ GITERR_CHECK_ALLOC(fl);
+
+ if (src->path)
+ memcpy(fl->path, src->path, pathlen);
+ fl->source.repo = src->repo;
+ fl->source.path = fl->path;
+ fl->source.mode = src->mode;
+ fl->source.options = src->options;
+
+ *out = fl;
+ return 0;
+}
+
+static int filter_list_check_attributes(
+ const char ***out, git_filter_def *fdef, const git_filter_source *src)
+{
+ int error;
+ size_t i;
+ const char **strs = git__calloc(fdef->nattrs, sizeof(const char *));
+ GITERR_CHECK_ALLOC(strs);
+
+ error = git_attr_get_many(
+ strs, src->repo, 0, src->path, fdef->nattrs, fdef->attrs);
+
+ /* if no values were found but no matches are needed, it's okay! */
+ if (error == GIT_ENOTFOUND && !fdef->nmatches) {
+ giterr_clear();
+ git__free((void *)strs);
+ return 0;
+ }
+
+ for (i = 0; !error && i < fdef->nattrs; ++i) {
+ const char *want = fdef->attrs[fdef->nattrs + i];
+ git_attr_t want_type, found_type;
+
+ if (!want)
+ continue;
+
+ want_type = git_attr_value(want);
+ found_type = git_attr_value(strs[i]);
+
+ if (want_type != found_type ||
+ (want_type == GIT_ATTR_VALUE_T && strcmp(want, strs[i])))
+ error = GIT_ENOTFOUND;
+ }
+
+ if (error)
+ git__free((void *)strs);
+ else
+ *out = strs;
+
+ return error;
+}
+
+int git_filter_list_new(
+ git_filter_list **out,
+ git_repository *repo,
+ git_filter_mode_t mode,
+ uint32_t options)
+{
+ git_filter_source src = { 0 };
+ src.repo = repo;
+ src.path = NULL;
+ src.mode = mode;
+ src.options = options;
+ return filter_list_new(out, &src);
+}
+
+int git_filter_list_load(
+ git_filter_list **filters,
+ git_repository *repo,
+ git_blob *blob, /* can be NULL */
+ const char *path,
+ git_filter_mode_t mode,
+ uint32_t options)
+{
+ int error = 0;
+ git_filter_list *fl = NULL;
+ git_filter_source src = { 0 };
+ git_filter_entry *fe;
+ size_t idx;
+ git_filter_def *fdef;
+
+ if (filter_registry_initialize() < 0)
+ return -1;
+
+ src.repo = repo;
+ src.path = path;
+ src.mode = mode;
+ src.options = options;
+ if (blob)
+ git_oid_cpy(&src.oid, git_blob_id(blob));
+
+ git_vector_foreach(&git__filter_registry->filters, idx, fdef) {
+ const char **values = NULL;
+ void *payload = NULL;
+
+ if (!fdef || !fdef->filter)
+ continue;
+
+ if (fdef->nattrs > 0) {
+ error = filter_list_check_attributes(&values, fdef, &src);
+ if (error == GIT_ENOTFOUND) {
+ error = 0;
+ continue;
+ } else if (error < 0)
+ break;
+ }
+
+ if (!fdef->initialized && (error = filter_initialize(fdef)) < 0)
+ break;
+
+ if (fdef->filter->check)
+ error = fdef->filter->check(
+ fdef->filter, &payload, &src, values);
+
+ git__free((void *)values);
+
+ if (error == GIT_PASSTHROUGH)
+ error = 0;
+ else if (error < 0)
+ break;
+ else {
+ if (!fl && (error = filter_list_new(&fl, &src)) < 0)
+ return error;
+
+ fe = git_array_alloc(fl->filters);
+ GITERR_CHECK_ALLOC(fe);
+ fe->filter = fdef->filter;
+ fe->payload = payload;
+ }
+ }
+
+ if (error && fl != NULL) {
+ git_array_clear(fl->filters);
+ git__free(fl);
+ fl = NULL;
+ }
+
+ *filters = fl;
+ return error;
+}
+
+void git_filter_list_free(git_filter_list *fl)
+{
+ uint32_t i;
+
+ if (!fl)
+ return;
+
+ for (i = 0; i < git_array_size(fl->filters); ++i) {
+ git_filter_entry *fe = git_array_get(fl->filters, i);
+ if (fe->filter->cleanup)
+ fe->filter->cleanup(fe->filter, fe->payload);
+ }
+
+ git_array_clear(fl->filters);
+ git__free(fl);
+}
+
+int git_filter_list_push(
+ git_filter_list *fl, git_filter *filter, void *payload)
+{
+ int error = 0;
+ size_t pos;
+ git_filter_def *fdef;
+ git_filter_entry *fe;
+
+ assert(fl && filter);
+
+ if (git_vector_search2(
+ &pos, &git__filter_registry->filters,
+ filter_def_filter_key_check, filter) < 0) {
+ giterr_set(GITERR_FILTER, "Cannot use an unregistered filter");
+ return -1;
+ }
+
+ fdef = git_vector_get(&git__filter_registry->filters, pos);
+
+ if (!fdef->initialized && (error = filter_initialize(fdef)) < 0)
+ return error;
+
+ fe = git_array_alloc(fl->filters);
+ GITERR_CHECK_ALLOC(fe);
+ fe->filter = filter;
+ fe->payload = payload;
+
+ return 0;
+}
+
+size_t git_filter_list_length(const git_filter_list *fl)
+{
+ return fl ? git_array_size(fl->filters) : 0;
+}
+
+static int filter_list_out_buffer_from_raw(
+ git_buf *out, const void *ptr, size_t size)
+{
+ if (git_buf_is_allocated(out))
+ git_buf_free(out);
+
+ if (!size) {
+ git_buf_init(out, 0);
+ } else {
+ out->ptr = (char *)ptr;
+ out->asize = 0;
+ out->size = size;
+ }
+
+ return 0;
+}
+
+int git_filter_list_apply_to_data(
+ git_buf *tgt, git_filter_list *fl, git_buf *src)
+{
+ int error = 0;
+ uint32_t i;
+ git_buf *dbuffer[2], local = GIT_BUF_INIT;
+ unsigned int si = 0;
+
+ git_buf_sanitize(tgt);
+ git_buf_sanitize(src);
+
+ if (!fl)
+ return filter_list_out_buffer_from_raw(tgt, src->ptr, src->size);
+
+ dbuffer[0] = src;
+ dbuffer[1] = tgt;
+
+ /* if `src` buffer is reallocable, then use it, otherwise copy it */
+ if (!git_buf_is_allocated(src)) {
+ if (git_buf_set(&local, src->ptr, src->size) < 0)
+ return -1;
+ dbuffer[0] = &local;
+ }
+
+ for (i = 0; i < git_array_size(fl->filters); ++i) {
+ unsigned int di = 1 - si;
+ uint32_t fidx = (fl->source.mode == GIT_FILTER_TO_WORKTREE) ?
+ i : git_array_size(fl->filters) - 1 - i;
+ git_filter_entry *fe = git_array_get(fl->filters, fidx);
+
+ dbuffer[di]->size = 0;
+
+ /* Apply the filter from dbuffer[src] to the other buffer;
+ * if the filtering is canceled by the user mid-filter,
+ * we skip to the next filter without changing the source
+ * of the double buffering (so that the text goes through
+ * cleanly).
+ */
+
+ error = fe->filter->apply(
+ fe->filter, &fe->payload, dbuffer[di], dbuffer[si], &fl->source);
+
+ if (error == GIT_PASSTHROUGH) {
+ /* PASSTHROUGH means filter decided not to process the buffer */
+ error = 0;
+ } else if (!error) {
+ git_buf_sanitize(dbuffer[di]); /* force NUL termination */
+ si = di; /* swap buffers */
+ } else {
+ tgt->size = 0;
+ goto cleanup;
+ }
+ }
+
+ /* Ensure that the output ends up in dbuffer[1] (i.e. the dest) */
+ if (si != 1)
+ git_buf_swap(dbuffer[0], dbuffer[1]);
+
+cleanup:
+ git_buf_free(&local); /* don't leak if we allocated locally */
+
+ return error;
+}
+
+int git_filter_list_apply_to_file(
+ git_buf *out,
+ git_filter_list *filters,
+ git_repository *repo,
+ const char *path)
+{
+ int error;
+ const char *base = repo ? git_repository_workdir(repo) : NULL;
+ git_buf abspath = GIT_BUF_INIT, raw = GIT_BUF_INIT;
+
+ if (!(error = git_path_join_unrooted(&abspath, path, base, NULL)) &&
+ !(error = git_futils_readbuffer(&raw, abspath.ptr)))
+ {
+ error = git_filter_list_apply_to_data(out, filters, &raw);
+
+ git_buf_free(&raw);
+ }
+
+ git_buf_free(&abspath);
+ return error;
+}
+
+int git_filter_list_apply_to_blob(
+ git_buf *out,
+ git_filter_list *filters,
+ git_blob *blob)
+{
+ git_buf in = GIT_BUF_INIT;
+ git_off_t rawsize = git_blob_rawsize(blob);
+
+ if (!git__is_sizet(rawsize)) {
+ giterr_set(GITERR_OS, "Blob is too large to filter");
+ return -1;
+ }
+
+ in.ptr = (char *)git_blob_rawcontent(blob);
+ in.asize = 0;
+ in.size = (size_t)rawsize;
+
+ if (filters)
+ git_oid_cpy(&filters->source.oid, git_blob_id(blob));
+
+ return git_filter_list_apply_to_data(out, filters, &in);
+}
diff --git a/src/filter.h b/src/filter.h
new file mode 100644
index 000000000..5a366108b
--- /dev/null
+++ b/src/filter.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_filter_h__
+#define INCLUDE_filter_h__
+
+#include "common.h"
+#include "git2/filter.h"
+
+/* Amount of file to examine for NUL byte when checking binary-ness */
+#define GIT_FILTER_BYTES_TO_CHECK_NUL 8000
+
+/* Possible CRLF values */
+typedef enum {
+ GIT_CRLF_GUESS = -1,
+ GIT_CRLF_BINARY = 0,
+ GIT_CRLF_TEXT,
+ GIT_CRLF_INPUT,
+ GIT_CRLF_CRLF,
+ GIT_CRLF_AUTO,
+} git_crlf_t;
+
+extern void git_filter_free(git_filter *filter);
+
+/*
+ * Available filters
+ */
+
+extern git_filter *git_crlf_filter_new(void);
+extern git_filter *git_ident_filter_new(void);
+
+#endif
diff --git a/src/fnmatch.c b/src/fnmatch.c
new file mode 100644
index 000000000..d7899e3e6
--- /dev/null
+++ b/src/fnmatch.c
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+/*
+ * This file contains code originally derrived from OpenBSD fnmatch.c
+ *
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Guido van Rossum.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6.
+ * Compares a filename or pathname to a pattern.
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "fnmatch.h"
+
+#define EOS '\0'
+
+#define RANGE_MATCH 1
+#define RANGE_NOMATCH 0
+#define RANGE_ERROR (-1)
+
+static int rangematch(const char *, char, int, char **);
+
+static int
+p_fnmatchx(const char *pattern, const char *string, int flags, size_t recurs)
+{
+ const char *stringstart;
+ char *newp;
+ char c, test;
+ int recurs_flags = flags & ~FNM_PERIOD;
+
+ if (recurs-- == 0)
+ return FNM_NORES;
+
+ for (stringstart = string;;)
+ switch (c = *pattern++) {
+ case EOS:
+ if ((flags & FNM_LEADING_DIR) && *string == '/')
+ return (0);
+ return (*string == EOS ? 0 : FNM_NOMATCH);
+ case '?':
+ if (*string == EOS)
+ return (FNM_NOMATCH);
+ if (*string == '/' && (flags & FNM_PATHNAME))
+ return (FNM_NOMATCH);
+ if (*string == '.' && (flags & FNM_PERIOD) &&
+ (string == stringstart ||
+ ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
+ return (FNM_NOMATCH);
+ ++string;
+ break;
+ case '*':
+ c = *pattern;
+
+ /* Let '**' override PATHNAME match for this segment.
+ * It will be restored if/when we recurse below.
+ */
+ if (c == '*') {
+ flags &= ~FNM_PATHNAME;
+ while (c == '*')
+ c = *++pattern;
+ if (c == '/')
+ c = *++pattern;
+ }
+
+ if (*string == '.' && (flags & FNM_PERIOD) &&
+ (string == stringstart ||
+ ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
+ return (FNM_NOMATCH);
+
+ /* Optimize for pattern with * at end or before /. */
+ if (c == EOS) {
+ if (flags & FNM_PATHNAME)
+ return ((flags & FNM_LEADING_DIR) ||
+ strchr(string, '/') == NULL ?
+ 0 : FNM_NOMATCH);
+ else
+ return (0);
+ } else if (c == '/' && (flags & FNM_PATHNAME)) {
+ if ((string = strchr(string, '/')) == NULL)
+ return (FNM_NOMATCH);
+ break;
+ }
+
+ /* General case, use recursion. */
+ while ((test = *string) != EOS) {
+ int e;
+
+ e = p_fnmatchx(pattern, string, recurs_flags, recurs);
+ if (e != FNM_NOMATCH)
+ return e;
+ if (test == '/' && (flags & FNM_PATHNAME))
+ break;
+ ++string;
+ }
+ return (FNM_NOMATCH);
+ case '[':
+ if (*string == EOS)
+ return (FNM_NOMATCH);
+ if (*string == '/' && (flags & FNM_PATHNAME))
+ return (FNM_NOMATCH);
+ if (*string == '.' && (flags & FNM_PERIOD) &&
+ (string == stringstart ||
+ ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
+ return (FNM_NOMATCH);
+
+ switch (rangematch(pattern, *string, flags, &newp)) {
+ case RANGE_ERROR:
+ /* not a good range, treat as normal text */
+ goto normal;
+ case RANGE_MATCH:
+ pattern = newp;
+ break;
+ case RANGE_NOMATCH:
+ return (FNM_NOMATCH);
+ }
+ ++string;
+ break;
+ case '\\':
+ if (!(flags & FNM_NOESCAPE)) {
+ if ((c = *pattern++) == EOS) {
+ c = '\\';
+ --pattern;
+ }
+ }
+ /* FALLTHROUGH */
+ default:
+ normal:
+ if (c != *string && !((flags & FNM_CASEFOLD) &&
+ (tolower((unsigned char)c) ==
+ tolower((unsigned char)*string))))
+ return (FNM_NOMATCH);
+ ++string;
+ break;
+ }
+ /* NOTREACHED */
+}
+
+static int
+rangematch(const char *pattern, char test, int flags, char **newp)
+{
+ int negate, ok;
+ char c, c2;
+
+ /*
+ * A bracket expression starting with an unquoted circumflex
+ * character produces unspecified results (IEEE 1003.2-1992,
+ * 3.13.2). This implementation treats it like '!', for
+ * consistency with the regular expression syntax.
+ * J.T. Conklin (conklin@ngai.kaleida.com)
+ */
+ if ((negate = (*pattern == '!' || *pattern == '^')) != 0)
+ ++pattern;
+
+ if (flags & FNM_CASEFOLD)
+ test = (char)tolower((unsigned char)test);
+
+ /*
+ * A right bracket shall lose its special meaning and represent
+ * itself in a bracket expression if it occurs first in the list.
+ * -- POSIX.2 2.8.3.2
+ */
+ ok = 0;
+ c = *pattern++;
+ do {
+ if (c == '\\' && !(flags & FNM_NOESCAPE))
+ c = *pattern++;
+ if (c == EOS)
+ return (RANGE_ERROR);
+ if (c == '/' && (flags & FNM_PATHNAME))
+ return (RANGE_NOMATCH);
+ if ((flags & FNM_CASEFOLD))
+ c = (char)tolower((unsigned char)c);
+ if (*pattern == '-'
+ && (c2 = *(pattern+1)) != EOS && c2 != ']') {
+ pattern += 2;
+ if (c2 == '\\' && !(flags & FNM_NOESCAPE))
+ c2 = *pattern++;
+ if (c2 == EOS)
+ return (RANGE_ERROR);
+ if (flags & FNM_CASEFOLD)
+ c2 = (char)tolower((unsigned char)c2);
+ if (c <= test && test <= c2)
+ ok = 1;
+ } else if (c == test)
+ ok = 1;
+ } while ((c = *pattern++) != ']');
+
+ *newp = (char *)pattern;
+ return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH);
+}
+
+int
+p_fnmatch(const char *pattern, const char *string, int flags)
+{
+ return p_fnmatchx(pattern, string, flags, 64);
+}
+
diff --git a/src/win32/fnmatch.h b/src/fnmatch.h
index 1309c6e9a..88af45939 100644
--- a/src/win32/fnmatch.h
+++ b/src/fnmatch.h
@@ -25,22 +25,23 @@
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
-#ifndef INCLUDE_fnmatch__w32_h__
-#define INCLUDE_fnmatch__w32_h__
+#ifndef INCLUDE_fnmatch__compat_h__
+#define INCLUDE_fnmatch__compat_h__
#include "common.h"
-#define FNM_NOMATCH 1 /* Match failed. */
-#define FNM_NOSYS 2 /* Function not supported (unused). */
+#define FNM_NOMATCH 1 /* Match failed. */
+#define FNM_NOSYS 2 /* Function not supported (unused). */
+#define FNM_NORES 3 /* Out of resources */
-#define FNM_NOESCAPE 0x01 /* Disable backslash escaping. */
-#define FNM_PATHNAME 0x02 /* Slash must be matched by slash. */
-#define FNM_PERIOD 0x04 /* Period must be matched by period. */
-#define FNM_LEADING_DIR 0x08 /* Ignore /<tail> after Imatch. */
-#define FNM_CASEFOLD 0x10 /* Case insensitive search. */
+#define FNM_NOESCAPE 0x01 /* Disable backslash escaping. */
+#define FNM_PATHNAME 0x02 /* Slash must be matched by slash. */
+#define FNM_PERIOD 0x04 /* Period must be matched by period. */
+#define FNM_LEADING_DIR 0x08 /* Ignore /<tail> after Imatch. */
+#define FNM_CASEFOLD 0x10 /* Case insensitive search. */
-#define FNM_IGNORECASE FNM_CASEFOLD
-#define FNM_FILE_NAME FNM_PATHNAME
+#define FNM_IGNORECASE FNM_CASEFOLD
+#define FNM_FILE_NAME FNM_PATHNAME
extern int p_fnmatch(const char *pattern, const char *string, int flags);
diff --git a/src/global.c b/src/global.c
new file mode 100644
index 000000000..1c4a7a1a9
--- /dev/null
+++ b/src/global.c
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "sysdir.h"
+#include "git2/threads.h"
+#include "thread-utils.h"
+
+
+git_mutex git__mwindow_mutex;
+
+#define MAX_SHUTDOWN_CB 8
+
+static git_global_shutdown_fn git__shutdown_callbacks[MAX_SHUTDOWN_CB];
+static git_atomic git__n_shutdown_callbacks;
+static git_atomic git__n_inits;
+
+void git__on_shutdown(git_global_shutdown_fn callback)
+{
+ int count = git_atomic_inc(&git__n_shutdown_callbacks);
+ assert(count <= MAX_SHUTDOWN_CB && count > 0);
+ git__shutdown_callbacks[count - 1] = callback;
+}
+
+static void git__shutdown(void)
+{
+ int pos;
+
+ for (pos = git_atomic_get(&git__n_shutdown_callbacks); pos > 0; pos = git_atomic_dec(&git__n_shutdown_callbacks)) {
+ git_global_shutdown_fn cb = git__swap(git__shutdown_callbacks[pos - 1], NULL);
+ if (cb != NULL)
+ cb();
+ }
+
+}
+
+/**
+ * Handle the global state with TLS
+ *
+ * If libgit2 is built with GIT_THREADS enabled,
+ * the `git_threads_init()` function must be called
+ * before calling any other function of the library.
+ *
+ * This function allocates a TLS index (using pthreads
+ * or the native Win32 API) to store the global state
+ * on a per-thread basis.
+ *
+ * Any internal method that requires global state will
+ * then call `git__global_state()` which returns a pointer
+ * to the global state structure; this pointer is lazily
+ * allocated on each thread.
+ *
+ * Before shutting down the library, the
+ * `git_threads_shutdown` method must be called to free
+ * the previously reserved TLS index.
+ *
+ * If libgit2 is built without threading support, the
+ * `git__global_statestate()` call returns a pointer to a single,
+ * statically allocated global state. The `git_thread_`
+ * 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 volatile LONG _mutex = 0;
+
+static int synchronized_threads_init(void)
+{
+ int error;
+
+ _tls_index = TlsAlloc();
+ if (git_mutex_init(&git__mwindow_mutex))
+ return -1;
+
+ /* Initialize any other subsystems that have global state */
+ if ((error = git_hash_global_init()) >= 0)
+ error = git_sysdir_global_init();
+
+ win32_pthread_initialize();
+
+ return error;
+}
+
+int git_threads_init(void)
+{
+ int error = 0;
+
+ /* Enter the lock */
+ while (InterlockedCompareExchange(&_mutex, 1, 0)) { Sleep(0); }
+
+ /* Only do work on a 0 -> 1 transition of the refcount */
+ if (1 == git_atomic_inc(&git__n_inits))
+ error = synchronized_threads_init();
+
+ /* Exit the lock */
+ InterlockedExchange(&_mutex, 0);
+
+ return error;
+}
+
+static void synchronized_threads_shutdown(void)
+{
+ /* Shut down any subsystems that have global state */
+ git__shutdown();
+ TlsFree(_tls_index);
+ git_mutex_free(&git__mwindow_mutex);
+}
+
+void git_threads_shutdown(void)
+{
+ /* Enter the lock */
+ while (InterlockedCompareExchange(&_mutex, 1, 0)) { Sleep(0); }
+
+ /* Only do work on a 1 -> 0 transition of the refcount */
+ if (0 == git_atomic_dec(&git__n_inits))
+ synchronized_threads_shutdown();
+
+ /* Exit the lock */
+ InterlockedExchange(&_mutex, 0);
+}
+
+git_global_st *git__global_state(void)
+{
+ void *ptr;
+
+ assert(git_atomic_get(&git__n_inits) > 0);
+
+ if ((ptr = TlsGetValue(_tls_index)) != NULL)
+ return ptr;
+
+ ptr = git__malloc(sizeof(git_global_st));
+ if (!ptr)
+ return NULL;
+
+ memset(ptr, 0x0, sizeof(git_global_st));
+ TlsSetValue(_tls_index, ptr);
+ return ptr;
+}
+
+#elif defined(GIT_THREADS) && defined(_POSIX_THREADS)
+
+static pthread_key_t _tls_key;
+static pthread_once_t _once_init = PTHREAD_ONCE_INIT;
+int init_error = 0;
+
+static void cb__free_status(void *st)
+{
+ git__free(st);
+}
+
+static void init_once(void)
+{
+ if ((init_error = git_mutex_init(&git__mwindow_mutex)) != 0)
+ return;
+ pthread_key_create(&_tls_key, &cb__free_status);
+
+ /* Initialize any other subsystems that have global state */
+ if ((init_error = git_hash_global_init()) >= 0)
+ init_error = git_sysdir_global_init();
+
+ GIT_MEMORY_BARRIER;
+}
+
+int git_threads_init(void)
+{
+ pthread_once(&_once_init, init_once);
+ git_atomic_inc(&git__n_inits);
+ return init_error;
+}
+
+void git_threads_shutdown(void)
+{
+ void *ptr = NULL;
+ pthread_once_t new_once = PTHREAD_ONCE_INIT;
+
+ if (git_atomic_dec(&git__n_inits) > 0) return;
+
+ /* Shut down any subsystems that have global state */
+ git__shutdown();
+
+ ptr = pthread_getspecific(_tls_key);
+ pthread_setspecific(_tls_key, NULL);
+ git__free(ptr);
+
+ pthread_key_delete(_tls_key);
+ git_mutex_free(&git__mwindow_mutex);
+ _once_init = new_once;
+}
+
+git_global_st *git__global_state(void)
+{
+ void *ptr;
+
+ assert(git_atomic_get(&git__n_inits) > 0);
+
+ if ((ptr = pthread_getspecific(_tls_key)) != NULL)
+ return ptr;
+
+ ptr = git__malloc(sizeof(git_global_st));
+ if (!ptr)
+ return NULL;
+
+ memset(ptr, 0x0, sizeof(git_global_st));
+ pthread_setspecific(_tls_key, ptr);
+ return ptr;
+}
+
+#else
+
+static git_global_st __state;
+
+int git_threads_init(void)
+{
+ git_atomic_inc(&git__n_inits);
+ return 0;
+}
+
+void git_threads_shutdown(void)
+{
+ /* Shut down any subsystems that have global state */
+ if (0 == git_atomic_dec(&git__n_inits))
+ git__shutdown();
+}
+
+git_global_st *git__global_state(void)
+{
+ return &__state;
+}
+
+#endif /* GIT_THREADS */
diff --git a/src/global.h b/src/global.h
new file mode 100644
index 000000000..778250376
--- /dev/null
+++ b/src/global.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_global_h__
+#define INCLUDE_global_h__
+
+#include "mwindow.h"
+#include "hash.h"
+
+typedef struct {
+ git_error *last_error;
+ git_error error_t;
+} git_global_st;
+
+git_global_st *git__global_state(void);
+
+extern git_mutex git__mwindow_mutex;
+
+#define GIT_GLOBAL (git__global_state())
+
+typedef void (*git_global_shutdown_fn)(void);
+
+extern void git__on_shutdown(git_global_shutdown_fn callback);
+
+#endif
diff --git a/src/graph.c b/src/graph.c
new file mode 100644
index 000000000..1c264d997
--- /dev/null
+++ b/src/graph.c
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "revwalk.h"
+#include "merge.h"
+#include "git2/graph.h"
+
+static int interesting(git_pqueue *list, git_commit_list *roots)
+{
+ unsigned int i;
+
+ for (i = 0; i < git_pqueue_size(list); i++) {
+ git_commit_list_node *commit = git_pqueue_get(list, i);
+ if ((commit->flags & STALE) == 0)
+ return 1;
+ }
+
+ while(roots) {
+ if ((roots->item->flags & STALE) == 0)
+ return 1;
+ roots = roots->next;
+ }
+
+ return 0;
+}
+
+static int mark_parents(git_revwalk *walk, git_commit_list_node *one,
+ git_commit_list_node *two)
+{
+ unsigned int i;
+ git_commit_list *roots = NULL;
+ git_pqueue list;
+
+ /* if the commit is repeated, we have a our merge base already */
+ if (one == two) {
+ one->flags |= PARENT1 | PARENT2 | RESULT;
+ return 0;
+ }
+
+ if (git_pqueue_init(&list, 0, 2, git_commit_list_time_cmp) < 0)
+ return -1;
+
+ if (git_commit_list_parse(walk, one) < 0)
+ goto on_error;
+ one->flags |= PARENT1;
+ if (git_pqueue_insert(&list, one) < 0)
+ goto on_error;
+
+ if (git_commit_list_parse(walk, two) < 0)
+ goto on_error;
+ two->flags |= PARENT2;
+ if (git_pqueue_insert(&list, two) < 0)
+ goto on_error;
+
+ /* as long as there are non-STALE commits */
+ while (interesting(&list, roots)) {
+ git_commit_list_node *commit = git_pqueue_pop(&list);
+ int flags;
+
+ if (commit == NULL)
+ break;
+
+ flags = commit->flags & (PARENT1 | PARENT2 | STALE);
+ if (flags == (PARENT1 | PARENT2)) {
+ if (!(commit->flags & RESULT))
+ commit->flags |= RESULT;
+ /* we mark the parents of a merge stale */
+ flags |= STALE;
+ }
+
+ for (i = 0; i < commit->out_degree; i++) {
+ git_commit_list_node *p = commit->parents[i];
+ if ((p->flags & flags) == flags)
+ continue;
+
+ if (git_commit_list_parse(walk, p) < 0)
+ goto on_error;
+
+ p->flags |= flags;
+ if (git_pqueue_insert(&list, p) < 0)
+ goto on_error;
+ }
+
+ /* Keep track of root commits, to make sure the path gets marked */
+ if (commit->out_degree == 0) {
+ if (git_commit_list_insert(commit, &roots) == NULL)
+ goto on_error;
+ }
+ }
+
+ git_commit_list_free(&roots);
+ git_pqueue_free(&list);
+ return 0;
+
+on_error:
+ git_commit_list_free(&roots);
+ git_pqueue_free(&list);
+ return -1;
+}
+
+
+static int ahead_behind(git_commit_list_node *one, git_commit_list_node *two,
+ size_t *ahead, size_t *behind)
+{
+ git_commit_list_node *commit;
+ git_pqueue pq;
+ int error = 0, i;
+ *ahead = 0;
+ *behind = 0;
+
+ if (git_pqueue_init(&pq, 0, 2, git_commit_list_time_cmp) < 0)
+ return -1;
+
+ if ((error = git_pqueue_insert(&pq, one)) < 0 ||
+ (error = git_pqueue_insert(&pq, two)) < 0)
+ goto done;
+
+ while ((commit = git_pqueue_pop(&pq)) != NULL) {
+ if (commit->flags & RESULT ||
+ (commit->flags & (PARENT1 | PARENT2)) == (PARENT1 | PARENT2))
+ continue;
+ else if (commit->flags & PARENT1)
+ (*behind)++;
+ else if (commit->flags & PARENT2)
+ (*ahead)++;
+
+ for (i = 0; i < commit->out_degree; i++) {
+ git_commit_list_node *p = commit->parents[i];
+ if ((error = git_pqueue_insert(&pq, p)) < 0)
+ goto done;
+ }
+ commit->flags |= RESULT;
+ }
+
+done:
+ git_pqueue_free(&pq);
+ return error;
+}
+
+int git_graph_ahead_behind(size_t *ahead, size_t *behind, git_repository *repo,
+ const git_oid *local, const git_oid *upstream)
+{
+ git_revwalk *walk;
+ git_commit_list_node *commit_u, *commit_l;
+
+ if (git_revwalk_new(&walk, repo) < 0)
+ return -1;
+
+ commit_u = git_revwalk__commit_lookup(walk, upstream);
+ if (commit_u == NULL)
+ goto on_error;
+
+ commit_l = git_revwalk__commit_lookup(walk, local);
+ if (commit_l == NULL)
+ goto on_error;
+
+ if (mark_parents(walk, commit_l, commit_u) < 0)
+ goto on_error;
+ if (ahead_behind(commit_l, commit_u, ahead, behind) < 0)
+ goto on_error;
+
+ git_revwalk_free(walk);
+
+ return 0;
+
+on_error:
+ git_revwalk_free(walk);
+ return -1;
+}
+
+int git_graph_descendant_of(git_repository *repo, const git_oid *commit, const git_oid *ancestor)
+{
+ git_oid merge_base;
+ int error;
+
+ if (git_oid_equal(commit, ancestor))
+ return 0;
+
+ error = git_merge_base(&merge_base, repo, commit, ancestor);
+ /* No merge-base found, it's not a descendant */
+ if (error == GIT_ENOTFOUND)
+ return 0;
+
+ if (error < 0)
+ return error;
+
+ return git_oid_equal(&merge_base, ancestor);
+}
diff --git a/src/hash.c b/src/hash.c
index b8b311bcb..f3645a913 100644
--- a/src/hash.c
+++ b/src/hash.c
@@ -1,92 +1,47 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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 "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;
+ git_hash_ctx ctx;
+ int error = 0;
- SHA1_Init(&ctx->c);
+ if (git_hash_ctx_init(&ctx) < 0)
+ return -1;
- return ctx;
-}
-
-void git_hash_free_ctx(git_hash_ctx *ctx)
-{
- 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 2b769a4c9..c47f33549 100644
--- a/src/hash.h
+++ b/src/hash.h
@@ -1,26 +1,39 @@
/*
- * hash.h
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_h__
#define INCLUDE_hash_h__
#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);
+int git_hash_ctx_init(git_hash_ctx *ctx);
+void git_hash_ctx_cleanup(git_hash_ctx *ctx);
+
+#if defined(OPENSSL_SHA1)
+# include "hash/hash_openssl.h"
+#elif defined(WIN32_SHA1)
+# include "hash/hash_win32.h"
+#else
+# include "hash/hash_generic.h"
+#endif
+
typedef struct {
void *data;
size_t len;
} git_buf_vec;
-git_hash_ctx *git_hash_new_ctx(void);
-void git_hash_free_ctx(git_hash_ctx *ctx);
-
-void git_hash_init(git_hash_ctx *c);
-void git_hash_update(git_hash_ctx *c, const void *data, size_t len);
-void git_hash_final(git_oid *out, git_hash_ctx *c);
+int git_hash_init(git_hash_ctx *c);
+int git_hash_update(git_hash_ctx *c, const void *data, size_t len);
+int git_hash_final(git_oid *out, git_hash_ctx *c);
-void git_hash_buf(git_oid *out, const void *data, size_t len);
-void git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n);
+int git_hash_buf(git_oid *out, const void *data, size_t len);
+int git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n);
#endif /* INCLUDE_hash_h__ */
diff --git a/src/sha1.c b/src/hash/hash_generic.c
index 0bccb4953..32fcd869c 100644
--- a/src/sha1.c
+++ b/src/hash/hash_generic.c
@@ -1,13 +1,13 @@
/*
- * SHA1 routine optimized to do word accesses rather than byte accesses,
- * and to avoid unnecessary copies into the context array.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * This was initially based on the Mozilla SHA1 implementation, although
- * none of the original Mozilla code remains.
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
-#include "sha1.h"
+#include "hash.h"
+#include "hash/hash_generic.h"
#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
@@ -53,11 +53,11 @@
*/
#if defined(__i386__) || defined(__x86_64__)
- #define setW(x, val) (*(volatile unsigned int *)&W(x) = (val))
+ #define setW(x, val) (*(volatile unsigned int *)&W(x) = (val))
#elif defined(__GNUC__) && defined(__arm__)
- #define setW(x, val) do { W(x) = (val); __asm__("":::"memory"); } while (0)
+ #define setW(x, val) do { W(x) = (val); __asm__("":::"memory"); } while (0)
#else
- #define setW(x, val) (W(x) = (val))
+ #define setW(x, val) (W(x) = (val))
#endif
/*
@@ -68,10 +68,10 @@
*/
#if defined(__i386__) || defined(__x86_64__) || \
- defined(_M_IX86) || defined(_M_X64) || \
- defined(__ppc__) || defined(__ppc64__) || \
- defined(__powerpc__) || defined(__powerpc64__) || \
- defined(__s390__) || defined(__s390x__)
+ defined(_M_IX86) || defined(_M_X64) || \
+ defined(__ppc__) || defined(__ppc64__) || \
+ defined(__powerpc__) || defined(__powerpc64__) || \
+ defined(__s390__) || defined(__s390x__)
#define get_be32(p) ntohl(*(const unsigned int *)(p))
#define put_be32(p, v) do { *(unsigned int *)(p) = htonl(v); } while (0)
@@ -81,14 +81,14 @@
#define get_be32(p) ( \
(*((const unsigned char *)(p) + 0) << 24) | \
(*((const unsigned char *)(p) + 1) << 16) | \
- (*((const unsigned char *)(p) + 2) << 8) | \
- (*((const unsigned char *)(p) + 3) << 0) )
+ (*((const unsigned char *)(p) + 2) << 8) | \
+ (*((const unsigned char *)(p) + 3) << 0) )
#define put_be32(p, v) do { \
unsigned int __v = (v); \
*((unsigned char *)(p) + 0) = __v >> 24; \
*((unsigned char *)(p) + 1) = __v >> 16; \
- *((unsigned char *)(p) + 2) = __v >> 8; \
- *((unsigned char *)(p) + 3) = __v >> 0; } while (0)
+ *((unsigned char *)(p) + 2) = __v >> 8; \
+ *((unsigned char *)(p) + 3) = __v >> 0; } while (0)
#endif
@@ -107,13 +107,13 @@
E += TEMP + SHA_ROL(A,5) + (fn) + (constant); \
B = SHA_ROR(B, 2); } while (0)
-#define T_0_15(t, A, B, C, D, E) SHA_ROUND(t, SHA_SRC, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E )
+#define T_0_15(t, A, B, C, D, E) SHA_ROUND(t, SHA_SRC, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E )
#define T_16_19(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E )
#define T_20_39(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0x6ed9eba1, A, B, C, D, E )
#define T_40_59(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, ((B&C)+(D&(B^C))) , 0x8f1bbcdc, A, B, C, D, E )
-#define T_60_79(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0xca62c1d6, A, B, C, D, E )
+#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];
@@ -221,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;
@@ -231,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, unsigned long len)
+int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len)
{
unsigned int lenW = ctx->size & 63;
@@ -243,25 +245,27 @@ void git__blk_SHA1_Update(blk_SHA_CTX *ctx, const void *data, unsigned long len)
if (lenW) {
unsigned int left = 64 - lenW;
if (len < left)
- left = len;
+ left = (unsigned int)len;
memcpy(lenW + (char *)ctx->W, data, left);
lenW = (lenW + left) & 63;
len -= left;
data = ((const char *)data + left);
if (lenW)
- return;
- blk_SHA1_Block(ctx, ctx->W);
+ 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];
@@ -272,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..daeb1cda8
--- /dev/null
+++ b/src/hash/hash_generic.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_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..9a55d472d
--- /dev/null
+++ b/src/hash/hash_openssl.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_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_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/hash/hash_win32.c b/src/hash/hash_win32.c
new file mode 100644
index 000000000..bb2231364
--- /dev/null
+++ b/src/hash/hash_win32.c
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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)
+{
+ char dll_path[MAX_PATH];
+ DWORD dll_path_len, size_len;
+
+ /* Only use CNG on Windows 2008 / Vista SP1 or better (Windows 6.0 SP1) */
+ if (!git_has_win32_version(6, 0, 1))
+ 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;
+}
+
+static void git_hash_global_shutdown(void)
+{
+ if (hash_prov.type == CNG)
+ hash_cng_prov_shutdown();
+ else if(hash_prov.type == CRYPTOAPI)
+ hash_cryptoapi_prov_shutdown();
+}
+
+int git_hash_global_init(void)
+{
+ int error = 0;
+
+ if (hash_prov.type != INVALID)
+ return 0;
+
+ if ((error = hash_cng_prov_init()) < 0)
+ error = hash_cryptoapi_prov_init();
+
+ git__on_shutdown(git_hash_global_shutdown);
+
+ return error;
+}
+
+/* CryptoAPI: available in Windows XP and newer */
+
+GIT_INLINE(int) hash_ctx_cryptoapi_init(git_hash_ctx *ctx)
+{
+ ctx->type = CRYPTOAPI;
+ ctx->prov = &hash_prov;
+
+ return git_hash_init(ctx);
+}
+
+GIT_INLINE(int) hash_cryptoapi_init(git_hash_ctx *ctx)
+{
+ if (ctx->ctx.cryptoapi.valid)
+ CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
+
+ if (!CryptCreateHash(ctx->prov->prov.cryptoapi.handle, CALG_SHA1, 0, 0, &ctx->ctx.cryptoapi.hash_handle)) {
+ ctx->ctx.cryptoapi.valid = 0;
+ return -1;
+ }
+
+ ctx->ctx.cryptoapi.valid = 1;
+ return 0;
+}
+
+GIT_INLINE(int) hash_cryptoapi_update(git_hash_ctx *ctx, const void *data, size_t len)
+{
+ assert(ctx->ctx.cryptoapi.valid);
+
+ if (!CryptHashData(ctx->ctx.cryptoapi.hash_handle, (const BYTE *)data, (DWORD)len, 0))
+ return -1;
+
+ return 0;
+}
+
+GIT_INLINE(int) hash_cryptoapi_final(git_oid *out, git_hash_ctx *ctx)
+{
+ DWORD len = 20;
+ int error = 0;
+
+ assert(ctx->ctx.cryptoapi.valid);
+
+ if (!CryptGetHashParam(ctx->ctx.cryptoapi.hash_handle, HP_HASHVAL, out->id, &len, 0))
+ error = -1;
+
+ CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
+ ctx->ctx.cryptoapi.valid = 0;
+
+ return error;
+}
+
+GIT_INLINE(void) hash_ctx_cryptoapi_cleanup(git_hash_ctx *ctx)
+{
+ if (ctx->ctx.cryptoapi.valid)
+ CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
+}
+
+/* CNG: Available in Windows Server 2008 and newer */
+
+GIT_INLINE(int) hash_ctx_cng_init(git_hash_ctx *ctx)
+{
+ if ((ctx->ctx.cng.hash_object = git__malloc(hash_prov.prov.cng.hash_object_size)) == NULL)
+ return -1;
+
+ if (hash_prov.prov.cng.create_hash(hash_prov.prov.cng.handle, &ctx->ctx.cng.hash_handle, ctx->ctx.cng.hash_object, hash_prov.prov.cng.hash_object_size, NULL, 0, 0) < 0) {
+ git__free(ctx->ctx.cng.hash_object);
+ return -1;
+ }
+
+ ctx->type = CNG;
+ ctx->prov = &hash_prov;
+
+ return 0;
+}
+
+GIT_INLINE(int) hash_cng_init(git_hash_ctx *ctx)
+{
+ BYTE hash[GIT_OID_RAWSZ];
+
+ if (!ctx->ctx.cng.updated)
+ return 0;
+
+ /* CNG needs to be finished to restart */
+ if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, hash, GIT_OID_RAWSZ, 0) < 0)
+ return -1;
+
+ ctx->ctx.cng.updated = 0;
+
+ return 0;
+}
+
+GIT_INLINE(int) hash_cng_update(git_hash_ctx *ctx, const void *data, size_t len)
+{
+ if (ctx->prov->prov.cng.hash_data(ctx->ctx.cng.hash_handle, (PBYTE)data, (ULONG)len, 0) < 0)
+ return -1;
+
+ return 0;
+}
+
+GIT_INLINE(int) hash_cng_final(git_oid *out, git_hash_ctx *ctx)
+{
+ if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, out->id, GIT_OID_RAWSZ, 0) < 0)
+ return -1;
+
+ ctx->ctx.cng.updated = 0;
+
+ return 0;
+}
+
+GIT_INLINE(void) hash_ctx_cng_cleanup(git_hash_ctx *ctx)
+{
+ ctx->prov->prov.cng.destroy_hash(ctx->ctx.cng.hash_handle);
+ git__free(ctx->ctx.cng.hash_object);
+}
+
+/* Indirection between CryptoAPI and CNG */
+
+int git_hash_ctx_init(git_hash_ctx *ctx)
+{
+ int error = 0;
+
+ assert(ctx);
+
+ /*
+ * When compiled with GIT_THREADS, the global hash_prov data is
+ * initialized with git_threads_init. Otherwise, it must be initialized
+ * at first use.
+ */
+ if (hash_prov.type == INVALID && (error = git_hash_global_init()) < 0)
+ return error;
+
+ memset(ctx, 0x0, sizeof(git_hash_ctx));
+
+ return (hash_prov.type == CNG) ? hash_ctx_cng_init(ctx) : hash_ctx_cryptoapi_init(ctx);
+}
+
+int git_hash_init(git_hash_ctx *ctx)
+{
+ assert(ctx && ctx->type);
+ return (ctx->type == CNG) ? hash_cng_init(ctx) : hash_cryptoapi_init(ctx);
+}
+
+int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len)
+{
+ assert(ctx && ctx->type);
+ return (ctx->type == CNG) ? hash_cng_update(ctx, data, len) : hash_cryptoapi_update(ctx, data, len);
+}
+
+int git_hash_final(git_oid *out, git_hash_ctx *ctx)
+{
+ assert(ctx && ctx->type);
+ return (ctx->type == CNG) ? hash_cng_final(out, ctx) : hash_cryptoapi_final(out, ctx);
+}
+
+void git_hash_ctx_cleanup(git_hash_ctx *ctx)
+{
+ assert(ctx);
+
+ if (ctx->type == CNG)
+ hash_ctx_cng_cleanup(ctx);
+ else if(ctx->type == CRYPTOAPI)
+ hash_ctx_cryptoapi_cleanup(ctx);
+}
diff --git a/src/hash/hash_win32.h b/src/hash/hash_win32.h
new file mode 100644
index 000000000..2eee5ca79
--- /dev/null
+++ b/src/hash/hash_win32.h
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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/hashsig.c b/src/hashsig.c
new file mode 100644
index 000000000..109f966ba
--- /dev/null
+++ b/src/hashsig.c
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "hashsig.h"
+#include "fileops.h"
+#include "util.h"
+
+typedef uint32_t hashsig_t;
+typedef uint64_t hashsig_state;
+
+#define HASHSIG_SCALE 100
+
+#define HASHSIG_MAX_RUN 80
+#define HASHSIG_HASH_START 0x012345678ABCDEF0LL
+#define HASHSIG_HASH_SHIFT 5
+
+#define HASHSIG_HASH_MIX(S,CH) \
+ (S) = ((S) << HASHSIG_HASH_SHIFT) - (S) + (hashsig_state)(CH)
+
+#define HASHSIG_HEAP_SIZE ((1 << 7) - 1)
+#define HASHSIG_HEAP_MIN_SIZE 4
+
+typedef int (*hashsig_cmp)(const void *a, const void *b, void *);
+
+typedef struct {
+ int size, asize;
+ hashsig_cmp cmp;
+ hashsig_t values[HASHSIG_HEAP_SIZE];
+} hashsig_heap;
+
+struct git_hashsig {
+ hashsig_heap mins;
+ hashsig_heap maxs;
+ git_hashsig_option_t opt;
+ int considered;
+};
+
+#define HEAP_LCHILD_OF(I) (((I)<<1)+1)
+#define HEAP_RCHILD_OF(I) (((I)<<1)+2)
+#define HEAP_PARENT_OF(I) (((I)-1)>>1)
+
+static void hashsig_heap_init(hashsig_heap *h, hashsig_cmp cmp)
+{
+ h->size = 0;
+ h->asize = HASHSIG_HEAP_SIZE;
+ h->cmp = cmp;
+}
+
+static int hashsig_cmp_max(const void *a, const void *b, void *payload)
+{
+ hashsig_t av = *(const hashsig_t *)a, bv = *(const hashsig_t *)b;
+ GIT_UNUSED(payload);
+ return (av < bv) ? -1 : (av > bv) ? 1 : 0;
+}
+
+static int hashsig_cmp_min(const void *a, const void *b, void *payload)
+{
+ hashsig_t av = *(const hashsig_t *)a, bv = *(const hashsig_t *)b;
+ GIT_UNUSED(payload);
+ return (av > bv) ? -1 : (av < bv) ? 1 : 0;
+}
+
+static void hashsig_heap_up(hashsig_heap *h, int el)
+{
+ int parent_el = HEAP_PARENT_OF(el);
+
+ while (el > 0 && h->cmp(&h->values[parent_el], &h->values[el], NULL) > 0) {
+ hashsig_t t = h->values[el];
+ h->values[el] = h->values[parent_el];
+ h->values[parent_el] = t;
+
+ el = parent_el;
+ parent_el = HEAP_PARENT_OF(el);
+ }
+}
+
+static void hashsig_heap_down(hashsig_heap *h, int el)
+{
+ hashsig_t v, lv, rv;
+
+ /* 'el < h->size / 2' tests if el is bottom row of heap */
+
+ while (el < h->size / 2) {
+ int lel = HEAP_LCHILD_OF(el), rel = HEAP_RCHILD_OF(el), swapel;
+
+ v = h->values[el];
+ lv = h->values[lel];
+ rv = h->values[rel];
+
+ if (h->cmp(&v, &lv, NULL) < 0 && h->cmp(&v, &rv, NULL) < 0)
+ break;
+
+ swapel = (h->cmp(&lv, &rv, NULL) < 0) ? lel : rel;
+
+ h->values[el] = h->values[swapel];
+ h->values[swapel] = v;
+
+ el = swapel;
+ }
+}
+
+static void hashsig_heap_sort(hashsig_heap *h)
+{
+ /* only need to do this at the end for signature comparison */
+ git__qsort_r(h->values, h->size, sizeof(hashsig_t), h->cmp, NULL);
+}
+
+static void hashsig_heap_insert(hashsig_heap *h, hashsig_t val)
+{
+ /* if heap is not full, insert new element */
+ if (h->size < h->asize) {
+ h->values[h->size++] = val;
+ hashsig_heap_up(h, h->size - 1);
+ }
+
+ /* if heap is full, pop top if new element should replace it */
+ else if (h->cmp(&val, &h->values[0], NULL) > 0) {
+ h->size--;
+ h->values[0] = h->values[h->size];
+ hashsig_heap_down(h, 0);
+ }
+
+}
+
+typedef struct {
+ int use_ignores;
+ uint8_t ignore_ch[256];
+} hashsig_in_progress;
+
+static void hashsig_in_progress_init(
+ hashsig_in_progress *prog, git_hashsig *sig)
+{
+ int i;
+
+ switch (sig->opt) {
+ case GIT_HASHSIG_IGNORE_WHITESPACE:
+ for (i = 0; i < 256; ++i)
+ prog->ignore_ch[i] = git__isspace_nonlf(i);
+ prog->use_ignores = 1;
+ break;
+ case GIT_HASHSIG_SMART_WHITESPACE:
+ for (i = 0; i < 256; ++i)
+ prog->ignore_ch[i] = git__isspace(i);
+ prog->use_ignores = 1;
+ break;
+ default:
+ memset(prog, 0, sizeof(*prog));
+ break;
+ }
+}
+
+#define HASHSIG_IN_PROGRESS_INIT { 1 }
+
+static int hashsig_add_hashes(
+ git_hashsig *sig,
+ const uint8_t *data,
+ size_t size,
+ hashsig_in_progress *prog)
+{
+ const uint8_t *scan = data, *end = data + size;
+ hashsig_state state = HASHSIG_HASH_START;
+ int use_ignores = prog->use_ignores, len;
+ uint8_t ch;
+
+ while (scan < end) {
+ state = HASHSIG_HASH_START;
+
+ for (len = 0; scan < end && len < HASHSIG_MAX_RUN; ) {
+ ch = *scan;
+
+ if (use_ignores)
+ for (; scan < end && git__isspace_nonlf(ch); ch = *scan)
+ ++scan;
+ else if (sig->opt != GIT_HASHSIG_NORMAL)
+ for (; scan < end && ch == '\r'; ch = *scan)
+ ++scan;
+
+ /* peek at next character to decide what to do next */
+ if (sig->opt == GIT_HASHSIG_SMART_WHITESPACE)
+ use_ignores = (ch == '\n');
+
+ if (scan >= end)
+ break;
+ ++scan;
+
+ /* check run terminator */
+ if (ch == '\n' || ch == '\0')
+ break;
+
+ ++len;
+ HASHSIG_HASH_MIX(state, ch);
+ }
+
+ if (len > 0) {
+ hashsig_heap_insert(&sig->mins, (hashsig_t)state);
+ hashsig_heap_insert(&sig->maxs, (hashsig_t)state);
+
+ sig->considered++;
+
+ while (scan < end && (*scan == '\n' || !*scan))
+ ++scan;
+ }
+ }
+
+ prog->use_ignores = use_ignores;
+
+ return 0;
+}
+
+static int hashsig_finalize_hashes(git_hashsig *sig)
+{
+ if (sig->mins.size < HASHSIG_HEAP_MIN_SIZE) {
+ giterr_set(GITERR_INVALID,
+ "File too small for similarity signature calculation");
+ return GIT_EBUFS;
+ }
+
+ hashsig_heap_sort(&sig->mins);
+ hashsig_heap_sort(&sig->maxs);
+
+ return 0;
+}
+
+static git_hashsig *hashsig_alloc(git_hashsig_option_t opts)
+{
+ git_hashsig *sig = git__calloc(1, sizeof(git_hashsig));
+ if (!sig)
+ return NULL;
+
+ hashsig_heap_init(&sig->mins, hashsig_cmp_min);
+ hashsig_heap_init(&sig->maxs, hashsig_cmp_max);
+ sig->opt = opts;
+
+ return sig;
+}
+
+int git_hashsig_create(
+ git_hashsig **out,
+ const char *buf,
+ size_t buflen,
+ git_hashsig_option_t opts)
+{
+ int error;
+ hashsig_in_progress prog;
+ git_hashsig *sig = hashsig_alloc(opts);
+ GITERR_CHECK_ALLOC(sig);
+
+ hashsig_in_progress_init(&prog, sig);
+
+ error = hashsig_add_hashes(sig, (const uint8_t *)buf, buflen, &prog);
+
+ if (!error)
+ error = hashsig_finalize_hashes(sig);
+
+ if (!error)
+ *out = sig;
+ else
+ git_hashsig_free(sig);
+
+ return error;
+}
+
+int git_hashsig_create_fromfile(
+ git_hashsig **out,
+ const char *path,
+ git_hashsig_option_t opts)
+{
+ uint8_t buf[0x1000];
+ ssize_t buflen = 0;
+ int error = 0, fd;
+ hashsig_in_progress prog;
+ git_hashsig *sig = hashsig_alloc(opts);
+ GITERR_CHECK_ALLOC(sig);
+
+ if ((fd = git_futils_open_ro(path)) < 0) {
+ git__free(sig);
+ return fd;
+ }
+
+ hashsig_in_progress_init(&prog, sig);
+
+ while (!error) {
+ if ((buflen = p_read(fd, buf, sizeof(buf))) <= 0) {
+ if ((error = (int)buflen) < 0)
+ giterr_set(GITERR_OS,
+ "Read error on '%s' calculating similarity hashes", path);
+ break;
+ }
+
+ error = hashsig_add_hashes(sig, buf, buflen, &prog);
+ }
+
+ p_close(fd);
+
+ if (!error)
+ error = hashsig_finalize_hashes(sig);
+
+ if (!error)
+ *out = sig;
+ else
+ git_hashsig_free(sig);
+
+ return error;
+}
+
+void git_hashsig_free(git_hashsig *sig)
+{
+ git__free(sig);
+}
+
+static int hashsig_heap_compare(const hashsig_heap *a, const hashsig_heap *b)
+{
+ int matches = 0, i, j, cmp;
+
+ assert(a->cmp == b->cmp);
+
+ /* hash heaps are sorted - just look for overlap vs total */
+
+ for (i = 0, j = 0; i < a->size && j < b->size; ) {
+ cmp = a->cmp(&a->values[i], &b->values[j], NULL);
+
+ if (cmp < 0)
+ ++i;
+ else if (cmp > 0)
+ ++j;
+ else {
+ ++i; ++j; ++matches;
+ }
+ }
+
+ return HASHSIG_SCALE * (matches * 2) / (a->size + b->size);
+}
+
+int git_hashsig_compare(const git_hashsig *a, const git_hashsig *b)
+{
+ /* if we have fewer than the maximum number of elements, then just use
+ * one array since the two arrays will be the same
+ */
+ if (a->mins.size < HASHSIG_HEAP_SIZE)
+ return hashsig_heap_compare(&a->mins, &b->mins);
+ else
+ return (hashsig_heap_compare(&a->mins, &b->mins) +
+ hashsig_heap_compare(&a->maxs, &b->maxs)) / 2;
+}
diff --git a/src/hashsig.h b/src/hashsig.h
new file mode 100644
index 000000000..8c920cbf1
--- /dev/null
+++ b/src/hashsig.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_hashsig_h__
+#define INCLUDE_hashsig_h__
+
+#include "common.h"
+
+/**
+ * Similarity signature of line hashes for a buffer
+ */
+typedef struct git_hashsig git_hashsig;
+
+typedef enum {
+ GIT_HASHSIG_NORMAL = 0, /* use all data */
+ GIT_HASHSIG_IGNORE_WHITESPACE = 1, /* ignore whitespace */
+ GIT_HASHSIG_SMART_WHITESPACE = 2, /* ignore \r and all space after \n */
+} git_hashsig_option_t;
+
+/**
+ * Build a similarity signature for a buffer
+ *
+ * If you have passed a whitespace-ignoring buffer, then the whitespace
+ * will be removed from the buffer while it is being processed, modifying
+ * the buffer in place. Sorry about that!
+ *
+ * This will return an error if the buffer doesn't contain enough data to
+ * compute a valid signature.
+ *
+ * @param out The array of hashed runs representing the file content
+ * @param buf The contents of the file to hash
+ * @param buflen The length of the data at `buf`
+ * @param generate_pairwise_hashes Should pairwise runs be hashed
+ */
+extern int git_hashsig_create(
+ git_hashsig **out,
+ const char *buf,
+ size_t buflen,
+ git_hashsig_option_t opts);
+
+/**
+ * Build a similarity signature from a file
+ *
+ * This walks through the file, only loading a maximum of 4K of file data at
+ * a time. Otherwise, it acts just like `git_hashsig_create`.
+ *
+ * This will return an error if the file doesn't contain enough data to
+ * compute a valid signature.
+ */
+extern int git_hashsig_create_fromfile(
+ git_hashsig **out,
+ const char *path,
+ git_hashsig_option_t opts);
+
+/**
+ * Release memory for a content similarity signature
+ */
+extern void git_hashsig_free(git_hashsig *sig);
+
+/**
+ * Measure similarity between two files
+ *
+ * @return <0 for error, [0 to 100] as similarity score
+ */
+extern int git_hashsig_compare(
+ const git_hashsig *a,
+ const git_hashsig *b);
+
+#endif
diff --git a/src/hashtable.c b/src/hashtable.c
deleted file mode 100644
index 86e5a113a..000000000
--- a/src/hashtable.c
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#include "common.h"
-#include "repository.h"
-#include "commit.h"
-
-#define MAX_LOOPS 5
-static const double max_load_factor = 0.65;
-
-static int resize_to(git_hashtable *self, size_t new_size);
-static int set_size(git_hashtable *self, size_t new_size);
-static git_hashtable_node *node_with_hash(git_hashtable *self, const void *key, int hash_id);
-static void node_swap_with(git_hashtable_node *self, git_hashtable_node *other);
-static int node_insert(git_hashtable *self, git_hashtable_node *new_node);
-static int insert_nodes(git_hashtable *self, git_hashtable_node *old_nodes, size_t old_size);
-
-static int resize_to(git_hashtable *self, size_t new_size)
-{
- git_hashtable_node *old_nodes = self->nodes;
- size_t old_size = self->size;
-
- self->is_resizing = 1;
-
- do {
- self->size = new_size;
- self->size_mask = new_size - 1;
- self->key_count = 0;
- self->nodes = git__calloc(1, sizeof(git_hashtable_node) * self->size);
-
- if (self->nodes == NULL)
- return GIT_ENOMEM;
-
- if (insert_nodes(self, old_nodes, old_size) == 0)
- self->is_resizing = 0;
- else {
- new_size *= 2;
- free(self->nodes);
- }
- } while(self->is_resizing);
-
- free(old_nodes);
- return GIT_SUCCESS;
-}
-
-static int set_size(git_hashtable *self, size_t new_size)
-{
- self->nodes = realloc(self->nodes, new_size * sizeof(git_hashtable_node));
- if (self->nodes == NULL)
- return GIT_ENOMEM;
-
- if (new_size > self->size) {
- memset(&self->nodes[self->size], 0x0, (new_size - self->size) * sizeof(git_hashtable_node));
- }
-
- self->size = new_size;
- self->size_mask = new_size - 1;
- return GIT_SUCCESS;
-}
-
-static git_hashtable_node *node_with_hash(git_hashtable *self, const void *key, int hash_id)
-{
- size_t pos = self->hash(key, hash_id) & self->size_mask;
- return git_hashtable_node_at(self->nodes, pos);
-}
-
-static void node_swap_with(git_hashtable_node *self, git_hashtable_node *other)
-{
- git_hashtable_node tmp = *self;
- *self = *other;
- *other = tmp;
-}
-
-static int node_insert(git_hashtable *self, git_hashtable_node *new_node)
-{
- int iteration, hash_id;
-
- for (iteration = 0; iteration < MAX_LOOPS; iteration++) {
- for (hash_id = 0; hash_id < GIT_HASHTABLE_HASHES; ++hash_id) {
- git_hashtable_node *node;
- node = node_with_hash(self, new_node->key, hash_id);
- node_swap_with(new_node, node);
- if(new_node->key == 0x0){
- self->key_count++;
- return GIT_SUCCESS;
- }
- }
- }
-
- if (self->is_resizing)
- return git__throw(GIT_EBUSY, "Failed to insert node. Hashtable is currently resizing");
-
- resize_to(self, self->size * 2);
- git_hashtable_insert(self, new_node->key, new_node->value);
- return GIT_SUCCESS;
-}
-
-static int insert_nodes(git_hashtable *self, git_hashtable_node *old_nodes, size_t old_size)
-{
- size_t i;
-
- for (i = 0; i < old_size; ++i) {
- git_hashtable_node *node = git_hashtable_node_at(old_nodes, i);
- if (node->key && git_hashtable_insert(self, node->key, node->value) < GIT_SUCCESS)
- return GIT_ENOMEM;
- }
-
- return GIT_SUCCESS;
-}
-
-git_hashtable *git_hashtable_alloc(size_t min_size,
- git_hash_ptr hash,
- git_hash_keyeq_ptr key_eq)
-{
- git_hashtable *table;
-
- assert(hash && key_eq);
-
- if ((table = git__malloc(sizeof(git_hashtable))) == NULL)
- return NULL;
-
- memset(table, 0x0, sizeof(git_hashtable));
-
- if (min_size < 8)
- min_size = 8;
-
- /* round up size to closest power of 2 */
- min_size--;
- min_size |= min_size >> 1;
- min_size |= min_size >> 2;
- min_size |= min_size >> 4;
- min_size |= min_size >> 8;
- min_size |= min_size >> 16;
-
- table->hash = hash;
- table->key_equal = key_eq;
-
- set_size(table, min_size + 1);
-
- return table;
-}
-
-void git_hashtable_clear(git_hashtable *self)
-{
- assert(self);
-
- memset(self->nodes, 0x0, sizeof(git_hashtable_node) * self->size);
- self->key_count = 0;
-}
-
-void git_hashtable_free(git_hashtable *self)
-{
- assert(self);
-
- free(self->nodes);
- free(self);
-}
-
-
-int git_hashtable_insert2(git_hashtable *self, const void *key, void *value, void **old_value)
-{
- int hash_id;
- git_hashtable_node *node;
-
- assert(self && self->nodes);
-
- *old_value = NULL;
-
- for (hash_id = 0; hash_id < GIT_HASHTABLE_HASHES; ++hash_id) {
- node = node_with_hash(self, key, hash_id);
-
- if (!node->key) {
- node->key = key;
- node->value = value;
- self->key_count++;
- return GIT_SUCCESS;
- }
-
- if (key == node->key || self->key_equal(key, node->key) == 0) {
- *old_value = node->value;
- node->key = key;
- node->value = value;
- return GIT_SUCCESS;
- }
- }
-
- /* no space in table; must do cuckoo dance */
- {
- git_hashtable_node x;
- x.key = key;
- x.value = value;
- return node_insert(self, &x);
- }
-}
-
-void *git_hashtable_lookup(git_hashtable *self, const void *key)
-{
- int hash_id;
- git_hashtable_node *node;
-
- assert(self && self->nodes);
-
- for (hash_id = 0; hash_id < GIT_HASHTABLE_HASHES; ++hash_id) {
- node = node_with_hash(self, key, hash_id);
- if (node->key && self->key_equal(key, node->key) == 0)
- return node->value;
- }
-
- return NULL;
-}
-
-int git_hashtable_remove(git_hashtable *self, const void *key)
-{
- int hash_id;
- git_hashtable_node *node;
-
- assert(self && self->nodes);
-
- for (hash_id = 0; hash_id < GIT_HASHTABLE_HASHES; ++hash_id) {
- node = node_with_hash(self, key, hash_id);
- if (node->key && self->key_equal(key, node->key) == 0) {
- node->key = NULL;
- node->value = NULL;
- self->key_count--;
- return GIT_SUCCESS;
- }
- }
-
- return git__throw(GIT_ENOTFOUND, "Entry not found in hash table");
-}
-
-int git_hashtable_merge(git_hashtable *self, git_hashtable *other)
-{
- if (resize_to(self, (self->size + other->size) * 2) < GIT_SUCCESS)
- return GIT_ENOMEM;
-
- return insert_nodes(self, other->nodes, other->key_count);
-}
-
diff --git a/src/hashtable.h b/src/hashtable.h
deleted file mode 100644
index be21be2b1..000000000
--- a/src/hashtable.h
+++ /dev/null
@@ -1,74 +0,0 @@
-#ifndef INCLUDE_hashtable_h__
-#define INCLUDE_hashtable_h__
-
-#include "git2/common.h"
-#include "git2/oid.h"
-#include "git2/odb.h"
-#include "common.h"
-
-#define GIT_HASHTABLE_HASHES 3
-
-typedef uint32_t (*git_hash_ptr)(const void *, int hash_id);
-typedef int (*git_hash_keyeq_ptr)(const void *key_a, const void *key_b);
-
-struct git_hashtable_node {
- const void *key;
- void *value;
-};
-
-struct git_hashtable {
- struct git_hashtable_node *nodes;
-
- size_t size_mask;
- size_t size;
- size_t key_count;
-
- int is_resizing;
-
- git_hash_ptr hash;
- git_hash_keyeq_ptr key_equal;
-};
-
-typedef struct git_hashtable_node git_hashtable_node;
-typedef struct git_hashtable git_hashtable;
-
-git_hashtable *git_hashtable_alloc(size_t min_size,
- git_hash_ptr hash,
- git_hash_keyeq_ptr key_eq);
-void *git_hashtable_lookup(git_hashtable *h, const void *key);
-int git_hashtable_remove(git_hashtable *table, const void *key);
-void git_hashtable_free(git_hashtable *h);
-void git_hashtable_clear(git_hashtable *h);
-int git_hashtable_merge(git_hashtable *self, git_hashtable *other);
-
-int git_hashtable_insert2(git_hashtable *h, const void *key, void *value, void **old_value);
-
-GIT_INLINE(int) git_hashtable_insert(git_hashtable *h, const void *key, void *value)
-{
- void *_unused;
- return git_hashtable_insert2(h, key, value, &_unused);
-}
-
-#define git_hashtable_node_at(nodes, pos) ((git_hashtable_node *)(&nodes[pos]))
-
-#define GIT_HASHTABLE_FOREACH(self, pkey, pvalue, code) {\
- git_hashtable *_self = (self);\
- git_hashtable_node *_nodes = _self->nodes;\
- unsigned int _i, _size = _self->size;\
- for (_i = 0; _i < _size; _i ++) {\
- git_hashtable_node *_node = git_hashtable_node_at(_nodes, _i);\
- if (_node->key)\
- {\
- pkey = _node->key;\
- pvalue = _node->value;\
- code;\
- }\
- }\
-}
-
-#define GIT_HASHTABLE_FOREACH_DELETE() {\
- _node->key = NULL; _node->value = NULL; _self->key_count--;\
-}
-
-
-#endif
diff --git a/src/ident.c b/src/ident.c
new file mode 100644
index 000000000..51630879d
--- /dev/null
+++ b/src/ident.c
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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/sys/filter.h"
+#include "filter.h"
+#include "buffer.h"
+#include "buf_text.h"
+
+static int ident_find_id(
+ const char **id_start, const char **id_end, const char *start, size_t len)
+{
+ const char *end = start + len, *found = NULL;
+
+ while (len > 3 && (found = memchr(start, '$', len)) != NULL) {
+ size_t remaining = (size_t)(end - found) - 1;
+ if (remaining < 3)
+ return GIT_ENOTFOUND;
+
+ start = found + 1;
+ len = remaining;
+
+ if (start[0] == 'I' && start[1] == 'd')
+ break;
+ }
+
+ if (len < 3 || !found)
+ return GIT_ENOTFOUND;
+ *id_start = found;
+
+ if ((found = memchr(start + 2, '$', len - 2)) == NULL)
+ return GIT_ENOTFOUND;
+
+ *id_end = found + 1;
+ return 0;
+}
+
+static int ident_insert_id(
+ git_buf *to, const git_buf *from, const git_filter_source *src)
+{
+ char oid[GIT_OID_HEXSZ+1];
+ const char *id_start, *id_end, *from_end = from->ptr + from->size;
+ size_t need_size;
+
+ /* replace $Id$ with blob id */
+
+ if (!git_filter_source_id(src))
+ return GIT_PASSTHROUGH;
+
+ git_oid_tostr(oid, sizeof(oid), git_filter_source_id(src));
+
+ if (ident_find_id(&id_start, &id_end, from->ptr, from->size) < 0)
+ return GIT_PASSTHROUGH;
+
+ need_size = (size_t)(id_start - from->ptr) +
+ 5 /* "$Id: " */ + GIT_OID_HEXSZ + 1 /* "$" */ +
+ (size_t)(from_end - id_end);
+
+ if (git_buf_grow(to, need_size) < 0)
+ return -1;
+
+ git_buf_set(to, from->ptr, (size_t)(id_start - from->ptr));
+ git_buf_put(to, "$Id: ", 5);
+ git_buf_put(to, oid, GIT_OID_HEXSZ);
+ git_buf_putc(to, '$');
+ git_buf_put(to, id_end, (size_t)(from_end - id_end));
+
+ return git_buf_oom(to) ? -1 : 0;
+}
+
+static int ident_remove_id(
+ git_buf *to, const git_buf *from)
+{
+ const char *id_start, *id_end, *from_end = from->ptr + from->size;
+ size_t need_size;
+
+ if (ident_find_id(&id_start, &id_end, from->ptr, from->size) < 0)
+ return GIT_PASSTHROUGH;
+
+ need_size = (size_t)(id_start - from->ptr) +
+ 4 /* "$Id$" */ + (size_t)(from_end - id_end);
+
+ if (git_buf_grow(to, need_size) < 0)
+ return -1;
+
+ git_buf_set(to, from->ptr, (size_t)(id_start - from->ptr));
+ git_buf_put(to, "$Id$", 4);
+ git_buf_put(to, id_end, (size_t)(from_end - id_end));
+
+ return git_buf_oom(to) ? -1 : 0;
+}
+
+static int ident_apply(
+ git_filter *self,
+ void **payload,
+ git_buf *to,
+ const git_buf *from,
+ const git_filter_source *src)
+{
+ GIT_UNUSED(self); GIT_UNUSED(payload);
+
+ /* Don't filter binary files */
+ if (git_buf_text_is_binary(from))
+ return GIT_PASSTHROUGH;
+
+ if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE)
+ return ident_insert_id(to, from, src);
+ else
+ return ident_remove_id(to, from);
+}
+
+git_filter *git_ident_filter_new(void)
+{
+ git_filter *f = git__calloc(1, sizeof(git_filter));
+
+ f->version = GIT_FILTER_VERSION;
+ f->attributes = "+ident"; /* apply to files with ident attribute set */
+ f->shutdown = git_filter_free;
+ f->apply = ident_apply;
+
+ return f;
+}
diff --git a/src/ignore.c b/src/ignore.c
new file mode 100644
index 000000000..78f01ac44
--- /dev/null
+++ b/src/ignore.c
@@ -0,0 +1,449 @@
+#include "git2/ignore.h"
+#include "common.h"
+#include "ignore.h"
+#include "attrcache.h"
+#include "path.h"
+#include "config.h"
+
+#define GIT_IGNORE_INTERNAL "[internal]exclude"
+
+#define GIT_IGNORE_DEFAULT_RULES ".\n..\n.git\n"
+
+static int parse_ignore_file(
+ git_repository *repo, git_attr_file *attrs, const char *data)
+{
+ int error = 0;
+ int ignore_case = false;
+ const char *scan = data, *context = NULL;
+ git_attr_fnmatch *match = NULL;
+
+ if (git_repository__cvar(&ignore_case, repo, GIT_CVAR_IGNORECASE) < 0)
+ giterr_clear();
+
+ /* if subdir file path, convert context for file paths */
+ if (attrs->entry &&
+ git_path_root(attrs->entry->path) < 0 &&
+ !git__suffixcmp(attrs->entry->path, "/" GIT_IGNORE_FILE))
+ context = attrs->entry->path;
+
+ if (git_mutex_lock(&attrs->lock) < 0) {
+ giterr_set(GITERR_OS, "Failed to lock ignore file");
+ return -1;
+ }
+
+ while (!error && *scan) {
+ if (!match && !(match = git__calloc(1, sizeof(*match)))) {
+ error = -1;
+ break;
+ }
+
+ match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG;
+
+ if (!(error = git_attr_fnmatch__parse(
+ match, &attrs->pool, context, &scan)))
+ {
+ match->flags |= GIT_ATTR_FNMATCH_IGNORE;
+
+ if (ignore_case)
+ match->flags |= GIT_ATTR_FNMATCH_ICASE;
+
+ scan = git__next_line(scan);
+ error = git_vector_insert(&attrs->rules, match);
+ }
+
+ if (error != 0) {
+ git__free(match->pattern);
+ match->pattern = NULL;
+
+ if (error == GIT_ENOTFOUND)
+ error = 0;
+ } else {
+ match = NULL; /* vector now "owns" the match */
+ }
+ }
+
+ git_mutex_unlock(&attrs->lock);
+ git__free(match);
+
+ return error;
+}
+
+static int push_ignore_file(
+ git_ignores *ignores,
+ git_vector *which_list,
+ const char *base,
+ const char *filename)
+{
+ int error = 0;
+ git_attr_file *file = NULL;
+
+ error = git_attr_cache__get(
+ &file, ignores->repo, GIT_ATTR_FILE__FROM_FILE,
+ base, filename, parse_ignore_file);
+ if (error < 0)
+ return error;
+
+ if (file != NULL) {
+ if ((error = git_vector_insert(which_list, file)) < 0)
+ git_attr_file__free(file);
+ }
+
+ return error;
+}
+
+static int push_one_ignore(void *payload, git_buf *path)
+{
+ git_ignores *ign = payload;
+ ign->depth++;
+ return push_ignore_file(ign, &ign->ign_path, path->ptr, GIT_IGNORE_FILE);
+}
+
+static int get_internal_ignores(git_attr_file **out, git_repository *repo)
+{
+ int error;
+
+ if ((error = git_attr_cache__init(repo)) < 0)
+ return error;
+
+ error = git_attr_cache__get(
+ out, repo, GIT_ATTR_FILE__IN_MEMORY, NULL, GIT_IGNORE_INTERNAL, NULL);
+
+ /* if internal rules list is empty, insert default rules */
+ if (!error && !(*out)->rules.length)
+ error = parse_ignore_file(repo, *out, GIT_IGNORE_DEFAULT_RULES);
+
+ return error;
+}
+
+int git_ignore__for_path(
+ git_repository *repo,
+ const char *path,
+ git_ignores *ignores)
+{
+ int error = 0;
+ const char *workdir = git_repository_workdir(repo);
+
+ assert(ignores && path);
+
+ memset(ignores, 0, sizeof(*ignores));
+ ignores->repo = repo;
+
+ /* Read the ignore_case flag */
+ if ((error = git_repository__cvar(
+ &ignores->ignore_case, repo, GIT_CVAR_IGNORECASE)) < 0)
+ goto cleanup;
+
+ if ((error = git_attr_cache__init(repo)) < 0)
+ goto cleanup;
+
+ /* given a unrooted path in a non-bare repo, resolve it */
+ if (workdir && git_path_root(path) < 0)
+ error = git_path_find_dir(&ignores->dir, path, workdir);
+ else
+ error = git_buf_joinpath(&ignores->dir, path, "");
+ if (error < 0)
+ goto cleanup;
+
+ if (workdir && !git__prefixcmp(ignores->dir.ptr, workdir))
+ ignores->dir_root = strlen(workdir);
+
+ /* set up internals */
+ if ((error = get_internal_ignores(&ignores->ign_internal, repo)) < 0)
+ goto cleanup;
+
+ /* load .gitignore up the path */
+ if (workdir != NULL) {
+ error = git_path_walk_up(
+ &ignores->dir, workdir, push_one_ignore, ignores);
+ if (error < 0)
+ goto cleanup;
+ }
+
+ /* load .git/info/exclude */
+ error = push_ignore_file(
+ ignores, &ignores->ign_global,
+ git_repository_path(repo), GIT_IGNORE_FILE_INREPO);
+ if (error < 0)
+ goto cleanup;
+
+ /* load core.excludesfile */
+ if (git_repository_attr_cache(repo)->cfg_excl_file != NULL)
+ error = push_ignore_file(
+ ignores, &ignores->ign_global, NULL,
+ git_repository_attr_cache(repo)->cfg_excl_file);
+
+cleanup:
+ if (error < 0)
+ git_ignore__free(ignores);
+
+ return error;
+}
+
+int git_ignore__push_dir(git_ignores *ign, const char *dir)
+{
+ if (git_buf_joinpath(&ign->dir, ign->dir.ptr, dir) < 0)
+ return -1;
+
+ ign->depth++;
+
+ return push_ignore_file(
+ ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE);
+}
+
+int git_ignore__pop_dir(git_ignores *ign)
+{
+ if (ign->ign_path.length > 0) {
+ git_attr_file *file = git_vector_last(&ign->ign_path);
+ const char *start = file->entry->path, *end;
+
+ /* - ign->dir looks something like "/home/user/a/b/" (or "a/b/c/d/")
+ * - file->path looks something like "a/b/.gitignore
+ *
+ * We are popping the last directory off ign->dir. We also want
+ * to remove the file from the vector if the popped directory
+ * matches the ignore path. We need to test if the "a/b" part of
+ * the file key matches the path we are about to pop.
+ */
+
+ if ((end = strrchr(start, '/')) != NULL) {
+ size_t dirlen = (end - start) + 1;
+ const char *relpath = ign->dir.ptr + ign->dir_root;
+ size_t pathlen = ign->dir.size - ign->dir_root;
+
+ if (pathlen == dirlen && !memcmp(relpath, start, dirlen)) {
+ git_vector_pop(&ign->ign_path);
+ git_attr_file__free(file);
+ }
+ }
+ }
+
+ if (--ign->depth > 0) {
+ git_buf_rtruncate_at_char(&ign->dir, '/');
+ git_path_to_dir(&ign->dir);
+ }
+
+ return 0;
+}
+
+void git_ignore__free(git_ignores *ignores)
+{
+ unsigned int i;
+ git_attr_file *file;
+
+ git_attr_file__free(ignores->ign_internal);
+
+ git_vector_foreach(&ignores->ign_path, i, file) {
+ git_attr_file__free(file);
+ ignores->ign_path.contents[i] = NULL;
+ }
+ git_vector_free(&ignores->ign_path);
+
+ git_vector_foreach(&ignores->ign_global, i, file) {
+ git_attr_file__free(file);
+ ignores->ign_global.contents[i] = NULL;
+ }
+ git_vector_free(&ignores->ign_global);
+
+ git_buf_free(&ignores->dir);
+}
+
+static bool ignore_lookup_in_rules(
+ int *ignored, git_attr_file *file, git_attr_path *path)
+{
+ size_t j;
+ git_attr_fnmatch *match;
+
+ git_vector_rforeach(&file->rules, j, match) {
+ if (git_attr_fnmatch__match(match, path)) {
+ *ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) ?
+ GIT_IGNORE_TRUE : GIT_IGNORE_FALSE;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+int git_ignore__lookup(
+ int *out, git_ignores *ignores, const char *pathname)
+{
+ unsigned int i;
+ git_attr_file *file;
+ git_attr_path path;
+
+ *out = GIT_IGNORE_NOTFOUND;
+
+ if (git_attr_path__init(
+ &path, pathname, git_repository_workdir(ignores->repo)) < 0)
+ return -1;
+
+ /* first process builtins - success means path was found */
+ if (ignore_lookup_in_rules(out, ignores->ign_internal, &path))
+ goto cleanup;
+
+ /* next process files in the path */
+ git_vector_foreach(&ignores->ign_path, i, file) {
+ if (ignore_lookup_in_rules(out, file, &path))
+ goto cleanup;
+ }
+
+ /* last process global ignores */
+ git_vector_foreach(&ignores->ign_global, i, file) {
+ if (ignore_lookup_in_rules(out, file, &path))
+ goto cleanup;
+ }
+
+cleanup:
+ git_attr_path__free(&path);
+ return 0;
+}
+
+int git_ignore_add_rule(git_repository *repo, const char *rules)
+{
+ int error;
+ git_attr_file *ign_internal = NULL;
+
+ if ((error = get_internal_ignores(&ign_internal, repo)) < 0)
+ return error;
+
+ error = parse_ignore_file(repo, ign_internal, rules);
+ git_attr_file__free(ign_internal);
+
+ return error;
+}
+
+int git_ignore_clear_internal_rules(git_repository *repo)
+{
+ int error;
+ git_attr_file *ign_internal;
+
+ if ((error = get_internal_ignores(&ign_internal, repo)) < 0)
+ return error;
+
+ if (!(error = git_attr_file__clear_rules(ign_internal, true)))
+ error = parse_ignore_file(
+ repo, ign_internal, GIT_IGNORE_DEFAULT_RULES);
+
+ git_attr_file__free(ign_internal);
+ return error;
+}
+
+int git_ignore_path_is_ignored(
+ int *ignored,
+ git_repository *repo,
+ const char *pathname)
+{
+ int error;
+ const char *workdir;
+ git_attr_path path;
+ git_ignores ignores;
+ unsigned int i;
+ git_attr_file *file;
+
+ assert(ignored && pathname);
+
+ workdir = repo ? git_repository_workdir(repo) : NULL;
+
+ memset(&path, 0, sizeof(path));
+ memset(&ignores, 0, sizeof(ignores));
+
+ if ((error = git_attr_path__init(&path, pathname, workdir)) < 0 ||
+ (error = git_ignore__for_path(repo, path.path, &ignores)) < 0)
+ goto cleanup;
+
+ while (1) {
+ /* first process builtins - success means path was found */
+ if (ignore_lookup_in_rules(ignored, ignores.ign_internal, &path))
+ goto cleanup;
+
+ /* next process files in the path */
+ git_vector_foreach(&ignores.ign_path, i, file) {
+ if (ignore_lookup_in_rules(ignored, file, &path))
+ goto cleanup;
+ }
+
+ /* last process global ignores */
+ git_vector_foreach(&ignores.ign_global, i, file) {
+ if (ignore_lookup_in_rules(ignored, file, &path))
+ goto cleanup;
+ }
+
+ /* move up one directory */
+ if (path.basename == path.path)
+ break;
+ path.basename[-1] = '\0';
+ while (path.basename > path.path && *path.basename != '/')
+ path.basename--;
+ if (path.basename > path.path)
+ path.basename++;
+ path.is_dir = 1;
+
+ if ((error = git_ignore__pop_dir(&ignores)) < 0)
+ break;
+ }
+
+ *ignored = 0;
+
+cleanup:
+ git_attr_path__free(&path);
+ git_ignore__free(&ignores);
+ return error;
+}
+
+int git_ignore__check_pathspec_for_exact_ignores(
+ git_repository *repo,
+ git_vector *vspec,
+ bool no_fnmatch)
+{
+ int error = 0;
+ size_t i;
+ git_attr_fnmatch *match;
+ int ignored;
+ git_buf path = GIT_BUF_INIT;
+ const char *wd, *filename;
+ git_index *idx;
+
+ if ((error = git_repository__ensure_not_bare(
+ repo, "validate pathspec")) < 0 ||
+ (error = git_repository_index(&idx, repo)) < 0)
+ return error;
+
+ wd = git_repository_workdir(repo);
+
+ git_vector_foreach(vspec, i, match) {
+ /* skip wildcard matches (if they are being used) */
+ if ((match->flags & GIT_ATTR_FNMATCH_HASWILD) != 0 &&
+ !no_fnmatch)
+ continue;
+
+ filename = match->pattern;
+
+ /* if file is already in the index, it's fine */
+ if (git_index_get_bypath(idx, filename, 0) != NULL)
+ continue;
+
+ if ((error = git_buf_joinpath(&path, wd, filename)) < 0)
+ break;
+
+ /* is there a file on disk that matches this exactly? */
+ if (!git_path_isfile(path.ptr))
+ continue;
+
+ /* is that file ignored? */
+ if ((error = git_ignore_path_is_ignored(&ignored, repo, filename)) < 0)
+ break;
+
+ if (ignored) {
+ giterr_set(GITERR_INVALID, "pathspec contains ignored file '%s'",
+ filename);
+ error = GIT_EINVALIDSPEC;
+ break;
+ }
+ }
+
+ git_index_free(idx);
+ git_buf_free(&path);
+
+ return error;
+}
+
diff --git a/src/ignore.h b/src/ignore.h
new file mode 100644
index 000000000..77668c661
--- /dev/null
+++ b/src/ignore.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_ignore_h__
+#define INCLUDE_ignore_h__
+
+#include "repository.h"
+#include "vector.h"
+#include "attr_file.h"
+
+#define GIT_IGNORE_FILE ".gitignore"
+#define GIT_IGNORE_FILE_INREPO "info/exclude"
+#define GIT_IGNORE_FILE_XDG "ignore"
+
+/* The git_ignores structure maintains three sets of ignores:
+ * - internal ignores
+ * - per directory ignores
+ * - global ignores (at lower priority than the others)
+ * As you traverse from one directory to another, you can push and pop
+ * directories onto git_ignores list efficiently.
+ */
+typedef struct {
+ git_repository *repo;
+ git_buf dir; /* current directory reflected in ign_path */
+ git_attr_file *ign_internal;
+ git_vector ign_path;
+ git_vector ign_global;
+ size_t dir_root; /* offset in dir to repo root */
+ int ignore_case;
+ int depth;
+} git_ignores;
+
+extern int git_ignore__for_path(
+ git_repository *repo, const char *path, git_ignores *ign);
+
+extern int git_ignore__push_dir(git_ignores *ign, const char *dir);
+
+extern int git_ignore__pop_dir(git_ignores *ign);
+
+extern void git_ignore__free(git_ignores *ign);
+
+enum {
+ GIT_IGNORE_UNCHECKED = -2,
+ GIT_IGNORE_NOTFOUND = -1,
+ GIT_IGNORE_FALSE = 0,
+ GIT_IGNORE_TRUE = 1,
+};
+
+extern int git_ignore__lookup(int *out, git_ignores *ign, const char *path);
+
+/* command line Git sometimes generates an error message if given a
+ * pathspec that contains an exact match to an ignored file (provided
+ * --force isn't also given). This makes it easy to check it that has
+ * happened. Returns GIT_EINVALIDSPEC if the pathspec contains ignored
+ * exact matches (that are not already present in the index).
+ */
+extern int git_ignore__check_pathspec_for_exact_ignores(
+ git_repository *repo, git_vector *pathspec, bool no_fnmatch);
+
+#endif
diff --git a/src/index.c b/src/index.c
index f20c2a7ab..b63a0bec6 100644
--- a/src/index.c
+++ b/src/index.c
@@ -1,26 +1,8 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
*/
#include <stddef.h>
@@ -28,9 +10,19 @@
#include "common.h"
#include "repository.h"
#include "index.h"
+#include "tree.h"
+#include "tree-cache.h"
#include "hash.h"
+#include "iterator.h"
+#include "pathspec.h"
+#include "ignore.h"
+#include "blob.h"
+
#include "git2/odb.h"
+#include "git2/oid.h"
#include "git2/blob.h"
+#include "git2/config.h"
+#include "git2/sys/index.h"
#define entry_size(type,len) ((offsetof(type, path) + (len) + 8) & ~7)
#define short_entry_size(len) entry_size(struct entry_short, len)
@@ -47,6 +39,9 @@ static const unsigned int INDEX_VERSION_NUMBER_EXT = 3;
static const unsigned int INDEX_HEADER_SIG = 0x44495243;
static const char INDEX_EXT_TREECACHE_SIG[] = {'T', 'R', 'E', 'E'};
static const char INDEX_EXT_UNMERGED_SIG[] = {'R', 'E', 'U', 'C'};
+static const char INDEX_EXT_CONFLICT_NAME_SIG[] = {'N', 'A', 'M', 'E'};
+
+#define INDEX_OWNER(idx) ((git_repository *)(GIT_REFCOUNT_OWNER(idx)))
struct index_header {
uint32_t signature;
@@ -93,101 +88,380 @@ struct entry_long {
char path[1]; /* arbitrary length */
};
+struct entry_srch_key {
+ const char *path;
+ size_t pathlen;
+ int stage;
+};
+
+struct entry_internal {
+ git_index_entry entry;
+ size_t pathlen;
+ char path[GIT_FLEX_ARRAY];
+};
+
+struct reuc_entry_internal {
+ git_index_reuc_entry entry;
+ size_t pathlen;
+ char path[GIT_FLEX_ARRAY];
+};
+
/* local declarations */
static size_t read_extension(git_index *index, const char *buffer, size_t buffer_size);
-static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffer_size);
static int read_header(struct index_header *dest, const void *buffer);
-static int read_tree(git_index *index, const char *buffer, size_t buffer_size);
-static int read_tree_internal(git_index_tree **, const char **, const char *, git_index_tree *);
-
static int parse_index(git_index *index, const char *buffer, size_t buffer_size);
-static int is_index_extended(git_index *index);
+static bool is_index_extended(git_index *index);
static int write_index(git_index *index, git_filebuf *file);
-static int index_srch(const void *key, const void *array_member)
+static void index_entry_free(git_index_entry *entry);
+static void index_entry_reuc_free(git_index_reuc_entry *reuc);
+
+int git_index_entry_srch(const void *key, const void *array_member)
+{
+ const struct entry_srch_key *srch_key = key;
+ const struct entry_internal *entry = array_member;
+ int cmp;
+ size_t len1, len2, len;
+
+ len1 = srch_key->pathlen;
+ len2 = entry->pathlen;
+ len = len1 < len2 ? len1 : len2;
+
+ cmp = memcmp(srch_key->path, entry->path, len);
+ if (cmp)
+ return cmp;
+ if (len1 < len2)
+ return -1;
+ if (len1 > len2)
+ return 1;
+
+ if (srch_key->stage != GIT_INDEX_STAGE_ANY)
+ return srch_key->stage - GIT_IDXENTRY_STAGE(&entry->entry);
+
+ return 0;
+}
+
+int git_index_entry_isrch(const void *key, const void *array_member)
+{
+ const struct entry_srch_key *srch_key = key;
+ const struct entry_internal *entry = array_member;
+ int cmp;
+ size_t len1, len2, len;
+
+ len1 = srch_key->pathlen;
+ len2 = entry->pathlen;
+ len = len1 < len2 ? len1 : len2;
+
+ cmp = strncasecmp(srch_key->path, entry->path, len);
+
+ if (cmp)
+ return cmp;
+ if (len1 < len2)
+ return -1;
+ if (len1 > len2)
+ return 1;
+
+ if (srch_key->stage != GIT_INDEX_STAGE_ANY)
+ return srch_key->stage - GIT_IDXENTRY_STAGE(&entry->entry);
+
+ return 0;
+}
+
+static int index_entry_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_entry_isrch_path(const void *path, const void *array_member)
{
const git_index_entry *entry = array_member;
- return strcmp(key, entry->path);
+ return strcasecmp((const char *)path, entry->path);
+}
+
+int git_index_entry_cmp(const void *a, const void *b)
+{
+ int diff;
+ const git_index_entry *entry_a = a;
+ const git_index_entry *entry_b = b;
+
+ diff = strcmp(entry_a->path, entry_b->path);
+
+ if (diff == 0)
+ diff = (GIT_IDXENTRY_STAGE(entry_a) - GIT_IDXENTRY_STAGE(entry_b));
+
+ return diff;
}
-static int index_cmp(const void *a, const void *b)
+int git_index_entry_icmp(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 = strcasecmp(entry_a->path, entry_b->path);
+
+ if (diff == 0)
+ diff = (GIT_IDXENTRY_STAGE(entry_a) - GIT_IDXENTRY_STAGE(entry_b));
+
+ return diff;
+}
+
+static int conflict_name_cmp(const void *a, const void *b)
+{
+ const git_index_name_entry *name_a = a;
+ const git_index_name_entry *name_b = b;
+
+ if (name_a->ancestor && !name_b->ancestor)
+ return 1;
+
+ if (!name_a->ancestor && name_b->ancestor)
+ return -1;
+
+ if (name_a->ancestor)
+ return strcmp(name_a->ancestor, name_b->ancestor);
+
+ if (!name_a->ours || !name_b->ours)
+ return 0;
+
+ return strcmp(name_a->ours, name_b->ours);
+}
+
+/**
+ * TODO: enable this when resolving case insensitive conflicts
+ */
+#if 0
+static int conflict_name_icmp(const void *a, const void *b)
+{
+ const git_index_name_entry *name_a = a;
+ const git_index_name_entry *name_b = b;
+
+ if (name_a->ancestor && !name_b->ancestor)
+ return 1;
+
+ if (!name_a->ancestor && name_b->ancestor)
+ return -1;
+
+ if (name_a->ancestor)
+ return strcasecmp(name_a->ancestor, name_b->ancestor);
+
+ if (!name_a->ours || !name_b->ours)
+ return 0;
+
+ return strcasecmp(name_a->ours, name_b->ours);
+}
+#endif
+
+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 unsigned int index_create_mode(unsigned int mode)
+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 void index_entry_reuc_free(git_index_reuc_entry *reuc)
+{
+ git__free(reuc);
+}
+
+static void index_entry_free(git_index_entry *entry)
+{
+ memset(&entry->id, 0, sizeof(entry->id));
+ git__free(entry);
+}
+
+unsigned int git_index__create_mode(unsigned int mode)
{
if (S_ISLNK(mode))
return S_IFLNK;
+
if (S_ISDIR(mode) || (mode & S_IFMT) == (S_IFLNK | S_IFDIR))
return (S_IFLNK | S_IFDIR);
- return S_IFREG | ((mode & 0100) ? 0755 : 0644);
+
+ return S_IFREG | GIT_PERMS_CANONICAL(mode);
+}
+
+static unsigned int index_merge_mode(
+ git_index *index, git_index_entry *existing, unsigned int mode)
+{
+ if (index->no_symlinks && S_ISREG(mode) &&
+ existing && S_ISLNK(existing->mode))
+ return existing->mode;
+
+ if (index->distrust_filemode && S_ISREG(mode))
+ return (existing && S_ISREG(existing->mode)) ?
+ existing->mode : git_index__create_mode(0666);
+
+ return git_index__create_mode(mode);
}
-static int index_initialize(git_index **index_out, git_repository *owner, const char *index_path)
+static int index_sort_if_needed(git_index *index, bool need_lock)
+{
+ /* not truly threadsafe because between when this checks and/or
+ * sorts the array another thread could come in and unsort it
+ */
+
+ if (git_vector_is_sorted(&index->entries))
+ return 0;
+
+ if (need_lock && git_mutex_lock(&index->lock) < 0) {
+ giterr_set(GITERR_OS, "Unable to lock index");
+ return -1;
+ }
+
+ git_vector_sort(&index->entries);
+
+ if (need_lock)
+ git_mutex_unlock(&index->lock);
+
+ return 0;
+}
+
+GIT_INLINE(int) index_find_in_entries(
+ size_t *out, git_vector *entries, git_vector_cmp entry_srch,
+ const char *path, size_t path_len, int stage)
+{
+ struct entry_srch_key srch_key;
+ srch_key.path = path;
+ srch_key.pathlen = !path_len ? strlen(path) : path_len;
+ srch_key.stage = stage;
+ return git_vector_bsearch2(out, entries, entry_srch, &srch_key);
+}
+
+GIT_INLINE(int) index_find(
+ size_t *out, git_index *index,
+ const char *path, size_t path_len, int stage, bool need_lock)
+{
+ if (index_sort_if_needed(index, need_lock) < 0)
+ return -1;
+
+ return index_find_in_entries(
+ out, &index->entries, index->entries_search, path, path_len, stage);
+}
+
+void git_index__set_ignore_case(git_index *index, bool ignore_case)
+{
+ index->ignore_case = ignore_case;
+
+ if (ignore_case) {
+ index->entries_cmp_path = git__strcasecmp_cb;
+ index->entries_search = git_index_entry_isrch;
+ index->entries_search_path = index_entry_isrch_path;
+ index->reuc_search = reuc_isrch;
+ } else {
+ index->entries_cmp_path = git__strcmp_cb;
+ index->entries_search = git_index_entry_srch;
+ index->entries_search_path = index_entry_srch_path;
+ index->reuc_search = reuc_srch;
+ }
+
+ git_vector_set_cmp(&index->entries,
+ ignore_case ? git_index_entry_icmp : git_index_entry_cmp);
+ index_sort_if_needed(index, true);
+
+ git_vector_set_cmp(&index->reuc, ignore_case ? reuc_icmp : reuc_cmp);
+ git_vector_sort(&index->reuc);
+}
+
+int git_index_open(git_index **index_out, const char *index_path)
{
git_index *index;
+ int error = -1;
- assert(index_out && index_path);
+ assert(index_out);
- index = git__malloc(sizeof(git_index));
- if (index == NULL)
- return GIT_ENOMEM;
+ index = git__calloc(1, sizeof(git_index));
+ GITERR_CHECK_ALLOC(index);
+
+ if (git_mutex_init(&index->lock)) {
+ giterr_set(GITERR_OS, "Failed to initialize lock");
+ git__free(index);
+ return -1;
+ }
- memset(index, 0x0, sizeof(git_index));
+ if (index_path != NULL) {
+ index->index_file_path = git__strdup(index_path);
+ if (!index->index_file_path)
+ goto fail;
- index->index_file_path = git__strdup(index_path);
- if (index->index_file_path == NULL) {
- free(index);
- return GIT_ENOMEM;
+ /* Check if index file is stored on disk already */
+ if (git_path_exists(index->index_file_path) == true)
+ index->on_disk = 1;
}
- index->repository = owner;
+ if (git_vector_init(&index->entries, 32, git_index_entry_cmp) < 0 ||
+ git_vector_init(&index->names, 8, conflict_name_cmp) < 0 ||
+ git_vector_init(&index->reuc, 8, reuc_cmp) < 0 ||
+ git_vector_init(&index->deleted, 8, git_index_entry_cmp) < 0)
+ goto fail;
- git_vector_init(&index->entries, 32, index_cmp);
+ index->entries_cmp_path = git__strcmp_cb;
+ index->entries_search = git_index_entry_srch;
+ index->entries_search_path = index_entry_srch_path;
+ index->reuc_search = reuc_srch;
- /* Check if index file is stored on disk already */
- if (git_futils_exists(index->index_file_path) == 0)
- index->on_disk = 1;
+ if (index_path != NULL && (error = git_index_read(index, true)) < 0)
+ goto fail;
*index_out = index;
- return git_index_read(index);
+ GIT_REFCOUNT_INC(index);
+
+ return 0;
+
+fail:
+ git_index_free(index);
+ return error;
}
-int git_index_open(git_index **index_out, const char *index_path)
+int git_index_new(git_index **out)
{
- return index_initialize(index_out, NULL, index_path);
+ return git_index_open(out, NULL);
}
-/*
- * Moved from `repository.c`
- */
-int git_repository_index(git_index **index_out, git_repository *repo)
+static void index_free(git_index *index)
{
- if (repo->is_bare)
- return git__throw(GIT_EBAREINDEX, "Failed to open index. Repository is bare");
+ /* index iterators increment the refcount of the index, so if we
+ * get here then there should be no outstanding iterators.
+ */
+ assert(!git_atomic_get(&index->readers));
+
+ git_index_clear(index);
+ git_vector_free(&index->entries);
+ git_vector_free(&index->names);
+ git_vector_free(&index->reuc);
+ git_vector_free(&index->deleted);
- return index_initialize(index_out, repo, repo->path_index);
+ git__free(index->index_file_path);
+ git_mutex_free(&index->lock);
+
+ git__memzero(index, sizeof(*index));
+ git__free(index);
}
void git_index_free(git_index *index)
@@ -195,579 +469,1335 @@ void git_index_free(git_index *index)
if (index == NULL)
return;
- git_index_clear(index);
- git_vector_free(&index->entries);
- git_vector_free(&index->unmerged);
-
- free(index->index_file_path);
- free(index);
+ GIT_REFCOUNT_DEC(index, index_free);
}
-static void free_tree(git_index_tree *tree)
+/* call with locked index */
+static void index_free_deleted(git_index *index)
{
- unsigned int i;
+ int readers = (int)git_atomic_get(&index->readers);
+ size_t i;
- if (tree == NULL)
+ if (readers > 0 || !index->deleted.length)
return;
- for (i = 0; i < tree->children_count; ++i)
- free_tree(tree->children[i]);
+ for (i = 0; i < index->deleted.length; ++i) {
+ git_index_entry *ie = git__swap(index->deleted.contents[i], NULL);
+ index_entry_free(ie);
+ }
- free(tree->name);
- free(tree->children);
- free(tree);
+ git_vector_clear(&index->deleted);
}
-void git_index_clear(git_index *index)
+/* call with locked index */
+static int index_remove_entry(git_index *index, size_t pos)
{
- unsigned int i;
+ int error = 0;
+ git_index_entry *entry = git_vector_get(&index->entries, pos);
- assert(index);
+ if (entry != NULL)
+ git_tree_cache_invalidate_path(index->tree, entry->path);
- for (i = 0; i < index->entries.length; ++i) {
- git_index_entry *e;
- e = git_vector_get(&index->entries, i);
- free(e->path);
- free(e);
- }
+ error = git_vector_remove(&index->entries, pos);
- for (i = 0; i < index->unmerged.length; ++i) {
- git_index_entry_unmerged *e;
- e = git_vector_get(&index->unmerged, i);
- free(e->path);
- free(e);
+ if (!error) {
+ if (git_atomic_get(&index->readers) > 0) {
+ error = git_vector_insert(&index->deleted, entry);
+ } else {
+ index_entry_free(entry);
+ }
}
- git_vector_clear(&index->entries);
- git_vector_clear(&index->unmerged);
- index->last_modified = 0;
+ return error;
+}
+
+int git_index_clear(git_index *index)
+{
+ int error = 0;
- free_tree(index->tree);
+ assert(index);
+
+ git_tree_cache_free(index->tree);
index->tree = NULL;
+
+ if (git_mutex_lock(&index->lock) < 0) {
+ giterr_set(GITERR_OS, "Failed to lock index");
+ return -1;
+ }
+
+ while (!error && index->entries.length > 0)
+ error = index_remove_entry(index, index->entries.length - 1);
+ index_free_deleted(index);
+
+ git_index_reuc_clear(index);
+ git_index_name_clear(index);
+
+ git_futils_filestamp_set(&index->stamp, NULL);
+
+ git_mutex_unlock(&index->lock);
+
+ return error;
}
-int git_index_read(git_index *index)
+static int create_index_error(int error, const char *msg)
{
- int error = GIT_SUCCESS, updated;
- git_fbuffer buffer = GIT_FBUFFER_INIT;
- time_t mtime;
+ giterr_set(GITERR_INDEX, msg);
+ return error;
+}
- assert(index->index_file_path);
+int git_index_set_caps(git_index *index, int caps)
+{
+ unsigned int old_ignore_case;
- if (!index->on_disk || git_futils_exists(index->index_file_path) < 0) {
- git_index_clear(index);
- index->on_disk = 0;
- return GIT_SUCCESS;
+ assert(index);
+
+ old_ignore_case = index->ignore_case;
+
+ if (caps == GIT_INDEXCAP_FROM_OWNER) {
+ git_repository *repo = INDEX_OWNER(index);
+ int val;
+
+ if (!repo)
+ return create_index_error(
+ -1, "Cannot access repository to set index caps");
+
+ if (!git_repository__cvar(&val, repo, GIT_CVAR_IGNORECASE))
+ index->ignore_case = (val != 0);
+ if (!git_repository__cvar(&val, repo, GIT_CVAR_FILEMODE))
+ index->distrust_filemode = (val == 0);
+ if (!git_repository__cvar(&val, repo, GIT_CVAR_SYMLINKS))
+ index->no_symlinks = (val == 0);
+ }
+ else {
+ index->ignore_case = ((caps & GIT_INDEXCAP_IGNORE_CASE) != 0);
+ index->distrust_filemode = ((caps & GIT_INDEXCAP_NO_FILEMODE) != 0);
+ index->no_symlinks = ((caps & GIT_INDEXCAP_NO_SYMLINKS) != 0);
}
- /* We don't want to update the mtime if we fail to parse the index */
- mtime = index->last_modified;
- error = git_futils_readbuffer_updated(&buffer, index->index_file_path, &mtime, &updated);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to read index");
+ if (old_ignore_case != index->ignore_case) {
+ git_index__set_ignore_case(index, (bool)index->ignore_case);
+ }
+
+ return 0;
+}
- if (updated) {
- git_index_clear(index);
- error = parse_index(index, buffer.data, buffer.len);
+int git_index_caps(const git_index *index)
+{
+ return ((index->ignore_case ? GIT_INDEXCAP_IGNORE_CASE : 0) |
+ (index->distrust_filemode ? GIT_INDEXCAP_NO_FILEMODE : 0) |
+ (index->no_symlinks ? GIT_INDEXCAP_NO_SYMLINKS : 0));
+}
- if (error == GIT_SUCCESS)
- index->last_modified = mtime;
+int git_index_read(git_index *index, int force)
+{
+ int error = 0, updated;
+ git_buf buffer = GIT_BUF_INIT;
+ git_futils_filestamp stamp = index->stamp;
- git_futils_freebuffer(&buffer);
+ if (!index->index_file_path)
+ return create_index_error(-1,
+ "Failed to read index: The index is in-memory only");
+
+ index->on_disk = git_path_exists(index->index_file_path);
+
+ if (!index->on_disk) {
+ if (force)
+ return git_index_clear(index);
+ return 0;
}
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to parse index");
+ updated = git_futils_filestamp_check(&stamp, index->index_file_path);
+ if (updated < 0) {
+ giterr_set(
+ GITERR_INDEX,
+ "Failed to read index: '%s' no longer exists",
+ index->index_file_path);
+ return updated;
+ }
+ if (!updated && !force)
+ return 0;
+
+ error = git_futils_readbuffer(&buffer, index->index_file_path);
+ if (error < 0)
+ return error;
+
+ error = git_index_clear(index);
+
+ if (!error)
+ error = parse_index(index, buffer.ptr, buffer.size);
+
+ if (!error)
+ git_futils_filestamp_set(&index->stamp, &stamp);
+
+ git_buf_free(&buffer);
return error;
}
+int git_index__changed_relative_to(
+ git_index *index, const git_futils_filestamp *fs)
+{
+ /* attempt to update index (ignoring errors) */
+ if (git_index_read(index, false) < 0)
+ giterr_clear();
+
+ return (index->stamp.mtime != fs->mtime ||
+ index->stamp.size != fs->size ||
+ index->stamp.ino != fs->ino);
+}
+
int git_index_write(git_index *index)
{
- git_filebuf file;
- struct stat indexst;
+ git_filebuf file = GIT_FILEBUF_INIT;
int error;
- git_vector_sort(&index->entries);
+ if (!index->index_file_path)
+ return create_index_error(-1,
+ "Failed to read index: The index is in-memory only");
- if ((error = git_filebuf_open(&file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to write index");
+ if (index_sort_if_needed(index, true) < 0)
+ return -1;
+ git_vector_sort(&index->reuc);
- if ((error = write_index(index, &file)) < GIT_SUCCESS) {
+ if ((error = git_filebuf_open(
+ &file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS, GIT_INDEX_FILE_MODE)) < 0) {
+ if (error == GIT_ELOCKED)
+ giterr_set(GITERR_INDEX, "The index is locked. This might be due to a concurrent or crashed process");
+
+ return error;
+ }
+
+ if ((error = write_index(index, &file)) < 0) {
git_filebuf_cleanup(&file);
- return git__rethrow(error, "Failed to write index");
+ return error;
}
- if ((error = git_filebuf_commit(&file)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to write index");
+ if ((error = git_filebuf_commit(&file)) < 0)
+ return error;
- if (p_stat(index->index_file_path, &indexst) == 0) {
- index->last_modified = indexst.st_mtime;
+ if (git_futils_filestamp_check(&index->stamp, index->index_file_path) < 0)
+ /* index could not be read from disk! */;
+ else
index->on_disk = 1;
- }
- return GIT_SUCCESS;
+ return 0;
}
-unsigned int git_index_entrycount(git_index *index)
+const char * git_index_path(const git_index *index)
{
assert(index);
- return index->entries.length;
+ return index->index_file_path;
+}
+
+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_unmerged(git_index *index)
+size_t git_index_entrycount(const git_index *index)
{
assert(index);
- return index->unmerged.length;
+ return index->entries.length;
}
-git_index_entry *git_index_get(git_index *index, unsigned int n)
+const git_index_entry *git_index_get_byindex(
+ git_index *index, size_t n)
{
- git_vector_sort(&index->entries);
+ assert(index);
+ if (index_sort_if_needed(index, true) < 0)
+ return NULL;
return git_vector_get(&index->entries, n);
}
-static int index_entry_init(git_index_entry **entry_out, git_index *index, const char *rel_path, int stage)
+const git_index_entry *git_index_get_bypath(
+ git_index *index, const char *path, int stage)
{
- git_index_entry *entry;
- char full_path[GIT_PATH_MAX];
+ size_t pos;
+
+ assert(index);
+
+ if (index_find(&pos, index, path, 0, stage, true) < 0) {
+ giterr_set(GITERR_INDEX, "Index does not contain %s", path);
+ return NULL;
+ }
+
+ return git_index_get_byindex(index, pos);
+}
+
+void git_index_entry__init_from_stat(
+ git_index_entry *entry, struct stat *st, bool trust_mode)
+{
+ entry->ctime.seconds = (git_time_t)st->st_ctime;
+ entry->mtime.seconds = (git_time_t)st->st_mtime;
+ /* entry->mtime.nanoseconds = st->st_mtimensec; */
+ /* entry->ctime.nanoseconds = st->st_ctimensec; */
+ entry->dev = st->st_rdev;
+ entry->ino = st->st_ino;
+ entry->mode = (!trust_mode && S_ISREG(st->st_mode)) ?
+ git_index__create_mode(0666) : git_index__create_mode(st->st_mode);
+ entry->uid = st->st_uid;
+ entry->gid = st->st_gid;
+ entry->file_size = st->st_size;
+}
+
+static git_index_entry *index_entry_alloc(const char *path)
+{
+ size_t pathlen = strlen(path);
+ struct entry_internal *entry =
+ git__calloc(sizeof(struct entry_internal) + pathlen + 1, 1);
+ if (!entry)
+ return NULL;
+
+ entry->pathlen = pathlen;
+ memcpy(entry->path, path, pathlen);
+ entry->entry.path = entry->path;
+
+ return (git_index_entry *)entry;
+}
+
+static int index_entry_init(
+ git_index_entry **entry_out, git_index *index, const char *rel_path)
+{
+ int error = 0;
+ git_index_entry *entry = NULL;
struct stat st;
git_oid oid;
- int error;
- if (index->repository == NULL)
- return git__throw(GIT_EBAREINDEX, "Failed to initialize entry. Repository is bare");
+ if (INDEX_OWNER(index) == NULL)
+ return create_index_error(-1,
+ "Could not initialize index entry. "
+ "Index is not backed up by an existing repository.");
- git_path_join(full_path, index->repository->path_workdir, rel_path);
+ /* write the blob to disk and get the oid and stat info */
+ error = git_blob__create_from_paths(
+ &oid, &st, INDEX_OWNER(index), NULL, rel_path, 0, true);
+ if (error < 0)
+ return error;
- if (p_lstat(full_path, &st) < 0)
- return git__throw(GIT_ENOTFOUND, "Failed to initialize entry. '%s' cannot be opened", full_path);
+ entry = index_entry_alloc(rel_path);
+ GITERR_CHECK_ALLOC(entry);
- if (stage < 0 || stage > 3)
- return git__throw(GIT_ERROR, "Failed to initialize entry. Invalid stage %i", stage);
+ entry->id = oid;
+ git_index_entry__init_from_stat(entry, &st, !index->distrust_filemode);
- /* write the blob to disk and get the oid */
- if ((error = git_blob_create_fromfile(&oid, index->repository, rel_path)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to initialize index entry");
+ *entry_out = (git_index_entry *)entry;
+ return 0;
+}
- entry = git__malloc(sizeof(git_index_entry));
+static git_index_reuc_entry *reuc_entry_alloc(const char *path)
+{
+ size_t pathlen = strlen(path);
+ struct reuc_entry_internal *entry =
+ git__calloc(sizeof(struct reuc_entry_internal) + pathlen + 1, 1);
if (!entry)
- return GIT_ENOMEM;
- memset(entry, 0x0, sizeof(git_index_entry));
+ return NULL;
- entry->ctime.seconds = (git_time_t)st.st_ctime;
- entry->mtime.seconds = (git_time_t)st.st_mtime;
- /* entry.mtime.nanoseconds = st.st_mtimensec; */
- /* entry.ctime.nanoseconds = st.st_ctimensec; */
- entry->dev= st.st_rdev;
- entry->ino = st.st_ino;
- entry->mode = index_create_mode(st.st_mode);
- entry->uid = st.st_uid;
- entry->gid = st.st_gid;
- entry->file_size = st.st_size;
- entry->oid = oid;
+ entry->pathlen = pathlen;
+ memcpy(entry->path, path, pathlen);
+ entry->entry.path = entry->path;
- entry->flags |= (stage << GIT_IDXENTRY_STAGESHIFT);
- entry->path = git__strdup(rel_path);
- if (entry->path == NULL) {
- free(entry);
- return GIT_ENOMEM;
- }
+ return (git_index_reuc_entry *)entry;
+}
+
+static int index_entry_reuc_init(git_index_reuc_entry **reuc_out,
+ const char *path,
+ int ancestor_mode, const git_oid *ancestor_oid,
+ int our_mode, const git_oid *our_oid,
+ int their_mode, const git_oid *their_oid)
+{
+ git_index_reuc_entry *reuc = NULL;
+
+ assert(reuc_out && path);
+
+ *reuc_out = reuc = reuc_entry_alloc(path);
+ GITERR_CHECK_ALLOC(reuc);
+
+ if ((reuc->mode[0] = ancestor_mode) > 0)
+ git_oid_cpy(&reuc->oid[0], ancestor_oid);
+
+ if ((reuc->mode[1] = our_mode) > 0)
+ git_oid_cpy(&reuc->oid[1], our_oid);
+
+ if ((reuc->mode[2] = their_mode) > 0)
+ git_oid_cpy(&reuc->oid[2], their_oid);
+
+ return 0;
+}
- *entry_out = entry;
- return GIT_SUCCESS;
+static void index_entry_cpy(git_index_entry *tgt, const git_index_entry *src)
+{
+ const char *tgt_path = tgt->path;
+ memcpy(tgt, src, sizeof(*tgt));
+ tgt->path = tgt_path; /* reset to existing path data */
}
-static git_index_entry *index_entry_dup(const git_index_entry *source_entry)
+static int index_entry_dup(git_index_entry **out, const git_index_entry *src)
{
git_index_entry *entry;
- entry = git__malloc(sizeof(git_index_entry));
- if (!entry)
- return NULL;
+ if (!src) {
+ *out = NULL;
+ return 0;
+ }
- memcpy(entry, source_entry, sizeof(git_index_entry));
+ *out = entry = index_entry_alloc(src->path);
+ GITERR_CHECK_ALLOC(entry);
- /* duplicate the path string so we own it */
- entry->path = git__strdup(entry->path);
- if (!entry->path)
- return NULL;
+ index_entry_cpy(entry, src);
- return entry;
+ return 0;
}
-static void index_entry_free(git_index_entry *entry)
+static int has_file_name(git_index *index,
+ const git_index_entry *entry, size_t pos, int ok_to_replace)
{
- if (!entry)
- return;
- free(entry->path);
- free(entry);
+ int retval = 0;
+ size_t len = strlen(entry->path);
+ int stage = GIT_IDXENTRY_STAGE(entry);
+ const char *name = entry->path;
+
+ while (pos < index->entries.length) {
+ struct entry_internal *p = index->entries.contents[pos++];
+
+ if (len >= p->pathlen)
+ break;
+ if (memcmp(name, p->path, len))
+ break;
+ if (GIT_IDXENTRY_STAGE(&p->entry) != stage)
+ continue;
+ if (p->path[len] != '/')
+ continue;
+ retval = -1;
+ if (!ok_to_replace)
+ break;
+
+ if (index_remove_entry(index, --pos) < 0)
+ break;
+ }
+ return retval;
+}
+
+/*
+ * Do we have another file with a pathname that is a proper
+ * subset of the name we're trying to add?
+ */
+static int has_dir_name(git_index *index,
+ const git_index_entry *entry, int ok_to_replace)
+{
+ int retval = 0;
+ int stage = GIT_IDXENTRY_STAGE(entry);
+ const char *name = entry->path;
+ const char *slash = name + strlen(name);
+
+ for (;;) {
+ size_t len, pos;
+
+ for (;;) {
+ if (*--slash == '/')
+ break;
+ if (slash <= entry->path)
+ return retval;
+ }
+ len = slash - name;
+
+ if (!index_find(&pos, index, name, len, stage, false)) {
+ retval = -1;
+ if (!ok_to_replace)
+ break;
+
+ if (index_remove_entry(index, pos) < 0)
+ break;
+ continue;
+ }
+
+ /*
+ * Trivial optimization: if we find an entry that
+ * already matches the sub-directory, then we know
+ * we're ok, and we can exit.
+ */
+ for (; pos < index->entries.length; ++pos) {
+ struct entry_internal *p = index->entries.contents[pos];
+
+ if (p->pathlen <= len ||
+ p->path[len] != '/' ||
+ memcmp(p->path, name, len))
+ break; /* not our subdirectory */
+
+ if (GIT_IDXENTRY_STAGE(&p->entry) == stage)
+ return retval;
+ }
+ }
+
+ return retval;
}
-static int index_insert(git_index *index, git_index_entry *entry, int replace)
+static int check_file_directory_collision(git_index *index,
+ git_index_entry *entry, size_t pos, int ok_to_replace)
{
- size_t path_length;
- int position;
- git_index_entry **entry_array;
+ int retval = has_file_name(index, entry, pos, ok_to_replace);
+ retval = retval + has_dir_name(index, entry, ok_to_replace);
- assert(index && entry);
+ if (retval) {
+ giterr_set(GITERR_INDEX,
+ "'%s' appears as both a file and a directory", entry->path);
+ return -1;
+ }
+
+ return 0;
+}
- if (entry->path == NULL)
- return git__throw(GIT_EMISSINGOBJDATA, "Failed to insert into index. Entry has no path");
+static int index_no_dups(void **old, void *new)
+{
+ const git_index_entry *entry = new;
+ GIT_UNUSED(old);
+ giterr_set(GITERR_INDEX, "'%s' appears multiple times at stage %d",
+ entry->path, GIT_IDXENTRY_STAGE(entry));
+ return GIT_EEXISTS;
+}
+
+/* index_insert takes ownership of the new entry - if it can't insert
+ * it, then it will return an error **and also free the entry**. When
+ * it replaces an existing entry, it will update the entry_ptr with the
+ * actual entry in the index (and free the passed in one).
+ */
+static int index_insert(
+ git_index *index, git_index_entry **entry_ptr, int replace)
+{
+ int error = 0;
+ size_t path_length, position;
+ git_index_entry *existing = NULL, *entry;
+
+ assert(index && entry_ptr);
+
+ entry = *entry_ptr;
/* make sure that the path length flag is correct */
- path_length = strlen(entry->path);
+ path_length = ((struct entry_internal *)entry)->pathlen;
entry->flags &= ~GIT_IDXENTRY_NAMEMASK;
if (path_length < GIT_IDXENTRY_NAMEMASK)
entry->flags |= path_length & GIT_IDXENTRY_NAMEMASK;
else
- entry->flags |= GIT_IDXENTRY_NAMEMASK;;
-
- /*
- * replacing is not requested: just insert entry at the end;
- * the index is no longer sorted
- */
- if (!replace) {
- if (git_vector_insert(&index->entries, entry) < GIT_SUCCESS)
- return GIT_ENOMEM;
+ entry->flags |= GIT_IDXENTRY_NAMEMASK;
- return GIT_SUCCESS;
+ if (git_mutex_lock(&index->lock) < 0) {
+ giterr_set(GITERR_OS, "Unable to acquire index lock");
+ return -1;
}
+ git_vector_sort(&index->entries);
+
/* look if an entry with this path already exists */
- position = git_index_find(index, entry->path);
+ if (!index_find(
+ &position, index, entry->path, 0, GIT_IDXENTRY_STAGE(entry), false)) {
+ existing = index->entries.contents[position];
+ /* update filemode to existing values if stat is not trusted */
+ entry->mode = index_merge_mode(index, existing, entry->mode);
+ }
+
+ /* look for tree / blob name collisions, removing conflicts if requested */
+ error = check_file_directory_collision(index, entry, position, replace);
+ if (error < 0)
+ /* skip changes */;
- /*
- * if no entry exists add the entry at the end;
- * the index is no longer sorted
+ /* if we are replacing an existing item, overwrite the existing entry
+ * and return it in place of the passed in one.
*/
- if (position == GIT_ENOTFOUND) {
- if (git_vector_insert(&index->entries, entry) < GIT_SUCCESS)
- return GIT_ENOMEM;
+ else if (existing) {
+ if (replace)
+ index_entry_cpy(existing, entry);
+ index_entry_free(entry);
+ *entry_ptr = entry = existing;
+ }
+ else {
+ /* if replace is not requested or no existing entry exists, insert
+ * at the sorted position. (Since we re-sort after each insert to
+ * check for dups, this is actually cheaper in the long run.)
+ */
+ error = git_vector_insert_sorted(&index->entries, entry, index_no_dups);
+ }
- return GIT_SUCCESS;
+ if (error < 0) {
+ index_entry_free(*entry_ptr);
+ *entry_ptr = NULL;
}
- /* exists, replace it */
- entry_array = (git_index_entry **) index->entries.contents;
- free(entry_array[position]->path);
- free(entry_array[position]);
- entry_array[position] = entry;
+ git_mutex_unlock(&index->lock);
- return GIT_SUCCESS;
+ return error;
}
-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;
+ const git_index_entry *conflict_entries[3];
+ int ancestor_mode, our_mode, their_mode;
+ git_oid const *ancestor_oid, *our_oid, *their_oid;
int ret;
- ret = index_entry_init(&entry, index, path, stage);
- if (ret)
- goto err;
+ if ((ret = git_index_conflict_get(&conflict_entries[0],
+ &conflict_entries[1], &conflict_entries[2], index, path)) < 0)
+ return ret;
+
+ ancestor_mode = conflict_entries[0] == NULL ? 0 : conflict_entries[0]->mode;
+ our_mode = conflict_entries[1] == NULL ? 0 : conflict_entries[1]->mode;
+ their_mode = conflict_entries[2] == NULL ? 0 : conflict_entries[2]->mode;
- ret = index_insert(index, entry, replace);
- if (ret)
- goto err;
+ ancestor_oid = conflict_entries[0] == NULL ? NULL : &conflict_entries[0]->id;
+ our_oid = conflict_entries[1] == NULL ? NULL : &conflict_entries[1]->id;
+ their_oid = conflict_entries[2] == NULL ? NULL : &conflict_entries[2]->id;
+
+ 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;
-err:
- index_entry_free(entry);
- return git__rethrow(ret, "Failed to append to index");
}
-int git_index_add(git_index *index, const char *path, int stage)
+int git_index_add_bypath(git_index *index, const char *path)
{
- return index_add(index, path, stage, 1);
-}
+ git_index_entry *entry = NULL;
+ int ret;
-int git_index_append(git_index *index, const char *path, int stage)
-{
- return index_add(index, path, stage, 0);
+ assert(index && path);
+
+ if ((ret = index_entry_init(&entry, index, path)) < 0 ||
+ (ret = index_insert(index, &entry, 1)) < 0)
+ return ret;
+
+ /* Adding implies conflict was resolved, move conflict entries to REUC */
+ if ((ret = index_conflict_to_reuc(index, path)) < 0 && ret != GIT_ENOTFOUND)
+ return ret;
+
+ git_tree_cache_invalidate_path(index->tree, entry->path);
+ return 0;
}
-static int index_add2(git_index *index, const git_index_entry *source_entry,
- int replace)
+int git_index_remove_bypath(git_index *index, const char *path)
{
- git_index_entry *entry = NULL;
int ret;
- entry = index_entry_dup(source_entry);
- if (entry == NULL) {
- ret = GIT_ENOMEM;
- goto err;
- }
+ assert(index && path);
- ret = index_insert(index, entry, replace);
- if (ret)
- goto err;
+ if (((ret = git_index_remove(index, path, 0)) < 0 &&
+ ret != GIT_ENOTFOUND) ||
+ ((ret = index_conflict_to_reuc(index, path)) < 0 &&
+ ret != GIT_ENOTFOUND))
+ return ret;
- return ret;
-err:
- index_entry_free(entry);
- return git__rethrow(ret, "Failed to append to index");
+ return 0;
}
-int git_index_add2(git_index *index, const git_index_entry *source_entry)
+static bool valid_filemode(const int filemode)
{
- return index_add2(index, source_entry, 1);
+ return (filemode == GIT_FILEMODE_BLOB ||
+ filemode == GIT_FILEMODE_BLOB_EXECUTABLE ||
+ filemode == GIT_FILEMODE_LINK ||
+ filemode == GIT_FILEMODE_COMMIT);
}
-int git_index_append2(git_index *index, const git_index_entry *source_entry)
+
+int git_index_add(git_index *index, const git_index_entry *source_entry)
{
- return index_add2(index, source_entry, 1);
+ git_index_entry *entry = NULL;
+ int ret;
+
+ assert(index && source_entry && source_entry->path);
+
+ if (!valid_filemode(source_entry->mode)) {
+ giterr_set(GITERR_INDEX, "invalid filemode");
+ return -1;
+ }
+
+ if ((ret = index_entry_dup(&entry, source_entry)) < 0 ||
+ (ret = index_insert(index, &entry, 1)) < 0)
+ return ret;
+
+ git_tree_cache_invalidate_path(index->tree, entry->path);
+ return 0;
}
-int git_index_remove(git_index *index, int position)
+int git_index_remove(git_index *index, const char *path, int stage)
{
- git_vector_sort(&index->entries);
- return git_vector_remove(&index->entries, (unsigned int)position);
+ int error;
+ size_t position;
+
+ if (git_mutex_lock(&index->lock) < 0) {
+ giterr_set(GITERR_OS, "Failed to lock index");
+ return -1;
+ }
+
+ if (index_find(&position, index, path, 0, stage, false) < 0) {
+ giterr_set(
+ GITERR_INDEX, "Index does not contain %s at stage %d", path, stage);
+ error = GIT_ENOTFOUND;
+ } else {
+ error = index_remove_entry(index, position);
+ }
+
+ git_mutex_unlock(&index->lock);
+ return error;
}
-int git_index_find(git_index *index, const char *path)
+int git_index_remove_directory(git_index *index, const char *dir, int stage)
{
- return git_vector_bsearch2(&index->entries, index_srch, path);
+ git_buf pfx = GIT_BUF_INIT;
+ int error = 0;
+ size_t pos;
+ git_index_entry *entry;
+
+ if (git_mutex_lock(&index->lock) < 0) {
+ giterr_set(GITERR_OS, "Failed to lock index");
+ return -1;
+ }
+
+ if (!(error = git_buf_sets(&pfx, dir)) &&
+ !(error = git_path_to_dir(&pfx)))
+ index_find(&pos, index, pfx.ptr, pfx.size, GIT_INDEX_STAGE_ANY, false);
+
+ while (!error) {
+ entry = git_vector_get(&index->entries, pos);
+ if (!entry || git__prefixcmp(entry->path, pfx.ptr) != 0)
+ break;
+
+ if (GIT_IDXENTRY_STAGE(entry) != stage) {
+ ++pos;
+ continue;
+ }
+
+ error = index_remove_entry(index, pos);
+
+ /* removed entry at 'pos' so we don't need to increment */
+ }
+
+ git_mutex_unlock(&index->lock);
+ git_buf_free(&pfx);
+
+ return error;
}
-void git_index_uniq(git_index *index)
+int git_index__find_pos(
+ size_t *out, git_index *index, const char *path, size_t path_len, int stage)
{
- git_vector_uniq(&index->entries);
+ assert(index && path);
+ return index_find(out, index, path, path_len, stage, true);
}
-const git_index_entry_unmerged *git_index_get_unmerged_bypath(git_index *index, const char *path)
+int git_index_find(size_t *at_pos, git_index *index, const char *path)
{
- int pos;
+ size_t pos;
+
assert(index && path);
- if (!index->unmerged.length)
- return NULL;
+ if (git_mutex_lock(&index->lock) < 0) {
+ giterr_set(GITERR_OS, "Failed to lock index");
+ return -1;
+ }
- if ((pos = git_vector_bsearch2(&index->unmerged, unmerged_srch, path)) < GIT_SUCCESS)
- return NULL;
+ if (git_vector_bsearch2(
+ &pos, &index->entries, index->entries_search_path, path) < 0) {
+ git_mutex_unlock(&index->lock);
+ giterr_set(GITERR_INDEX, "Index does not contain %s", path);
+ return GIT_ENOTFOUND;
+ }
- return git_vector_get(&index->unmerged, pos);
+ /* Since our binary search only looked at path, we may be in the
+ * middle of a list of stages.
+ */
+ for (; pos > 0; --pos) {
+ const git_index_entry *prev = git_vector_get(&index->entries, pos - 1);
+
+ if (index->entries_cmp_path(prev->path, path) != 0)
+ break;
+ }
+
+ if (at_pos)
+ *at_pos = pos;
+
+ git_mutex_unlock(&index->lock);
+ return 0;
}
-const git_index_entry_unmerged *git_index_get_unmerged_byindex(git_index *index, unsigned int n)
+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)
{
- assert(index);
- return git_vector_get(&index->unmerged, n);
-}
+ git_index_entry *entries[3] = { 0 };
+ unsigned short i;
+ int ret = 0;
+ assert (index);
-static int read_tree_internal(git_index_tree **out,
- const char **buffer_in, const char *buffer_end, git_index_tree *parent)
-{
- git_index_tree *tree;
- const char *name_start, *buffer;
- long count;
- int error = GIT_SUCCESS;
+ if ((ret = index_entry_dup(&entries[0], ancestor_entry)) < 0 ||
+ (ret = index_entry_dup(&entries[1], our_entry)) < 0 ||
+ (ret = index_entry_dup(&entries[2], their_entry)) < 0)
+ goto on_error;
- if ((tree = git__malloc(sizeof(git_index_tree))) == NULL)
- return GIT_ENOMEM;
+ for (i = 0; i < 3; i++) {
+ if (entries[i] == NULL)
+ continue;
- memset(tree, 0x0, sizeof(git_index_tree));
- tree->parent = parent;
+ /* Make sure stage is correct */
+ GIT_IDXENTRY_STAGE_SET(entries[i], i + 1);
- buffer = name_start = *buffer_in;
+ if ((ret = index_insert(index, &entries[i], 1)) < 0)
+ goto on_error;
- if ((buffer = memchr(buffer, '\0', buffer_end - buffer)) == NULL) {
- error = GIT_EOBJCORRUPTED;
- goto cleanup;
+ entries[i] = NULL; /* don't free if later entry fails */
}
- /* NUL-terminated tree name */
- tree->name = git__strdup(name_start);
- if (tree->name == NULL) {
- error = GIT_ENOMEM;
- goto cleanup;
- }
+ return 0;
- if (++buffer >= buffer_end) {
- error = GIT_EOBJCORRUPTED;
- goto cleanup;
+on_error:
+ for (i = 0; i < 3; i++) {
+ if (entries[i] != NULL)
+ index_entry_free(entries[i]);
}
- /* Blank-terminated ASCII decimal number of entries in this tree */
- if (git__strtol32(&count, buffer, &buffer, 10) < GIT_SUCCESS || count < -1) {
- error = GIT_EOBJCORRUPTED;
- goto cleanup;
- }
+ return ret;
+}
- /* Invalidated TREE. Free the tree but report success */
- if (count == -1) {
- /* FIXME: return buffer_end or the end position for
- * this single tree entry */
- *buffer_in = buffer_end;
- *out = NULL;
- free_tree(tree); /* Needs to be done manually */
- return GIT_SUCCESS;
+static int index_conflict__get_byindex(
+ const git_index_entry **ancestor_out,
+ const git_index_entry **our_out,
+ const git_index_entry **their_out,
+ git_index *index,
+ size_t n)
+{
+ const git_index_entry *conflict_entry;
+ const char *path = NULL;
+ size_t count;
+ int stage, len = 0;
+
+ assert(ancestor_out && our_out && their_out && index);
+
+ *ancestor_out = NULL;
+ *our_out = NULL;
+ *their_out = NULL;
+
+ for (count = git_index_entrycount(index); n < count; ++n) {
+ conflict_entry = git_vector_get(&index->entries, n);
+
+ if (path && index->entries_cmp_path(conflict_entry->path, path) != 0)
+ break;
+
+ stage = GIT_IDXENTRY_STAGE(conflict_entry);
+ path = conflict_entry->path;
+
+ switch (stage) {
+ case 3:
+ *their_out = conflict_entry;
+ len++;
+ break;
+ case 2:
+ *our_out = conflict_entry;
+ len++;
+ break;
+ case 1:
+ *ancestor_out = conflict_entry;
+ len++;
+ break;
+ default:
+ break;
+ };
}
- tree->entries = count;
+ return len;
+}
+
+int git_index_conflict_get(
+ const git_index_entry **ancestor_out,
+ const git_index_entry **our_out,
+ const git_index_entry **their_out,
+ git_index *index,
+ const char *path)
+{
+ size_t pos;
+ int len = 0;
- if (*buffer != ' ' || ++buffer >= buffer_end) {
- error = GIT_EOBJCORRUPTED;
- goto cleanup;
- }
+ assert(ancestor_out && our_out && their_out && index && path);
- /* Number of children of the tree, newline-terminated */
- if (git__strtol32(&count, buffer, &buffer, 10) < GIT_SUCCESS ||
- count < 0) {
- error = GIT_EOBJCORRUPTED;
- goto cleanup;
+ *ancestor_out = NULL;
+ *our_out = NULL;
+ *their_out = NULL;
+
+ if (git_index_find(&pos, index, path) < 0)
+ return GIT_ENOTFOUND;
+
+ if ((len = index_conflict__get_byindex(
+ ancestor_out, our_out, their_out, index, pos)) < 0)
+ return len;
+ else if (len == 0)
+ return GIT_ENOTFOUND;
+
+ return 0;
+}
+
+static int index_conflict_remove(git_index *index, const char *path)
+{
+ size_t pos = 0;
+ git_index_entry *conflict_entry;
+ int error = 0;
+
+ if (path != NULL && git_index_find(&pos, index, path) < 0)
+ return GIT_ENOTFOUND;
+
+ if (git_mutex_lock(&index->lock) < 0) {
+ giterr_set(GITERR_OS, "Unable to lock index");
+ return -1;
}
- tree->children_count = count;
+ while ((conflict_entry = git_vector_get(&index->entries, pos)) != NULL) {
- if (*buffer != '\n' || ++buffer >= buffer_end) {
- error = GIT_EOBJCORRUPTED;
- goto cleanup;
+ if (path != NULL &&
+ index->entries_cmp_path(conflict_entry->path, path) != 0)
+ break;
+
+ if (GIT_IDXENTRY_STAGE(conflict_entry) == 0) {
+ pos++;
+ continue;
+ }
+
+ if ((error = index_remove_entry(index, pos)) < 0)
+ break;
}
- /* 160-bit SHA-1 for this tree and it's children */
- if (buffer + GIT_OID_RAWSZ > buffer_end) {
- error = GIT_EOBJCORRUPTED;
- goto cleanup;
+ git_mutex_unlock(&index->lock);
+
+ return error;
+}
+
+int git_index_conflict_remove(git_index *index, const char *path)
+{
+ assert(index && path);
+ return index_conflict_remove(index, path);
+}
+
+int git_index_conflict_cleanup(git_index *index)
+{
+ assert(index);
+ return index_conflict_remove(index, NULL);
+}
+
+int git_index_has_conflicts(const git_index *index)
+{
+ size_t i;
+ git_index_entry *entry;
+
+ assert(index);
+
+ git_vector_foreach(&index->entries, i, entry) {
+ if (GIT_IDXENTRY_STAGE(entry) > 0)
+ return 1;
}
- git_oid_fromraw(&tree->oid, (const unsigned char *)buffer);
- buffer += GIT_OID_RAWSZ;
+ return 0;
+}
+
+int git_index_conflict_iterator_new(
+ git_index_conflict_iterator **iterator_out,
+ git_index *index)
+{
+ git_index_conflict_iterator *it = NULL;
+
+ assert(iterator_out && index);
+
+ it = git__calloc(1, sizeof(git_index_conflict_iterator));
+ GITERR_CHECK_ALLOC(it);
+
+ it->index = index;
- /* Parse children: */
- if (tree->children_count > 0) {
- unsigned int i;
- int err;
+ *iterator_out = it;
+ return 0;
+}
+
+int git_index_conflict_next(
+ const git_index_entry **ancestor_out,
+ const git_index_entry **our_out,
+ const git_index_entry **their_out,
+ git_index_conflict_iterator *iterator)
+{
+ const git_index_entry *entry;
+ int len;
- tree->children = git__malloc(tree->children_count * sizeof(git_index_tree *));
- if (tree->children == NULL)
- goto cleanup;
+ assert(ancestor_out && our_out && their_out && iterator);
- for (i = 0; i < tree->children_count; ++i) {
- err = read_tree_internal(&tree->children[i], &buffer, buffer_end, tree);
+ *ancestor_out = NULL;
+ *our_out = NULL;
+ *their_out = NULL;
- if (err < GIT_SUCCESS)
- goto cleanup;
+ while (iterator->cur < iterator->index->entries.length) {
+ entry = git_index_get_byindex(iterator->index, iterator->cur);
+
+ if (git_index_entry_stage(entry) > 0) {
+ if ((len = index_conflict__get_byindex(
+ ancestor_out,
+ our_out,
+ their_out,
+ iterator->index,
+ iterator->cur)) < 0)
+ return len;
+
+ iterator->cur += len;
+ return 0;
}
+
+ iterator->cur++;
+ }
+
+ return GIT_ITEROVER;
+}
+
+void git_index_conflict_iterator_free(git_index_conflict_iterator *iterator)
+{
+ if (iterator == NULL)
+ return;
+
+ git__free(iterator);
+}
+
+unsigned int git_index_name_entrycount(git_index *index)
+{
+ assert(index);
+ return (unsigned int)index->names.length;
+}
+
+const git_index_name_entry *git_index_name_get_byindex(
+ git_index *index, size_t n)
+{
+ assert(index);
+
+ git_vector_sort(&index->names);
+ return git_vector_get(&index->names, n);
+}
+
+static void index_name_entry_free(git_index_name_entry *ne)
+{
+ if (!ne)
+ return;
+ git__free(ne->ancestor);
+ git__free(ne->ours);
+ git__free(ne->theirs);
+ git__free(ne);
+}
+
+int git_index_name_add(git_index *index,
+ const char *ancestor, const char *ours, const char *theirs)
+{
+ git_index_name_entry *conflict_name;
+
+ assert((ancestor && ours) || (ancestor && theirs) || (ours && theirs));
+
+ conflict_name = git__calloc(1, sizeof(git_index_name_entry));
+ GITERR_CHECK_ALLOC(conflict_name);
+
+ if ((ancestor && !(conflict_name->ancestor = git__strdup(ancestor))) ||
+ (ours && !(conflict_name->ours = git__strdup(ours))) ||
+ (theirs && !(conflict_name->theirs = git__strdup(theirs))) ||
+ git_vector_insert(&index->names, conflict_name) < 0)
+ {
+ index_name_entry_free(conflict_name);
+ return -1;
}
- *buffer_in = buffer;
- *out = tree;
- return GIT_SUCCESS;
+ return 0;
+}
+
+void git_index_name_clear(git_index *index)
+{
+ size_t i;
+ git_index_name_entry *conflict_name;
+
+ assert(index);
+
+ git_vector_foreach(&index->names, i, conflict_name)
+ index_name_entry_free(conflict_name);
+
+ git_vector_clear(&index->names);
+}
+
+unsigned int git_index_reuc_entrycount(git_index *index)
+{
+ assert(index);
+ return (unsigned int)index->reuc.length;
+}
+
+static int index_reuc_insert(
+ git_index *index,
+ git_index_reuc_entry *reuc,
+ int replace)
+{
+ git_index_reuc_entry **existing = NULL;
+ size_t position;
+
+ assert(index && reuc && reuc->path != NULL);
+
+ if (!git_index_reuc_find(&position, index, reuc->path))
+ existing = (git_index_reuc_entry **)&index->reuc.contents[position];
+
+ if (!replace || !existing)
+ return git_vector_insert(&index->reuc, reuc);
+
+ /* exists, replace it */
+ git__free(*existing);
+ *existing = reuc;
+
+ return 0;
+}
+
+int git_index_reuc_add(git_index *index, const char *path,
+ int ancestor_mode, const git_oid *ancestor_oid,
+ int our_mode, const git_oid *our_oid,
+ int their_mode, const 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);
- cleanup:
- free_tree(tree);
return error;
}
-static int read_tree(git_index *index, const char *buffer, size_t buffer_size)
+int git_index_reuc_find(size_t *at_pos, git_index *index, const char *path)
+{
+ return git_vector_bsearch2(at_pos, &index->reuc, index->reuc_search, path);
+}
+
+const git_index_reuc_entry *git_index_reuc_get_bypath(
+ git_index *index, const char *path)
+{
+ size_t pos;
+ assert(index && path);
+
+ if (!index->reuc.length)
+ return NULL;
+
+ git_vector_sort(&index->reuc);
+
+ if (git_index_reuc_find(&pos, index, path) < 0)
+ return NULL;
+
+ return git_vector_get(&index->reuc, pos);
+}
+
+const git_index_reuc_entry *git_index_reuc_get_byindex(
+ git_index *index, size_t n)
+{
+ assert(index);
+
+ git_vector_sort(&index->reuc);
+ return git_vector_get(&index->reuc, n);
+}
+
+int git_index_reuc_remove(git_index *index, size_t position)
{
- const char *buffer_end = buffer + buffer_size;
int error;
+ git_index_reuc_entry *reuc;
+
+ git_vector_sort(&index->reuc);
- error = read_tree_internal(&index->tree, &buffer, buffer_end, NULL);
+ reuc = git_vector_get(&index->reuc, position);
+ error = git_vector_remove(&index->reuc, position);
- if (buffer < buffer_end)
- return GIT_EOBJCORRUPTED;
+ if (!error)
+ index_entry_reuc_free(reuc);
return error;
}
-static int read_unmerged(git_index *index, const char *buffer, size_t size)
+void git_index_reuc_clear(git_index *index)
+{
+ size_t i;
+
+ assert(index);
+
+ for (i = 0; i < index->reuc.length; ++i)
+ index_entry_reuc_free(git__swap(index->reuc.contents[i], NULL));
+
+ git_vector_clear(&index->reuc);
+}
+
+static int index_error_invalid(const char *message)
+{
+ giterr_set(GITERR_INDEX, "Invalid data in index - %s", message);
+ return -1;
+}
+
+static int read_reuc(git_index *index, const char *buffer, size_t size)
{
const char *endptr;
size_t len;
int i;
- git_vector_init(&index->unmerged, 16, unmerged_cmp);
+ /* If 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;
+ len = p_strnlen(buffer, size) + 1;
if (size <= len)
- return git__throw(GIT_ERROR, "Failed to read unmerged entries");
-
- if ((lost = git__malloc(sizeof(git_index_entry_unmerged))) == NULL)
- return GIT_ENOMEM;
+ return index_error_invalid("reading reuc entries");
- if (git_vector_insert(&index->unmerged, lost) < GIT_SUCCESS)
- return git__throw(GIT_ERROR, "Failed to read unmerged entries");
-
- lost->path = git__strdup(buffer);
- if (!lost->path)
- return GIT_ENOMEM;
+ lost = reuc_entry_alloc(buffer);
+ GITERR_CHECK_ALLOC(lost);
size -= len;
buffer += len;
+ /* read 3 ASCII octal numbers for stage entries */
for (i = 0; i < 3; i++) {
- long tmp;
+ int tmp;
- if (git__strtol32(&tmp, buffer, &endptr, 8) < GIT_SUCCESS ||
- !endptr || endptr == buffer || *endptr || tmp > UINT_MAX)
- return GIT_ERROR;
+ if (git__strtol32(&tmp, buffer, &endptr, 8) < 0 ||
+ !endptr || endptr == buffer || *endptr ||
+ (unsigned)tmp > UINT_MAX) {
+ index_entry_reuc_free(lost);
+ return index_error_invalid("reading reuc entry stage");
+ }
lost->mode[i] = tmp;
len = (endptr + 1) - buffer;
- if (size <= len)
- return git__throw(GIT_ERROR, "Failed to read unmerged entries");
+ if (size <= len) {
+ index_entry_reuc_free(lost);
+ return index_error_invalid("reading reuc entry stage");
+ }
size -= len;
buffer += len;
}
+ /* read up to 3 OIDs for stage entries */
for (i = 0; i < 3; i++) {
if (!lost->mode[i])
continue;
- if (size < 20)
- return git__throw(GIT_ERROR, "Failed to read unmerged entries");
+ if (size < 20) {
+ index_entry_reuc_free(lost);
+ return index_error_invalid("reading reuc entry oid");
+ }
+
git_oid_fromraw(&lost->oid[i], (const unsigned char *) buffer);
size -= 20;
buffer += 20;
}
+
+ /* entry was read successfully - insert into reuc vector */
+ if (git_vector_insert(&index->reuc, lost) < 0)
+ return -1;
}
- return GIT_SUCCESS;
+ /* entries are guaranteed to be sorted on-disk */
+ git_vector_set_sorted(&index->reuc, true);
+
+ return 0;
}
-static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffer_size)
+
+static int read_conflict_names(git_index *index, const char *buffer, size_t size)
+{
+ size_t len;
+
+ /* This gets called multiple times, the vector might already be initialized */
+ if (index->names._alloc_size == 0 &&
+ git_vector_init(&index->names, 16, conflict_name_cmp) < 0)
+ return -1;
+
+#define read_conflict_name(ptr) \
+ len = p_strnlen(buffer, size) + 1; \
+ if (size < len) \
+ return index_error_invalid("reading conflict name entries"); \
+ \
+ if (len == 1) \
+ ptr = NULL; \
+ else { \
+ ptr = git__malloc(len); \
+ GITERR_CHECK_ALLOC(ptr); \
+ memcpy(ptr, buffer, len); \
+ } \
+ \
+ buffer += len; \
+ size -= len;
+
+ while (size) {
+ git_index_name_entry *conflict_name = git__calloc(1, sizeof(git_index_name_entry));
+ GITERR_CHECK_ALLOC(conflict_name);
+
+ read_conflict_name(conflict_name->ancestor);
+ read_conflict_name(conflict_name->ours);
+ read_conflict_name(conflict_name->theirs);
+
+ if (git_vector_insert(&index->names, conflict_name) < 0)
+ return -1;
+ }
+
+#undef read_conflict_name
+
+ /* entries are guaranteed to be sorted on-disk */
+ git_vector_set_sorted(&index->names, true);
+
+ return 0;
+}
+
+static size_t read_entry(
+ git_index_entry **out, const void *buffer, size_t buffer_size)
{
size_t path_length, entry_size;
uint16_t flags_raw;
const char *path_ptr;
const struct entry_short *source = buffer;
+ git_index_entry entry = {{0}};
if (INDEX_FOOTER_SIZE + minimal_entry_size > buffer_size)
return 0;
- memset(dest, 0x0, sizeof(git_index_entry));
-
- dest->ctime.seconds = (git_time_t)ntohl(source->ctime.seconds);
- dest->ctime.nanoseconds = ntohl(source->ctime.nanoseconds);
- dest->mtime.seconds = (git_time_t)ntohl(source->mtime.seconds);
- dest->mtime.nanoseconds = ntohl(source->mtime.nanoseconds);
- dest->dev = ntohl(source->dev);
- dest->ino = ntohl(source->ino);
- dest->mode = ntohl(source->mode);
- dest->uid = ntohl(source->uid);
- dest->gid = ntohl(source->gid);
- dest->file_size = ntohl(source->file_size);
- git_oid_cpy(&dest->oid, &source->oid);
- dest->flags = ntohs(source->flags);
-
- if (dest->flags & GIT_IDXENTRY_EXTENDED) {
+ entry.ctime.seconds = (git_time_t)ntohl(source->ctime.seconds);
+ entry.ctime.nanoseconds = ntohl(source->ctime.nanoseconds);
+ entry.mtime.seconds = (git_time_t)ntohl(source->mtime.seconds);
+ entry.mtime.nanoseconds = ntohl(source->mtime.nanoseconds);
+ entry.dev = ntohl(source->dev);
+ entry.ino = ntohl(source->ino);
+ entry.mode = ntohl(source->mode);
+ entry.uid = ntohl(source->uid);
+ entry.gid = ntohl(source->gid);
+ entry.file_size = ntohl(source->file_size);
+ git_oid_cpy(&entry.id, &source->oid);
+ entry.flags = ntohs(source->flags);
+
+ if (entry.flags & GIT_IDXENTRY_EXTENDED) {
const struct entry_long *source_l = (const struct entry_long *)source;
path_ptr = source_l->path;
flags_raw = ntohs(source_l->flags_extended);
- memcpy(&dest->flags_extended, &flags_raw, 2);
+ memcpy(&entry.flags_extended, &flags_raw, 2);
} else
path_ptr = source->path;
- path_length = dest->flags & GIT_IDXENTRY_NAMEMASK;
+ path_length = entry.flags & GIT_IDXENTRY_NAMEMASK;
/* if this is a very long string, we must find its
* real length without overflowing */
@@ -776,12 +1806,12 @@ static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffe
path_end = memchr(path_ptr, '\0', buffer_size);
if (path_end == NULL)
- return 0;
+ return 0;
path_length = path_end - path_ptr;
}
- if (dest->flags & GIT_IDXENTRY_EXTENDED)
+ if (entry.flags & GIT_IDXENTRY_EXTENDED)
entry_size = long_entry_size(path_length);
else
entry_size = short_entry_size(path_length);
@@ -789,8 +1819,10 @@ static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffe
if (INDEX_FOOTER_SIZE + entry_size > buffer_size)
return 0;
- dest->path = git__strdup(path_ptr);
- assert(dest->path);
+ entry.path = (char *)path_ptr;
+
+ if (index_entry_dup(out, &entry) < 0)
+ return 0;
return entry_size;
}
@@ -801,15 +1833,15 @@ static int read_header(struct index_header *dest, const void *buffer)
dest->signature = ntohl(source->signature);
if (dest->signature != INDEX_HEADER_SIG)
- return GIT_EOBJCORRUPTED;
+ return index_error_invalid("incorrect header signature");
dest->version = ntohl(source->version);
if (dest->version != INDEX_VERSION_NUMBER_EXT &&
dest->version != INDEX_VERSION_NUMBER)
- return GIT_EOBJCORRUPTED;
+ return index_error_invalid("incorrect header version");
dest->entry_count = ntohl(source->entry_count);
- return GIT_SUCCESS;
+ return 0;
}
static size_t read_extension(git_index *index, const char *buffer, size_t buffer_size)
@@ -825,17 +1857,22 @@ static size_t read_extension(git_index *index, const char *buffer, size_t buffer
total_size = dest.extension_size + sizeof(struct index_extension);
- if (buffer_size - total_size < INDEX_FOOTER_SIZE)
+ if (dest.extension_size > total_size ||
+ buffer_size < total_size ||
+ buffer_size - total_size < INDEX_FOOTER_SIZE)
return 0;
/* optional extension */
if (dest.signature[0] >= 'A' && dest.signature[0] <= 'Z') {
/* tree cache */
if (memcmp(dest.signature, INDEX_EXT_TREECACHE_SIG, 4) == 0) {
- if (read_tree(index, buffer + 8, dest.extension_size) < GIT_SUCCESS)
+ 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) < GIT_SUCCESS)
+ if (read_reuc(index, buffer + 8, dest.extension_size) < 0)
+ return 0;
+ } else if (memcmp(dest.signature, INDEX_EXT_CONFLICT_NAME_SIG, 4) == 0) {
+ if (read_conflict_names(index, buffer + 8, dest.extension_size) < 0)
return 0;
}
/* else, unsupported extension. We cannot parse this, but we can skip
@@ -851,55 +1888,62 @@ static size_t read_extension(git_index *index, const char *buffer, size_t buffer
static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
{
+ int error = 0;
unsigned int i;
- struct index_header header;
+ struct index_header header = { 0 };
git_oid checksum_calculated, checksum_expected;
#define seek_forward(_increase) { \
- if (_increase >= buffer_size) \
- return git__throw(GIT_EOBJCORRUPTED, "Failed to seek forward. Buffer size exceeded"); \
+ if (_increase >= buffer_size) { \
+ error = index_error_invalid("ran out of data while parsing"); \
+ goto done; } \
buffer += _increase; \
buffer_size -= _increase;\
}
if (buffer_size < INDEX_HEADER_SIZE + INDEX_FOOTER_SIZE)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Buffer too small");
+ return index_error_invalid("insufficient buffer space");
/* Precalculate the SHA1 of the files's contents -- we'll match it to
* the provided SHA1 in the footer */
git_hash_buf(&checksum_calculated, buffer, buffer_size - INDEX_FOOTER_SIZE);
/* Parse header */
- if (read_header(&header, buffer) < GIT_SUCCESS)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Header is corrupted");
+ if ((error = read_header(&header, buffer)) < 0)
+ return error;
seek_forward(INDEX_HEADER_SIZE);
- git_vector_clear(&index->entries);
+ if (git_mutex_lock(&index->lock) < 0) {
+ giterr_set(GITERR_OS, "Unable to acquire index lock");
+ return -1;
+ }
+
+ assert(!index->entries.length);
/* Parse all the entries */
for (i = 0; i < header.entry_count && buffer_size > INDEX_FOOTER_SIZE; ++i) {
- size_t entry_size;
git_index_entry *entry;
-
- entry = git__malloc(sizeof(git_index_entry));
- if (entry == NULL)
- return GIT_ENOMEM;
-
- entry_size = read_entry(entry, buffer, buffer_size);
+ size_t entry_size = read_entry(&entry, buffer, buffer_size);
/* 0 bytes read means an object corruption */
- if (entry_size == 0)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Entry size is zero");
+ if (entry_size == 0) {
+ error = index_error_invalid("invalid entry");
+ goto done;
+ }
- if (git_vector_insert(&index->entries, entry) < GIT_SUCCESS)
- return GIT_ENOMEM;
+ if ((error = git_vector_insert(&index->entries, entry)) < 0) {
+ index_entry_free(entry);
+ goto done;
+ }
seek_forward(entry_size);
}
- if (i != header.entry_count)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Header entries changed while parsing");
+ if (i != header.entry_count) {
+ error = index_error_invalid("header entries changed while parsing");
+ goto done;
+ }
/* There's still space for some extensions! */
while (buffer_size > INDEX_FOOTER_SIZE) {
@@ -908,62 +1952,78 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
extension_size = read_extension(index, buffer, buffer_size);
/* see if we have read any bytes from the extension */
- if (extension_size == 0)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Extension size is zero");
+ if (extension_size == 0) {
+ error = index_error_invalid("extension is truncated");
+ goto done;
+ }
seek_forward(extension_size);
}
- if (buffer_size != INDEX_FOOTER_SIZE)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Buffer size does not match index footer size");
+ if (buffer_size != INDEX_FOOTER_SIZE) {
+ error = index_error_invalid(
+ "buffer size does not match index footer size");
+ goto done;
+ }
/* 160-bit SHA-1 over the content of the index file before this checksum. */
git_oid_fromraw(&checksum_expected, (const unsigned char *)buffer);
- if (git_oid_cmp(&checksum_calculated, &checksum_expected) != 0)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Calculated checksum does not match expected checksum");
+ if (git_oid__cmp(&checksum_calculated, &checksum_expected) != 0) {
+ error = index_error_invalid(
+ "calculated checksum does not match expected");
+ goto done;
+ }
#undef seek_forward
- /* force sorting in the vector: the entries are
- * assured to be sorted on the index */
- index->entries.sorted = 1;
- return GIT_SUCCESS;
+ /* Entries are stored case-sensitively on disk, so re-sort now if
+ * in-memory index is supposed to be case-insensitive
+ */
+ git_vector_set_sorted(&index->entries, !index->ignore_case);
+ error = index_sort_if_needed(index, false);
+
+done:
+ git_mutex_unlock(&index->lock);
+ return error;
}
-static int is_index_extended(git_index *index)
+static bool is_index_extended(git_index *index)
{
- unsigned int i, extended;
+ size_t i, extended;
+ git_index_entry *entry;
extended = 0;
- for (i = 0; i < index->entries.length; ++i) {
- git_index_entry *entry;
- entry = git_vector_get(&index->entries, i);
+ git_vector_foreach(&index->entries, i, entry) {
entry->flags &= ~GIT_IDXENTRY_EXTENDED;
if (entry->flags_extended & GIT_IDXENTRY_EXTENDED_FLAGS) {
extended++;
entry->flags |= GIT_IDXENTRY_EXTENDED;
}
}
- return extended;
+
+ return (extended > 0);
}
static int write_disk_entry(git_filebuf *file, git_index_entry *entry)
{
+ void *mem = NULL;
struct entry_short *ondisk;
size_t path_len, disk_size;
char *path;
- path_len = strlen(entry->path);
+ path_len = ((struct entry_internal *)entry)->pathlen;
if (entry->flags & GIT_IDXENTRY_EXTENDED)
disk_size = long_entry_size(path_len);
else
disk_size = short_entry_size(path_len);
- if (git_filebuf_reserve(file, (void **)&ondisk, disk_size) < GIT_SUCCESS)
- return GIT_ENOMEM;
+ if (git_filebuf_reserve(file, &mem, disk_size) < 0)
+ return -1;
+
+ ondisk = (struct entry_short *)mem;
memset(ondisk, 0x0, disk_size);
@@ -981,14 +2041,14 @@ static int write_disk_entry(git_filebuf *file, git_index_entry *entry)
ondisk->mtime.seconds = htonl((uint32_t)entry->mtime.seconds);
ondisk->ctime.nanoseconds = htonl(entry->ctime.nanoseconds);
ondisk->mtime.nanoseconds = htonl(entry->mtime.nanoseconds);
- ondisk->dev = htonl(entry->dev);
- ondisk->ino = htonl(entry->ino);
+ ondisk->dev = htonl(entry->dev);
+ ondisk->ino = htonl(entry->ino);
ondisk->mode = htonl(entry->mode);
- ondisk->uid = htonl(entry->uid);
- ondisk->gid = htonl(entry->gid);
+ ondisk->uid = htonl(entry->uid);
+ ondisk->gid = htonl(entry->gid);
ondisk->file_size = htonl((uint32_t)entry->file_size);
- git_oid_cpy(&ondisk->oid, &entry->oid);
+ git_oid_cpy(&ondisk->oid, &entry->id);
ondisk->flags = htons(entry->flags);
@@ -1003,58 +2063,559 @@ static int write_disk_entry(git_filebuf *file, git_index_entry *entry)
memcpy(path, entry->path, path_len);
- return GIT_SUCCESS;
+ return 0;
}
static int write_entries(git_index *index, git_filebuf *file)
{
- unsigned int i;
+ int error = 0;
+ size_t i;
+ git_vector case_sorted, *entries;
+ git_index_entry *entry;
- for (i = 0; i < index->entries.length; ++i) {
- git_index_entry *entry;
- entry = git_vector_get(&index->entries, i);
- if (write_disk_entry(file, entry) < GIT_SUCCESS)
- return GIT_ENOMEM;
+ if (git_mutex_lock(&index->lock) < 0) {
+ giterr_set(GITERR_OS, "Failed to lock index");
+ return -1;
+ }
+
+ /* If index->entries is sorted case-insensitively, then we need
+ * to re-sort it case-sensitively before writing */
+ if (index->ignore_case) {
+ git_vector_dup(&case_sorted, &index->entries, git_index_entry_cmp);
+ git_vector_sort(&case_sorted);
+ entries = &case_sorted;
+ } else {
+ entries = &index->entries;
}
- return GIT_SUCCESS;
+ git_vector_foreach(entries, i, entry)
+ if ((error = write_disk_entry(file, entry)) < 0)
+ break;
+
+ git_mutex_unlock(&index->lock);
+
+ if (index->ignore_case)
+ git_vector_free(&case_sorted);
+
+ return error;
+}
+
+static int write_extension(git_filebuf *file, struct index_extension *header, git_buf *data)
+{
+ struct index_extension ondisk;
+ int error = 0;
+
+ memset(&ondisk, 0x0, sizeof(struct index_extension));
+ memcpy(&ondisk, header, 4);
+ ondisk.extension_size = htonl(header->extension_size);
+
+ if ((error = git_filebuf_write(file, &ondisk, sizeof(struct index_extension))) == 0)
+ error = git_filebuf_write(file, data->ptr, data->size);
+
+ return error;
+}
+
+static int create_name_extension_data(git_buf *name_buf, git_index_name_entry *conflict_name)
+{
+ int error = 0;
+
+ if (conflict_name->ancestor == NULL)
+ error = git_buf_put(name_buf, "\0", 1);
+ else
+ error = git_buf_put(name_buf, conflict_name->ancestor, strlen(conflict_name->ancestor) + 1);
+
+ if (error != 0)
+ goto on_error;
+
+ if (conflict_name->ours == NULL)
+ error = git_buf_put(name_buf, "\0", 1);
+ else
+ error = git_buf_put(name_buf, conflict_name->ours, strlen(conflict_name->ours) + 1);
+
+ if (error != 0)
+ goto on_error;
+
+ if (conflict_name->theirs == NULL)
+ error = git_buf_put(name_buf, "\0", 1);
+ else
+ error = git_buf_put(name_buf, conflict_name->theirs, strlen(conflict_name->theirs) + 1);
+
+on_error:
+ return error;
+}
+
+static int write_name_extension(git_index *index, git_filebuf *file)
+{
+ git_buf name_buf = GIT_BUF_INIT;
+ git_vector *out = &index->names;
+ git_index_name_entry *conflict_name;
+ struct index_extension extension;
+ size_t i;
+ int error = 0;
+
+ git_vector_foreach(out, i, conflict_name) {
+ if ((error = create_name_extension_data(&name_buf, conflict_name)) < 0)
+ goto done;
+ }
+
+ memset(&extension, 0x0, sizeof(struct index_extension));
+ memcpy(&extension.signature, INDEX_EXT_CONFLICT_NAME_SIG, 4);
+ extension.extension_size = (uint32_t)name_buf.size;
+
+ error = write_extension(file, &extension, &name_buf);
+
+ git_buf_free(&name_buf);
+
+done:
+ return error;
+}
+
+static int create_reuc_extension_data(git_buf *reuc_buf, git_index_reuc_entry *reuc)
+{
+ int i;
+ int error = 0;
+
+ if ((error = git_buf_put(reuc_buf, reuc->path, strlen(reuc->path) + 1)) < 0)
+ return error;
+
+ for (i = 0; i < 3; i++) {
+ if ((error = git_buf_printf(reuc_buf, "%o", reuc->mode[i])) < 0 ||
+ (error = git_buf_put(reuc_buf, "\0", 1)) < 0)
+ return error;
+ }
+
+ for (i = 0; i < 3; i++) {
+ if (reuc->mode[i] && (error = git_buf_put(reuc_buf, (char *)&reuc->oid[i].id, GIT_OID_RAWSZ)) < 0)
+ return error;
+ }
+
+ return 0;
+}
+
+static int write_reuc_extension(git_index *index, git_filebuf *file)
+{
+ git_buf reuc_buf = GIT_BUF_INIT;
+ git_vector *out = &index->reuc;
+ git_index_reuc_entry *reuc;
+ struct index_extension extension;
+ size_t i;
+ int error = 0;
+
+ git_vector_foreach(out, i, reuc) {
+ if ((error = create_reuc_extension_data(&reuc_buf, reuc)) < 0)
+ goto done;
+ }
+
+ memset(&extension, 0x0, sizeof(struct index_extension));
+ memcpy(&extension.signature, INDEX_EXT_UNMERGED_SIG, 4);
+ extension.extension_size = (uint32_t)reuc_buf.size;
+
+ error = write_extension(file, &extension, &reuc_buf);
+
+ git_buf_free(&reuc_buf);
+
+done:
+ return error;
}
static int write_index(git_index *index, git_filebuf *file)
{
- int error = GIT_SUCCESS;
git_oid hash_final;
-
struct index_header header;
-
- int is_extended;
+ bool is_extended;
+ uint32_t index_version_number;
assert(index && file);
is_extended = is_index_extended(index);
+ index_version_number = is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER;
header.signature = htonl(INDEX_HEADER_SIG);
- header.version = htonl(is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER);
- header.entry_count = htonl(index->entries.length);
+ header.version = htonl(index_version_number);
+ header.entry_count = htonl((uint32_t)index->entries.length);
+
+ if (git_filebuf_write(file, &header, sizeof(struct index_header)) < 0)
+ return -1;
- git_filebuf_write(file, &header, sizeof(struct index_header));
+ if (write_entries(index, file) < 0)
+ return -1;
- error = write_entries(index, file);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to write index");
+ /* TODO: write tree cache extension */
- /* TODO: write extensions (tree cache) */
+ /* write the rename conflict extension */
+ if (index->names.length > 0 && write_name_extension(index, file) < 0)
+ return -1;
+
+ /* write the reuc extension */
+ if (index->reuc.length > 0 && write_reuc_extension(index, file) < 0)
+ return -1;
/* get out the hash for all the contents we've appended to the file */
git_filebuf_hash(&hash_final, file);
/* write it at the end of the file */
- git_filebuf_write(file, hash_final.id, GIT_OID_RAWSZ);
-
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write index");
+ return git_filebuf_write(file, hash_final.id, GIT_OID_RAWSZ);
}
int git_index_entry_stage(const git_index_entry *entry)
{
- return (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT;
+ return GIT_IDXENTRY_STAGE(entry);
+}
+
+typedef struct read_tree_data {
+ git_vector *old_entries;
+ git_vector *new_entries;
+ git_vector_cmp entry_cmp;
+} read_tree_data;
+
+static int read_tree_cb(
+ const char *root, const git_tree_entry *tentry, void *payload)
+{
+ read_tree_data *data = payload;
+ git_index_entry *entry = NULL, *old_entry;
+ git_buf path = GIT_BUF_INIT;
+ size_t pos;
+
+ if (git_tree_entry__is_tree(tentry))
+ return 0;
+
+ if (git_buf_joinpath(&path, root, tentry->filename) < 0)
+ return -1;
+
+ entry = index_entry_alloc(path.ptr);
+ GITERR_CHECK_ALLOC(entry);
+
+ entry->mode = tentry->attr;
+ entry->id = tentry->oid;
+
+ /* look for corresponding old entry and copy data to new entry */
+ if (data->old_entries != NULL &&
+ !index_find_in_entries(
+ &pos, data->old_entries, data->entry_cmp, path.ptr, 0, 0) &&
+ (old_entry = git_vector_get(data->old_entries, pos)) != NULL &&
+ entry->mode == old_entry->mode &&
+ git_oid_equal(&entry->id, &old_entry->id))
+ {
+ index_entry_cpy(entry, old_entry);
+ entry->flags_extended = 0;
+ }
+
+ if (path.size < GIT_IDXENTRY_NAMEMASK)
+ entry->flags = path.size & GIT_IDXENTRY_NAMEMASK;
+ else
+ entry->flags = GIT_IDXENTRY_NAMEMASK;
+
+ git_buf_free(&path);
+
+ if (git_vector_insert(data->new_entries, entry) < 0) {
+ index_entry_free(entry);
+ return -1;
+ }
+
+ return 0;
+}
+
+int git_index_read_tree(git_index *index, const git_tree *tree)
+{
+ int error = 0;
+ git_vector entries = GIT_VECTOR_INIT;
+ read_tree_data data;
+
+ git_vector_set_cmp(&entries, index->entries._cmp); /* match sort */
+
+ data.old_entries = &index->entries;
+ data.new_entries = &entries;
+ data.entry_cmp = index->entries_search;
+
+ if (index_sort_if_needed(index, true) < 0)
+ return -1;
+
+ error = git_tree_walk(tree, GIT_TREEWALK_POST, read_tree_cb, &data);
+
+ if (!error) {
+ git_vector_sort(&entries);
+
+ if ((error = git_index_clear(index)) < 0)
+ /* well, this isn't good */;
+ else if (git_mutex_lock(&index->lock) < 0) {
+ giterr_set(GITERR_OS, "Unable to acquire index lock");
+ error = -1;
+ } else {
+ git_vector_swap(&entries, &index->entries);
+ git_mutex_unlock(&index->lock);
+ }
+ }
+
+ git_vector_free(&entries);
+
+ return error;
+}
+
+git_repository *git_index_owner(const git_index *index)
+{
+ return INDEX_OWNER(index);
+}
+
+int git_index_add_all(
+ git_index *index,
+ const git_strarray *paths,
+ unsigned int flags,
+ git_index_matched_path_cb cb,
+ void *payload)
+{
+ int error;
+ git_repository *repo;
+ git_iterator *wditer = NULL;
+ const git_index_entry *wd = NULL;
+ git_index_entry *entry;
+ git_pathspec ps;
+ const char *match;
+ size_t existing;
+ bool no_fnmatch = (flags & GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH) != 0;
+ int ignorecase;
+ git_oid blobid;
+
+ assert(index);
+
+ if (INDEX_OWNER(index) == NULL)
+ return create_index_error(-1,
+ "Could not add paths to index. "
+ "Index is not backed up by an existing repository.");
+
+ repo = INDEX_OWNER(index);
+ if ((error = git_repository__ensure_not_bare(repo, "index add all")) < 0)
+ return error;
+
+ if (git_repository__cvar(&ignorecase, repo, GIT_CVAR_IGNORECASE) < 0)
+ return -1;
+
+ if ((error = git_pathspec__init(&ps, paths)) < 0)
+ return error;
+
+ /* optionally check that pathspec doesn't mention any ignored files */
+ if ((flags & GIT_INDEX_ADD_CHECK_PATHSPEC) != 0 &&
+ (flags & GIT_INDEX_ADD_FORCE) == 0 &&
+ (error = git_ignore__check_pathspec_for_exact_ignores(
+ repo, &ps.pathspec, no_fnmatch)) < 0)
+ goto cleanup;
+
+ if ((error = git_iterator_for_workdir(
+ &wditer, repo, 0, ps.prefix, ps.prefix)) < 0)
+ goto cleanup;
+
+ while (!(error = git_iterator_advance(&wd, wditer))) {
+
+ /* check if path actually matches */
+ if (!git_pathspec__match(
+ &ps.pathspec, wd->path, no_fnmatch, (bool)ignorecase, &match, NULL))
+ continue;
+
+ /* skip ignored items that are not already in the index */
+ if ((flags & GIT_INDEX_ADD_FORCE) == 0 &&
+ git_iterator_current_is_ignored(wditer) &&
+ index_find(&existing, index, wd->path, 0, 0, true) < 0)
+ continue;
+
+ /* issue notification callback if requested */
+ if (cb && (error = cb(wd->path, match, payload)) != 0) {
+ if (error > 0) /* return > 0 means skip this one */
+ continue;
+ if (error < 0) { /* return < 0 means abort */
+ giterr_set_after_callback(error);
+ break;
+ }
+ }
+
+ /* TODO: Should we check if the file on disk is already an exact
+ * match to the file in the index and skip this work if it is?
+ */
+
+ /* write the blob to disk and get the oid */
+ if ((error = git_blob_create_fromworkdir(&blobid, repo, wd->path)) < 0)
+ break;
+
+ /* make the new entry to insert */
+ if ((error = index_entry_dup(&entry, wd)) < 0)
+ break;
+
+ entry->id = blobid;
+
+ /* add working directory item to index */
+ if ((error = index_insert(index, &entry, 1)) < 0)
+ break;
+
+ git_tree_cache_invalidate_path(index->tree, wd->path);
+
+ /* add implies conflict resolved, move conflict entries to REUC */
+ if ((error = index_conflict_to_reuc(index, wd->path)) < 0) {
+ if (error != GIT_ENOTFOUND)
+ break;
+ giterr_clear();
+ }
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+cleanup:
+ git_iterator_free(wditer);
+ git_pathspec__clear(&ps);
+
+ return error;
+}
+
+enum {
+ INDEX_ACTION_NONE = 0,
+ INDEX_ACTION_UPDATE = 1,
+ INDEX_ACTION_REMOVE = 2,
+};
+
+static int index_apply_to_all(
+ git_index *index,
+ int action,
+ const git_strarray *paths,
+ git_index_matched_path_cb cb,
+ void *payload)
+{
+ int error = 0;
+ size_t i;
+ git_pathspec ps;
+ const char *match;
+ git_buf path = GIT_BUF_INIT;
+
+ assert(index);
+
+ if ((error = git_pathspec__init(&ps, paths)) < 0)
+ return error;
+
+ git_vector_sort(&index->entries);
+
+ for (i = 0; !error && i < index->entries.length; ++i) {
+ git_index_entry *entry = git_vector_get(&index->entries, i);
+
+ /* check if path actually matches */
+ if (!git_pathspec__match(
+ &ps.pathspec, entry->path, false, (bool)index->ignore_case,
+ &match, NULL))
+ continue;
+
+ /* issue notification callback if requested */
+ if (cb && (error = cb(entry->path, match, payload)) != 0) {
+ if (error > 0) { /* return > 0 means skip this one */
+ error = 0;
+ continue;
+ }
+ if (error < 0) /* return < 0 means abort */
+ break;
+ }
+
+ /* index manipulation may alter entry, so don't depend on it */
+ if ((error = git_buf_sets(&path, entry->path)) < 0)
+ break;
+
+ switch (action) {
+ case INDEX_ACTION_NONE:
+ break;
+ case INDEX_ACTION_UPDATE:
+ error = git_index_add_bypath(index, path.ptr);
+
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+
+ error = git_index_remove_bypath(index, path.ptr);
+
+ if (!error) /* back up foreach if we removed this */
+ i--;
+ }
+ break;
+ case INDEX_ACTION_REMOVE:
+ if (!(error = git_index_remove_bypath(index, path.ptr)))
+ i--; /* back up foreach if we removed this */
+ break;
+ default:
+ giterr_set(GITERR_INVALID, "Unknown index action %d", action);
+ error = -1;
+ break;
+ }
+ }
+
+ git_buf_free(&path);
+ git_pathspec__clear(&ps);
+
+ return error;
+}
+
+int git_index_remove_all(
+ git_index *index,
+ const git_strarray *pathspec,
+ git_index_matched_path_cb cb,
+ void *payload)
+{
+ int error = index_apply_to_all(
+ index, INDEX_ACTION_REMOVE, pathspec, cb, payload);
+
+ if (error) /* make sure error is set if callback stopped iteration */
+ giterr_set_after_callback(error);
+
+ return error;
+}
+
+int git_index_update_all(
+ git_index *index,
+ const git_strarray *pathspec,
+ git_index_matched_path_cb cb,
+ void *payload)
+{
+ int error = index_apply_to_all(
+ index, INDEX_ACTION_UPDATE, pathspec, cb, payload);
+
+ if (error) /* make sure error is set if callback stopped iteration */
+ giterr_set_after_callback(error);
+
+ return error;
+}
+
+int git_index_snapshot_new(git_vector *snap, git_index *index)
+{
+ int error;
+
+ GIT_REFCOUNT_INC(index);
+
+ if (git_mutex_lock(&index->lock) < 0) {
+ giterr_set(GITERR_OS, "Failed to lock index");
+ return -1;
+ }
+
+ git_atomic_inc(&index->readers);
+ git_vector_sort(&index->entries);
+
+ error = git_vector_dup(snap, &index->entries, index->entries._cmp);
+
+ git_mutex_unlock(&index->lock);
+
+ if (error < 0)
+ git_index_free(index);
+
+ return error;
+}
+
+void git_index_snapshot_release(git_vector *snap, git_index *index)
+{
+ git_vector_free(snap);
+
+ git_atomic_dec(&index->readers);
+
+ if (!git_mutex_lock(&index->lock)) {
+ index_free_deleted(index); /* try to free pending deleted items */
+ git_mutex_unlock(&index->lock);
+ }
+
+ git_index_free(index);
+}
+
+int git_index_snapshot_find(
+ size_t *out, git_vector *entries, git_vector_cmp entry_srch,
+ const char *path, size_t path_len, int stage)
+{
+ return index_find_in_entries(out, entries, entry_srch, path, path_len, stage);
}
diff --git a/src/index.h b/src/index.h
index f2402fd71..50a0b4b6c 100644
--- a/src/index.h
+++ b/src/index.h
@@ -1,36 +1,96 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_index_h__
#define INCLUDE_index_h__
#include "fileops.h"
#include "filebuf.h"
#include "vector.h"
+#include "tree-cache.h"
#include "git2/odb.h"
#include "git2/index.h"
-struct git_index_tree {
- char *name;
-
- struct git_index_tree *parent;
- struct git_index_tree **children;
- size_t children_count;
-
- size_t entries;
- git_oid oid;
-};
-
-typedef struct git_index_tree git_index_tree;
+#define GIT_INDEX_FILE "index"
+#define GIT_INDEX_FILE_MODE 0666
struct git_index {
- git_repository *repository;
+ git_refcount rc;
+
char *index_file_path;
+ git_futils_filestamp stamp;
- time_t last_modified;
git_vector entries;
+ git_mutex lock; /* lock held while entries is being changed */
+ git_vector deleted; /* deleted entries if readers > 0 */
+ git_atomic readers; /* number of active iterators */
+
unsigned int on_disk:1;
- git_index_tree *tree;
+ unsigned int ignore_case:1;
+ unsigned int distrust_filemode:1;
+ unsigned int no_symlinks:1;
- git_vector unmerged;
+ git_tree_cache *tree;
+
+ git_vector names;
+ 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;
};
+struct git_index_conflict_iterator {
+ git_index *index;
+ size_t cur;
+};
+
+extern void git_index_entry__init_from_stat(
+ git_index_entry *entry, struct stat *st, bool trust_mode);
+
+/* Index entry comparison functions for array sorting */
+extern int git_index_entry_cmp(const void *a, const void *b);
+extern int git_index_entry_icmp(const void *a, const void *b);
+
+/* Index entry search functions for search using a search spec */
+extern int git_index_entry_srch(const void *a, const void *b);
+extern int git_index_entry_isrch(const void *a, const void *b);
+
+/* Search index for `path`, returning GIT_ENOTFOUND if it does not exist
+ * (but not setting an error message).
+ *
+ * `at_pos` is set to the position where it is or would be inserted.
+ * Pass `path_len` as strlen of path or 0 to call strlen internally.
+ */
+extern int git_index__find_pos(
+ size_t *at_pos, git_index *index, const char *path, size_t path_len, int stage);
+
+extern void git_index__set_ignore_case(git_index *index, bool ignore_case);
+
+extern unsigned int git_index__create_mode(unsigned int mode);
+
+GIT_INLINE(const git_futils_filestamp *) git_index__filestamp(git_index *index)
+{
+ return &index->stamp;
+}
+
+extern int git_index__changed_relative_to(git_index *index, const git_futils_filestamp *fs);
+
+/* Copy the current entries vector *and* increment the index refcount.
+ * Call `git_index__release_snapshot` when done.
+ */
+extern int git_index_snapshot_new(git_vector *snap, git_index *index);
+extern void git_index_snapshot_release(git_vector *snap, git_index *index);
+
+/* Allow searching in a snapshot; entries must already be sorted! */
+extern int git_index_snapshot_find(
+ size_t *at_pos, git_vector *snap, git_vector_cmp entry_srch,
+ const char *path, size_t path_len, int stage);
+
+
#endif
diff --git a/src/indexer.c b/src/indexer.c
index fcc0229e3..25c3d0537 100644
--- a/src/indexer.c
+++ b/src/indexer.c
@@ -1,32 +1,12 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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/indexer.h"
#include "git2/object.h"
-#include "git2/zlib.h"
-#include "git2/oid.h"
#include "common.h"
#include "pack.h"
@@ -34,7 +14,9 @@
#include "posix.h"
#include "pack.h"
#include "filebuf.h"
-#include "sha1.h"
+#include "oid.h"
+#include "oidmap.h"
+#include "zstream.h"
#define UINT31_MAX (0x7FFFFFFF)
@@ -45,177 +27,936 @@ struct entry {
uint64_t offset_long;
};
-typedef struct git_indexer {
- struct git_pack_file *pack;
- struct stat st;
+struct git_indexer {
+ unsigned int parsed_header :1,
+ opened_pack :1,
+ have_stream :1,
+ have_delta :1;
struct git_pack_header hdr;
+ struct git_pack_file *pack;
+ unsigned int mode;
+ git_off_t off;
+ git_off_t entry_start;
+ git_packfile_stream stream;
size_t nr_objects;
git_vector objects;
- git_filebuf file;
+ git_vector deltas;
unsigned int fanout[256];
+ git_hash_ctx hash_ctx;
git_oid hash;
-} git_indexer;
+ git_transfer_progress_cb progress_cb;
+ void *progress_payload;
+ char objbuf[8*1024];
+
+ /* Needed to look up objects which we want to inject to fix a thin pack */
+ git_odb *odb;
-const git_oid *git_indexer_hash(git_indexer *idx)
+ /* Fields for calculating the packfile trailer (hash of everything before it) */
+ char inbuf[GIT_OID_RAWSZ];
+ size_t inbuf_len;
+ git_hash_ctx trailer;
+};
+
+struct delta_info {
+ git_off_t delta_off;
+};
+
+const git_oid *git_indexer_hash(const git_indexer *idx)
{
return &idx->hash;
}
-static int parse_header(git_indexer *idx)
+static int parse_header(struct git_pack_header *hdr, struct git_pack_file *pack)
{
int error;
+ git_map map;
- /* Verify we recognize this pack file format. */
- if ((error = p_read(idx->pack->mwf.fd, &idx->hdr, sizeof(idx->hdr))) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to read in pack header");
+ if ((error = p_mmap(&map, sizeof(*hdr), GIT_PROT_READ, GIT_MAP_SHARED, pack->mwf.fd, 0)) < 0)
+ return error;
- if (idx->hdr.hdr_signature != ntohl(PACK_SIGNATURE))
- return git__throw(GIT_EOBJCORRUPTED, "Wrong pack signature");
+ memcpy(hdr, map.data, sizeof(*hdr));
+ p_munmap(&map);
- if (!pack_version_ok(idx->hdr.hdr_version))
- return git__throw(GIT_EOBJCORRUPTED, "Wrong pack version");
+ /* Verify we recognize this pack file format. */
+ if (hdr->hdr_signature != ntohl(PACK_SIGNATURE)) {
+ giterr_set(GITERR_INDEXER, "Wrong pack signature");
+ return -1;
+ }
+ if (!pack_version_ok(hdr->hdr_version)) {
+ giterr_set(GITERR_INDEXER, "Wrong pack version");
+ return -1;
+ }
- return GIT_SUCCESS;
+ return 0;
}
-int objects_cmp(const void *a, const void *b)
+static int objects_cmp(const void *a, const void *b)
{
const struct entry *entrya = a;
const struct entry *entryb = b;
- return git_oid_cmp(&entrya->oid, &entryb->oid);
+ return git_oid__cmp(&entrya->oid, &entryb->oid);
}
-int git_indexer_new(git_indexer **out, const char *packname)
+int git_indexer_new(
+ git_indexer **out,
+ const char *prefix,
+ unsigned int mode,
+ git_odb *odb,
+ git_transfer_progress_cb progress_cb,
+ void *progress_payload)
{
git_indexer *idx;
- unsigned int namelen;
- int ret, error;
+ git_buf path = GIT_BUF_INIT, tmp_path = GIT_BUF_INIT;
+ static const char suff[] = "/pack";
+ int error, fd = -1;
+
+ idx = git__calloc(1, sizeof(git_indexer));
+ GITERR_CHECK_ALLOC(idx);
+ idx->odb = odb;
+ idx->progress_cb = progress_cb;
+ idx->progress_payload = progress_payload;
+ idx->mode = mode ? mode : GIT_PACK_FILE_MODE;
+ git_hash_ctx_init(&idx->trailer);
+
+ error = git_buf_joinpath(&path, prefix, suff);
+ if (error < 0)
+ goto cleanup;
- if (git_path_root(packname) < 0)
- return git__throw(GIT_EINVALIDPATH, "Path is not absolute");
+ fd = git_futils_mktmp(&tmp_path, git_buf_cstr(&path), idx->mode);
+ git_buf_free(&path);
+ if (fd < 0)
+ goto cleanup;
- idx = git__malloc(sizeof(git_indexer));
- if (idx == NULL)
- return GIT_ENOMEM;
+ error = git_packfile_alloc(&idx->pack, git_buf_cstr(&tmp_path));
+ git_buf_free(&tmp_path);
- memset(idx, 0x0, sizeof(*idx));
+ if (error < 0)
+ goto cleanup;
- namelen = strlen(packname);
- idx->pack = git__malloc(sizeof(struct git_pack_file) + namelen + 1);
- if (idx->pack == NULL) {
- error = GIT_ENOMEM;
+ idx->pack->mwf.fd = fd;
+ if ((error = git_mwindow_file_register(&idx->pack->mwf)) < 0)
goto cleanup;
+
+ *out = idx;
+ return 0;
+
+cleanup:
+ if (fd != -1)
+ p_close(fd);
+
+ git_buf_free(&path);
+ git_buf_free(&tmp_path);
+ git__free(idx);
+ return -1;
+}
+
+/* Try to store the delta so we can try to resolve it later */
+static int store_delta(git_indexer *idx)
+{
+ struct delta_info *delta;
+
+ delta = git__calloc(1, sizeof(struct delta_info));
+ GITERR_CHECK_ALLOC(delta);
+ delta->delta_off = idx->entry_start;
+
+ if (git_vector_insert(&idx->deltas, delta) < 0)
+ return -1;
+
+ return 0;
+}
+
+static void hash_header(git_hash_ctx *ctx, git_off_t len, git_otype type)
+{
+ char buffer[64];
+ size_t hdrlen;
+
+ hdrlen = git_odb__format_object_header(buffer, sizeof(buffer), (size_t)len, type);
+ git_hash_update(ctx, buffer, hdrlen);
+}
+
+static int hash_object_stream(git_indexer*idx, git_packfile_stream *stream)
+{
+ ssize_t read;
+
+ assert(idx && stream);
+
+ do {
+ if ((read = git_packfile_stream_read(stream, idx->objbuf, sizeof(idx->objbuf))) < 0)
+ break;
+
+ git_hash_update(&idx->hash_ctx, idx->objbuf, read);
+ } while (read > 0);
+
+ if (read < 0)
+ return (int)read;
+
+ return 0;
+}
+
+/* In order to create the packfile stream, we need to skip over the delta base description */
+static int advance_delta_offset(git_indexer *idx, git_otype type)
+{
+ git_mwindow *w = NULL;
+
+ assert(type == GIT_OBJ_REF_DELTA || type == GIT_OBJ_OFS_DELTA);
+
+ if (type == GIT_OBJ_REF_DELTA) {
+ idx->off += GIT_OID_RAWSZ;
+ } else {
+ git_off_t base_off = get_delta_base(idx->pack, &w, &idx->off, type, idx->entry_start);
+ git_mwindow_close(&w);
+ if (base_off < 0)
+ return (int)base_off;
+ }
+
+ return 0;
+}
+
+/* Read from the stream and discard any output */
+static int read_object_stream(git_indexer *idx, git_packfile_stream *stream)
+{
+ ssize_t read;
+
+ assert(stream);
+
+ do {
+ read = git_packfile_stream_read(stream, idx->objbuf, sizeof(idx->objbuf));
+ } while (read > 0);
+
+ if (read < 0)
+ return (int)read;
+
+ return 0;
+}
+
+static int crc_object(uint32_t *crc_out, git_mwindow_file *mwf, git_off_t start, git_off_t size)
+{
+ void *ptr;
+ uint32_t crc;
+ unsigned int left, len;
+ git_mwindow *w = NULL;
+
+ crc = crc32(0L, Z_NULL, 0);
+ while (size) {
+ ptr = git_mwindow_open(mwf, &w, start, (size_t)size, &left);
+ if (ptr == NULL)
+ return -1;
+
+ len = min(left, (unsigned int)size);
+ crc = crc32(crc, ptr, len);
+ size -= len;
+ start += len;
+ git_mwindow_close(&w);
+ }
+
+ *crc_out = htonl(crc);
+ return 0;
+}
+
+static int store_object(git_indexer *idx)
+{
+ int i, error;
+ khiter_t k;
+ git_oid oid;
+ struct entry *entry;
+ git_off_t entry_size;
+ struct git_pack_entry *pentry;
+ git_hash_ctx *ctx = &idx->hash_ctx;
+ git_off_t entry_start = idx->entry_start;
+
+ entry = git__calloc(1, sizeof(*entry));
+ GITERR_CHECK_ALLOC(entry);
+
+ pentry = git__calloc(1, sizeof(struct git_pack_entry));
+ GITERR_CHECK_ALLOC(pentry);
+
+ git_hash_final(&oid, ctx);
+ entry_size = idx->off - entry_start;
+ if (entry_start > UINT31_MAX) {
+ entry->offset = UINT32_MAX;
+ entry->offset_long = entry_start;
+ } else {
+ entry->offset = (uint32_t)entry_start;
}
- memset(idx->pack, 0x0, sizeof(struct git_pack_file));
- memcpy(idx->pack->pack_name, packname, namelen + 1);
+ git_oid_cpy(&pentry->sha1, &oid);
+ pentry->offset = entry_start;
+
+ k = kh_put(oid, idx->pack->idx_cache, &pentry->sha1, &error);
+ if (!error) {
+ git__free(pentry);
+ goto on_error;
+ }
+
+ kh_value(idx->pack->idx_cache, k) = pentry;
+
+ git_oid_cpy(&entry->oid, &oid);
+
+ if (crc_object(&entry->crc, &idx->pack->mwf, entry_start, entry_size) < 0)
+ goto on_error;
+
+ /* Add the object to the list */
+ if (git_vector_insert(&idx->objects, entry) < 0)
+ goto on_error;
+
+ for (i = oid.id[0]; i < 256; ++i) {
+ idx->fanout[i]++;
+ }
+
+ return 0;
+
+on_error:
+ git__free(entry);
- ret = p_stat(packname, &idx->st);
- if (ret < 0) {
- if (errno == ENOENT)
- error = git__throw(GIT_ENOTFOUND, "Failed to stat packfile. File not found");
+ return -1;
+}
+
+static int save_entry(git_indexer *idx, struct entry *entry, struct git_pack_entry *pentry, git_off_t entry_start)
+{
+ int i, error;
+ khiter_t k;
+
+ if (entry_start > UINT31_MAX) {
+ entry->offset = UINT32_MAX;
+ entry->offset_long = entry_start;
+ } else {
+ entry->offset = (uint32_t)entry_start;
+ }
+
+ pentry->offset = entry_start;
+ k = kh_put(oid, idx->pack->idx_cache, &pentry->sha1, &error);
+ if (!error)
+ return -1;
+
+ kh_value(idx->pack->idx_cache, k) = pentry;
+
+ /* Add the object to the list */
+ if (git_vector_insert(&idx->objects, entry) < 0)
+ return -1;
+
+ for (i = entry->oid.id[0]; i < 256; ++i) {
+ idx->fanout[i]++;
+ }
+
+ return 0;
+}
+
+static int hash_and_save(git_indexer *idx, git_rawobj *obj, git_off_t entry_start)
+{
+ git_oid oid;
+ size_t entry_size;
+ struct entry *entry;
+ struct git_pack_entry *pentry = NULL;
+
+ entry = git__calloc(1, sizeof(*entry));
+ GITERR_CHECK_ALLOC(entry);
+
+ if (git_odb__hashobj(&oid, obj) < 0) {
+ giterr_set(GITERR_INDEXER, "Failed to hash object");
+ goto on_error;
+ }
+
+ pentry = git__calloc(1, sizeof(struct git_pack_entry));
+ GITERR_CHECK_ALLOC(pentry);
+
+ git_oid_cpy(&pentry->sha1, &oid);
+ git_oid_cpy(&entry->oid, &oid);
+ entry->crc = crc32(0L, Z_NULL, 0);
+
+ entry_size = (size_t)(idx->off - entry_start);
+ if (crc_object(&entry->crc, &idx->pack->mwf, entry_start, entry_size) < 0)
+ goto on_error;
+
+ return save_entry(idx, entry, pentry, entry_start);
+
+on_error:
+ git__free(pentry);
+ git__free(entry);
+ git__free(obj->data);
+ return -1;
+}
+
+static int do_progress_callback(git_indexer *idx, git_transfer_progress *stats)
+{
+ if (idx->progress_cb)
+ return giterr_set_after_callback_function(
+ idx->progress_cb(stats, idx->progress_payload),
+ "indexer progress");
+ return 0;
+}
+
+/* Hash everything but the last 20B of input */
+static void hash_partially(git_indexer *idx, const uint8_t *data, size_t size)
+{
+ size_t to_expell, to_keep;
+
+ if (size == 0)
+ return;
+
+ /* Easy case, dump the buffer and the data minus the last 20 bytes */
+ if (size >= GIT_OID_RAWSZ) {
+ git_hash_update(&idx->trailer, idx->inbuf, idx->inbuf_len);
+ git_hash_update(&idx->trailer, data, size - GIT_OID_RAWSZ);
+
+ data += size - GIT_OID_RAWSZ;
+ memcpy(idx->inbuf, data, GIT_OID_RAWSZ);
+ idx->inbuf_len = GIT_OID_RAWSZ;
+ return;
+ }
+
+ /* We can just append */
+ if (idx->inbuf_len + size <= GIT_OID_RAWSZ) {
+ memcpy(idx->inbuf + idx->inbuf_len, data, size);
+ idx->inbuf_len += size;
+ return;
+ }
+
+ /* We need to partially drain the buffer and then append */
+ to_keep = GIT_OID_RAWSZ - size;
+ to_expell = idx->inbuf_len - to_keep;
+
+ git_hash_update(&idx->trailer, idx->inbuf, to_expell);
+
+ memmove(idx->inbuf, idx->inbuf + to_expell, to_keep);
+ memcpy(idx->inbuf + to_keep, data, size);
+ idx->inbuf_len += size - to_expell;
+}
+
+static int write_at(git_indexer *idx, const void *data, git_off_t offset, size_t size)
+{
+ git_file fd = idx->pack->mwf.fd;
+ long page_size = git__page_size();
+ git_off_t page_start, page_offset;
+ unsigned char *map_data;
+ git_map map;
+ int error;
+
+ /* the offset needs to be at the beginning of the a page boundary */
+ page_start = (offset / page_size) * page_size;
+ page_offset = offset - page_start;
+
+ if ((error = p_mmap(&map, page_offset + size, GIT_PROT_WRITE, GIT_MAP_SHARED, fd, page_start)) < 0)
+ return error;
+
+ map_data = (unsigned char *)map.data;
+ memcpy(map_data + page_offset, data, size);
+ p_munmap(&map);
+
+ return 0;
+}
+
+static int append_to_pack(git_indexer *idx, const void *data, size_t size)
+{
+ git_off_t current_size = idx->pack->mwf.size;
+
+ /* add the extra space we need at the end */
+ if (p_ftruncate(idx->pack->mwf.fd, current_size + size) < 0) {
+ giterr_system_set(errno);
+ return -1;
+ }
+
+ return write_at(idx, data, idx->pack->mwf.size, size);
+}
+
+int git_indexer_append(git_indexer *idx, const void *data, size_t size, git_transfer_progress *stats)
+{
+ int error = -1;
+ size_t processed;
+ struct git_pack_header *hdr = &idx->hdr;
+ git_mwindow_file *mwf = &idx->pack->mwf;
+
+ assert(idx && data && stats);
+
+ processed = stats->indexed_objects;
+
+ if ((error = append_to_pack(idx, data, size)) < 0)
+ return error;
+
+ hash_partially(idx, data, (int)size);
+
+ /* Make sure we set the new size of the pack */
+ idx->pack->mwf.size += size;
+
+ if (!idx->parsed_header) {
+ unsigned int total_objects;
+
+ if ((unsigned)idx->pack->mwf.size < sizeof(struct git_pack_header))
+ return 0;
+
+ if ((error = parse_header(&idx->hdr, idx->pack)) < 0)
+ return error;
+
+ idx->parsed_header = 1;
+ idx->nr_objects = ntohl(hdr->hdr_entries);
+ idx->off = sizeof(struct git_pack_header);
+
+ /* for now, limit to 2^32 objects */
+ assert(idx->nr_objects == (size_t)((unsigned int)idx->nr_objects));
+ if (idx->nr_objects == (size_t)((unsigned int)idx->nr_objects))
+ total_objects = (unsigned int)idx->nr_objects;
else
- error = git__throw(GIT_EOSERR, "Failed to stat packfile.");
+ total_objects = UINT_MAX;
- goto cleanup;
+ idx->pack->idx_cache = git_oidmap_alloc();
+ GITERR_CHECK_ALLOC(idx->pack->idx_cache);
+
+ idx->pack->has_cache = 1;
+ if (git_vector_init(&idx->objects, total_objects, objects_cmp) < 0)
+ return -1;
+
+ if (git_vector_init(&idx->deltas, total_objects / 2, NULL) < 0)
+ return -1;
+
+ stats->received_objects = 0;
+ stats->local_objects = 0;
+ stats->total_deltas = 0;
+ stats->indexed_deltas = 0;
+ processed = stats->indexed_objects = 0;
+ stats->total_objects = total_objects;
+
+ if ((error = do_progress_callback(idx, stats)) != 0)
+ return error;
}
- ret = p_open(idx->pack->pack_name, O_RDONLY);
- if (ret < 0) {
- error = git__throw(GIT_EOSERR, "Failed to open packfile");
- goto cleanup;
+ /* Now that we have data in the pack, let's try to parse it */
+
+ /* As the file grows any windows we try to use will be out of date */
+ git_mwindow_free_all(mwf);
+
+ while (processed < idx->nr_objects) {
+ git_packfile_stream *stream = &idx->stream;
+ git_off_t entry_start = idx->off;
+ size_t entry_size;
+ git_otype type;
+ git_mwindow *w = NULL;
+
+ if (idx->pack->mwf.size <= idx->off + 20)
+ return 0;
+
+ if (!idx->have_stream) {
+ error = git_packfile_unpack_header(&entry_size, &type, mwf, &w, &idx->off);
+ if (error == GIT_EBUFS) {
+ idx->off = entry_start;
+ return 0;
+ }
+ if (error < 0)
+ goto on_error;
+
+ git_mwindow_close(&w);
+ idx->entry_start = entry_start;
+ git_hash_ctx_init(&idx->hash_ctx);
+
+ if (type == GIT_OBJ_REF_DELTA || type == GIT_OBJ_OFS_DELTA) {
+ error = advance_delta_offset(idx, type);
+ if (error == GIT_EBUFS) {
+ idx->off = entry_start;
+ return 0;
+ }
+ if (error < 0)
+ goto on_error;
+
+ idx->have_delta = 1;
+ } else {
+ idx->have_delta = 0;
+ hash_header(&idx->hash_ctx, entry_size, type);
+ }
+
+ idx->have_stream = 1;
+
+ error = git_packfile_stream_open(stream, idx->pack, idx->off);
+ if (error < 0)
+ goto on_error;
+ }
+
+ if (idx->have_delta) {
+ error = read_object_stream(idx, stream);
+ } else {
+ error = hash_object_stream(idx, stream);
+ }
+
+ idx->off = stream->curpos;
+ if (error == GIT_EBUFS)
+ return 0;
+
+ /* We want to free the stream reasorces no matter what here */
+ idx->have_stream = 0;
+ git_packfile_stream_free(stream);
+
+ if (error < 0)
+ goto on_error;
+
+ if (idx->have_delta) {
+ error = store_delta(idx);
+ } else {
+ error = store_object(idx);
+ }
+
+ if (error < 0)
+ goto on_error;
+
+ if (!idx->have_delta) {
+ stats->indexed_objects = (unsigned int)++processed;
+ }
+ stats->received_objects++;
+
+ if ((error = do_progress_callback(idx, stats)) != 0)
+ goto on_error;
}
- idx->pack->mwf.fd = ret;
- idx->pack->mwf.size = (git_off_t)idx->st.st_size;
+ return 0;
+
+on_error:
+ git_mwindow_free_all(mwf);
+ return error;
+}
+
+static int index_path(git_buf *path, git_indexer *idx, const char *suffix)
+{
+ const char prefix[] = "pack-";
+ size_t slash = (size_t)path->size;
+
+ /* search backwards for '/' */
+ while (slash > 0 && path->ptr[slash - 1] != '/')
+ slash--;
+
+ if (git_buf_grow(path, slash + 1 + strlen(prefix) +
+ GIT_OID_HEXSZ + strlen(suffix) + 1) < 0)
+ return -1;
+
+ git_buf_truncate(path, slash);
+ git_buf_puts(path, prefix);
+ git_oid_fmt(path->ptr + git_buf_len(path), &idx->hash);
+ path->size += GIT_OID_HEXSZ;
+ git_buf_puts(path, suffix);
- error = parse_header(idx);
- if (error < GIT_SUCCESS) {
- error = git__rethrow(error, "Failed to parse packfile header");
+ return git_buf_oom(path) ? -1 : 0;
+}
+
+/**
+ * Rewind the packfile by the trailer, as we might need to fix the
+ * packfile by injecting objects at the tail and must overwrite it.
+ */
+static void seek_back_trailer(git_indexer *idx)
+{
+ idx->pack->mwf.size -= GIT_OID_RAWSZ;
+ git_mwindow_free_all(&idx->pack->mwf);
+}
+
+static int inject_object(git_indexer *idx, git_oid *id)
+{
+ git_odb_object *obj;
+ struct entry *entry;
+ struct git_pack_entry *pentry = NULL;
+ git_oid foo = {{0}};
+ unsigned char hdr[64];
+ git_buf buf = GIT_BUF_INIT;
+ git_off_t entry_start;
+ const void *data;
+ size_t len, hdr_len;
+ int error;
+
+ seek_back_trailer(idx);
+ entry_start = idx->pack->mwf.size;
+
+ if (git_odb_read(&obj, idx->odb, id) < 0)
+ return -1;
+
+ data = git_odb_object_data(obj);
+ len = git_odb_object_size(obj);
+
+ entry = git__calloc(1, sizeof(*entry));
+ GITERR_CHECK_ALLOC(entry);
+
+ entry->crc = crc32(0L, Z_NULL, 0);
+
+ /* Write out the object header */
+ hdr_len = git_packfile__object_header(hdr, len, git_odb_object_type(obj));
+ if ((error = append_to_pack(idx, hdr, hdr_len)) < 0)
goto cleanup;
- }
- idx->nr_objects = ntohl(idx->hdr.hdr_entries);
+ idx->pack->mwf.size += hdr_len;
+ entry->crc = crc32(entry->crc, hdr, (uInt)hdr_len);
- error = git_vector_init(&idx->objects, idx->nr_objects, objects_cmp);
- if (error < GIT_SUCCESS) {
+ if ((error = git_zstream_deflatebuf(&buf, data, len)) < 0)
goto cleanup;
- }
- *out = idx;
+ /* And then the compressed object */
+ if ((error = append_to_pack(idx, buf.ptr, buf.size)) < 0)
+ goto cleanup;
+
+ idx->pack->mwf.size += buf.size;
+ entry->crc = htonl(crc32(entry->crc, (unsigned char *)buf.ptr, (uInt)buf.size));
+ git_buf_free(&buf);
- return GIT_SUCCESS;
+ /* Write a fake trailer so the pack functions play ball */
+
+ if ((error = append_to_pack(idx, &foo, GIT_OID_RAWSZ)) < 0)
+ goto cleanup;
+
+ idx->pack->mwf.size += GIT_OID_RAWSZ;
+
+ pentry = git__calloc(1, sizeof(struct git_pack_entry));
+ GITERR_CHECK_ALLOC(pentry);
+
+ git_oid_cpy(&pentry->sha1, id);
+ git_oid_cpy(&entry->oid, id);
+ idx->off = entry_start + hdr_len + len;
+
+ error = save_entry(idx, entry, pentry, entry_start);
cleanup:
- git_indexer_free(idx);
+ if (error) {
+ git__free(entry);
+ git__free(pentry);
+ }
+ git_odb_object_free(obj);
return error;
}
-static void index_path(char *path, git_indexer *idx)
+static int fix_thin_pack(git_indexer *idx, git_transfer_progress *stats)
+{
+ int error, found_ref_delta = 0;
+ unsigned int i;
+ struct delta_info *delta;
+ size_t size;
+ git_otype type;
+ git_mwindow *w = NULL;
+ git_off_t curpos = 0;
+ unsigned char *base_info;
+ unsigned int left = 0;
+ git_oid base;
+
+ assert(git_vector_length(&idx->deltas) > 0);
+
+ if (idx->odb == NULL) {
+ giterr_set(GITERR_INDEXER, "cannot fix a thin pack without an ODB");
+ return -1;
+ }
+
+ /* Loop until we find the first REF delta */
+ git_vector_foreach(&idx->deltas, i, delta) {
+ if (!delta)
+ continue;
+
+ curpos = delta->delta_off;
+ error = git_packfile_unpack_header(&size, &type, &idx->pack->mwf, &w, &curpos);
+ git_mwindow_close(&w);
+ if (error < 0)
+ return error;
+
+ if (type == GIT_OBJ_REF_DELTA) {
+ found_ref_delta = 1;
+ break;
+ }
+ }
+
+ if (!found_ref_delta) {
+ giterr_set(GITERR_INDEXER, "no REF_DELTA found, cannot inject object");
+ return -1;
+ }
+
+ /* curpos now points to the base information, which is an OID */
+ base_info = git_mwindow_open(&idx->pack->mwf, &w, curpos, GIT_OID_RAWSZ, &left);
+ if (base_info == NULL) {
+ giterr_set(GITERR_INDEXER, "failed to map delta information");
+ return -1;
+ }
+
+ git_oid_fromraw(&base, base_info);
+ git_mwindow_close(&w);
+
+ if (inject_object(idx, &base) < 0)
+ return -1;
+
+ stats->local_objects++;
+
+ return 0;
+}
+
+static int resolve_deltas(git_indexer *idx, git_transfer_progress *stats)
{
- char *ptr;
- const char prefix[] = "pack-", suffix[] = ".idx\0";
+ unsigned int i;
+ struct delta_info *delta;
+ int progressed = 0, non_null = 0, progress_cb_result;
+
+ while (idx->deltas.length > 0) {
+ progressed = 0;
+ non_null = 0;
+ git_vector_foreach(&idx->deltas, i, delta) {
+ git_rawobj obj;
+
+ if (!delta)
+ continue;
+
+ non_null = 1;
+ idx->off = delta->delta_off;
+ if (git_packfile_unpack(&obj, idx->pack, &idx->off) < 0)
+ continue;
+
+ if (hash_and_save(idx, &obj, delta->delta_off) < 0)
+ continue;
+
+ git__free(obj.data);
+ stats->indexed_objects++;
+ stats->indexed_deltas++;
+ progressed = 1;
+ if ((progress_cb_result = do_progress_callback(idx, stats)) < 0)
+ return progress_cb_result;
+
+ /* remove from the list */
+ git_vector_set(NULL, &idx->deltas, i, NULL);
+ git__free(delta);
+ }
- ptr = strrchr(path, '/') + 1;
+ /* if none were actually set, we're done */
+ if (!non_null)
+ break;
+
+ if (!progressed && (fix_thin_pack(idx, stats) < 0)) {
+ giterr_set(GITERR_INDEXER, "missing delta bases");
+ return -1;
+ }
+ }
- memcpy(ptr, prefix, STRLEN(prefix));
- ptr += STRLEN(prefix);
- git_oid_fmt(ptr, &idx->hash);
- ptr += GIT_OID_HEXSZ;
- memcpy(ptr, suffix, STRLEN(suffix));
+ return 0;
}
-int git_indexer_write(git_indexer *idx)
+static int update_header_and_rehash(git_indexer *idx, git_transfer_progress *stats)
+{
+ void *ptr;
+ size_t chunk = 1024*1024;
+ git_off_t hashed = 0;
+ git_mwindow *w = NULL;
+ git_mwindow_file *mwf;
+ unsigned int left;
+ git_hash_ctx *ctx;
+
+ mwf = &idx->pack->mwf;
+ ctx = &idx->trailer;
+
+ git_hash_ctx_init(ctx);
+
+
+ /* Update the header to include the numer of local objects we injected */
+ idx->hdr.hdr_entries = htonl(stats->total_objects + stats->local_objects);
+ if (write_at(idx, &idx->hdr, 0, sizeof(struct git_pack_header)) < 0)
+ return -1;
+
+ /*
+ * We now use the same technique as before to determine the
+ * hash. We keep reading up to the end and let
+ * hash_partially() keep the existing trailer out of the
+ * calculation.
+ */
+ git_mwindow_free_all(mwf);
+ idx->inbuf_len = 0;
+ while (hashed < mwf->size) {
+ ptr = git_mwindow_open(mwf, &w, hashed, chunk, &left);
+ if (ptr == NULL)
+ return -1;
+
+ hash_partially(idx, ptr, left);
+ hashed += left;
+
+ git_mwindow_close(&w);
+ }
+
+ return 0;
+}
+
+int git_indexer_commit(git_indexer *idx, git_transfer_progress *stats)
{
git_mwindow *w = NULL;
- int error, namelen;
unsigned int i, long_offsets = 0, left;
+ int error;
struct git_pack_idx_header hdr;
- char filename[GIT_PATH_MAX];
+ git_buf filename = GIT_BUF_INIT;
struct entry *entry;
- void *packfile_hash;
- git_oid file_hash;
- SHA_CTX ctx;
+ git_oid trailer_hash, file_hash;
+ git_hash_ctx ctx;
+ git_filebuf index_file = {0};
+ void *packfile_trailer;
+
+ 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 - 20) {
+ giterr_set(GITERR_INDEXER, "Unexpected data at the end of the pack");
+ return -1;
+ }
+
+ packfile_trailer = git_mwindow_open(&idx->pack->mwf, &w, idx->pack->mwf.size - GIT_OID_RAWSZ, GIT_OID_RAWSZ, &left);
+ if (packfile_trailer == NULL) {
+ git_mwindow_close(&w);
+ goto on_error;
+ }
+
+ /* Compare the packfile trailer as it was sent to us and what we calculated */
+ git_oid_fromraw(&file_hash, packfile_trailer);
+ git_mwindow_close(&w);
+
+ git_hash_final(&trailer_hash, &idx->trailer);
+ if (git_oid_cmp(&file_hash, &trailer_hash)) {
+ giterr_set(GITERR_INDEXER, "packfile trailer mismatch");
+ return -1;
+ }
+
+ /* Freeze the number of deltas */
+ stats->total_deltas = stats->total_objects - stats->indexed_objects;
+
+ if ((error = resolve_deltas(idx, stats)) < 0)
+ return error;
+
+ if (stats->indexed_objects != stats->total_objects) {
+ giterr_set(GITERR_INDEXER, "early EOF");
+ return -1;
+ }
+
+ if (stats->local_objects > 0) {
+ if (update_header_and_rehash(idx, stats) < 0)
+ return -1;
+
+ git_hash_final(&trailer_hash, &idx->trailer);
+ write_at(idx, &trailer_hash, idx->pack->mwf.size - GIT_OID_RAWSZ, GIT_OID_RAWSZ);
+ }
git_vector_sort(&idx->objects);
- namelen = strlen(idx->pack->pack_name);
- memcpy(filename, idx->pack->pack_name, namelen);
- memcpy(filename + namelen - STRLEN("pack"), "idx\0", STRLEN("idx\0"));
+ git_buf_sets(&filename, idx->pack->pack_name);
+ git_buf_shorten(&filename, strlen("pack"));
+ git_buf_puts(&filename, "idx");
+ if (git_buf_oom(&filename))
+ return -1;
- error = git_filebuf_open(&idx->file, filename, GIT_FILEBUF_HASH_CONTENTS);
+ if (git_filebuf_open(&index_file, filename.ptr,
+ GIT_FILEBUF_HASH_CONTENTS, idx->mode) < 0)
+ goto on_error;
/* Write out the header */
hdr.idx_signature = htonl(PACK_IDX_SIGNATURE);
hdr.idx_version = htonl(2);
- error = git_filebuf_write(&idx->file, &hdr, sizeof(hdr));
+ git_filebuf_write(&index_file, &hdr, sizeof(hdr));
/* Write out the fanout table */
for (i = 0; i < 256; ++i) {
uint32_t n = htonl(idx->fanout[i]);
- error = git_filebuf_write(&idx->file, &n, sizeof(n));
- if (error < GIT_SUCCESS)
- goto cleanup;
+ git_filebuf_write(&index_file, &n, sizeof(n));
}
/* Write out the object names (SHA-1 hashes) */
- SHA1_Init(&ctx);
git_vector_foreach(&idx->objects, i, entry) {
- error = git_filebuf_write(&idx->file, &entry->oid, sizeof(git_oid));
- SHA1_Update(&ctx, &entry->oid, GIT_OID_RAWSZ);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ git_filebuf_write(&index_file, &entry->oid, sizeof(git_oid));
+ git_hash_update(&ctx, &entry->oid, GIT_OID_RAWSZ);
}
- SHA1_Final(idx->hash.id, &ctx);
+ git_hash_final(&idx->hash, &ctx);
/* Write out the CRC32 values */
git_vector_foreach(&idx->objects, i, entry) {
- error = git_filebuf_write(&idx->file, &entry->crc, sizeof(uint32_t));
- if (error < GIT_SUCCESS)
- goto cleanup;
+ git_filebuf_write(&index_file, &entry->crc, sizeof(uint32_t));
}
/* Write out the offsets */
@@ -227,9 +968,7 @@ int git_indexer_write(git_indexer *idx)
else
n = htonl(entry->offset);
- error = git_filebuf_write(&idx->file, &n, sizeof(uint32_t));
- if (error < GIT_SUCCESS)
- goto cleanup;
+ git_filebuf_write(&index_file, &n, sizeof(uint32_t));
}
/* Write out the long offsets */
@@ -242,141 +981,69 @@ int git_indexer_write(git_indexer *idx)
split[0] = htonl(entry->offset_long >> 32);
split[1] = htonl(entry->offset_long & 0xffffffff);
- error = git_filebuf_write(&idx->file, &split, sizeof(uint32_t) * 2);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ git_filebuf_write(&index_file, &split, sizeof(uint32_t) * 2);
}
- /* Write out the packfile trailer */
+ /* Write out the packfile trailer to the index */
+ if (git_filebuf_write(&index_file, &trailer_hash, GIT_OID_RAWSZ) < 0)
+ goto on_error;
- packfile_hash = git_mwindow_open(&idx->pack->mwf, &w, idx->st.st_size - GIT_OID_RAWSZ, GIT_OID_RAWSZ, &left);
- if (packfile_hash == NULL) {
- error = git__rethrow(GIT_ENOMEM, "Failed to open window to packfile hash");
- goto cleanup;
- }
+ /* Write out the hash of the idx */
+ if (git_filebuf_hash(&trailer_hash, &index_file) < 0)
+ goto on_error;
- memcpy(&file_hash, packfile_hash, GIT_OID_RAWSZ);
+ git_filebuf_write(&index_file, &trailer_hash, sizeof(git_oid));
- git_mwindow_close(&w);
+ /* Figure out what the final name should be */
+ if (index_path(&filename, idx, ".idx") < 0)
+ goto on_error;
- error = git_filebuf_write(&idx->file, &file_hash, sizeof(git_oid));
+ /* Commit file */
+ if (git_filebuf_commit_at(&index_file, filename.ptr) < 0)
+ goto on_error;
+
+ git_mwindow_free_all(&idx->pack->mwf);
+ /* We need to close the descriptor here so Windows doesn't choke on commit_at */
+ if (p_close(idx->pack->mwf.fd) < 0) {
+ giterr_set(GITERR_OS, "failed to close packfile");
+ goto on_error;
+ }
- /* Write out the index sha */
- error = git_filebuf_hash(&file_hash, &idx->file);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ idx->pack->mwf.fd = -1;
- error = git_filebuf_write(&idx->file, &file_hash, sizeof(git_oid));
- if (error < GIT_SUCCESS)
- goto cleanup;
+ if (index_path(&filename, idx, ".pack") < 0)
+ goto on_error;
- /* Figure out what the final name should be */
- index_path(filename, idx);
- /* Commit file */
- error = git_filebuf_commit_at(&idx->file, filename);
+ /* And don't forget to rename the packfile to its new place. */
+ p_rename(idx->pack->pack_name, git_buf_cstr(&filename));
-cleanup:
- if (error < GIT_SUCCESS)
- git_filebuf_cleanup(&idx->file);
+ git_buf_free(&filename);
+ return 0;
- return error;
+on_error:
+ git_mwindow_free_all(&idx->pack->mwf);
+ git_filebuf_cleanup(&index_file);
+ git_buf_free(&filename);
+ git_hash_ctx_cleanup(&ctx);
+ return -1;
}
-int git_indexer_run(git_indexer *idx, git_indexer_stats *stats)
+void git_indexer_free(git_indexer *idx)
{
- git_mwindow_file *mwf;
- off_t off = sizeof(struct git_pack_header);
- int error;
- struct entry *entry;
- unsigned int left, processed;
-
- assert(idx && stats);
-
- mwf = &idx->pack->mwf;
- error = git_mwindow_file_register(mwf);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to register mwindow file");
-
- stats->total = idx->nr_objects;
- stats->processed = processed = 0;
-
- while (processed < idx->nr_objects) {
- git_rawobj obj;
- git_oid oid;
- git_mwindow *w = NULL;
- char hdr[512] = {0}; /* FIXME: How long should this be? */
- int i, hdr_len;
- off_t entry_start = off;
- void *packed;
- size_t entry_size;
-
- entry = git__malloc(sizeof(struct entry));
- memset(entry, 0x0, sizeof(struct entry));
-
- if (off > UINT31_MAX) {
- entry->offset = UINT32_MAX;
- entry->offset_long = off;
- } else {
- entry->offset = off;
- }
-
- error = git_packfile_unpack(&obj, idx->pack, &off);
- if (error < GIT_SUCCESS) {
- error = git__rethrow(error, "Failed to unpack object");
- goto cleanup;
- }
-
- error = git_odb__hash_obj(&oid, hdr, sizeof(hdr), &hdr_len, &obj);
- if (error < GIT_SUCCESS) {
- error = git__rethrow(error, "Failed to hash object");
- goto cleanup;
- }
-
- git_oid_cpy(&entry->oid, &oid);
- entry->crc = crc32(0L, Z_NULL, 0);
-
- entry_size = off - entry_start;
- packed = git_mwindow_open(mwf, &w, entry_start, entry_size, &left);
- if (packed == NULL) {
- error = git__rethrow(error, "Failed to open window to read packed data");
- goto cleanup;
- }
- entry->crc = htonl(crc32(entry->crc, packed, entry_size));
- git_mwindow_close(&w);
+ if (idx == NULL)
+ return;
- /* Add the object to the list */
- error = git_vector_insert(&idx->objects, entry);
- if (error < GIT_SUCCESS) {
- error = git__rethrow(error, "Failed to add entry to list");
- goto cleanup;
- }
+ git_vector_free_deep(&idx->objects);
- for (i = oid.id[0]; i < 256; ++i) {
- idx->fanout[i]++;
- }
+ if (idx->pack && idx->pack->idx_cache) {
+ struct git_pack_entry *pentry;
+ kh_foreach_value(
+ idx->pack->idx_cache, pentry, { git__free(pentry); });
- free(obj.data);
-
- stats->processed = ++processed;
+ git_oidmap_free(idx->pack->idx_cache);
}
-cleanup:
- git_mwindow_free_all(mwf);
-
- return error;
-
-}
-
-void git_indexer_free(git_indexer *idx)
-{
- unsigned int i;
- struct entry *e;
-
- p_close(idx->pack->mwf.fd);
- git_vector_foreach(&idx->objects, i, e)
- free(e);
- git_vector_free(&idx->objects);
- free(idx->pack);
- free(idx);
+ git_vector_free_deep(&idx->deltas);
+ git_packfile_free(idx->pack);
+ git__free(idx);
}
-
diff --git a/src/iterator.c b/src/iterator.c
new file mode 100644
index 000000000..c664f17cd
--- /dev/null
+++ b/src/iterator.c
@@ -0,0 +1,1636 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "iterator.h"
+#include "tree.h"
+#include "index.h"
+#include "ignore.h"
+#include "buffer.h"
+#include "submodule.h"
+#include <ctype.h>
+
+#define ITERATOR_SET_CB(P,NAME_LC) do { \
+ (P)->cb.current = NAME_LC ## _iterator__current; \
+ (P)->cb.advance = NAME_LC ## _iterator__advance; \
+ (P)->cb.advance_into = NAME_LC ## _iterator__advance_into; \
+ (P)->cb.seek = NAME_LC ## _iterator__seek; \
+ (P)->cb.reset = NAME_LC ## _iterator__reset; \
+ (P)->cb.at_end = NAME_LC ## _iterator__at_end; \
+ (P)->cb.free = NAME_LC ## _iterator__free; \
+ } while (0)
+
+#define ITERATOR_CASE_FLAGS \
+ (GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_IGNORE_CASE)
+
+#define ITERATOR_BASE_INIT(P,NAME_LC,NAME_UC,REPO) do { \
+ (P)->base.type = GIT_ITERATOR_TYPE_ ## NAME_UC; \
+ (P)->base.cb = &(P)->cb; \
+ ITERATOR_SET_CB(P,NAME_LC); \
+ (P)->base.repo = (REPO); \
+ (P)->base.start = start ? git__strdup(start) : NULL; \
+ (P)->base.end = end ? git__strdup(end) : NULL; \
+ if ((start && !(P)->base.start) || (end && !(P)->base.end)) { \
+ git__free(P); return -1; } \
+ (P)->base.prefixcomp = git__prefixcmp; \
+ (P)->base.flags = flags & ~ITERATOR_CASE_FLAGS; \
+ if ((P)->base.flags & GIT_ITERATOR_DONT_AUTOEXPAND) \
+ (P)->base.flags |= GIT_ITERATOR_INCLUDE_TREES; \
+ } while (0)
+
+#define iterator__flag(I,F) ((((git_iterator *)(I))->flags & GIT_ITERATOR_ ## F) != 0)
+#define iterator__ignore_case(I) iterator__flag(I,IGNORE_CASE)
+#define iterator__include_trees(I) iterator__flag(I,INCLUDE_TREES)
+#define iterator__dont_autoexpand(I) iterator__flag(I,DONT_AUTOEXPAND)
+#define iterator__do_autoexpand(I) !iterator__flag(I,DONT_AUTOEXPAND)
+
+#define GIT_ITERATOR_FIRST_ACCESS (1 << 15)
+#define iterator__has_been_accessed(I) iterator__flag(I,FIRST_ACCESS)
+
+#define iterator__end(I) ((git_iterator *)(I))->end
+#define iterator__past_end(I,PATH) \
+ (iterator__end(I) && ((git_iterator *)(I))->prefixcomp((PATH),iterator__end(I)) > 0)
+
+
+static int iterator__reset_range(
+ git_iterator *iter, const char *start, const char *end)
+{
+ if (start) {
+ if (iter->start)
+ git__free(iter->start);
+ iter->start = git__strdup(start);
+ GITERR_CHECK_ALLOC(iter->start);
+ }
+
+ if (end) {
+ if (iter->end)
+ git__free(iter->end);
+ iter->end = git__strdup(end);
+ GITERR_CHECK_ALLOC(iter->end);
+ }
+
+ iter->flags &= ~GIT_ITERATOR_FIRST_ACCESS;
+
+ return 0;
+}
+
+static int iterator__update_ignore_case(
+ git_iterator *iter,
+ git_iterator_flag_t flags)
+{
+ int error = 0, ignore_case = -1;
+
+ if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0)
+ ignore_case = true;
+ else if ((flags & GIT_ITERATOR_DONT_IGNORE_CASE) != 0)
+ ignore_case = false;
+ else {
+ git_index *index;
+
+ if (!(error = git_repository_index__weakptr(&index, iter->repo)))
+ ignore_case = (index->ignore_case != false);
+ }
+
+ if (ignore_case > 0)
+ iter->flags = (iter->flags | GIT_ITERATOR_IGNORE_CASE);
+ else if (ignore_case == 0)
+ iter->flags = (iter->flags & ~GIT_ITERATOR_IGNORE_CASE);
+
+ iter->prefixcomp = iterator__ignore_case(iter) ?
+ git__prefixcmp_icase : git__prefixcmp;
+
+ return error;
+}
+
+GIT_INLINE(void) iterator__clear_entry(const git_index_entry **entry)
+{
+ if (entry) *entry = NULL;
+}
+
+
+static int empty_iterator__noop(const git_index_entry **e, git_iterator *i)
+{
+ GIT_UNUSED(i);
+ iterator__clear_entry(e);
+ return GIT_ITEROVER;
+}
+
+static int empty_iterator__seek(git_iterator *i, const char *p)
+{
+ GIT_UNUSED(i); GIT_UNUSED(p);
+ return -1;
+}
+
+static int empty_iterator__reset(git_iterator *i, const char *s, const char *e)
+{
+ GIT_UNUSED(i); GIT_UNUSED(s); GIT_UNUSED(e);
+ return 0;
+}
+
+static int empty_iterator__at_end(git_iterator *i)
+{
+ GIT_UNUSED(i);
+ return 1;
+}
+
+static void empty_iterator__free(git_iterator *i)
+{
+ GIT_UNUSED(i);
+}
+
+typedef struct {
+ git_iterator base;
+ git_iterator_callbacks cb;
+} empty_iterator;
+
+int git_iterator_for_nothing(
+ git_iterator **iter,
+ git_iterator_flag_t flags,
+ const char *start,
+ const char *end)
+{
+ empty_iterator *i = git__calloc(1, sizeof(empty_iterator));
+ GITERR_CHECK_ALLOC(i);
+
+#define empty_iterator__current empty_iterator__noop
+#define empty_iterator__advance empty_iterator__noop
+#define empty_iterator__advance_into empty_iterator__noop
+
+ ITERATOR_BASE_INIT(i, empty, EMPTY, NULL);
+
+ if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0)
+ i->base.flags |= GIT_ITERATOR_IGNORE_CASE;
+
+ *iter = (git_iterator *)i;
+ return 0;
+}
+
+
+typedef struct tree_iterator_entry tree_iterator_entry;
+struct tree_iterator_entry {
+ tree_iterator_entry *parent;
+ const git_tree_entry *te;
+ git_tree *tree;
+};
+
+typedef struct tree_iterator_frame tree_iterator_frame;
+struct tree_iterator_frame {
+ tree_iterator_frame *up, *down;
+
+ size_t n_entries; /* items in this frame */
+ size_t current; /* start of currently active range in frame */
+ size_t next; /* start of next range in frame */
+
+ const char *start;
+ size_t startlen;
+
+ tree_iterator_entry *entries[GIT_FLEX_ARRAY];
+};
+
+typedef struct {
+ git_iterator base;
+ git_iterator_callbacks cb;
+ tree_iterator_frame *head, *root;
+ git_pool pool;
+ git_index_entry entry;
+ git_buf path;
+ int path_ambiguities;
+ bool path_has_filename;
+ bool entry_is_current;
+ int (*strncomp)(const char *a, const char *b, size_t sz);
+} tree_iterator;
+
+static char *tree_iterator__current_filename(
+ tree_iterator *ti, const git_tree_entry *te)
+{
+ if (!ti->path_has_filename) {
+ if (git_buf_joinpath(&ti->path, ti->path.ptr, te->filename) < 0)
+ return NULL;
+
+ if (git_tree_entry__is_tree(te) && git_buf_putc(&ti->path, '/') < 0)
+ return NULL;
+
+ ti->path_has_filename = true;
+ }
+
+ return ti->path.ptr;
+}
+
+static void tree_iterator__rewrite_filename(tree_iterator *ti)
+{
+ tree_iterator_entry *scan = ti->head->entries[ti->head->current];
+ ssize_t strpos = ti->path.size;
+ const git_tree_entry *te;
+
+ if (strpos && ti->path.ptr[strpos - 1] == '/')
+ strpos--;
+
+ for (; scan && (te = scan->te); scan = scan->parent) {
+ strpos -= te->filename_len;
+ memcpy(&ti->path.ptr[strpos], te->filename, te->filename_len);
+ strpos -= 1; /* separator */
+ }
+}
+
+static int tree_iterator__te_cmp(
+ const git_tree_entry *a,
+ const git_tree_entry *b,
+ int (*compare)(const char *, const char *, size_t))
+{
+ return git_path_cmp(
+ a->filename, a->filename_len, a->attr == GIT_FILEMODE_TREE,
+ b->filename, b->filename_len, b->attr == GIT_FILEMODE_TREE,
+ compare);
+}
+
+static int tree_iterator__ci_cmp(const void *a, const void *b, void *p)
+{
+ const tree_iterator_entry *ae = a, *be = b;
+ int cmp = tree_iterator__te_cmp(ae->te, be->te, git__strncasecmp);
+
+ if (!cmp) {
+ /* stabilize sort order among equivalent names */
+ if (!ae->parent->te || !be->parent->te)
+ cmp = tree_iterator__te_cmp(ae->te, be->te, git__strncmp);
+ else
+ cmp = tree_iterator__ci_cmp(ae->parent, be->parent, p);
+ }
+
+ return cmp;
+}
+
+static int tree_iterator__search_cmp(const void *key, const void *val, void *p)
+{
+ const tree_iterator_frame *tf = key;
+ const git_tree_entry *te = ((tree_iterator_entry *)val)->te;
+
+ return git_path_cmp(
+ tf->start, tf->startlen, false,
+ te->filename, te->filename_len, te->attr == GIT_FILEMODE_TREE,
+ ((tree_iterator *)p)->strncomp);
+}
+
+static bool tree_iterator__move_to_next(
+ tree_iterator *ti, tree_iterator_frame *tf)
+{
+ if (tf->next > tf->current + 1)
+ ti->path_ambiguities--;
+
+ if (!tf->up) { /* at root */
+ tf->current = tf->next;
+ return false;
+ }
+
+ for (; tf->current < tf->next; tf->current++) {
+ git_tree_free(tf->entries[tf->current]->tree);
+ tf->entries[tf->current]->tree = NULL;
+ }
+
+ return (tf->current < tf->n_entries);
+}
+
+static int tree_iterator__set_next(tree_iterator *ti, tree_iterator_frame *tf)
+{
+ int error = 0;
+ const git_tree_entry *te, *last = NULL;
+
+ tf->next = tf->current;
+
+ for (; tf->next < tf->n_entries; tf->next++, last = te) {
+ te = tf->entries[tf->next]->te;
+
+ if (last && tree_iterator__te_cmp(last, te, ti->strncomp))
+ break;
+
+ /* try to load trees for items in [current,next) range */
+ if (!error && git_tree_entry__is_tree(te))
+ error = git_tree_lookup(
+ &tf->entries[tf->next]->tree, ti->base.repo, &te->oid);
+ }
+
+ if (tf->next > tf->current + 1)
+ ti->path_ambiguities++;
+
+ /* if a tree lookup failed, advance over this span and return failure */
+ if (error < 0) {
+ tree_iterator__move_to_next(ti, tf);
+ return error;
+ }
+
+ if (last && !tree_iterator__current_filename(ti, last))
+ return -1; /* must have been allocation failure */
+
+ return 0;
+}
+
+GIT_INLINE(bool) tree_iterator__at_tree(tree_iterator *ti)
+{
+ return (ti->head->current < ti->head->n_entries &&
+ ti->head->entries[ti->head->current]->tree != NULL);
+}
+
+static int tree_iterator__push_frame(tree_iterator *ti)
+{
+ int error = 0;
+ tree_iterator_frame *head = ti->head, *tf = NULL;
+ size_t i, n_entries = 0;
+
+ if (head->current >= head->n_entries || !head->entries[head->current]->tree)
+ return GIT_ITEROVER;
+
+ for (i = head->current; i < head->next; ++i)
+ n_entries += git_tree_entrycount(head->entries[i]->tree);
+
+ tf = git__calloc(sizeof(tree_iterator_frame) +
+ n_entries * sizeof(tree_iterator_entry *), 1);
+ GITERR_CHECK_ALLOC(tf);
+
+ tf->n_entries = n_entries;
+
+ tf->up = head;
+ head->down = tf;
+ ti->head = tf;
+
+ for (i = head->current, n_entries = 0; i < head->next; ++i) {
+ git_tree *tree = head->entries[i]->tree;
+ size_t j, max_j = git_tree_entrycount(tree);
+
+ for (j = 0; j < max_j; ++j) {
+ tree_iterator_entry *entry = git_pool_malloc(&ti->pool, 1);
+ GITERR_CHECK_ALLOC(entry);
+
+ entry->parent = head->entries[i];
+ entry->te = git_tree_entry_byindex(tree, j);
+ entry->tree = NULL;
+
+ tf->entries[n_entries++] = entry;
+ }
+ }
+
+ /* if ignore_case, sort entries case insensitively */
+ if (iterator__ignore_case(ti))
+ git__tsort_r(
+ (void **)tf->entries, tf->n_entries, tree_iterator__ci_cmp, tf);
+
+ /* pick tf->current based on "start" (or start at zero) */
+ if (head->startlen > 0) {
+ git__bsearch_r((void **)tf->entries, tf->n_entries, head,
+ tree_iterator__search_cmp, ti, &tf->current);
+
+ while (tf->current &&
+ !tree_iterator__search_cmp(head, tf->entries[tf->current-1], ti))
+ tf->current--;
+
+ if ((tf->start = strchr(head->start, '/')) != NULL) {
+ tf->start++;
+ tf->startlen = strlen(tf->start);
+ }
+ }
+
+ ti->path_has_filename = ti->entry_is_current = false;
+
+ if ((error = tree_iterator__set_next(ti, tf)) < 0)
+ return error;
+
+ /* autoexpand as needed */
+ if (!iterator__include_trees(ti) && tree_iterator__at_tree(ti))
+ return tree_iterator__push_frame(ti);
+
+ return 0;
+}
+
+static bool tree_iterator__pop_frame(tree_iterator *ti, bool final)
+{
+ tree_iterator_frame *tf = ti->head;
+
+ if (!tf->up)
+ return false;
+
+ ti->head = tf->up;
+ ti->head->down = NULL;
+
+ tree_iterator__move_to_next(ti, tf);
+
+ if (!final) { /* if final, don't bother to clean up */
+ git_pool_free_array(&ti->pool, tf->n_entries, (void **)tf->entries);
+ git_buf_rtruncate_at_char(&ti->path, '/');
+ }
+
+ git__free(tf);
+
+ return true;
+}
+
+static void tree_iterator__pop_all(tree_iterator *ti, bool to_end, bool final)
+{
+ while (tree_iterator__pop_frame(ti, final)) /* pop to root */;
+
+ if (!final) {
+ ti->head->current = to_end ? ti->head->n_entries : 0;
+ ti->path_ambiguities = 0;
+ git_buf_clear(&ti->path);
+ }
+}
+
+static int tree_iterator__update_entry(tree_iterator *ti)
+{
+ tree_iterator_frame *tf;
+ const git_tree_entry *te;
+
+ if (ti->entry_is_current)
+ return 0;
+
+ tf = ti->head;
+ te = tf->entries[tf->current]->te;
+
+ ti->entry.mode = te->attr;
+ git_oid_cpy(&ti->entry.id, &te->oid);
+
+ ti->entry.path = tree_iterator__current_filename(ti, te);
+ GITERR_CHECK_ALLOC(ti->entry.path);
+
+ if (ti->path_ambiguities > 0)
+ tree_iterator__rewrite_filename(ti);
+
+ if (iterator__past_end(ti, ti->entry.path)) {
+ tree_iterator__pop_all(ti, true, false);
+ return GIT_ITEROVER;
+ }
+
+ ti->entry_is_current = true;
+
+ return 0;
+}
+
+static int tree_iterator__current(
+ const git_index_entry **entry, git_iterator *self)
+{
+ int error;
+ tree_iterator *ti = (tree_iterator *)self;
+ tree_iterator_frame *tf = ti->head;
+
+ iterator__clear_entry(entry);
+
+ if (tf->current >= tf->n_entries)
+ return GIT_ITEROVER;
+
+ if ((error = tree_iterator__update_entry(ti)) < 0)
+ return error;
+
+ if (entry)
+ *entry = &ti->entry;
+
+ ti->base.flags |= GIT_ITERATOR_FIRST_ACCESS;
+
+ return 0;
+}
+
+static int tree_iterator__advance_into(
+ const git_index_entry **entry, git_iterator *self)
+{
+ int error = 0;
+ tree_iterator *ti = (tree_iterator *)self;
+
+ iterator__clear_entry(entry);
+
+ if (tree_iterator__at_tree(ti))
+ error = tree_iterator__push_frame(ti);
+
+ if (!error && entry)
+ error = tree_iterator__current(entry, self);
+
+ return error;
+}
+
+static int tree_iterator__advance(
+ const git_index_entry **entry, git_iterator *self)
+{
+ int error;
+ tree_iterator *ti = (tree_iterator *)self;
+ tree_iterator_frame *tf = ti->head;
+
+ iterator__clear_entry(entry);
+
+ if (tf->current >= tf->n_entries)
+ return GIT_ITEROVER;
+
+ if (!iterator__has_been_accessed(ti))
+ return tree_iterator__current(entry, self);
+
+ if (iterator__do_autoexpand(ti) && iterator__include_trees(ti) &&
+ tree_iterator__at_tree(ti))
+ return tree_iterator__advance_into(entry, self);
+
+ if (ti->path_has_filename) {
+ git_buf_rtruncate_at_char(&ti->path, '/');
+ ti->path_has_filename = ti->entry_is_current = false;
+ }
+
+ /* scan forward and up, advancing in frame or popping frame when done */
+ while (!tree_iterator__move_to_next(ti, tf) &&
+ tree_iterator__pop_frame(ti, false))
+ tf = ti->head;
+
+ /* find next and load trees */
+ if ((error = tree_iterator__set_next(ti, tf)) < 0)
+ return error;
+
+ /* deal with include_trees / auto_expand as needed */
+ if (!iterator__include_trees(ti) && tree_iterator__at_tree(ti))
+ return tree_iterator__advance_into(entry, self);
+
+ return tree_iterator__current(entry, self);
+}
+
+static int tree_iterator__seek(git_iterator *self, const char *prefix)
+{
+ GIT_UNUSED(self); GIT_UNUSED(prefix);
+ return -1;
+}
+
+static int tree_iterator__reset(
+ git_iterator *self, const char *start, const char *end)
+{
+ tree_iterator *ti = (tree_iterator *)self;
+
+ tree_iterator__pop_all(ti, false, false);
+
+ if (iterator__reset_range(self, start, end) < 0)
+ return -1;
+
+ return tree_iterator__push_frame(ti); /* re-expand root tree */
+}
+
+static int tree_iterator__at_end(git_iterator *self)
+{
+ tree_iterator *ti = (tree_iterator *)self;
+ return (ti->head->current >= ti->head->n_entries);
+}
+
+static void tree_iterator__free(git_iterator *self)
+{
+ tree_iterator *ti = (tree_iterator *)self;
+
+ tree_iterator__pop_all(ti, true, false);
+
+ git_tree_free(ti->head->entries[0]->tree);
+ git__free(ti->head);
+ git_pool_clear(&ti->pool);
+ git_buf_free(&ti->path);
+}
+
+static int tree_iterator__create_root_frame(tree_iterator *ti, git_tree *tree)
+{
+ size_t sz = sizeof(tree_iterator_frame) + sizeof(tree_iterator_entry);
+ tree_iterator_frame *root = git__calloc(sz, sizeof(char));
+ GITERR_CHECK_ALLOC(root);
+
+ root->n_entries = 1;
+ root->next = 1;
+ root->start = ti->base.start;
+ root->startlen = root->start ? strlen(root->start) : 0;
+ root->entries[0] = git_pool_mallocz(&ti->pool, 1);
+ GITERR_CHECK_ALLOC(root->entries[0]);
+ root->entries[0]->tree = tree;
+
+ ti->head = ti->root = root;
+
+ return 0;
+}
+
+int git_iterator_for_tree(
+ git_iterator **iter,
+ git_tree *tree,
+ git_iterator_flag_t flags,
+ const char *start,
+ const char *end)
+{
+ int error;
+ tree_iterator *ti;
+
+ if (tree == NULL)
+ return git_iterator_for_nothing(iter, flags, start, end);
+
+ if ((error = git_object_dup((git_object **)&tree, (git_object *)tree)) < 0)
+ return error;
+
+ ti = git__calloc(1, sizeof(tree_iterator));
+ GITERR_CHECK_ALLOC(ti);
+
+ ITERATOR_BASE_INIT(ti, tree, TREE, git_tree_owner(tree));
+
+ if ((error = iterator__update_ignore_case((git_iterator *)ti, flags)) < 0)
+ goto fail;
+ ti->strncomp = iterator__ignore_case(ti) ? git__strncasecmp : git__strncmp;
+
+ if ((error = git_pool_init(&ti->pool, sizeof(tree_iterator_entry),0)) < 0 ||
+ (error = tree_iterator__create_root_frame(ti, tree)) < 0 ||
+ (error = tree_iterator__push_frame(ti)) < 0) /* expand root now */
+ goto fail;
+
+ *iter = (git_iterator *)ti;
+ return 0;
+
+fail:
+ git_iterator_free((git_iterator *)ti);
+ return error;
+}
+
+
+typedef struct {
+ git_iterator base;
+ git_iterator_callbacks cb;
+ git_index *index;
+ git_vector entries;
+ git_vector_cmp entry_srch;
+ size_t current;
+ /* when not in autoexpand mode, use these to represent "tree" state */
+ git_buf partial;
+ size_t partial_pos;
+ char restore_terminator;
+ git_index_entry tree_entry;
+} index_iterator;
+
+static const git_index_entry *index_iterator__index_entry(index_iterator *ii)
+{
+ const git_index_entry *ie = git_vector_get(&ii->entries, ii->current);
+
+ if (ie != NULL && iterator__past_end(ii, ie->path)) {
+ ii->current = git_vector_length(&ii->entries);
+ ie = NULL;
+ }
+
+ return ie;
+}
+
+static const git_index_entry *index_iterator__skip_conflicts(index_iterator *ii)
+{
+ const git_index_entry *ie;
+
+ while ((ie = index_iterator__index_entry(ii)) != NULL &&
+ git_index_entry_stage(ie) != 0)
+ ii->current++;
+
+ return ie;
+}
+
+static void index_iterator__next_prefix_tree(index_iterator *ii)
+{
+ const char *slash;
+
+ if (!iterator__include_trees(ii))
+ return;
+
+ slash = strchr(&ii->partial.ptr[ii->partial_pos], '/');
+
+ if (slash != NULL) {
+ ii->partial_pos = (slash - ii->partial.ptr) + 1;
+ ii->restore_terminator = ii->partial.ptr[ii->partial_pos];
+ ii->partial.ptr[ii->partial_pos] = '\0';
+ } else {
+ ii->partial_pos = ii->partial.size;
+ }
+
+ if (index_iterator__index_entry(ii) == NULL)
+ ii->partial_pos = ii->partial.size;
+}
+
+static int index_iterator__first_prefix_tree(index_iterator *ii)
+{
+ const git_index_entry *ie = index_iterator__skip_conflicts(ii);
+ const char *scan, *prior, *slash;
+
+ if (!ie || !iterator__include_trees(ii))
+ return 0;
+
+ /* find longest common prefix with prior index entry */
+ for (scan = slash = ie->path, prior = ii->partial.ptr;
+ *scan && *scan == *prior; ++scan, ++prior)
+ if (*scan == '/')
+ slash = scan;
+
+ if (git_buf_sets(&ii->partial, ie->path) < 0)
+ return -1;
+
+ ii->partial_pos = (slash - ie->path) + 1;
+ index_iterator__next_prefix_tree(ii);
+
+ return 0;
+}
+
+#define index_iterator__at_tree(I) \
+ (iterator__include_trees(I) && (I)->partial_pos < (I)->partial.size)
+
+static int index_iterator__current(
+ const git_index_entry **entry, git_iterator *self)
+{
+ index_iterator *ii = (index_iterator *)self;
+ const git_index_entry *ie = git_vector_get(&ii->entries, ii->current);
+
+ if (ie != NULL && index_iterator__at_tree(ii)) {
+ ii->tree_entry.path = ii->partial.ptr;
+ ie = &ii->tree_entry;
+ }
+
+ if (entry)
+ *entry = ie;
+
+ ii->base.flags |= GIT_ITERATOR_FIRST_ACCESS;
+
+ return (ie != NULL) ? 0 : GIT_ITEROVER;
+}
+
+static int index_iterator__at_end(git_iterator *self)
+{
+ index_iterator *ii = (index_iterator *)self;
+ return (ii->current >= git_vector_length(&ii->entries));
+}
+
+static int index_iterator__advance(
+ const git_index_entry **entry, git_iterator *self)
+{
+ index_iterator *ii = (index_iterator *)self;
+ size_t entrycount = git_vector_length(&ii->entries);
+ const git_index_entry *ie;
+
+ if (!iterator__has_been_accessed(ii))
+ return index_iterator__current(entry, self);
+
+ if (index_iterator__at_tree(ii)) {
+ if (iterator__do_autoexpand(ii)) {
+ ii->partial.ptr[ii->partial_pos] = ii->restore_terminator;
+ index_iterator__next_prefix_tree(ii);
+ } else {
+ /* advance to sibling tree (i.e. find entry with new prefix) */
+ while (ii->current < entrycount) {
+ ii->current++;
+
+ if (!(ie = git_vector_get(&ii->entries, ii->current)) ||
+ ii->base.prefixcomp(ie->path, ii->partial.ptr) != 0)
+ break;
+ }
+
+ if (index_iterator__first_prefix_tree(ii) < 0)
+ return -1;
+ }
+ } else {
+ if (ii->current < entrycount)
+ ii->current++;
+
+ if (index_iterator__first_prefix_tree(ii) < 0)
+ return -1;
+ }
+
+ return index_iterator__current(entry, self);
+}
+
+static int index_iterator__advance_into(
+ const git_index_entry **entry, git_iterator *self)
+{
+ index_iterator *ii = (index_iterator *)self;
+ const git_index_entry *ie = git_vector_get(&ii->entries, ii->current);
+
+ if (ie != NULL && index_iterator__at_tree(ii)) {
+ if (ii->restore_terminator)
+ ii->partial.ptr[ii->partial_pos] = ii->restore_terminator;
+ index_iterator__next_prefix_tree(ii);
+ }
+
+ return index_iterator__current(entry, self);
+}
+
+static int index_iterator__seek(git_iterator *self, const char *prefix)
+{
+ GIT_UNUSED(self); GIT_UNUSED(prefix);
+ return -1;
+}
+
+static int index_iterator__reset(
+ git_iterator *self, const char *start, const char *end)
+{
+ index_iterator *ii = (index_iterator *)self;
+ const git_index_entry *ie;
+
+ if (iterator__reset_range(self, start, end) < 0)
+ return -1;
+
+ ii->current = 0;
+
+ if (ii->base.start)
+ git_index_snapshot_find(
+ &ii->current, &ii->entries, ii->entry_srch, ii->base.start, 0, 0);
+
+ if ((ie = index_iterator__skip_conflicts(ii)) == NULL)
+ return 0;
+
+ if (git_buf_sets(&ii->partial, ie->path) < 0)
+ return -1;
+
+ ii->partial_pos = 0;
+
+ if (ii->base.start) {
+ size_t startlen = strlen(ii->base.start);
+
+ ii->partial_pos = (startlen > ii->partial.size) ?
+ ii->partial.size : startlen;
+ }
+
+ index_iterator__next_prefix_tree(ii);
+
+ return 0;
+}
+
+static void index_iterator__free(git_iterator *self)
+{
+ index_iterator *ii = (index_iterator *)self;
+ git_index_snapshot_release(&ii->entries, ii->index);
+ ii->index = NULL;
+ git_buf_free(&ii->partial);
+}
+
+int git_iterator_for_index(
+ git_iterator **iter,
+ git_index *index,
+ git_iterator_flag_t flags,
+ const char *start,
+ const char *end)
+{
+ int error = 0;
+ index_iterator *ii = git__calloc(1, sizeof(index_iterator));
+ GITERR_CHECK_ALLOC(ii);
+
+ if ((error = git_index_snapshot_new(&ii->entries, index)) < 0) {
+ git__free(ii);
+ return error;
+ }
+ ii->index = index;
+
+ ITERATOR_BASE_INIT(ii, index, INDEX, git_index_owner(index));
+
+ if ((error = iterator__update_ignore_case((git_iterator *)ii, flags)) < 0) {
+ git_iterator_free((git_iterator *)ii);
+ return error;
+ }
+
+ ii->entry_srch = iterator__ignore_case(ii) ?
+ git_index_entry_isrch : git_index_entry_srch;
+
+ git_vector_set_cmp(&ii->entries, iterator__ignore_case(ii) ?
+ git_index_entry_icmp : git_index_entry_cmp);
+ git_vector_sort(&ii->entries);
+
+ git_buf_init(&ii->partial, 0);
+ ii->tree_entry.mode = GIT_FILEMODE_TREE;
+
+ index_iterator__reset((git_iterator *)ii, NULL, NULL);
+
+ *iter = (git_iterator *)ii;
+ return 0;
+}
+
+
+typedef struct fs_iterator_frame fs_iterator_frame;
+struct fs_iterator_frame {
+ fs_iterator_frame *next;
+ git_vector entries;
+ size_t index;
+ int is_ignored;
+};
+
+typedef struct fs_iterator fs_iterator;
+struct fs_iterator {
+ git_iterator base;
+ git_iterator_callbacks cb;
+ fs_iterator_frame *stack;
+ git_index_entry entry;
+ git_buf path;
+ size_t root_len;
+ uint32_t dirload_flags;
+ int depth;
+
+ int (*enter_dir_cb)(fs_iterator *self);
+ int (*leave_dir_cb)(fs_iterator *self);
+ int (*update_entry_cb)(fs_iterator *self);
+};
+
+#define FS_MAX_DEPTH 100
+
+static fs_iterator_frame *fs_iterator__alloc_frame(fs_iterator *fi)
+{
+ fs_iterator_frame *ff = git__calloc(1, sizeof(fs_iterator_frame));
+ git_vector_cmp entry_compare = CASESELECT(
+ iterator__ignore_case(fi),
+ git_path_with_stat_cmp_icase, git_path_with_stat_cmp);
+
+ if (ff && git_vector_init(&ff->entries, 0, entry_compare) < 0) {
+ git__free(ff);
+ ff = NULL;
+ }
+
+ return ff;
+}
+
+static void fs_iterator__free_frame(fs_iterator_frame *ff)
+{
+ git_vector_free_deep(&ff->entries);
+ git__free(ff);
+}
+
+static void fs_iterator__pop_frame(
+ fs_iterator *fi, fs_iterator_frame *ff, bool pop_last)
+{
+ if (fi && fi->stack == ff) {
+ if (!ff->next && !pop_last) {
+ memset(&fi->entry, 0, sizeof(fi->entry));
+ return;
+ }
+
+ if (fi->leave_dir_cb)
+ (void)fi->leave_dir_cb(fi);
+
+ fi->stack = ff->next;
+ fi->depth--;
+ }
+
+ fs_iterator__free_frame(ff);
+}
+
+static int fs_iterator__update_entry(fs_iterator *fi);
+static int fs_iterator__advance_over(
+ const git_index_entry **entry, git_iterator *self);
+
+static int fs_iterator__entry_cmp(const void *i, const void *item)
+{
+ const fs_iterator *fi = (const fs_iterator *)i;
+ const git_path_with_stat *ps = item;
+ return fi->base.prefixcomp(fi->base.start, ps->path);
+}
+
+static void fs_iterator__seek_frame_start(
+ fs_iterator *fi, fs_iterator_frame *ff)
+{
+ if (!ff)
+ return;
+
+ if (fi->base.start)
+ git_vector_bsearch2(
+ &ff->index, &ff->entries, fs_iterator__entry_cmp, fi);
+ else
+ ff->index = 0;
+}
+
+static int fs_iterator__expand_dir(fs_iterator *fi)
+{
+ int error;
+ fs_iterator_frame *ff;
+
+ if (fi->depth > FS_MAX_DEPTH) {
+ giterr_set(GITERR_REPOSITORY,
+ "Directory nesting is too deep (%d)", fi->depth);
+ return -1;
+ }
+
+ ff = fs_iterator__alloc_frame(fi);
+ GITERR_CHECK_ALLOC(ff);
+
+ error = git_path_dirload_with_stat(
+ fi->path.ptr, fi->root_len, fi->dirload_flags,
+ fi->base.start, fi->base.end, &ff->entries);
+
+ if (error < 0) {
+ git_error_state last_error = { 0 };
+ giterr_capture(&last_error, error);
+
+ /* these callbacks may clear the error message */
+ fs_iterator__free_frame(ff);
+ fs_iterator__advance_over(NULL, (git_iterator *)fi);
+ /* next time return value we skipped to */
+ fi->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS;
+
+ return giterr_restore(&last_error);
+ }
+
+ if (ff->entries.length == 0) {
+ fs_iterator__free_frame(ff);
+ return GIT_ENOTFOUND;
+ }
+ fi->base.stat_calls += ff->entries.length;
+
+ fs_iterator__seek_frame_start(fi, ff);
+
+ ff->next = fi->stack;
+ fi->stack = ff;
+ fi->depth++;
+
+ if (fi->enter_dir_cb && (error = fi->enter_dir_cb(fi)) < 0)
+ return error;
+
+ return fs_iterator__update_entry(fi);
+}
+
+static int fs_iterator__current(
+ const git_index_entry **entry, git_iterator *self)
+{
+ fs_iterator *fi = (fs_iterator *)self;
+ const git_index_entry *fe = (fi->entry.path == NULL) ? NULL : &fi->entry;
+
+ if (entry)
+ *entry = fe;
+
+ fi->base.flags |= GIT_ITERATOR_FIRST_ACCESS;
+
+ return (fe != NULL) ? 0 : GIT_ITEROVER;
+}
+
+static int fs_iterator__at_end(git_iterator *self)
+{
+ return (((fs_iterator *)self)->entry.path == NULL);
+}
+
+static int fs_iterator__advance_into(
+ const git_index_entry **entry, git_iterator *iter)
+{
+ int error = 0;
+ fs_iterator *fi = (fs_iterator *)iter;
+
+ iterator__clear_entry(entry);
+
+ /* Allow you to explicitly advance into a commit/submodule (as well as a
+ * tree) to avoid cases where an entry is mislabeled as a submodule in
+ * the working directory. The fs iterator will never have COMMMIT
+ * entries on it's own, but a wrapper might add them.
+ */
+ if (fi->entry.path != NULL &&
+ (fi->entry.mode == GIT_FILEMODE_TREE ||
+ fi->entry.mode == GIT_FILEMODE_COMMIT))
+ /* returns GIT_ENOTFOUND if the directory is empty */
+ error = fs_iterator__expand_dir(fi);
+
+ if (!error && entry)
+ error = fs_iterator__current(entry, iter);
+
+ if (!error && !fi->entry.path)
+ error = GIT_ITEROVER;
+
+ return error;
+}
+
+static int fs_iterator__advance_over(
+ const git_index_entry **entry, git_iterator *self)
+{
+ int error = 0;
+ fs_iterator *fi = (fs_iterator *)self;
+ fs_iterator_frame *ff;
+ git_path_with_stat *next;
+
+ if (entry != NULL)
+ *entry = NULL;
+
+ while (fi->entry.path != NULL) {
+ ff = fi->stack;
+ next = git_vector_get(&ff->entries, ++ff->index);
+
+ if (next != NULL)
+ break;
+
+ fs_iterator__pop_frame(fi, ff, false);
+ }
+
+ error = fs_iterator__update_entry(fi);
+
+ if (!error && entry != NULL)
+ error = fs_iterator__current(entry, self);
+
+ return error;
+}
+
+static int fs_iterator__advance(
+ const git_index_entry **entry, git_iterator *self)
+{
+ fs_iterator *fi = (fs_iterator *)self;
+
+ if (!iterator__has_been_accessed(fi))
+ return fs_iterator__current(entry, self);
+
+ /* given include_trees & autoexpand, we might have to go into a tree */
+ if (iterator__do_autoexpand(fi) &&
+ fi->entry.path != NULL &&
+ fi->entry.mode == GIT_FILEMODE_TREE)
+ {
+ int error = fs_iterator__advance_into(entry, self);
+ if (error != GIT_ENOTFOUND)
+ return error;
+ /* continue silently past empty directories if autoexpanding */
+ giterr_clear();
+ }
+
+ return fs_iterator__advance_over(entry, self);
+}
+
+static int fs_iterator__seek(git_iterator *self, const char *prefix)
+{
+ GIT_UNUSED(self);
+ GIT_UNUSED(prefix);
+ /* pop stack until matching prefix */
+ /* find prefix item in current frame */
+ /* push subdirectories as deep as possible while matching */
+ return 0;
+}
+
+static int fs_iterator__reset(
+ git_iterator *self, const char *start, const char *end)
+{
+ int error;
+ fs_iterator *fi = (fs_iterator *)self;
+
+ while (fi->stack != NULL && fi->stack->next != NULL)
+ fs_iterator__pop_frame(fi, fi->stack, false);
+ fi->depth = 0;
+
+ if ((error = iterator__reset_range(self, start, end)) < 0)
+ return error;
+
+ fs_iterator__seek_frame_start(fi, fi->stack);
+
+ error = fs_iterator__update_entry(fi);
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ return error;
+}
+
+static void fs_iterator__free(git_iterator *self)
+{
+ fs_iterator *fi = (fs_iterator *)self;
+
+ while (fi->stack != NULL)
+ fs_iterator__pop_frame(fi, fi->stack, true);
+
+ git_buf_free(&fi->path);
+}
+
+static int fs_iterator__update_entry(fs_iterator *fi)
+{
+ git_path_with_stat *ps;
+
+ memset(&fi->entry, 0, sizeof(fi->entry));
+
+ if (!fi->stack)
+ return GIT_ITEROVER;
+
+ ps = git_vector_get(&fi->stack->entries, fi->stack->index);
+ if (!ps)
+ return GIT_ITEROVER;
+
+ git_buf_truncate(&fi->path, fi->root_len);
+ if (git_buf_put(&fi->path, ps->path, ps->path_len) < 0)
+ return -1;
+
+ if (iterator__past_end(fi, fi->path.ptr + fi->root_len))
+ return GIT_ITEROVER;
+
+ fi->entry.path = ps->path;
+ git_index_entry__init_from_stat(&fi->entry, &ps->st, true);
+
+ /* need different mode here to keep directories during iteration */
+ fi->entry.mode = git_futils_canonical_mode(ps->st.st_mode);
+
+ /* allow wrapper to check/update the entry (can force skip) */
+ if (fi->update_entry_cb &&
+ fi->update_entry_cb(fi) == GIT_ENOTFOUND)
+ return fs_iterator__advance_over(NULL, (git_iterator *)fi);
+
+ /* if this is a tree and trees aren't included, then skip */
+ if (fi->entry.mode == GIT_FILEMODE_TREE && !iterator__include_trees(fi)) {
+ int error = fs_iterator__advance_into(NULL, (git_iterator *)fi);
+ if (error != GIT_ENOTFOUND)
+ return error;
+ giterr_clear();
+ return fs_iterator__advance_over(NULL, (git_iterator *)fi);
+ }
+
+ return 0;
+}
+
+static int fs_iterator__initialize(
+ git_iterator **out, fs_iterator *fi, const char *root)
+{
+ int error;
+
+ if (git_buf_sets(&fi->path, root) < 0 || git_path_to_dir(&fi->path) < 0) {
+ git__free(fi);
+ return -1;
+ }
+ fi->root_len = fi->path.size;
+
+ fi->dirload_flags =
+ (iterator__ignore_case(fi) ? GIT_PATH_DIR_IGNORE_CASE : 0) |
+ (iterator__flag(fi, PRECOMPOSE_UNICODE) ?
+ GIT_PATH_DIR_PRECOMPOSE_UNICODE : 0);
+
+ if ((error = fs_iterator__expand_dir(fi)) < 0) {
+ if (error == GIT_ENOTFOUND || error == GIT_ITEROVER) {
+ giterr_clear();
+ error = 0;
+ } else {
+ git_iterator_free((git_iterator *)fi);
+ fi = NULL;
+ }
+ }
+
+ *out = (git_iterator *)fi;
+ return error;
+}
+
+int git_iterator_for_filesystem(
+ git_iterator **out,
+ const char *root,
+ git_iterator_flag_t flags,
+ const char *start,
+ const char *end)
+{
+ fs_iterator *fi = git__calloc(1, sizeof(fs_iterator));
+ GITERR_CHECK_ALLOC(fi);
+
+ ITERATOR_BASE_INIT(fi, fs, FS, NULL);
+
+ if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0)
+ fi->base.flags |= GIT_ITERATOR_IGNORE_CASE;
+
+ return fs_iterator__initialize(out, fi, root);
+}
+
+
+typedef struct {
+ fs_iterator fi;
+ git_ignores ignores;
+ int is_ignored;
+} workdir_iterator;
+
+GIT_INLINE(bool) workdir_path_is_dotgit(const git_buf *path)
+{
+ size_t len;
+
+ if (!path || (len = path->size) < 4)
+ return false;
+
+ if (path->ptr[len - 1] == '/')
+ len--;
+
+ if (tolower(path->ptr[len - 1]) != 't' ||
+ tolower(path->ptr[len - 2]) != 'i' ||
+ tolower(path->ptr[len - 3]) != 'g' ||
+ tolower(path->ptr[len - 4]) != '.')
+ return false;
+
+ return (len == 4 || path->ptr[len - 5] == '/');
+}
+
+static int workdir_iterator__enter_dir(fs_iterator *fi)
+{
+ workdir_iterator *wi = (workdir_iterator *)fi;
+ fs_iterator_frame *ff = fi->stack;
+ size_t pos;
+ git_path_with_stat *entry;
+ bool found_submodules = false;
+
+ /* check if this directory is ignored */
+ if (git_ignore__lookup(
+ &ff->is_ignored, &wi->ignores, fi->path.ptr + fi->root_len) < 0) {
+ giterr_clear();
+ ff->is_ignored = GIT_IGNORE_NOTFOUND;
+ }
+
+ /* if this is not the top level directory... */
+ if (ff->next != NULL) {
+ ssize_t slash_pos = git_buf_rfind_next(&fi->path, '/');
+
+ /* inherit ignored from parent if no rule specified */
+ if (ff->is_ignored <= GIT_IGNORE_NOTFOUND)
+ ff->is_ignored = ff->next->is_ignored;
+
+ /* push new ignores for files in this directory */
+ (void)git_ignore__push_dir(&wi->ignores, &fi->path.ptr[slash_pos + 1]);
+ }
+
+ /* convert submodules to GITLINK and remove trailing slashes */
+ git_vector_foreach(&ff->entries, pos, entry) {
+ if (!S_ISDIR(entry->st.st_mode) || !strcmp(GIT_DIR, entry->path))
+ continue;
+
+ if (git_submodule__is_submodule(fi->base.repo, entry->path)) {
+ entry->st.st_mode = GIT_FILEMODE_COMMIT;
+ entry->path_len--;
+ entry->path[entry->path_len] = '\0';
+ found_submodules = true;
+ }
+ }
+
+ /* if we renamed submodules, re-sort and re-seek to start */
+ if (found_submodules) {
+ git_vector_set_sorted(&ff->entries, 0);
+ git_vector_sort(&ff->entries);
+ fs_iterator__seek_frame_start(fi, ff);
+ }
+
+ return 0;
+}
+
+static int workdir_iterator__leave_dir(fs_iterator *fi)
+{
+ workdir_iterator *wi = (workdir_iterator *)fi;
+ git_ignore__pop_dir(&wi->ignores);
+ return 0;
+}
+
+static int workdir_iterator__update_entry(fs_iterator *fi)
+{
+ workdir_iterator *wi = (workdir_iterator *)fi;
+
+ /* skip over .git entries */
+ if (workdir_path_is_dotgit(&fi->path))
+ return GIT_ENOTFOUND;
+
+ /* reset is_ignored since we haven't checked yet */
+ wi->is_ignored = GIT_IGNORE_UNCHECKED;
+
+ return 0;
+}
+
+static void workdir_iterator__free(git_iterator *self)
+{
+ workdir_iterator *wi = (workdir_iterator *)self;
+ fs_iterator__free(self);
+ git_ignore__free(&wi->ignores);
+}
+
+int git_iterator_for_workdir_ext(
+ git_iterator **out,
+ git_repository *repo,
+ const char *repo_workdir,
+ git_iterator_flag_t flags,
+ const char *start,
+ const char *end)
+{
+ int error, precompose = 0;
+ workdir_iterator *wi;
+
+ if (!repo_workdir) {
+ if (git_repository__ensure_not_bare(repo, "scan working directory") < 0)
+ return GIT_EBAREREPO;
+ repo_workdir = git_repository_workdir(repo);
+ }
+
+ /* initialize as an fs iterator then do overrides */
+ wi = git__calloc(1, sizeof(workdir_iterator));
+ GITERR_CHECK_ALLOC(wi);
+ ITERATOR_BASE_INIT((&wi->fi), fs, FS, repo);
+
+ wi->fi.base.type = GIT_ITERATOR_TYPE_WORKDIR;
+ wi->fi.cb.free = workdir_iterator__free;
+ wi->fi.enter_dir_cb = workdir_iterator__enter_dir;
+ wi->fi.leave_dir_cb = workdir_iterator__leave_dir;
+ wi->fi.update_entry_cb = workdir_iterator__update_entry;
+
+ if ((error = iterator__update_ignore_case((git_iterator *)wi, flags)) < 0 ||
+ (error = git_ignore__for_path(repo, ".gitignore", &wi->ignores)) < 0)
+ {
+ git_iterator_free((git_iterator *)wi);
+ return error;
+ }
+
+ /* try to look up precompose and set flag if appropriate */
+ if (git_repository__cvar(&precompose, repo, GIT_CVAR_PRECOMPOSE) < 0)
+ giterr_clear();
+ else if (precompose)
+ wi->fi.base.flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE;
+
+ return fs_iterator__initialize(out, &wi->fi, repo_workdir);
+}
+
+
+void git_iterator_free(git_iterator *iter)
+{
+ if (iter == NULL)
+ return;
+
+ iter->cb->free(iter);
+
+ git__free(iter->start);
+ git__free(iter->end);
+
+ memset(iter, 0, sizeof(*iter));
+
+ git__free(iter);
+}
+
+int git_iterator_set_ignore_case(git_iterator *iter, bool ignore_case)
+{
+ bool desire_ignore_case = (ignore_case != 0);
+
+ if (iterator__ignore_case(iter) == desire_ignore_case)
+ return 0;
+
+ if (iter->type == GIT_ITERATOR_TYPE_EMPTY) {
+ if (desire_ignore_case)
+ iter->flags |= GIT_ITERATOR_IGNORE_CASE;
+ else
+ iter->flags &= ~GIT_ITERATOR_IGNORE_CASE;
+ } else {
+ giterr_set(GITERR_INVALID,
+ "Cannot currently set ignore case on non-empty iterators");
+ return -1;
+ }
+
+ return 0;
+}
+
+git_index *git_iterator_get_index(git_iterator *iter)
+{
+ if (iter->type == GIT_ITERATOR_TYPE_INDEX)
+ return ((index_iterator *)iter)->index;
+ return NULL;
+}
+
+int git_iterator_current_tree_entry(
+ const git_tree_entry **tree_entry, git_iterator *iter)
+{
+ if (iter->type != GIT_ITERATOR_TYPE_TREE)
+ *tree_entry = NULL;
+ else {
+ tree_iterator_frame *tf = ((tree_iterator *)iter)->head;
+ *tree_entry = (tf->current < tf->n_entries) ?
+ tf->entries[tf->current]->te : NULL;
+ }
+
+ return 0;
+}
+
+int git_iterator_current_parent_tree(
+ const git_tree **tree_ptr,
+ git_iterator *iter,
+ const char *parent_path)
+{
+ tree_iterator *ti = (tree_iterator *)iter;
+ tree_iterator_frame *tf;
+ const char *scan = parent_path;
+ const git_tree_entry *te;
+
+ *tree_ptr = NULL;
+
+ if (iter->type != GIT_ITERATOR_TYPE_TREE)
+ return 0;
+
+ for (tf = ti->root; *scan; ) {
+ if (!(tf = tf->down) ||
+ tf->current >= tf->n_entries ||
+ !(te = tf->entries[tf->current]->te) ||
+ ti->strncomp(scan, te->filename, te->filename_len) != 0)
+ return 0;
+
+ scan += te->filename_len;
+ if (*scan == '/')
+ scan++;
+ }
+
+ *tree_ptr = tf->entries[tf->current]->tree;
+ return 0;
+}
+
+static void workdir_iterator_update_is_ignored(workdir_iterator *wi)
+{
+ if (git_ignore__lookup(
+ &wi->is_ignored, &wi->ignores, wi->fi.entry.path) < 0) {
+ giterr_clear();
+ wi->is_ignored = GIT_IGNORE_NOTFOUND;
+ }
+
+ /* use ignore from containing frame stack */
+ if (wi->is_ignored <= GIT_IGNORE_NOTFOUND)
+ wi->is_ignored = wi->fi.stack->is_ignored;
+}
+
+bool git_iterator_current_is_ignored(git_iterator *iter)
+{
+ workdir_iterator *wi = (workdir_iterator *)iter;
+
+ if (iter->type != GIT_ITERATOR_TYPE_WORKDIR)
+ return false;
+
+ if (wi->is_ignored != GIT_IGNORE_UNCHECKED)
+ return (bool)(wi->is_ignored == GIT_IGNORE_TRUE);
+
+ workdir_iterator_update_is_ignored(wi);
+
+ return (bool)(wi->is_ignored == GIT_IGNORE_TRUE);
+}
+
+bool git_iterator_current_tree_is_ignored(git_iterator *iter)
+{
+ workdir_iterator *wi = (workdir_iterator *)iter;
+
+ if (iter->type != GIT_ITERATOR_TYPE_WORKDIR)
+ return false;
+
+ return (bool)(wi->fi.stack->is_ignored == GIT_IGNORE_TRUE);
+}
+
+int git_iterator_cmp(git_iterator *iter, const char *path_prefix)
+{
+ const git_index_entry *entry;
+
+ /* a "done" iterator is after every prefix */
+ if (git_iterator_current(&entry, iter) < 0 || entry == NULL)
+ return 1;
+
+ /* a NULL prefix is after any valid iterator */
+ if (!path_prefix)
+ return -1;
+
+ return iter->prefixcomp(entry->path, path_prefix);
+}
+
+int git_iterator_current_workdir_path(git_buf **path, git_iterator *iter)
+{
+ workdir_iterator *wi = (workdir_iterator *)iter;
+
+ if (iter->type != GIT_ITERATOR_TYPE_WORKDIR || !wi->fi.entry.path)
+ *path = NULL;
+ else
+ *path = &wi->fi.path;
+
+ return 0;
+}
+
+int git_iterator_advance_over_with_status(
+ const git_index_entry **entryptr,
+ git_iterator_status_t *status,
+ git_iterator *iter)
+{
+ int error = 0;
+ workdir_iterator *wi = (workdir_iterator *)iter;
+ char *base = NULL;
+ const git_index_entry *entry;
+
+ *status = GIT_ITERATOR_STATUS_NORMAL;
+
+ if (iter->type != GIT_ITERATOR_TYPE_WORKDIR)
+ return git_iterator_advance(entryptr, iter);
+ if ((error = git_iterator_current(&entry, iter)) < 0)
+ return error;
+
+ if (!S_ISDIR(entry->mode)) {
+ workdir_iterator_update_is_ignored(wi);
+ if (wi->is_ignored == GIT_IGNORE_TRUE)
+ *status = GIT_ITERATOR_STATUS_IGNORED;
+ return git_iterator_advance(entryptr, iter);
+ }
+
+ *status = GIT_ITERATOR_STATUS_EMPTY;
+
+ base = git__strdup(entry->path);
+ GITERR_CHECK_ALLOC(base);
+
+ /* scan inside directory looking for a non-ignored item */
+ while (entry && !iter->prefixcomp(entry->path, base)) {
+ workdir_iterator_update_is_ignored(wi);
+
+ /* if we found an explicitly ignored item, then update from
+ * EMPTY to IGNORED
+ */
+ if (wi->is_ignored == GIT_IGNORE_TRUE)
+ *status = GIT_ITERATOR_STATUS_IGNORED;
+ else if (S_ISDIR(entry->mode)) {
+ error = git_iterator_advance_into(&entry, iter);
+
+ if (!error)
+ continue;
+ else if (error == GIT_ENOTFOUND) {
+ error = 0;
+ wi->is_ignored = GIT_IGNORE_TRUE; /* mark empty dirs ignored */
+ } else
+ break; /* real error, stop here */
+ } else {
+ /* we found a non-ignored item, treat parent as untracked */
+ *status = GIT_ITERATOR_STATUS_NORMAL;
+ break;
+ }
+
+ if ((error = git_iterator_advance(&entry, iter)) < 0)
+ break;
+ }
+
+ /* wrap up scan back to base directory */
+ while (entry && !iter->prefixcomp(entry->path, base))
+ if ((error = git_iterator_advance(&entry, iter)) < 0)
+ break;
+
+ *entryptr = entry;
+ git__free(base);
+
+ return error;
+}
+
diff --git a/src/iterator.h b/src/iterator.h
new file mode 100644
index 000000000..d88ad5191
--- /dev/null
+++ b/src/iterator.h
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_iterator_h__
+#define INCLUDE_iterator_h__
+
+#include "common.h"
+#include "git2/index.h"
+#include "vector.h"
+#include "buffer.h"
+
+typedef struct git_iterator git_iterator;
+
+typedef enum {
+ GIT_ITERATOR_TYPE_EMPTY = 0,
+ GIT_ITERATOR_TYPE_TREE = 1,
+ GIT_ITERATOR_TYPE_INDEX = 2,
+ GIT_ITERATOR_TYPE_WORKDIR = 3,
+ GIT_ITERATOR_TYPE_FS = 4,
+} git_iterator_type_t;
+
+typedef enum {
+ /** ignore case for entry sort order */
+ GIT_ITERATOR_IGNORE_CASE = (1u << 0),
+ /** force case sensitivity for entry sort order */
+ GIT_ITERATOR_DONT_IGNORE_CASE = (1u << 1),
+ /** return tree items in addition to blob items */
+ GIT_ITERATOR_INCLUDE_TREES = (1u << 2),
+ /** don't flatten trees, requiring advance_into (implies INCLUDE_TREES) */
+ GIT_ITERATOR_DONT_AUTOEXPAND = (1u << 3),
+ /** convert precomposed unicode to decomposed unicode */
+ GIT_ITERATOR_PRECOMPOSE_UNICODE = (1u << 4),
+} git_iterator_flag_t;
+
+typedef struct {
+ int (*current)(const git_index_entry **, git_iterator *);
+ int (*advance)(const git_index_entry **, git_iterator *);
+ int (*advance_into)(const git_index_entry **, git_iterator *);
+ int (*seek)(git_iterator *, const char *prefix);
+ int (*reset)(git_iterator *, const char *start, const char *end);
+ int (*at_end)(git_iterator *);
+ void (*free)(git_iterator *);
+} git_iterator_callbacks;
+
+struct git_iterator {
+ git_iterator_type_t type;
+ git_iterator_callbacks *cb;
+ git_repository *repo;
+ char *start;
+ char *end;
+ int (*prefixcomp)(const char *str, const char *prefix);
+ size_t stat_calls;
+ unsigned int flags;
+};
+
+extern int git_iterator_for_nothing(
+ git_iterator **out,
+ git_iterator_flag_t flags,
+ const char *start,
+ const char *end);
+
+/* tree iterators will match the ignore_case value from the index of the
+ * repository, unless you override with a non-zero flag value
+ */
+extern int git_iterator_for_tree(
+ git_iterator **out,
+ git_tree *tree,
+ git_iterator_flag_t flags,
+ const char *start,
+ const char *end);
+
+/* index iterators will take the ignore_case value from the index; the
+ * ignore_case flags are not used
+ */
+extern int git_iterator_for_index(
+ git_iterator **out,
+ git_index *index,
+ git_iterator_flag_t flags,
+ const char *start,
+ const char *end);
+
+extern int git_iterator_for_workdir_ext(
+ git_iterator **out,
+ git_repository *repo,
+ const char *repo_workdir,
+ git_iterator_flag_t flags,
+ const char *start,
+ const char *end);
+
+/* workdir iterators will match the ignore_case value from the index of the
+ * repository, unless you override with a non-zero flag value
+ */
+GIT_INLINE(int) git_iterator_for_workdir(
+ git_iterator **out,
+ git_repository *repo,
+ git_iterator_flag_t flags,
+ const char *start,
+ const char *end)
+{
+ return git_iterator_for_workdir_ext(out, repo, NULL, flags, start, end);
+}
+
+/* for filesystem iterators, you have to explicitly pass in the ignore_case
+ * behavior that you desire
+ */
+extern int git_iterator_for_filesystem(
+ git_iterator **out,
+ const char *root,
+ git_iterator_flag_t flags,
+ const char *start,
+ const char *end);
+
+extern void git_iterator_free(git_iterator *iter);
+
+/* Return a git_index_entry structure for the current value the iterator
+ * is looking at or NULL if the iterator is at the end.
+ *
+ * The entry may noy be fully populated. Tree iterators will only have a
+ * value mode, OID, and path. Workdir iterators will not have an OID (but
+ * you can use `git_iterator_current_oid()` to calculate it on demand).
+ *
+ * You do not need to free the entry. It is still "owned" by the iterator.
+ * Once you call `git_iterator_advance()` then the old entry is no longer
+ * guaranteed to be valid - it may be freed or just overwritten in place.
+ */
+GIT_INLINE(int) git_iterator_current(
+ const git_index_entry **entry, git_iterator *iter)
+{
+ return iter->cb->current(entry, iter);
+}
+
+/**
+ * Advance to the next item for the iterator.
+ *
+ * If GIT_ITERATOR_INCLUDE_TREES is set, this may be a tree item. If
+ * GIT_ITERATOR_DONT_AUTOEXPAND is set, calling this again when on a tree
+ * item will skip over all the items under that tree.
+ */
+GIT_INLINE(int) git_iterator_advance(
+ const git_index_entry **entry, git_iterator *iter)
+{
+ return iter->cb->advance(entry, iter);
+}
+
+/**
+ * Iterate into a tree item (when GIT_ITERATOR_DONT_AUTOEXPAND is set).
+ *
+ * git_iterator_advance() steps through all items being iterated over
+ * (either with or without trees, depending on GIT_ITERATOR_INCLUDE_TREES),
+ * but if GIT_ITERATOR_DONT_AUTOEXPAND is set, it will skip to the next
+ * sibling of a tree instead of going to the first child of the tree. In
+ * that case, use this function to advance to the first child of the tree.
+ *
+ * If the current item is not a tree, this is a no-op.
+ *
+ * For filesystem and working directory iterators, a tree (i.e. directory)
+ * can be empty. In that case, this function returns GIT_ENOTFOUND and
+ * does not advance. That can't happen for tree and index iterators.
+ */
+GIT_INLINE(int) git_iterator_advance_into(
+ const git_index_entry **entry, git_iterator *iter)
+{
+ return iter->cb->advance_into(entry, iter);
+}
+
+/**
+ * Advance into a tree or skip over it if it is empty.
+ *
+ * Because `git_iterator_advance_into` may return GIT_ENOTFOUND if the
+ * directory is empty (only with filesystem and working directory
+ * iterators) and a common response is to just call `git_iterator_advance`
+ * when that happens, this bundles the two into a single simple call.
+ */
+GIT_INLINE(int) git_iterator_advance_into_or_over(
+ const git_index_entry **entry, git_iterator *iter)
+{
+ int error = iter->cb->advance_into(entry, iter);
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = iter->cb->advance(entry, iter);
+ }
+ return error;
+}
+
+/* Seek is currently unimplemented */
+GIT_INLINE(int) git_iterator_seek(
+ git_iterator *iter, const char *prefix)
+{
+ return iter->cb->seek(iter, prefix);
+}
+
+/**
+ * Go back to the start of the iteration.
+ *
+ * This resets the iterator to the start of the iteration. It also allows
+ * you to reset the `start` and `end` pathname boundaries of the iteration
+ * when doing so.
+ */
+GIT_INLINE(int) git_iterator_reset(
+ git_iterator *iter, const char *start, const char *end)
+{
+ return iter->cb->reset(iter, start, end);
+}
+
+/**
+ * Check if the iterator is at the end
+ *
+ * @return 0 if not at end, >0 if at end
+ */
+GIT_INLINE(int) git_iterator_at_end(git_iterator *iter)
+{
+ return iter->cb->at_end(iter);
+}
+
+GIT_INLINE(git_iterator_type_t) git_iterator_type(git_iterator *iter)
+{
+ return iter->type;
+}
+
+GIT_INLINE(git_repository *) git_iterator_owner(git_iterator *iter)
+{
+ return iter->repo;
+}
+
+GIT_INLINE(git_iterator_flag_t) git_iterator_flags(git_iterator *iter)
+{
+ return iter->flags;
+}
+
+GIT_INLINE(bool) git_iterator_ignore_case(git_iterator *iter)
+{
+ return ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0);
+}
+
+extern int git_iterator_set_ignore_case(git_iterator *iter, bool ignore_case);
+
+extern int git_iterator_current_tree_entry(
+ const git_tree_entry **entry_out, git_iterator *iter);
+
+extern int git_iterator_current_parent_tree(
+ const git_tree **tree_out, git_iterator *iter, const char *parent_path);
+
+extern bool git_iterator_current_is_ignored(git_iterator *iter);
+
+extern bool git_iterator_current_tree_is_ignored(git_iterator *iter);
+
+extern int git_iterator_cmp(
+ git_iterator *iter, const char *path_prefix);
+
+/**
+ * Get full path of the current item from a workdir iterator. This will
+ * return NULL for a non-workdir iterator. The git_buf is still owned by
+ * the iterator; this is exposed just for efficiency.
+ */
+extern int git_iterator_current_workdir_path(
+ git_buf **path, git_iterator *iter);
+
+/* Return index pointer if index iterator, else NULL */
+extern git_index *git_iterator_get_index(git_iterator *iter);
+
+typedef enum {
+ GIT_ITERATOR_STATUS_NORMAL = 0,
+ GIT_ITERATOR_STATUS_IGNORED = 1,
+ GIT_ITERATOR_STATUS_EMPTY = 2
+} git_iterator_status_t;
+
+/* Advance over a directory and check if it contains no files or just
+ * ignored files.
+ *
+ * In a tree or the index, all directories will contain files, but in the
+ * working directory it is possible to have an empty directory tree or a
+ * tree that only contains ignored files. Many Git operations treat these
+ * cases specially. This advances over a directory (presumably an
+ * untracked directory) but checks during the scan if there are any files
+ * and any non-ignored files.
+ */
+extern int git_iterator_advance_over_with_status(
+ const git_index_entry **entry, git_iterator_status_t *status, git_iterator *iter);
+
+#endif
diff --git a/src/khash.h b/src/khash.h
new file mode 100644
index 000000000..242204464
--- /dev/null
+++ b/src/khash.h
@@ -0,0 +1,610 @@
+/* The MIT License
+
+ Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk>
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+
+/*
+ An example:
+
+#include "khash.h"
+KHASH_MAP_INIT_INT(32, char)
+int main() {
+ int ret, is_missing;
+ khiter_t k;
+ khash_t(32) *h = kh_init(32);
+ k = kh_put(32, h, 5, &ret);
+ kh_value(h, k) = 10;
+ k = kh_get(32, h, 10);
+ is_missing = (k == kh_end(h));
+ k = kh_get(32, h, 5);
+ kh_del(32, h, k);
+ for (k = kh_begin(h); k != kh_end(h); ++k)
+ if (kh_exist(h, k)) kh_value(h, k) = 1;
+ kh_destroy(32, h);
+ return 0;
+}
+*/
+
+/*
+ 2011-12-29 (0.2.7):
+
+ * Minor code clean up; no actual effect.
+
+ 2011-09-16 (0.2.6):
+
+ * The capacity is a power of 2. This seems to dramatically improve the
+ speed for simple keys. Thank Zilong Tan for the suggestion. Reference:
+
+ - http://code.google.com/p/ulib/
+ - http://nothings.org/computer/judy/
+
+ * Allow to optionally use linear probing which usually has better
+ performance for random input. Double hashing is still the default as it
+ is more robust to certain non-random input.
+
+ * Added Wang's integer hash function (not used by default). This hash
+ function is more robust to certain non-random input.
+
+ 2011-02-14 (0.2.5):
+
+ * Allow to declare global functions.
+
+ 2009-09-26 (0.2.4):
+
+ * Improve portability
+
+ 2008-09-19 (0.2.3):
+
+ * Corrected the example
+ * Improved interfaces
+
+ 2008-09-11 (0.2.2):
+
+ * Improved speed a little in kh_put()
+
+ 2008-09-10 (0.2.1):
+
+ * Added kh_clear()
+ * Fixed a compiling error
+
+ 2008-09-02 (0.2.0):
+
+ * Changed to token concatenation which increases flexibility.
+
+ 2008-08-31 (0.1.2):
+
+ * Fixed a bug in kh_get(), which has not been tested previously.
+
+ 2008-08-31 (0.1.1):
+
+ * Added destructor
+*/
+
+
+#ifndef __AC_KHASH_H
+#define __AC_KHASH_H
+
+/*!
+ @header
+
+ Generic hash table library.
+ */
+
+#define AC_VERSION_KHASH_H "0.2.6"
+
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+/* compipler specific configuration */
+
+#if UINT_MAX == 0xffffffffu
+typedef unsigned int khint32_t;
+#elif ULONG_MAX == 0xffffffffu
+typedef unsigned long khint32_t;
+#endif
+
+#if ULONG_MAX == ULLONG_MAX
+typedef unsigned long khint64_t;
+#else
+typedef unsigned long long khint64_t;
+#endif
+
+#ifdef _MSC_VER
+#define kh_inline __inline
+#else
+#define kh_inline inline
+#endif
+
+typedef khint32_t khint_t;
+typedef khint_t khiter_t;
+
+#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2)
+#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1)
+#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3)
+#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1)))
+#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1)))
+#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1)))
+#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1))
+
+#ifdef KHASH_LINEAR
+#define __ac_inc(k, m) 1
+#else
+#define __ac_inc(k, m) (((k)>>3 ^ (k)<<3) | 1) & (m)
+#endif
+
+#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4)
+
+#ifndef kroundup32
+#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))
+#endif
+
+#ifndef kcalloc
+#define kcalloc(N,Z) calloc(N,Z)
+#endif
+#ifndef kmalloc
+#define kmalloc(Z) malloc(Z)
+#endif
+#ifndef krealloc
+#define krealloc(P,Z) realloc(P,Z)
+#endif
+#ifndef kfree
+#define kfree(P) free(P)
+#endif
+
+static const double __ac_HASH_UPPER = 0.77;
+
+#define __KHASH_TYPE(name, khkey_t, khval_t) \
+ typedef struct { \
+ khint_t n_buckets, size, n_occupied, upper_bound; \
+ khint32_t *flags; \
+ khkey_t *keys; \
+ khval_t *vals; \
+ } kh_##name##_t;
+
+#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \
+ extern kh_##name##_t *kh_init_##name(void); \
+ extern void kh_destroy_##name(kh_##name##_t *h); \
+ extern void kh_clear_##name(kh_##name##_t *h); \
+ extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \
+ extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \
+ extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \
+ extern void kh_del_##name(kh_##name##_t *h, khint_t x);
+
+#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
+ SCOPE kh_##name##_t *kh_init_##name(void) { \
+ return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \
+ } \
+ SCOPE void kh_destroy_##name(kh_##name##_t *h) \
+ { \
+ if (h) { \
+ kfree((void *)h->keys); kfree(h->flags); \
+ kfree((void *)h->vals); \
+ kfree(h); \
+ } \
+ } \
+ SCOPE void kh_clear_##name(kh_##name##_t *h) \
+ { \
+ if (h && h->flags) { \
+ memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \
+ h->size = h->n_occupied = 0; \
+ } \
+ } \
+ SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
+ { \
+ if (h->n_buckets) { \
+ khint_t inc, k, i, last, mask; \
+ mask = h->n_buckets - 1; \
+ k = __hash_func(key); i = k & mask; \
+ inc = __ac_inc(k, mask); last = i; /* inc==1 for linear probing */ \
+ while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
+ i = (i + inc) & mask; \
+ if (i == last) return h->n_buckets; \
+ } \
+ return __ac_iseither(h->flags, i)? h->n_buckets : i; \
+ } else return 0; \
+ } \
+ SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
+ { /* This function uses 0.25*n_bucktes bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \
+ khint32_t *new_flags = 0; \
+ khint_t j = 1; \
+ { \
+ kroundup32(new_n_buckets); \
+ if (new_n_buckets < 4) new_n_buckets = 4; \
+ if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \
+ else { /* hash table size to be changed (shrink or expand); rehash */ \
+ new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
+ if (!new_flags) return -1; \
+ memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
+ if (h->n_buckets < new_n_buckets) { /* expand */ \
+ khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
+ if (!new_keys) return -1; \
+ h->keys = new_keys; \
+ if (kh_is_map) { \
+ khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
+ if (!new_vals) return -1; \
+ h->vals = new_vals; \
+ } \
+ } /* otherwise shrink */ \
+ } \
+ } \
+ if (j) { /* rehashing is needed */ \
+ for (j = 0; j != h->n_buckets; ++j) { \
+ if (__ac_iseither(h->flags, j) == 0) { \
+ khkey_t key = h->keys[j]; \
+ khval_t val; \
+ khint_t new_mask; \
+ new_mask = new_n_buckets - 1; \
+ if (kh_is_map) val = h->vals[j]; \
+ __ac_set_isdel_true(h->flags, j); \
+ while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \
+ khint_t inc, k, i; \
+ k = __hash_func(key); \
+ i = k & new_mask; \
+ inc = __ac_inc(k, new_mask); \
+ while (!__ac_isempty(new_flags, i)) i = (i + inc) & new_mask; \
+ __ac_set_isempty_false(new_flags, i); \
+ if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \
+ { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \
+ if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \
+ __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \
+ } else { /* write the element and jump out of the loop */ \
+ h->keys[i] = key; \
+ if (kh_is_map) h->vals[i] = val; \
+ break; \
+ } \
+ } \
+ } \
+ } \
+ if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \
+ h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
+ if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
+ } \
+ kfree(h->flags); /* free the working space */ \
+ h->flags = new_flags; \
+ h->n_buckets = new_n_buckets; \
+ h->n_occupied = h->size; \
+ h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \
+ } \
+ return 0; \
+ } \
+ SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
+ { \
+ khint_t x; \
+ if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \
+ if (h->n_buckets > (h->size<<1)) { \
+ if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \
+ *ret = -1; return h->n_buckets; \
+ } \
+ } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \
+ *ret = -1; return h->n_buckets; \
+ } \
+ } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \
+ { \
+ khint_t inc, k, i, site, last, mask = h->n_buckets - 1; \
+ x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \
+ if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \
+ else { \
+ inc = __ac_inc(k, mask); last = i; \
+ while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
+ if (__ac_isdel(h->flags, i)) site = i; \
+ i = (i + inc) & mask; \
+ if (i == last) { x = site; break; } \
+ } \
+ if (x == h->n_buckets) { \
+ if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \
+ else x = i; \
+ } \
+ } \
+ } \
+ if (__ac_isempty(h->flags, x)) { /* not present at all */ \
+ h->keys[x] = key; \
+ __ac_set_isboth_false(h->flags, x); \
+ ++h->size; ++h->n_occupied; \
+ *ret = 1; \
+ } else if (__ac_isdel(h->flags, x)) { /* deleted */ \
+ h->keys[x] = key; \
+ __ac_set_isboth_false(h->flags, x); \
+ ++h->size; \
+ *ret = 2; \
+ } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \
+ return x; \
+ } \
+ SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \
+ { \
+ if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \
+ __ac_set_isdel_true(h->flags, x); \
+ --h->size; \
+ } \
+ }
+
+#define KHASH_DECLARE(name, khkey_t, khval_t) \
+ __KHASH_TYPE(name, khkey_t, khval_t) \
+ __KHASH_PROTOTYPES(name, khkey_t, khval_t)
+
+#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
+ __KHASH_TYPE(name, khkey_t, khval_t) \
+ __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
+
+#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
+ KHASH_INIT2(name, static kh_inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
+
+/* --- BEGIN OF HASH FUNCTIONS --- */
+
+/*! @function
+ @abstract Integer hash function
+ @param key The integer [khint32_t]
+ @return The hash value [khint_t]
+ */
+#define kh_int_hash_func(key) (khint32_t)(key)
+/*! @function
+ @abstract Integer comparison function
+ */
+#define kh_int_hash_equal(a, b) ((a) == (b))
+/*! @function
+ @abstract 64-bit integer hash function
+ @param key The integer [khint64_t]
+ @return The hash value [khint_t]
+ */
+#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11)
+/*! @function
+ @abstract 64-bit integer comparison function
+ */
+#define kh_int64_hash_equal(a, b) ((a) == (b))
+/*! @function
+ @abstract const char* hash function
+ @param s Pointer to a null terminated string
+ @return The hash value
+ */
+static kh_inline khint_t __ac_X31_hash_string(const char *s)
+{
+ khint_t h = (khint_t)*s;
+ if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s;
+ return h;
+}
+/*! @function
+ @abstract Another interface to const char* hash function
+ @param key Pointer to a null terminated string [const char*]
+ @return The hash value [khint_t]
+ */
+#define kh_str_hash_func(key) __ac_X31_hash_string(key)
+/*! @function
+ @abstract Const char* comparison function
+ */
+#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0)
+
+static kh_inline khint_t __ac_Wang_hash(khint_t key)
+{
+ key += ~(key << 15);
+ key ^= (key >> 10);
+ key += (key << 3);
+ key ^= (key >> 6);
+ key += ~(key << 11);
+ key ^= (key >> 16);
+ return key;
+}
+#define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key)
+
+/* --- END OF HASH FUNCTIONS --- */
+
+/* Other convenient macros... */
+
+/*!
+ @abstract Type of the hash table.
+ @param name Name of the hash table [symbol]
+ */
+#define khash_t(name) kh_##name##_t
+
+/*! @function
+ @abstract Initiate a hash table.
+ @param name Name of the hash table [symbol]
+ @return Pointer to the hash table [khash_t(name)*]
+ */
+#define kh_init(name) kh_init_##name()
+
+/*! @function
+ @abstract Destroy a hash table.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ */
+#define kh_destroy(name, h) kh_destroy_##name(h)
+
+/*! @function
+ @abstract Reset a hash table without deallocating memory.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ */
+#define kh_clear(name, h) kh_clear_##name(h)
+
+/*! @function
+ @abstract Resize a hash table.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param s New size [khint_t]
+ */
+#define kh_resize(name, h, s) kh_resize_##name(h, s)
+
+/*! @function
+ @abstract Insert a key to the hash table.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param k Key [type of keys]
+ @param r Extra return code: 0 if the key is present in the hash table;
+ 1 if the bucket is empty (never used); 2 if the element in
+ the bucket has been deleted [int*]
+ @return Iterator to the inserted element [khint_t]
+ */
+#define kh_put(name, h, k, r) kh_put_##name(h, k, r)
+
+/*! @function
+ @abstract Retrieve a key from the hash table.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param k Key [type of keys]
+ @return Iterator to the found element, or kh_end(h) is the element is absent [khint_t]
+ */
+#define kh_get(name, h, k) kh_get_##name(h, k)
+
+/*! @function
+ @abstract Remove a key from the hash table.
+ @param name Name of the hash table [symbol]
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param k Iterator to the element to be deleted [khint_t]
+ */
+#define kh_del(name, h, k) kh_del_##name(h, k)
+
+/*! @function
+ @abstract Test whether a bucket contains data.
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param x Iterator to the bucket [khint_t]
+ @return 1 if containing data; 0 otherwise [int]
+ */
+#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x)))
+
+/*! @function
+ @abstract Get key given an iterator
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param x Iterator to the bucket [khint_t]
+ @return Key [type of keys]
+ */
+#define kh_key(h, x) ((h)->keys[x])
+
+/*! @function
+ @abstract Get value given an iterator
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param x Iterator to the bucket [khint_t]
+ @return Value [type of values]
+ @discussion For hash sets, calling this results in segfault.
+ */
+#define kh_val(h, x) ((h)->vals[x])
+
+/*! @function
+ @abstract Alias of kh_val()
+ */
+#define kh_value(h, x) ((h)->vals[x])
+
+/*! @function
+ @abstract Get the start iterator
+ @param h Pointer to the hash table [khash_t(name)*]
+ @return The start iterator [khint_t]
+ */
+#define kh_begin(h) (khint_t)(0)
+
+/*! @function
+ @abstract Get the end iterator
+ @param h Pointer to the hash table [khash_t(name)*]
+ @return The end iterator [khint_t]
+ */
+#define kh_end(h) ((h)->n_buckets)
+
+/*! @function
+ @abstract Get the number of elements in the hash table
+ @param h Pointer to the hash table [khash_t(name)*]
+ @return Number of elements in the hash table [khint_t]
+ */
+#define kh_size(h) ((h)->size)
+
+/*! @function
+ @abstract Get the number of buckets in the hash table
+ @param h Pointer to the hash table [khash_t(name)*]
+ @return Number of buckets in the hash table [khint_t]
+ */
+#define kh_n_buckets(h) ((h)->n_buckets)
+
+/*! @function
+ @abstract Iterate over the entries in the hash table
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param kvar Variable to which key will be assigned
+ @param vvar Variable to which value will be assigned
+ @param code Block of code to execute
+ */
+#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \
+ for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
+ if (!kh_exist(h,__i)) continue; \
+ (kvar) = kh_key(h,__i); \
+ (vvar) = kh_val(h,__i); \
+ code; \
+ } }
+
+/*! @function
+ @abstract Iterate over the values in the hash table
+ @param h Pointer to the hash table [khash_t(name)*]
+ @param vvar Variable to which value will be assigned
+ @param code Block of code to execute
+ */
+#define kh_foreach_value(h, vvar, code) { khint_t __i; \
+ for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
+ if (!kh_exist(h,__i)) continue; \
+ (vvar) = kh_val(h,__i); \
+ code; \
+ } }
+
+/* More conenient interfaces */
+
+/*! @function
+ @abstract Instantiate a hash set containing integer keys
+ @param name Name of the hash table [symbol]
+ */
+#define KHASH_SET_INIT_INT(name) \
+ KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal)
+
+/*! @function
+ @abstract Instantiate a hash map containing integer keys
+ @param name Name of the hash table [symbol]
+ @param khval_t Type of values [type]
+ */
+#define KHASH_MAP_INIT_INT(name, khval_t) \
+ KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal)
+
+/*! @function
+ @abstract Instantiate a hash map containing 64-bit integer keys
+ @param name Name of the hash table [symbol]
+ */
+#define KHASH_SET_INIT_INT64(name) \
+ KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal)
+
+/*! @function
+ @abstract Instantiate a hash map containing 64-bit integer keys
+ @param name Name of the hash table [symbol]
+ @param khval_t Type of values [type]
+ */
+#define KHASH_MAP_INIT_INT64(name, khval_t) \
+ KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal)
+
+typedef const char *kh_cstr_t;
+/*! @function
+ @abstract Instantiate a hash map containing const char* keys
+ @param name Name of the hash table [symbol]
+ */
+#define KHASH_SET_INIT_STR(name) \
+ KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal)
+
+/*! @function
+ @abstract Instantiate a hash map containing const char* keys
+ @param name Name of the hash table [symbol]
+ @param khval_t Type of values [type]
+ */
+#define KHASH_MAP_INIT_STR(name, khval_t) \
+ KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal)
+
+#endif /* __AC_KHASH_H */
diff --git a/src/map.h b/src/map.h
index 1dfbeeb4b..722eb7a30 100644
--- a/src/map.h
+++ b/src/map.h
@@ -1,3 +1,9 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_map_h__
#define INCLUDE_map_h__
@@ -5,27 +11,37 @@
/* p_mmap() prot values */
-#define GIT_PROT_NONE 0x0
-#define GIT_PROT_READ 0x1
+#define GIT_PROT_NONE 0x0
+#define GIT_PROT_READ 0x1
#define GIT_PROT_WRITE 0x2
-#define GIT_PROT_EXEC 0x4
+#define GIT_PROT_EXEC 0x4
/* git__mmmap() flags values */
-#define GIT_MAP_FILE 0
-#define GIT_MAP_SHARED 1
+#define GIT_MAP_FILE 0
+#define GIT_MAP_SHARED 1
#define GIT_MAP_PRIVATE 2
-#define GIT_MAP_TYPE 0xf
-#define GIT_MAP_FIXED 0x10
+#define GIT_MAP_TYPE 0xf
+#define GIT_MAP_FIXED 0x10
-typedef struct { /* memory mapped buffer */
- void *data; /* data bytes */
- size_t len; /* data length */
+#ifdef __amigaos4__
+#define MAP_FAILED 0
+#endif
+
+typedef struct { /* memory mapped buffer */
+ void *data; /* data bytes */
+ size_t len; /* data length */
#ifdef GIT_WIN32
- HANDLE fmh; /* file mapping handle */
+ HANDLE fmh; /* file mapping handle */
#endif
} git_map;
+#define GIT_MMAP_VALIDATE(out, len, prot, flags) do { \
+ assert(out != NULL && len > 0); \
+ assert((prot & GIT_PROT_WRITE) || (prot & GIT_PROT_READ)); \
+ assert((flags & GIT_MAP_FIXED) == 0); } while (0)
+
extern int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset);
extern int p_munmap(git_map *map);
+extern long git__page_size(void);
#endif /* INCLUDE_map_h__ */
diff --git a/src/merge.c b/src/merge.c
new file mode 100644
index 000000000..a279d31d4
--- /dev/null
+++ b/src/merge.c
@@ -0,0 +1,2863 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "posix.h"
+#include "buffer.h"
+#include "repository.h"
+#include "revwalk.h"
+#include "commit_list.h"
+#include "merge.h"
+#include "path.h"
+#include "refs.h"
+#include "object.h"
+#include "iterator.h"
+#include "refs.h"
+#include "diff.h"
+#include "checkout.h"
+#include "tree.h"
+#include "merge_file.h"
+#include "blob.h"
+#include "hashsig.h"
+#include "oid.h"
+#include "index.h"
+#include "filebuf.h"
+#include "config.h"
+
+#include "git2/types.h"
+#include "git2/repository.h"
+#include "git2/object.h"
+#include "git2/commit.h"
+#include "git2/merge.h"
+#include "git2/refs.h"
+#include "git2/reset.h"
+#include "git2/checkout.h"
+#include "git2/signature.h"
+#include "git2/config.h"
+#include "git2/tree.h"
+#include "git2/sys/index.h"
+
+#define GIT_MERGE_INDEX_ENTRY_EXISTS(X) ((X).mode != 0)
+#define GIT_MERGE_INDEX_ENTRY_ISFILE(X) S_ISREG((X).mode)
+
+typedef enum {
+ TREE_IDX_ANCESTOR = 0,
+ TREE_IDX_OURS = 1,
+ TREE_IDX_THEIRS = 2
+} merge_tree_index_t;
+
+/* Tracks D/F conflicts */
+struct merge_diff_df_data {
+ const char *df_path;
+ const char *prev_path;
+ git_merge_diff *prev_conflict;
+};
+
+
+/* Merge base computation */
+
+int git_merge_base_many(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[])
+{
+ git_revwalk *walk;
+ git_vector list;
+ git_commit_list *result = NULL;
+ int error = -1;
+ unsigned int i;
+ git_commit_list_node *commit;
+
+ assert(out && repo && input_array);
+
+ if (length < 2) {
+ giterr_set(GITERR_INVALID, "At least two commits are required to find an ancestor. Provided 'length' was %u.", length);
+ return -1;
+ }
+
+ if (git_vector_init(&list, length - 1, NULL) < 0)
+ return -1;
+
+ if (git_revwalk_new(&walk, repo) < 0)
+ goto cleanup;
+
+ for (i = 1; i < length; i++) {
+ commit = git_revwalk__commit_lookup(walk, &input_array[i]);
+ if (commit == NULL)
+ goto cleanup;
+
+ git_vector_insert(&list, commit);
+ }
+
+ commit = git_revwalk__commit_lookup(walk, &input_array[0]);
+ if (commit == NULL)
+ goto cleanup;
+
+ if (git_merge__bases_many(&result, walk, commit, &list) < 0)
+ goto cleanup;
+
+ if (!result) {
+ giterr_set(GITERR_MERGE, "No merge base found");
+ error = GIT_ENOTFOUND;
+ goto cleanup;
+ }
+
+ git_oid_cpy(out, &result->item->oid);
+
+ error = 0;
+
+cleanup:
+ git_commit_list_free(&result);
+ git_revwalk_free(walk);
+ git_vector_free(&list);
+ return error;
+}
+
+int git_merge_base_octopus(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[])
+{
+ git_oid result;
+ unsigned int i;
+ int error = -1;
+
+ assert(out && repo && input_array);
+
+ if (length < 2) {
+ giterr_set(GITERR_INVALID, "At least two commits are required to find an ancestor. Provided 'length' was %u.", length);
+ return -1;
+ }
+
+ result = input_array[0];
+ for (i = 1; i < length; i++) {
+ error = git_merge_base(&result, repo, &result, &input_array[i]);
+ if (error < 0)
+ return error;
+ }
+
+ *out = result;
+
+ return 0;
+}
+
+int git_merge_base(git_oid *out, git_repository *repo, const git_oid *one, const git_oid *two)
+{
+ git_revwalk *walk;
+ git_vector list;
+ git_commit_list *result = NULL;
+ git_commit_list_node *commit;
+ void *contents[1];
+
+ if (git_revwalk_new(&walk, repo) < 0)
+ return -1;
+
+ commit = git_revwalk__commit_lookup(walk, two);
+ if (commit == NULL)
+ goto on_error;
+
+ /* This is just one value, so we can do it on the stack */
+ memset(&list, 0x0, sizeof(git_vector));
+ contents[0] = commit;
+ list.length = 1;
+ list.contents = contents;
+
+ commit = git_revwalk__commit_lookup(walk, one);
+ if (commit == NULL)
+ goto on_error;
+
+ if (git_merge__bases_many(&result, walk, commit, &list) < 0)
+ goto on_error;
+
+ if (!result) {
+ git_revwalk_free(walk);
+ giterr_set(GITERR_MERGE, "No merge base found");
+ return GIT_ENOTFOUND;
+ }
+
+ git_oid_cpy(out, &result->item->oid);
+ git_commit_list_free(&result);
+ git_revwalk_free(walk);
+
+ return 0;
+
+on_error:
+ git_revwalk_free(walk);
+ return -1;
+}
+
+static int interesting(git_pqueue *list)
+{
+ size_t i;
+
+ for (i = 0; i < git_pqueue_size(list); i++) {
+ git_commit_list_node *commit = git_pqueue_get(list, i);
+ if ((commit->flags & STALE) == 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+int git_merge__bases_many(git_commit_list **out, git_revwalk *walk, git_commit_list_node *one, git_vector *twos)
+{
+ int error;
+ unsigned int i;
+ git_commit_list_node *two;
+ git_commit_list *result = NULL, *tmp = NULL;
+ git_pqueue list;
+
+ /* If there's only the one commit, there can be no merge bases */
+ if (twos->length == 0) {
+ *out = NULL;
+ return 0;
+ }
+
+ /* if the commit is repeated, we have a our merge base already */
+ git_vector_foreach(twos, i, two) {
+ if (one == two)
+ return git_commit_list_insert(one, out) ? 0 : -1;
+ }
+
+ if (git_pqueue_init(&list, 0, twos->length * 2, git_commit_list_time_cmp) < 0)
+ return -1;
+
+ if (git_commit_list_parse(walk, one) < 0)
+ return -1;
+
+ one->flags |= PARENT1;
+ if (git_pqueue_insert(&list, one) < 0)
+ return -1;
+
+ git_vector_foreach(twos, i, two) {
+ git_commit_list_parse(walk, two);
+ two->flags |= PARENT2;
+ if (git_pqueue_insert(&list, two) < 0)
+ return -1;
+ }
+
+ /* as long as there are non-STALE commits */
+ while (interesting(&list)) {
+ git_commit_list_node *commit = git_pqueue_pop(&list);
+ int flags;
+
+ if (commit == NULL)
+ break;
+
+ flags = commit->flags & (PARENT1 | PARENT2 | STALE);
+ if (flags == (PARENT1 | PARENT2)) {
+ if (!(commit->flags & RESULT)) {
+ commit->flags |= RESULT;
+ if (git_commit_list_insert(commit, &result) == NULL)
+ return -1;
+ }
+ /* we mark the parents of a merge stale */
+ flags |= STALE;
+ }
+
+ for (i = 0; i < commit->out_degree; i++) {
+ git_commit_list_node *p = commit->parents[i];
+ if ((p->flags & flags) == flags)
+ continue;
+
+ if ((error = git_commit_list_parse(walk, p)) < 0)
+ return error;
+
+ p->flags |= flags;
+ if (git_pqueue_insert(&list, p) < 0)
+ return -1;
+ }
+ }
+
+ git_pqueue_free(&list);
+
+ /* filter out any stale commits in the results */
+ tmp = result;
+ result = NULL;
+
+ while (tmp) {
+ struct git_commit_list *next = tmp->next;
+ if (!(tmp->item->flags & STALE))
+ if (git_commit_list_insert_by_date(tmp->item, &result) == NULL)
+ return -1;
+
+ git__free(tmp);
+ tmp = next;
+ }
+
+ *out = result;
+ return 0;
+}
+
+int git_repository_mergehead_foreach(
+ git_repository *repo,
+ git_repository_mergehead_foreach_cb cb,
+ void *payload)
+{
+ git_buf merge_head_path = GIT_BUF_INIT, merge_head_file = GIT_BUF_INIT;
+ char *buffer, *line;
+ size_t line_num = 1;
+ git_oid oid;
+ int error = 0;
+
+ assert(repo && cb);
+
+ if ((error = git_buf_joinpath(&merge_head_path, repo->path_repository,
+ GIT_MERGE_HEAD_FILE)) < 0)
+ return error;
+
+ if ((error = git_futils_readbuffer(&merge_head_file,
+ git_buf_cstr(&merge_head_path))) < 0)
+ goto cleanup;
+
+ buffer = merge_head_file.ptr;
+
+ while ((line = git__strsep(&buffer, "\n")) != NULL) {
+ if (strlen(line) != GIT_OID_HEXSZ) {
+ giterr_set(GITERR_INVALID, "Unable to parse OID - invalid length");
+ error = -1;
+ goto cleanup;
+ }
+
+ if ((error = git_oid_fromstr(&oid, line)) < 0)
+ goto cleanup;
+
+ if ((error = cb(&oid, payload)) != 0) {
+ giterr_set_after_callback(error);
+ goto cleanup;
+ }
+
+ ++line_num;
+ }
+
+ if (*buffer) {
+ giterr_set(GITERR_MERGE, "No EOL at line %d", line_num);
+ error = -1;
+ goto cleanup;
+ }
+
+cleanup:
+ git_buf_free(&merge_head_path);
+ git_buf_free(&merge_head_file);
+
+ return error;
+}
+
+GIT_INLINE(int) index_entry_cmp(const git_index_entry *a, const git_index_entry *b)
+{
+ int value = 0;
+
+ if (a->path == NULL)
+ return (b->path == NULL) ? 0 : 1;
+
+ if ((value = a->mode - b->mode) == 0 &&
+ (value = git_oid__cmp(&a->id, &b->id)) == 0)
+ value = strcmp(a->path, b->path);
+
+ return value;
+}
+
+/* Conflict resolution */
+
+static int merge_conflict_resolve_trivial(
+ int *resolved,
+ git_merge_diff_list *diff_list,
+ const git_merge_diff *conflict)
+{
+ int ours_empty, theirs_empty;
+ int ours_changed, theirs_changed, ours_theirs_differ;
+ git_index_entry const *result = NULL;
+ int error = 0;
+
+ assert(resolved && diff_list && conflict);
+
+ *resolved = 0;
+
+ if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE ||
+ conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED)
+ return 0;
+
+ if (conflict->our_status == GIT_DELTA_RENAMED ||
+ conflict->their_status == GIT_DELTA_RENAMED)
+ return 0;
+
+ ours_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry);
+ theirs_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry);
+
+ ours_changed = (conflict->our_status != GIT_DELTA_UNMODIFIED);
+ theirs_changed = (conflict->their_status != GIT_DELTA_UNMODIFIED);
+ ours_theirs_differ = ours_changed && theirs_changed &&
+ index_entry_cmp(&conflict->our_entry, &conflict->their_entry);
+
+ /*
+ * Note: with only one ancestor, some cases are not distinct:
+ *
+ * 16: ancest:anc1/anc2, head:anc1, remote:anc2 = result:no merge
+ * 3: ancest:(empty)^, head:head, remote:(empty) = result:no merge
+ * 2: ancest:(empty)^, head:(empty), remote:remote = result:no merge
+ *
+ * Note that the two cases that take D/F conflicts into account
+ * specifically do not need to be explicitly tested, as D/F conflicts
+ * would fail the *empty* test:
+ *
+ * 3ALT: ancest:(empty)+, head:head, remote:*empty* = result:head
+ * 2ALT: ancest:(empty)+, head:*empty*, remote:remote = result:remote
+ *
+ * Note that many of these cases need not be explicitly tested, as
+ * they simply degrade to "all different" cases (eg, 11):
+ *
+ * 4: ancest:(empty)^, head:head, remote:remote = result:no merge
+ * 7: ancest:ancest+, head:(empty), remote:remote = result:no merge
+ * 9: ancest:ancest+, head:head, remote:(empty) = result:no merge
+ * 11: ancest:ancest+, head:head, remote:remote = result:no merge
+ */
+
+ /* 5ALT: ancest:*, head:head, remote:head = result:head */
+ if (ours_changed && !ours_empty && !ours_theirs_differ)
+ result = &conflict->our_entry;
+ /* 6: ancest:ancest+, head:(empty), remote:(empty) = result:no merge */
+ else if (ours_changed && ours_empty && theirs_empty)
+ *resolved = 0;
+ /* 8: ancest:ancest^, head:(empty), remote:ancest = result:no merge */
+ else if (ours_empty && !theirs_changed)
+ *resolved = 0;
+ /* 10: ancest:ancest^, head:ancest, remote:(empty) = result:no merge */
+ else if (!ours_changed && theirs_empty)
+ *resolved = 0;
+ /* 13: ancest:ancest+, head:head, remote:ancest = result:head */
+ else if (ours_changed && !theirs_changed)
+ result = &conflict->our_entry;
+ /* 14: ancest:ancest+, head:ancest, remote:remote = result:remote */
+ else if (!ours_changed && theirs_changed)
+ result = &conflict->their_entry;
+ else
+ *resolved = 0;
+
+ if (result != NULL &&
+ GIT_MERGE_INDEX_ENTRY_EXISTS(*result) &&
+ (error = git_vector_insert(&diff_list->staged, (void *)result)) >= 0)
+ *resolved = 1;
+
+ /* Note: trivial resolution does not update the REUC. */
+
+ return error;
+}
+
+static int merge_conflict_resolve_one_removed(
+ int *resolved,
+ git_merge_diff_list *diff_list,
+ const git_merge_diff *conflict)
+{
+ int ours_empty, theirs_empty;
+ int ours_changed, theirs_changed;
+ int error = 0;
+
+ assert(resolved && diff_list && conflict);
+
+ *resolved = 0;
+
+ if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE ||
+ conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED)
+ return 0;
+
+ ours_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry);
+ theirs_empty = !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry);
+
+ ours_changed = (conflict->our_status != GIT_DELTA_UNMODIFIED);
+ theirs_changed = (conflict->their_status != GIT_DELTA_UNMODIFIED);
+
+ /* Removed in both */
+ if (ours_changed && ours_empty && theirs_empty)
+ *resolved = 1;
+ /* Removed in ours */
+ else if (ours_empty && !theirs_changed)
+ *resolved = 1;
+ /* Removed in theirs */
+ else if (!ours_changed && theirs_empty)
+ *resolved = 1;
+
+ if (*resolved)
+ git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict);
+
+ return error;
+}
+
+static int merge_conflict_resolve_one_renamed(
+ int *resolved,
+ git_merge_diff_list *diff_list,
+ const git_merge_diff *conflict)
+{
+ int ours_renamed, theirs_renamed;
+ int ours_changed, theirs_changed;
+ git_index_entry *merged;
+ int error = 0;
+
+ assert(resolved && diff_list && conflict);
+
+ *resolved = 0;
+
+ if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ||
+ !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry))
+ return 0;
+
+ ours_renamed = (conflict->our_status == GIT_DELTA_RENAMED);
+ theirs_renamed = (conflict->their_status == GIT_DELTA_RENAMED);
+
+ if (!ours_renamed && !theirs_renamed)
+ return 0;
+
+ /* Reject one file in a 2->1 conflict */
+ if (conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 ||
+ conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2 ||
+ conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED)
+ return 0;
+
+ ours_changed = (git_oid__cmp(&conflict->ancestor_entry.id, &conflict->our_entry.id) != 0);
+ theirs_changed = (git_oid__cmp(&conflict->ancestor_entry.id, &conflict->their_entry.id) != 0);
+
+ /* if both are modified (and not to a common target) require a merge */
+ if (ours_changed && theirs_changed &&
+ git_oid__cmp(&conflict->our_entry.id, &conflict->their_entry.id) != 0)
+ return 0;
+
+ if ((merged = git_pool_malloc(&diff_list->pool, sizeof(git_index_entry))) == NULL)
+ return -1;
+
+ if (ours_changed)
+ memcpy(merged, &conflict->our_entry, sizeof(git_index_entry));
+ else
+ memcpy(merged, &conflict->their_entry, sizeof(git_index_entry));
+
+ if (ours_renamed)
+ merged->path = conflict->our_entry.path;
+ else
+ merged->path = conflict->their_entry.path;
+
+ *resolved = 1;
+
+ git_vector_insert(&diff_list->staged, merged);
+ git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict);
+
+ return error;
+}
+
+static int merge_conflict_resolve_automerge(
+ int *resolved,
+ git_merge_diff_list *diff_list,
+ const git_merge_diff *conflict,
+ unsigned int merge_file_favor)
+{
+ const git_index_entry *ancestor = NULL, *ours = NULL, *theirs = NULL;
+ git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT;
+ git_merge_file_result result = {0};
+ git_index_entry *index_entry;
+ git_odb *odb = NULL;
+ git_oid automerge_oid;
+ int error = 0;
+
+ assert(resolved && diff_list && conflict);
+
+ *resolved = 0;
+
+ if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ||
+ !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry))
+ return 0;
+
+ /* Reject D/F conflicts */
+ if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE)
+ return 0;
+
+ /* Reject submodules. */
+ if (S_ISGITLINK(conflict->ancestor_entry.mode) ||
+ S_ISGITLINK(conflict->our_entry.mode) ||
+ S_ISGITLINK(conflict->their_entry.mode))
+ return 0;
+
+ /* Reject link/file conflicts. */
+ if ((S_ISLNK(conflict->ancestor_entry.mode) ^ S_ISLNK(conflict->our_entry.mode)) ||
+ (S_ISLNK(conflict->ancestor_entry.mode) ^ S_ISLNK(conflict->their_entry.mode)))
+ return 0;
+
+ /* Reject name conflicts */
+ if (conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 ||
+ conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED)
+ return 0;
+
+ if ((conflict->our_status & GIT_DELTA_RENAMED) == GIT_DELTA_RENAMED &&
+ (conflict->their_status & GIT_DELTA_RENAMED) == GIT_DELTA_RENAMED &&
+ strcmp(conflict->ancestor_entry.path, conflict->their_entry.path) != 0)
+ return 0;
+
+ /* Reject binary conflicts */
+ if (conflict->binary)
+ return 0;
+
+ ancestor = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ?
+ &conflict->ancestor_entry : NULL;
+ ours = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ?
+ &conflict->our_entry : NULL;
+ theirs = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ?
+ &conflict->their_entry : NULL;
+
+ opts.favor = merge_file_favor;
+
+ if ((error = git_repository_odb(&odb, diff_list->repo)) < 0 ||
+ (error = git_merge_file_from_index(&result, diff_list->repo, ancestor, ours, theirs, &opts)) < 0 ||
+ !result.automergeable ||
+ (error = git_odb_write(&automerge_oid, odb, result.ptr, result.len, GIT_OBJ_BLOB)) < 0)
+ goto done;
+
+ if ((index_entry = git_pool_malloc(&diff_list->pool, sizeof(git_index_entry))) == NULL)
+ GITERR_CHECK_ALLOC(index_entry);
+
+ index_entry->path = git_pool_strdup(&diff_list->pool, result.path);
+ GITERR_CHECK_ALLOC(index_entry->path);
+
+ index_entry->file_size = result.len;
+ index_entry->mode = result.mode;
+ git_oid_cpy(&index_entry->id, &automerge_oid);
+
+ git_vector_insert(&diff_list->staged, index_entry);
+ git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict);
+
+ *resolved = 1;
+
+done:
+ git_merge_file_result_free(&result);
+ git_odb_free(odb);
+
+ return error;
+}
+
+static int merge_conflict_resolve(
+ int *out,
+ git_merge_diff_list *diff_list,
+ const git_merge_diff *conflict,
+ unsigned int merge_file_favor)
+{
+ int resolved = 0;
+ int error = 0;
+
+ *out = 0;
+
+ if ((error = merge_conflict_resolve_trivial(&resolved, diff_list, conflict)) < 0)
+ goto done;
+
+ if (!resolved && (error = merge_conflict_resolve_one_removed(&resolved, diff_list, conflict)) < 0)
+ goto done;
+
+ if (!resolved && (error = merge_conflict_resolve_one_renamed(&resolved, diff_list, conflict)) < 0)
+ goto done;
+
+ if (!resolved && (error = merge_conflict_resolve_automerge(&resolved, diff_list, conflict, merge_file_favor)) < 0)
+ goto done;
+
+ *out = resolved;
+
+done:
+ return error;
+}
+
+/* Rename detection and coalescing */
+
+struct merge_diff_similarity {
+ unsigned char similarity;
+ size_t other_idx;
+};
+
+static int index_entry_similarity_exact(
+ git_repository *repo,
+ git_index_entry *a,
+ size_t a_idx,
+ git_index_entry *b,
+ size_t b_idx,
+ void **cache,
+ const git_merge_options *opts)
+{
+ GIT_UNUSED(repo);
+ GIT_UNUSED(a_idx);
+ GIT_UNUSED(b_idx);
+ GIT_UNUSED(cache);
+ GIT_UNUSED(opts);
+
+ if (git_oid__cmp(&a->id, &b->id) == 0)
+ return 100;
+
+ return 0;
+}
+
+static int index_entry_similarity_calc(
+ void **out,
+ git_repository *repo,
+ git_index_entry *entry,
+ const git_merge_options *opts)
+{
+ git_blob *blob;
+ git_diff_file diff_file = {{{0}}};
+ git_off_t blobsize;
+ int error;
+
+ *out = NULL;
+
+ if ((error = git_blob_lookup(&blob, repo, &entry->id)) < 0)
+ return error;
+
+ git_oid_cpy(&diff_file.id, &entry->id);
+ diff_file.path = entry->path;
+ diff_file.size = entry->file_size;
+ diff_file.mode = entry->mode;
+ diff_file.flags = 0;
+
+ blobsize = git_blob_rawsize(blob);
+
+ /* file too big for rename processing */
+ if (!git__is_sizet(blobsize))
+ return 0;
+
+ error = opts->metric->buffer_signature(out, &diff_file,
+ git_blob_rawcontent(blob), (size_t)blobsize,
+ opts->metric->payload);
+
+ git_blob_free(blob);
+
+ return error;
+}
+
+static int index_entry_similarity_inexact(
+ git_repository *repo,
+ git_index_entry *a,
+ size_t a_idx,
+ git_index_entry *b,
+ size_t b_idx,
+ void **cache,
+ const git_merge_options *opts)
+{
+ int score = 0;
+ int error = 0;
+
+ if (GIT_MODE_TYPE(a->mode) != GIT_MODE_TYPE(b->mode))
+ return 0;
+
+ /* update signature cache if needed */
+ if (!cache[a_idx] && (error = index_entry_similarity_calc(&cache[a_idx], repo, a, opts)) < 0)
+ return error;
+ if (!cache[b_idx] && (error = index_entry_similarity_calc(&cache[b_idx], repo, b, opts)) < 0)
+ return error;
+
+ /* some metrics may not wish to process this file (too big / too small) */
+ if (!cache[a_idx] || !cache[b_idx])
+ return 0;
+
+ /* compare signatures */
+ if (opts->metric->similarity(
+ &score, cache[a_idx], cache[b_idx], opts->metric->payload) < 0)
+ return -1;
+
+ /* clip score */
+ if (score < 0)
+ score = 0;
+ else if (score > 100)
+ score = 100;
+
+ return score;
+}
+
+static int merge_diff_mark_similarity(
+ git_repository *repo,
+ git_merge_diff_list *diff_list,
+ struct merge_diff_similarity *similarity_ours,
+ struct merge_diff_similarity *similarity_theirs,
+ int (*similarity_fn)(git_repository *, git_index_entry *, size_t, git_index_entry *, size_t, void **, const git_merge_options *),
+ void **cache,
+ const git_merge_options *opts)
+{
+ size_t i, j;
+ git_merge_diff *conflict_src, *conflict_tgt;
+ int similarity;
+
+ git_vector_foreach(&diff_list->conflicts, i, conflict_src) {
+ /* Items can be the source of a rename iff they have an item in the
+ * ancestor slot and lack an item in the ours or theirs slot. */
+ if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->ancestor_entry) ||
+ (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry) &&
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry)))
+ continue;
+
+ git_vector_foreach(&diff_list->conflicts, j, conflict_tgt) {
+ size_t our_idx = diff_list->conflicts.length + j;
+ size_t their_idx = (diff_list->conflicts.length * 2) + j;
+
+ if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->ancestor_entry))
+ continue;
+
+ if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->our_entry) &&
+ !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->our_entry)) {
+ similarity = similarity_fn(repo, &conflict_src->ancestor_entry, i, &conflict_tgt->our_entry, our_idx, cache, opts);
+
+ if (similarity == GIT_EBUFS)
+ continue;
+ else if (similarity < 0)
+ return similarity;
+
+ if (similarity > similarity_ours[i].similarity &&
+ similarity > similarity_ours[j].similarity) {
+ /* Clear previous best similarity */
+ if (similarity_ours[i].similarity > 0)
+ similarity_ours[similarity_ours[i].other_idx].similarity = 0;
+
+ if (similarity_ours[j].similarity > 0)
+ similarity_ours[similarity_ours[j].other_idx].similarity = 0;
+
+ similarity_ours[i].similarity = similarity;
+ similarity_ours[i].other_idx = j;
+
+ similarity_ours[j].similarity = similarity;
+ similarity_ours[j].other_idx = i;
+ }
+ }
+
+ if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_tgt->their_entry) &&
+ !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict_src->their_entry)) {
+ similarity = similarity_fn(repo, &conflict_src->ancestor_entry, i, &conflict_tgt->their_entry, their_idx, cache, opts);
+
+ if (similarity > similarity_theirs[i].similarity &&
+ similarity > similarity_theirs[j].similarity) {
+ /* Clear previous best similarity */
+ if (similarity_theirs[i].similarity > 0)
+ similarity_theirs[similarity_theirs[i].other_idx].similarity = 0;
+
+ if (similarity_theirs[j].similarity > 0)
+ similarity_theirs[similarity_theirs[j].other_idx].similarity = 0;
+
+ similarity_theirs[i].similarity = similarity;
+ similarity_theirs[i].other_idx = j;
+
+ similarity_theirs[j].similarity = similarity;
+ similarity_theirs[j].other_idx = i;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Rename conflicts:
+ *
+ * Ancestor Ours Theirs
+ *
+ * 0a A A A No rename
+ * b A A* A No rename (ours was rewritten)
+ * c A A A* No rename (theirs rewritten)
+ * 1a A A B[A] Rename or rename/edit
+ * b A B[A] A (automergeable)
+ * 2 A B[A] B[A] Both renamed (automergeable)
+ * 3a A B[A] Rename/delete
+ * b A B[A] (same)
+ * 4a A B[A] B Rename/add [B~ours B~theirs]
+ * b A B B[A] (same)
+ * 5 A B[A] C[A] Both renamed ("1 -> 2")
+ * 6 A C[A] Both renamed ("2 -> 1")
+ * B C[B] [C~ours C~theirs] (automergeable)
+ */
+static void merge_diff_mark_rename_conflict(
+ git_merge_diff_list *diff_list,
+ struct merge_diff_similarity *similarity_ours,
+ bool ours_renamed,
+ size_t ours_source_idx,
+ struct merge_diff_similarity *similarity_theirs,
+ bool theirs_renamed,
+ size_t theirs_source_idx,
+ git_merge_diff *target,
+ const git_merge_options *opts)
+{
+ git_merge_diff *ours_source = NULL, *theirs_source = NULL;
+
+ if (ours_renamed)
+ ours_source = diff_list->conflicts.contents[ours_source_idx];
+
+ if (theirs_renamed)
+ theirs_source = diff_list->conflicts.contents[theirs_source_idx];
+
+ /* Detect 2->1 conflicts */
+ if (ours_renamed && theirs_renamed) {
+ /* Both renamed to the same target name. */
+ if (ours_source_idx == theirs_source_idx)
+ ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED;
+ else {
+ ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1;
+ theirs_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1;
+ }
+ } else if (ours_renamed) {
+ /* If our source was also renamed in theirs, this is a 1->2 */
+ if (similarity_theirs[ours_source_idx].similarity >= opts->rename_threshold)
+ ours_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2;
+
+ else if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->their_entry)) {
+ ours_source->type = GIT_MERGE_DIFF_RENAMED_ADDED;
+ target->type = GIT_MERGE_DIFF_RENAMED_ADDED;
+ }
+
+ else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(ours_source->their_entry))
+ ours_source->type = GIT_MERGE_DIFF_RENAMED_DELETED;
+
+ else if (ours_source->type == GIT_MERGE_DIFF_MODIFIED_DELETED)
+ ours_source->type = GIT_MERGE_DIFF_RENAMED_MODIFIED;
+ } else if (theirs_renamed) {
+ /* If their source was also renamed in ours, this is a 1->2 */
+ if (similarity_ours[theirs_source_idx].similarity >= opts->rename_threshold)
+ theirs_source->type = GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2;
+
+ else if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->our_entry)) {
+ theirs_source->type = GIT_MERGE_DIFF_RENAMED_ADDED;
+ target->type = GIT_MERGE_DIFF_RENAMED_ADDED;
+ }
+
+ else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(theirs_source->our_entry))
+ theirs_source->type = GIT_MERGE_DIFF_RENAMED_DELETED;
+
+ else if (theirs_source->type == GIT_MERGE_DIFF_MODIFIED_DELETED)
+ theirs_source->type = GIT_MERGE_DIFF_RENAMED_MODIFIED;
+ }
+}
+
+GIT_INLINE(void) merge_diff_coalesce_rename(
+ git_index_entry *source_entry,
+ git_delta_t *source_status,
+ git_index_entry *target_entry,
+ git_delta_t *target_status)
+{
+ /* Coalesce the rename target into the rename source. */
+ memcpy(source_entry, target_entry, sizeof(git_index_entry));
+ *source_status = GIT_DELTA_RENAMED;
+
+ memset(target_entry, 0x0, sizeof(git_index_entry));
+ *target_status = GIT_DELTA_UNMODIFIED;
+}
+
+static void merge_diff_list_coalesce_renames(
+ git_merge_diff_list *diff_list,
+ struct merge_diff_similarity *similarity_ours,
+ struct merge_diff_similarity *similarity_theirs,
+ const git_merge_options *opts)
+{
+ size_t i;
+ bool ours_renamed = 0, theirs_renamed = 0;
+ size_t ours_source_idx = 0, theirs_source_idx = 0;
+ git_merge_diff *ours_source, *theirs_source, *target;
+
+ for (i = 0; i < diff_list->conflicts.length; i++) {
+ target = diff_list->conflicts.contents[i];
+
+ ours_renamed = 0;
+ theirs_renamed = 0;
+
+ if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->our_entry) &&
+ similarity_ours[i].similarity >= opts->rename_threshold) {
+ ours_source_idx = similarity_ours[i].other_idx;
+
+ ours_source = diff_list->conflicts.contents[ours_source_idx];
+
+ merge_diff_coalesce_rename(
+ &ours_source->our_entry,
+ &ours_source->our_status,
+ &target->our_entry,
+ &target->our_status);
+
+ similarity_ours[ours_source_idx].similarity = 0;
+ similarity_ours[i].similarity = 0;
+
+ ours_renamed = 1;
+ }
+
+ /* insufficient to determine direction */
+ if (GIT_MERGE_INDEX_ENTRY_EXISTS(target->their_entry) &&
+ similarity_theirs[i].similarity >= opts->rename_threshold) {
+ theirs_source_idx = similarity_theirs[i].other_idx;
+
+ theirs_source = diff_list->conflicts.contents[theirs_source_idx];
+
+ merge_diff_coalesce_rename(
+ &theirs_source->their_entry,
+ &theirs_source->their_status,
+ &target->their_entry,
+ &target->their_status);
+
+ similarity_theirs[theirs_source_idx].similarity = 0;
+ similarity_theirs[i].similarity = 0;
+
+ theirs_renamed = 1;
+ }
+
+ merge_diff_mark_rename_conflict(diff_list,
+ similarity_ours, ours_renamed, ours_source_idx,
+ similarity_theirs, theirs_renamed, theirs_source_idx,
+ target, opts);
+ }
+}
+
+static int merge_diff_empty(const git_vector *conflicts, size_t idx, void *p)
+{
+ git_merge_diff *conflict = conflicts->contents[idx];
+
+ GIT_UNUSED(p);
+
+ return (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) &&
+ !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) &&
+ !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry));
+}
+
+static void merge_diff_list_count_candidates(
+ git_merge_diff_list *diff_list,
+ size_t *src_count,
+ size_t *tgt_count)
+{
+ git_merge_diff *entry;
+ size_t i;
+
+ *src_count = 0;
+ *tgt_count = 0;
+
+ git_vector_foreach(&diff_list->conflicts, i, entry) {
+ if (GIT_MERGE_INDEX_ENTRY_EXISTS(entry->ancestor_entry) &&
+ (!GIT_MERGE_INDEX_ENTRY_EXISTS(entry->our_entry) ||
+ !GIT_MERGE_INDEX_ENTRY_EXISTS(entry->their_entry)))
+ src_count++;
+ else if (!GIT_MERGE_INDEX_ENTRY_EXISTS(entry->ancestor_entry))
+ tgt_count++;
+ }
+}
+
+int git_merge_diff_list__find_renames(
+ git_repository *repo,
+ git_merge_diff_list *diff_list,
+ const git_merge_options *opts)
+{
+ struct merge_diff_similarity *similarity_ours, *similarity_theirs;
+ void **cache = NULL;
+ size_t cache_size = 0;
+ size_t src_count, tgt_count, i;
+ int error = 0;
+
+ assert(diff_list && opts);
+
+ if ((opts->flags & GIT_MERGE_TREE_FIND_RENAMES) == 0)
+ return 0;
+
+ similarity_ours = git__calloc(diff_list->conflicts.length,
+ sizeof(struct merge_diff_similarity));
+ GITERR_CHECK_ALLOC(similarity_ours);
+
+ similarity_theirs = git__calloc(diff_list->conflicts.length,
+ sizeof(struct merge_diff_similarity));
+ GITERR_CHECK_ALLOC(similarity_theirs);
+
+ /* Calculate similarity between items that were deleted from the ancestor
+ * and added in the other branch.
+ */
+ if ((error = merge_diff_mark_similarity(repo, diff_list, similarity_ours,
+ similarity_theirs, index_entry_similarity_exact, NULL, opts)) < 0)
+ goto done;
+
+ if (diff_list->conflicts.length <= opts->target_limit) {
+ cache_size = diff_list->conflicts.length * 3;
+ cache = git__calloc(cache_size, sizeof(void *));
+ GITERR_CHECK_ALLOC(cache);
+
+ merge_diff_list_count_candidates(diff_list, &src_count, &tgt_count);
+
+ if (src_count > opts->target_limit || tgt_count > opts->target_limit) {
+ /* TODO: report! */
+ } else {
+ if ((error = merge_diff_mark_similarity(
+ repo, diff_list, similarity_ours, similarity_theirs,
+ index_entry_similarity_inexact, cache, opts)) < 0)
+ goto done;
+ }
+ }
+
+ /* For entries that are appropriately similar, merge the new name's entry
+ * into the old name.
+ */
+ merge_diff_list_coalesce_renames(diff_list, similarity_ours, similarity_theirs, opts);
+
+ /* And remove any entries that were merged and are now empty. */
+ git_vector_remove_matching(&diff_list->conflicts, merge_diff_empty, NULL);
+
+done:
+ if (cache != NULL) {
+ for (i = 0; i < cache_size; ++i) {
+ if (cache[i] != NULL)
+ opts->metric->free_signature(cache[i], opts->metric->payload);
+ }
+
+ git__free(cache);
+ }
+
+ git__free(similarity_ours);
+ git__free(similarity_theirs);
+
+ return error;
+}
+
+/* Directory/file conflict handling */
+
+GIT_INLINE(const char *) merge_diff_path(
+ const git_merge_diff *conflict)
+{
+ if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry))
+ return conflict->ancestor_entry.path;
+ else if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry))
+ return conflict->our_entry.path;
+ else if (GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry))
+ return conflict->their_entry.path;
+
+ return NULL;
+}
+
+GIT_INLINE(bool) merge_diff_any_side_added_or_modified(
+ const git_merge_diff *conflict)
+{
+ if (conflict->our_status == GIT_DELTA_ADDED ||
+ conflict->our_status == GIT_DELTA_MODIFIED ||
+ conflict->their_status == GIT_DELTA_ADDED ||
+ conflict->their_status == GIT_DELTA_MODIFIED)
+ return true;
+
+ return false;
+}
+
+GIT_INLINE(bool) path_is_prefixed(const char *parent, const char *child)
+{
+ size_t child_len = strlen(child);
+ size_t parent_len = strlen(parent);
+
+ if (child_len < parent_len ||
+ strncmp(parent, child, parent_len) != 0)
+ return 0;
+
+ return (child[parent_len] == '/');
+}
+
+GIT_INLINE(int) merge_diff_detect_df_conflict(
+ struct merge_diff_df_data *df_data,
+ git_merge_diff *conflict)
+{
+ const char *cur_path = merge_diff_path(conflict);
+
+ /* Determine if this is a D/F conflict or the child of one */
+ if (df_data->df_path &&
+ path_is_prefixed(df_data->df_path, cur_path))
+ conflict->type = GIT_MERGE_DIFF_DF_CHILD;
+ else if(df_data->df_path)
+ df_data->df_path = NULL;
+ else if (df_data->prev_path &&
+ merge_diff_any_side_added_or_modified(df_data->prev_conflict) &&
+ merge_diff_any_side_added_or_modified(conflict) &&
+ path_is_prefixed(df_data->prev_path, cur_path)) {
+ conflict->type = GIT_MERGE_DIFF_DF_CHILD;
+
+ df_data->prev_conflict->type = GIT_MERGE_DIFF_DIRECTORY_FILE;
+ df_data->df_path = df_data->prev_path;
+ }
+
+ df_data->prev_path = cur_path;
+ df_data->prev_conflict = conflict;
+
+ return 0;
+}
+
+/* Conflict handling */
+
+GIT_INLINE(int) merge_diff_detect_type(
+ git_merge_diff *conflict)
+{
+ if (conflict->our_status == GIT_DELTA_ADDED &&
+ conflict->their_status == GIT_DELTA_ADDED)
+ conflict->type = GIT_MERGE_DIFF_BOTH_ADDED;
+ else if (conflict->our_status == GIT_DELTA_MODIFIED &&
+ conflict->their_status == GIT_DELTA_MODIFIED)
+ conflict->type = GIT_MERGE_DIFF_BOTH_MODIFIED;
+ else if (conflict->our_status == GIT_DELTA_DELETED &&
+ conflict->their_status == GIT_DELTA_DELETED)
+ conflict->type = GIT_MERGE_DIFF_BOTH_DELETED;
+ else if (conflict->our_status == GIT_DELTA_MODIFIED &&
+ conflict->their_status == GIT_DELTA_DELETED)
+ conflict->type = GIT_MERGE_DIFF_MODIFIED_DELETED;
+ else if (conflict->our_status == GIT_DELTA_DELETED &&
+ conflict->their_status == GIT_DELTA_MODIFIED)
+ conflict->type = GIT_MERGE_DIFF_MODIFIED_DELETED;
+ else
+ conflict->type = GIT_MERGE_DIFF_NONE;
+
+ return 0;
+}
+
+GIT_INLINE(int) merge_diff_detect_binary(
+ git_repository *repo,
+ git_merge_diff *conflict)
+{
+ git_blob *ancestor_blob = NULL, *our_blob = NULL, *their_blob = NULL;
+ int error = 0;
+
+ if (GIT_MERGE_INDEX_ENTRY_ISFILE(conflict->ancestor_entry)) {
+ if ((error = git_blob_lookup(&ancestor_blob, repo, &conflict->ancestor_entry.id)) < 0)
+ goto done;
+
+ conflict->binary = git_blob_is_binary(ancestor_blob);
+ }
+
+ if (!conflict->binary &&
+ GIT_MERGE_INDEX_ENTRY_ISFILE(conflict->our_entry)) {
+ if ((error = git_blob_lookup(&our_blob, repo, &conflict->our_entry.id)) < 0)
+ goto done;
+
+ conflict->binary = git_blob_is_binary(our_blob);
+ }
+
+ if (!conflict->binary &&
+ GIT_MERGE_INDEX_ENTRY_ISFILE(conflict->their_entry)) {
+ if ((error = git_blob_lookup(&their_blob, repo, &conflict->their_entry.id)) < 0)
+ goto done;
+
+ conflict->binary = git_blob_is_binary(their_blob);
+ }
+
+done:
+ git_blob_free(ancestor_blob);
+ git_blob_free(our_blob);
+ git_blob_free(their_blob);
+
+ return error;
+}
+
+GIT_INLINE(int) index_entry_dup_pool(
+ git_index_entry *out,
+ git_pool *pool,
+ const git_index_entry *src)
+{
+ if (src != NULL) {
+ memcpy(out, src, sizeof(git_index_entry));
+
+ if ((out->path = git_pool_strdup(pool, src->path)) == NULL)
+ return -1;
+ }
+
+ return 0;
+}
+
+GIT_INLINE(int) merge_delta_type_from_index_entries(
+ const git_index_entry *ancestor,
+ const git_index_entry *other)
+{
+ if (ancestor == NULL && other == NULL)
+ return GIT_DELTA_UNMODIFIED;
+ else if (ancestor == NULL && other != NULL)
+ return GIT_DELTA_ADDED;
+ else if (ancestor != NULL && other == NULL)
+ return GIT_DELTA_DELETED;
+ else if (S_ISDIR(ancestor->mode) ^ S_ISDIR(other->mode))
+ return GIT_DELTA_TYPECHANGE;
+ else if(S_ISLNK(ancestor->mode) ^ S_ISLNK(other->mode))
+ return GIT_DELTA_TYPECHANGE;
+ else if (git_oid__cmp(&ancestor->id, &other->id) ||
+ ancestor->mode != other->mode)
+ return GIT_DELTA_MODIFIED;
+
+ return GIT_DELTA_UNMODIFIED;
+}
+
+static git_merge_diff *merge_diff_from_index_entries(
+ git_merge_diff_list *diff_list,
+ const git_index_entry **entries)
+{
+ git_merge_diff *conflict;
+ git_pool *pool = &diff_list->pool;
+
+ if ((conflict = git_pool_malloc(pool, sizeof(git_merge_diff))) == NULL)
+ return NULL;
+
+ if (index_entry_dup_pool(&conflict->ancestor_entry, pool, entries[TREE_IDX_ANCESTOR]) < 0 ||
+ index_entry_dup_pool(&conflict->our_entry, pool, entries[TREE_IDX_OURS]) < 0 ||
+ index_entry_dup_pool(&conflict->their_entry, pool, entries[TREE_IDX_THEIRS]) < 0)
+ return NULL;
+
+ conflict->our_status = merge_delta_type_from_index_entries(
+ entries[TREE_IDX_ANCESTOR], entries[TREE_IDX_OURS]);
+ conflict->their_status = merge_delta_type_from_index_entries(
+ entries[TREE_IDX_ANCESTOR], entries[TREE_IDX_THEIRS]);
+
+ return conflict;
+}
+
+/* Merge trees */
+
+static int merge_diff_list_insert_conflict(
+ git_merge_diff_list *diff_list,
+ struct merge_diff_df_data *merge_df_data,
+ const git_index_entry *tree_items[3])
+{
+ git_merge_diff *conflict;
+
+ if ((conflict = merge_diff_from_index_entries(diff_list, tree_items)) == NULL ||
+ merge_diff_detect_type(conflict) < 0 ||
+ merge_diff_detect_df_conflict(merge_df_data, conflict) < 0 ||
+ merge_diff_detect_binary(diff_list->repo, conflict) < 0 ||
+ git_vector_insert(&diff_list->conflicts, conflict) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int merge_diff_list_insert_unmodified(
+ git_merge_diff_list *diff_list,
+ const git_index_entry *tree_items[3])
+{
+ int error = 0;
+ git_index_entry *entry;
+
+ entry = git_pool_malloc(&diff_list->pool, sizeof(git_index_entry));
+ GITERR_CHECK_ALLOC(entry);
+
+ if ((error = index_entry_dup_pool(entry, &diff_list->pool, tree_items[0])) >= 0)
+ error = git_vector_insert(&diff_list->staged, entry);
+
+ return error;
+}
+
+int git_merge_diff_list__find_differences(
+ git_merge_diff_list *diff_list,
+ const git_tree *ancestor_tree,
+ const git_tree *our_tree,
+ const git_tree *their_tree)
+{
+ git_iterator *iterators[3] = {0};
+ const git_index_entry *items[3] = {0}, *best_cur_item, *cur_items[3];
+ git_vector_cmp entry_compare = git_index_entry_cmp;
+ struct merge_diff_df_data df_data = {0};
+ int cur_item_modified;
+ size_t i, j;
+ int error = 0;
+
+ assert(diff_list && (our_tree || their_tree));
+
+ if ((error = git_iterator_for_tree(&iterators[TREE_IDX_ANCESTOR], (git_tree *)ancestor_tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 ||
+ (error = git_iterator_for_tree(&iterators[TREE_IDX_OURS], (git_tree *)our_tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 ||
+ (error = git_iterator_for_tree(&iterators[TREE_IDX_THEIRS], (git_tree *)their_tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0)
+ goto done;
+
+ /* Set up the iterators */
+ for (i = 0; i < 3; i++) {
+ error = git_iterator_current(&items[i], iterators[i]);
+
+ if (error < 0 && error != GIT_ITEROVER)
+ goto done;
+ }
+
+ while (true) {
+ for (i = 0; i < 3; i++)
+ cur_items[i] = NULL;
+
+ best_cur_item = NULL;
+ cur_item_modified = 0;
+
+ /* Find the next path(s) to consume from each iterator */
+ for (i = 0; i < 3; i++) {
+ if (items[i] == NULL) {
+ cur_item_modified = 1;
+ continue;
+ }
+
+ if (best_cur_item == NULL) {
+ best_cur_item = items[i];
+ cur_items[i] = items[i];
+ } else {
+ int path_diff = entry_compare(items[i], best_cur_item);
+
+ if (path_diff < 0) {
+ /*
+ * Found an item that sorts before our current item, make
+ * our current item this one.
+ */
+ for (j = 0; j < i; j++)
+ cur_items[j] = NULL;
+
+ cur_item_modified = 1;
+ best_cur_item = items[i];
+ cur_items[i] = items[i];
+ } else if (path_diff > 0) {
+ /* No entry for the current item, this is modified */
+ cur_item_modified = 1;
+ } else if (path_diff == 0) {
+ cur_items[i] = items[i];
+
+ if (!cur_item_modified)
+ cur_item_modified = index_entry_cmp(best_cur_item, items[i]);
+ }
+ }
+ }
+
+ if (best_cur_item == NULL)
+ break;
+
+ if (cur_item_modified)
+ error = merge_diff_list_insert_conflict(diff_list, &df_data, cur_items);
+ else
+ error = merge_diff_list_insert_unmodified(diff_list, cur_items);
+ if (error < 0)
+ goto done;
+
+ /* Advance each iterator that participated */
+ for (i = 0; i < 3; i++) {
+ if (cur_items[i] == NULL)
+ continue;
+
+ error = git_iterator_advance(&items[i], iterators[i]);
+
+ if (error < 0 && error != GIT_ITEROVER)
+ goto done;
+ }
+ }
+
+done:
+ for (i = 0; i < 3; i++)
+ git_iterator_free(iterators[i]);
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ return error;
+}
+
+git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo)
+{
+ git_merge_diff_list *diff_list = git__calloc(1, sizeof(git_merge_diff_list));
+
+ if (diff_list == NULL)
+ return NULL;
+
+ diff_list->repo = repo;
+
+ if (git_vector_init(&diff_list->staged, 0, NULL) < 0 ||
+ git_vector_init(&diff_list->conflicts, 0, NULL) < 0 ||
+ git_vector_init(&diff_list->resolved, 0, NULL) < 0 ||
+ git_pool_init(&diff_list->pool, 1, 0) < 0)
+ return NULL;
+
+ return diff_list;
+}
+
+void git_merge_diff_list__free(git_merge_diff_list *diff_list)
+{
+ if (!diff_list)
+ return;
+
+ git_vector_free(&diff_list->staged);
+ git_vector_free(&diff_list->conflicts);
+ git_vector_free(&diff_list->resolved);
+ git_pool_clear(&diff_list->pool);
+ git__free(diff_list);
+}
+
+static int merge_normalize_opts(
+ git_repository *repo,
+ git_merge_options *opts,
+ const git_merge_options *given)
+{
+ git_config *cfg = NULL;
+ int error = 0;
+
+ assert(repo && opts);
+
+ if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
+ return error;
+
+ if (given != NULL)
+ memcpy(opts, given, sizeof(git_merge_options));
+ else {
+ git_merge_options init = GIT_MERGE_OPTIONS_INIT;
+ memcpy(opts, &init, sizeof(init));
+
+ opts->flags = GIT_MERGE_TREE_FIND_RENAMES;
+ opts->rename_threshold = GIT_MERGE_TREE_RENAME_THRESHOLD;
+ }
+
+ if (!opts->target_limit) {
+ int limit = git_config__get_int_force(cfg, "merge.renamelimit", 0);
+
+ if (!limit)
+ limit = git_config__get_int_force(cfg, "diff.renamelimit", 0);
+
+ opts->target_limit = (limit <= 0) ?
+ GIT_MERGE_TREE_TARGET_LIMIT : (unsigned int)limit;
+ }
+
+ /* assign the internal metric with whitespace flag as payload */
+ if (!opts->metric) {
+ opts->metric = git__malloc(sizeof(git_diff_similarity_metric));
+ GITERR_CHECK_ALLOC(opts->metric);
+
+ opts->metric->file_signature = git_diff_find_similar__hashsig_for_file;
+ opts->metric->buffer_signature = git_diff_find_similar__hashsig_for_buf;
+ opts->metric->free_signature = git_diff_find_similar__hashsig_free;
+ opts->metric->similarity = git_diff_find_similar__calc_similarity;
+
+ if (opts->flags & GIT_DIFF_FIND_IGNORE_WHITESPACE)
+ opts->metric->payload = (void *)GIT_HASHSIG_IGNORE_WHITESPACE;
+ else if (opts->flags & GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE)
+ opts->metric->payload = (void *)GIT_HASHSIG_NORMAL;
+ else
+ opts->metric->payload = (void *)GIT_HASHSIG_SMART_WHITESPACE;
+ }
+
+ return 0;
+}
+
+
+static int merge_index_insert_reuc(
+ git_index *index,
+ size_t idx,
+ const git_index_entry *entry)
+{
+ const git_index_reuc_entry *reuc;
+ int mode[3] = { 0, 0, 0 };
+ git_oid const *oid[3] = { NULL, NULL, NULL };
+ size_t i;
+
+ if (!GIT_MERGE_INDEX_ENTRY_EXISTS(*entry))
+ return 0;
+
+ if ((reuc = git_index_reuc_get_bypath(index, entry->path)) != NULL) {
+ for (i = 0; i < 3; i++) {
+ mode[i] = reuc->mode[i];
+ oid[i] = &reuc->oid[i];
+ }
+ }
+
+ mode[idx] = entry->mode;
+ oid[idx] = &entry->id;
+
+ return git_index_reuc_add(index, entry->path,
+ mode[0], oid[0], mode[1], oid[1], mode[2], oid[2]);
+}
+
+int index_from_diff_list(git_index **out, git_merge_diff_list *diff_list)
+{
+ git_index *index;
+ size_t i;
+ git_index_entry *entry;
+ git_merge_diff *conflict;
+ int error = 0;
+
+ *out = NULL;
+
+ if ((error = git_index_new(&index)) < 0)
+ return error;
+
+ git_vector_foreach(&diff_list->staged, i, entry) {
+ if ((error = git_index_add(index, entry)) < 0)
+ goto on_error;
+ }
+
+ git_vector_foreach(&diff_list->conflicts, i, conflict) {
+ const git_index_entry *ancestor =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ?
+ &conflict->ancestor_entry : NULL;
+
+ const git_index_entry *ours =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ?
+ &conflict->our_entry : NULL;
+
+ const git_index_entry *theirs =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ?
+ &conflict->their_entry : NULL;
+
+ if ((error = git_index_conflict_add(index, ancestor, ours, theirs)) < 0)
+ goto on_error;
+ }
+
+ /* Add each rename entry to the rename portion of the index. */
+ git_vector_foreach(&diff_list->conflicts, i, conflict) {
+ const char *ancestor_path, *our_path, *their_path;
+
+ if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry))
+ continue;
+
+ ancestor_path = conflict->ancestor_entry.path;
+
+ our_path =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ?
+ conflict->our_entry.path : NULL;
+
+ their_path =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ?
+ conflict->their_entry.path : NULL;
+
+ if ((our_path && strcmp(ancestor_path, our_path) != 0) ||
+ (their_path && strcmp(ancestor_path, their_path) != 0)) {
+ if ((error = git_index_name_add(index, ancestor_path, our_path, their_path)) < 0)
+ goto on_error;
+ }
+ }
+
+ /* Add each entry in the resolved conflict to the REUC independently, since
+ * the paths may differ due to renames. */
+ git_vector_foreach(&diff_list->resolved, i, conflict) {
+ const git_index_entry *ancestor =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ?
+ &conflict->ancestor_entry : NULL;
+
+ const git_index_entry *ours =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ?
+ &conflict->our_entry : NULL;
+
+ const git_index_entry *theirs =
+ GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ?
+ &conflict->their_entry : NULL;
+
+ if (ancestor != NULL &&
+ (error = merge_index_insert_reuc(index, TREE_IDX_ANCESTOR, ancestor)) < 0)
+ goto on_error;
+
+ if (ours != NULL &&
+ (error = merge_index_insert_reuc(index, TREE_IDX_OURS, ours)) < 0)
+ goto on_error;
+
+ if (theirs != NULL &&
+ (error = merge_index_insert_reuc(index, TREE_IDX_THEIRS, theirs)) < 0)
+ goto on_error;
+ }
+
+ *out = index;
+ return 0;
+
+on_error:
+ git_index_free(index);
+
+ return error;
+}
+
+int git_merge_trees(
+ git_index **out,
+ git_repository *repo,
+ const git_tree *ancestor_tree,
+ const git_tree *our_tree,
+ const git_tree *their_tree,
+ const git_merge_options *given_opts)
+{
+ git_merge_diff_list *diff_list;
+ git_merge_options opts;
+ git_merge_diff *conflict;
+ git_vector changes;
+ size_t i;
+ int error = 0;
+
+ assert(out && repo && (our_tree || their_tree));
+
+ *out = NULL;
+
+ GITERR_CHECK_VERSION(given_opts, GIT_MERGE_OPTIONS_VERSION, "git_merge_options");
+
+ if ((error = merge_normalize_opts(repo, &opts, given_opts)) < 0)
+ return error;
+
+ diff_list = git_merge_diff_list__alloc(repo);
+ GITERR_CHECK_ALLOC(diff_list);
+
+ if ((error = git_merge_diff_list__find_differences(diff_list, ancestor_tree, our_tree, their_tree)) < 0 ||
+ (error = git_merge_diff_list__find_renames(repo, diff_list, &opts)) < 0)
+ goto done;
+
+ memcpy(&changes, &diff_list->conflicts, sizeof(git_vector));
+ git_vector_clear(&diff_list->conflicts);
+
+ git_vector_foreach(&changes, i, conflict) {
+ int resolved = 0;
+
+ if ((error = merge_conflict_resolve(&resolved, diff_list, conflict, opts.file_favor)) < 0)
+ goto done;
+
+ if (!resolved)
+ git_vector_insert(&diff_list->conflicts, conflict);
+ }
+
+ if (!given_opts || !given_opts->metric)
+ git__free(opts.metric);
+
+ error = index_from_diff_list(out, diff_list);
+
+done:
+ git_merge_diff_list__free(diff_list);
+
+ return error;
+}
+
+int git_merge_commits(
+ git_index **out,
+ git_repository *repo,
+ const git_commit *our_commit,
+ const git_commit *their_commit,
+ const git_merge_options *opts)
+{
+ git_oid ancestor_oid;
+ git_commit *ancestor_commit = NULL;
+ git_tree *our_tree = NULL, *their_tree = NULL, *ancestor_tree = NULL;
+ int error = 0;
+
+ if ((error = git_merge_base(&ancestor_oid, repo, git_commit_id(our_commit), git_commit_id(their_commit))) < 0 &&
+ error == GIT_ENOTFOUND)
+ giterr_clear();
+ else if (error < 0 ||
+ (error = git_commit_lookup(&ancestor_commit, repo, &ancestor_oid)) < 0 ||
+ (error = git_commit_tree(&ancestor_tree, ancestor_commit)) < 0)
+ goto done;
+
+ if ((error = git_commit_tree(&our_tree, our_commit)) < 0 ||
+ (error = git_commit_tree(&their_tree, their_commit)) < 0 ||
+ (error = git_merge_trees(out, repo, ancestor_tree, our_tree, their_tree, opts)) < 0)
+ goto done;
+
+done:
+ git_commit_free(ancestor_commit);
+ git_tree_free(our_tree);
+ git_tree_free(their_tree);
+ git_tree_free(ancestor_tree);
+
+ return error;
+}
+
+/* Merge setup / cleanup */
+
+static int write_orig_head(
+ git_repository *repo,
+ const git_merge_head *our_head)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ int error = 0;
+
+ assert(repo && our_head);
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_ORIG_HEAD_FILE)) == 0 &&
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) == 0 &&
+ (error = git_filebuf_printf(&file, "%s\n", our_head->oid_str)) == 0)
+ error = git_filebuf_commit(&file);
+
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ return error;
+}
+
+static int write_merge_head(
+ git_repository *repo,
+ const git_merge_head *heads[],
+ size_t heads_len)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ size_t i;
+ int error = 0;
+
+ assert(repo && heads);
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_HEAD_FILE)) < 0 ||
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0)
+ goto cleanup;
+
+ for (i = 0; i < heads_len; i++) {
+ if ((error = git_filebuf_printf(&file, "%s\n", heads[i]->oid_str)) < 0)
+ goto cleanup;
+ }
+
+ error = git_filebuf_commit(&file);
+
+cleanup:
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ return error;
+}
+
+static int write_merge_mode(git_repository *repo)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ int error = 0;
+
+ assert(repo);
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MODE_FILE)) < 0 ||
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0)
+ goto cleanup;
+
+ if ((error = git_filebuf_write(&file, "no-ff", 5)) < 0)
+ goto cleanup;
+
+ error = git_filebuf_commit(&file);
+
+cleanup:
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ return error;
+}
+
+struct merge_msg_entry {
+ const git_merge_head *merge_head;
+ bool written;
+};
+
+static int msg_entry_is_branch(
+ const struct merge_msg_entry *entry,
+ git_vector *entries)
+{
+ GIT_UNUSED(entries);
+
+ return (entry->written == 0 &&
+ entry->merge_head->remote_url == NULL &&
+ entry->merge_head->ref_name != NULL &&
+ git__strncmp(GIT_REFS_HEADS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_HEADS_DIR)) == 0);
+}
+
+static int msg_entry_is_tracking(
+ const struct merge_msg_entry *entry,
+ git_vector *entries)
+{
+ GIT_UNUSED(entries);
+
+ return (entry->written == 0 &&
+ entry->merge_head->remote_url == NULL &&
+ entry->merge_head->ref_name != NULL &&
+ git__strncmp(GIT_REFS_REMOTES_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_REMOTES_DIR)) == 0);
+}
+
+static int msg_entry_is_tag(
+ const struct merge_msg_entry *entry,
+ git_vector *entries)
+{
+ GIT_UNUSED(entries);
+
+ return (entry->written == 0 &&
+ entry->merge_head->remote_url == NULL &&
+ entry->merge_head->ref_name != NULL &&
+ git__strncmp(GIT_REFS_TAGS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_TAGS_DIR)) == 0);
+}
+
+static int msg_entry_is_remote(
+ const struct merge_msg_entry *entry,
+ git_vector *entries)
+{
+ if (entry->written == 0 &&
+ entry->merge_head->remote_url != NULL &&
+ entry->merge_head->ref_name != NULL &&
+ git__strncmp(GIT_REFS_HEADS_DIR, entry->merge_head->ref_name, strlen(GIT_REFS_HEADS_DIR)) == 0)
+ {
+ struct merge_msg_entry *existing;
+
+ /* Match only branches from the same remote */
+ if (entries->length == 0)
+ return 1;
+
+ existing = git_vector_get(entries, 0);
+
+ return (git__strcmp(existing->merge_head->remote_url,
+ entry->merge_head->remote_url) == 0);
+ }
+
+ return 0;
+}
+
+static int msg_entry_is_oid(
+ const struct merge_msg_entry *merge_msg_entry)
+{
+ return (merge_msg_entry->written == 0 &&
+ merge_msg_entry->merge_head->ref_name == NULL &&
+ merge_msg_entry->merge_head->remote_url == NULL);
+}
+
+static int merge_msg_entry_written(
+ const struct merge_msg_entry *merge_msg_entry)
+{
+ return (merge_msg_entry->written == 1);
+}
+
+static int merge_msg_entries(
+ git_vector *v,
+ const struct merge_msg_entry *entries,
+ size_t len,
+ int (*match)(const struct merge_msg_entry *entry, git_vector *entries))
+{
+ size_t i;
+ int matches, total = 0;
+
+ git_vector_clear(v);
+
+ for (i = 0; i < len; i++) {
+ if ((matches = match(&entries[i], v)) < 0)
+ return matches;
+ else if (!matches)
+ continue;
+
+ git_vector_insert(v, (struct merge_msg_entry *)&entries[i]);
+ total++;
+ }
+
+ return total;
+}
+
+static int merge_msg_write_entries(
+ git_filebuf *file,
+ git_vector *entries,
+ const char *item_name,
+ const char *item_plural_name,
+ size_t ref_name_skip,
+ const char *source,
+ char sep)
+{
+ struct merge_msg_entry *entry;
+ size_t i;
+ int error = 0;
+
+ if (entries->length == 0)
+ return 0;
+
+ if (sep && (error = git_filebuf_printf(file, "%c ", sep)) < 0)
+ goto done;
+
+ if ((error = git_filebuf_printf(file, "%s ",
+ (entries->length == 1) ? item_name : item_plural_name)) < 0)
+ goto done;
+
+ git_vector_foreach(entries, i, entry) {
+ if (i > 0 &&
+ (error = git_filebuf_printf(file, "%s", (i == entries->length - 1) ? " and " : ", ")) < 0)
+ goto done;
+
+ if ((error = git_filebuf_printf(file, "'%s'", entry->merge_head->ref_name + ref_name_skip)) < 0)
+ goto done;
+
+ entry->written = 1;
+ }
+
+ if (source)
+ error = git_filebuf_printf(file, " of %s", source);
+
+done:
+ return error;
+}
+
+static int merge_msg_write_branches(
+ git_filebuf *file,
+ git_vector *entries,
+ char sep)
+{
+ return merge_msg_write_entries(file, entries,
+ "branch", "branches", strlen(GIT_REFS_HEADS_DIR), NULL, sep);
+}
+
+static int merge_msg_write_tracking(
+ git_filebuf *file,
+ git_vector *entries,
+ char sep)
+{
+ return merge_msg_write_entries(file, entries,
+ "remote-tracking branch", "remote-tracking branches", 0, NULL, sep);
+}
+
+static int merge_msg_write_tags(
+ git_filebuf *file,
+ git_vector *entries,
+ char sep)
+{
+ return merge_msg_write_entries(file, entries,
+ "tag", "tags", strlen(GIT_REFS_TAGS_DIR), NULL, sep);
+}
+
+static int merge_msg_write_remotes(
+ git_filebuf *file,
+ git_vector *entries,
+ char sep)
+{
+ const char *source;
+
+ if (entries->length == 0)
+ return 0;
+
+ source = ((struct merge_msg_entry *)entries->contents[0])->merge_head->remote_url;
+
+ return merge_msg_write_entries(file, entries,
+ "branch", "branches", strlen(GIT_REFS_HEADS_DIR), source, sep);
+}
+
+static int write_merge_msg(
+ git_repository *repo,
+ const git_merge_head *heads[],
+ size_t heads_len)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ struct merge_msg_entry *entries;
+ git_vector matching = GIT_VECTOR_INIT;
+ size_t i;
+ char sep = 0;
+ int error = 0;
+
+ assert(repo && heads);
+
+ entries = git__calloc(heads_len, sizeof(struct merge_msg_entry));
+ GITERR_CHECK_ALLOC(entries);
+
+ if (git_vector_init(&matching, heads_len, NULL) < 0) {
+ git__free(entries);
+ return -1;
+ }
+
+ for (i = 0; i < heads_len; i++)
+ entries[i].merge_head = heads[i];
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 ||
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0 ||
+ (error = git_filebuf_write(&file, "Merge ", 6)) < 0)
+ goto cleanup;
+
+ /*
+ * This is to emulate the format of MERGE_MSG by core git.
+ *
+ * Core git will write all the commits specified by OID, in the order
+ * provided, until the first named branch or tag is reached, at which
+ * point all branches will be written in the order provided, then all
+ * tags, then all remote tracking branches and finally all commits that
+ * were specified by OID that were not already written.
+ *
+ * Yes. Really.
+ */
+ for (i = 0; i < heads_len; i++) {
+ if (!msg_entry_is_oid(&entries[i]))
+ break;
+
+ if ((error = git_filebuf_printf(&file,
+ "%scommit '%s'", (i > 0) ? "; " : "",
+ entries[i].merge_head->oid_str)) < 0)
+ goto cleanup;
+
+ entries[i].written = 1;
+ }
+
+ if (i)
+ sep = ';';
+
+ if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_branch)) < 0 ||
+ (error = merge_msg_write_branches(&file, &matching, sep)) < 0)
+ goto cleanup;
+
+ if (matching.length)
+ sep =',';
+
+ if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_tracking)) < 0 ||
+ (error = merge_msg_write_tracking(&file, &matching, sep)) < 0)
+ goto cleanup;
+
+ if (matching.length)
+ sep =',';
+
+ if ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_tag)) < 0 ||
+ (error = merge_msg_write_tags(&file, &matching, sep)) < 0)
+ goto cleanup;
+
+ if (matching.length)
+ sep =',';
+
+ /* We should never be called with multiple remote branches, but handle
+ * it in case we are... */
+ while ((error = merge_msg_entries(&matching, entries, heads_len, msg_entry_is_remote)) > 0) {
+ if ((error = merge_msg_write_remotes(&file, &matching, sep)) < 0)
+ goto cleanup;
+
+ if (matching.length)
+ sep =',';
+ }
+
+ if (error < 0)
+ goto cleanup;
+
+ for (i = 0; i < heads_len; i++) {
+ if (merge_msg_entry_written(&entries[i]))
+ continue;
+
+ if ((error = git_filebuf_printf(&file, "; commit '%s'",
+ entries[i].merge_head->oid_str)) < 0)
+ goto cleanup;
+ }
+
+ if ((error = git_filebuf_printf(&file, "\n")) < 0 ||
+ (error = git_filebuf_commit(&file)) < 0)
+ goto cleanup;
+
+cleanup:
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ git_vector_free(&matching);
+ git__free(entries);
+
+ return error;
+}
+
+int git_merge__setup(
+ git_repository *repo,
+ const git_merge_head *our_head,
+ const git_merge_head *heads[],
+ size_t heads_len)
+{
+ int error = 0;
+
+ assert (repo && our_head && heads);
+
+ if ((error = write_orig_head(repo, our_head)) == 0 &&
+ (error = write_merge_head(repo, heads, heads_len)) == 0 &&
+ (error = write_merge_mode(repo)) == 0) {
+ error = write_merge_msg(repo, heads, heads_len);
+ }
+
+ return error;
+}
+
+/* Merge branches */
+
+static int merge_ancestor_head(
+ git_merge_head **ancestor_head,
+ git_repository *repo,
+ const git_merge_head *our_head,
+ const git_merge_head **their_heads,
+ size_t their_heads_len)
+{
+ git_oid *oids, ancestor_oid;
+ size_t i;
+ int error = 0;
+
+ assert(repo && our_head && their_heads);
+
+ oids = git__calloc(their_heads_len + 1, sizeof(git_oid));
+ GITERR_CHECK_ALLOC(oids);
+
+ git_oid_cpy(&oids[0], git_commit_id(our_head->commit));
+
+ for (i = 0; i < their_heads_len; i++)
+ git_oid_cpy(&oids[i + 1], &their_heads[i]->oid);
+
+ if ((error = git_merge_base_many(&ancestor_oid, repo, their_heads_len + 1, oids)) < 0)
+ goto on_error;
+
+ error = git_merge_head_from_id(ancestor_head, repo, &ancestor_oid);
+
+on_error:
+ git__free(oids);
+ return error;
+}
+
+const char *merge_their_label(const char *branchname)
+{
+ const char *slash;
+
+ if ((slash = strrchr(branchname, '/')) == NULL)
+ return branchname;
+
+ if (*(slash+1) == '\0')
+ return "theirs";
+
+ return slash+1;
+}
+
+static int merge_normalize_checkout_opts(
+ git_repository *repo,
+ git_checkout_options *checkout_opts,
+ const git_checkout_options *given_checkout_opts,
+ const git_merge_head *ancestor_head,
+ const git_merge_head *our_head,
+ size_t their_heads_len,
+ const git_merge_head **their_heads)
+{
+ int error = 0;
+
+ GIT_UNUSED(repo);
+
+ if (given_checkout_opts != NULL)
+ memcpy(checkout_opts, given_checkout_opts, sizeof(git_checkout_options));
+ else {
+ git_checkout_options default_checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
+ default_checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE |
+ GIT_CHECKOUT_ALLOW_CONFLICTS;
+
+ memcpy(checkout_opts, &default_checkout_opts, sizeof(git_checkout_options));
+ }
+
+ /* TODO: for multiple ancestors in merge-recursive, this is "merged common ancestors" */
+ if (!checkout_opts->ancestor_label) {
+ if (ancestor_head && ancestor_head->commit)
+ checkout_opts->ancestor_label = git_commit_summary(ancestor_head->commit);
+ else
+ checkout_opts->ancestor_label = "ancestor";
+ }
+
+ if (!checkout_opts->our_label) {
+ if (our_head && our_head->ref_name)
+ checkout_opts->our_label = our_head->ref_name;
+ else
+ checkout_opts->our_label = "ours";
+ }
+
+ if (!checkout_opts->their_label) {
+ if (their_heads_len == 1 && their_heads[0]->ref_name)
+ checkout_opts->their_label = merge_their_label(their_heads[0]->ref_name);
+ else if (their_heads_len == 1)
+ checkout_opts->their_label = their_heads[0]->oid_str;
+ else
+ checkout_opts->their_label = "theirs";
+ }
+
+ return error;
+}
+
+static int merge_affected_paths(git_vector *paths, git_repository *repo, git_index *index_new)
+{
+ git_tree *head_tree = NULL;
+ git_iterator *iter_head = NULL, *iter_new = NULL;
+ git_diff *merged_list = NULL;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_delta *delta;
+ size_t i;
+ const git_index_entry *e;
+ char *path;
+ int error = 0;
+
+ if ((error = git_repository_head_tree(&head_tree, repo)) < 0 ||
+ (error = git_iterator_for_tree(&iter_head, head_tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 ||
+ (error = git_iterator_for_index(&iter_new, index_new, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 ||
+ (error = git_diff__from_iterators(&merged_list, repo, iter_head, iter_new, &opts)) < 0)
+ goto done;
+
+ git_vector_foreach(&merged_list->deltas, i, delta) {
+ path = git__strdup(delta->new_file.path);
+ GITERR_CHECK_ALLOC(path);
+
+ if ((error = git_vector_insert(paths, path)) < 0)
+ goto on_error;
+ }
+
+ for (i = 0; i < git_index_entrycount(index_new); i++) {
+ e = git_index_get_byindex(index_new, i);
+
+ if (git_index_entry_stage(e) != 0 &&
+ (git_vector_last(paths) == NULL ||
+ strcmp(git_vector_last(paths), e->path) != 0)) {
+
+ path = git__strdup(e->path);
+ GITERR_CHECK_ALLOC(path);
+
+ if ((error = git_vector_insert(paths, path)) < 0)
+ goto on_error;
+ }
+ }
+
+ goto done;
+
+on_error:
+ git_vector_foreach(paths, i, path)
+ git__free(path);
+
+ git_vector_clear(paths);
+
+done:
+ git_tree_free(head_tree);
+ git_iterator_free(iter_head);
+ git_iterator_free(iter_new);
+ git_diff_free(merged_list);
+
+ return error;
+}
+
+static int merge_check_index(size_t *conflicts, git_repository *repo, git_index *index_new, git_vector *merged_paths)
+{
+ git_tree *head_tree = NULL;
+ git_index *index_repo = NULL;
+ git_iterator *iter_repo = NULL, *iter_new = NULL;
+ git_diff *staged_diff_list = NULL, *index_diff_list = NULL;
+ git_diff_delta *delta;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_vector staged_paths = GIT_VECTOR_INIT;
+ size_t i;
+ int error = 0;
+
+ GIT_UNUSED(merged_paths);
+
+ *conflicts = 0;
+
+ /* No staged changes may exist unless the change staged is identical to
+ * the result of the merge. This allows one to apply to merge manually,
+ * then run merge. Any other staged change would be overwritten by
+ * a reset merge.
+ */
+ if ((error = git_repository_head_tree(&head_tree, repo)) < 0 ||
+ (error = git_repository_index(&index_repo, repo)) < 0 ||
+ (error = git_diff_tree_to_index(&staged_diff_list, repo, head_tree, index_repo, &opts)) < 0)
+ goto done;
+
+ if (staged_diff_list->deltas.length == 0)
+ goto done;
+
+ git_vector_foreach(&staged_diff_list->deltas, i, delta) {
+ if ((error = git_vector_insert(&staged_paths, (char *)delta->new_file.path)) < 0)
+ goto done;
+ }
+
+ opts.pathspec.count = staged_paths.length;
+ opts.pathspec.strings = (char **)staged_paths.contents;
+
+ if ((error = git_iterator_for_index(&iter_repo, index_repo, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 ||
+ (error = git_iterator_for_index(&iter_new, index_new, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 ||
+ (error = git_diff__from_iterators(&index_diff_list, repo, iter_repo, iter_new, &opts)) < 0)
+ goto done;
+
+ *conflicts = index_diff_list->deltas.length;
+
+done:
+ git_tree_free(head_tree);
+ git_index_free(index_repo);
+ git_iterator_free(iter_repo);
+ git_iterator_free(iter_new);
+ git_diff_free(staged_diff_list);
+ git_diff_free(index_diff_list);
+ git_vector_free(&staged_paths);
+
+ return error;
+}
+
+static int merge_check_workdir(size_t *conflicts, git_repository *repo, git_index *index_new, git_vector *merged_paths)
+{
+ git_index *index_repo = NULL;
+ git_diff *wd_diff_list = NULL;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ int error = 0;
+
+ GIT_UNUSED(index_new);
+
+ *conflicts = 0;
+
+ opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
+
+ /* Workdir changes may exist iff they do not conflict with changes that
+ * will be applied by the merge (including conflicts). Ensure that there
+ * are no changes in the workdir to these paths.
+ */
+ opts.pathspec.count = merged_paths->length;
+ opts.pathspec.strings = (char **)merged_paths->contents;
+
+ if ((error = git_diff_index_to_workdir(&wd_diff_list, repo, index_repo, &opts)) < 0)
+ goto done;
+
+ *conflicts = wd_diff_list->deltas.length;
+
+done:
+ git_index_free(index_repo);
+ git_diff_free(wd_diff_list);
+
+ return error;
+}
+
+int git_merge__indexes(git_repository *repo, git_index *index_new)
+{
+ git_index *index_repo = NULL;
+ int index_repo_caps = 0;
+ git_vector paths = GIT_VECTOR_INIT;
+ size_t index_conflicts = 0, wd_conflicts = 0, conflicts, i;
+ char *path;
+ const git_index_entry *e;
+ const git_index_name_entry *name;
+ const git_index_reuc_entry *reuc;
+ int error = 0;
+
+ if ((error = git_repository_index(&index_repo, repo)) < 0)
+ goto done;
+
+ /* Set the index to case sensitive to handle the merge */
+ index_repo_caps = git_index_caps(index_repo);
+
+ if ((error = git_index_set_caps(index_repo, (index_repo_caps & ~GIT_INDEXCAP_IGNORE_CASE))) < 0)
+ goto done;
+
+ /* Make sure the index and workdir state do not prevent merging */
+ if ((error = merge_affected_paths(&paths, repo, index_new)) < 0 ||
+ (error = merge_check_index(&index_conflicts, repo, index_new, &paths)) < 0 ||
+ (error = merge_check_workdir(&wd_conflicts, repo, index_new, &paths)) < 0)
+ goto done;
+
+ if ((conflicts = index_conflicts + wd_conflicts) > 0) {
+ giterr_set(GITERR_MERGE, "%d uncommitted change%s would be overwritten by merge",
+ conflicts, (conflicts != 1) ? "s" : "");
+ error = GIT_EMERGECONFLICT;
+
+ goto done;
+ }
+
+ /* Remove removed items from the index */
+ git_vector_foreach(&paths, i, path) {
+ if (git_index_get_bypath(index_new, path, 0) == NULL) {
+ if ((error = git_index_remove(index_repo, path, 0)) < 0 &&
+ error != GIT_ENOTFOUND)
+ goto done;
+ }
+ }
+
+ /* Add updated items to the index */
+ git_vector_foreach(&paths, i, path) {
+ if ((e = git_index_get_bypath(index_new, path, 0)) != NULL) {
+ if ((error = git_index_add(index_repo, e)) < 0)
+ goto done;
+ }
+ }
+
+ /* Add conflicts */
+ git_index_conflict_cleanup(index_repo);
+
+ for (i = 0; i < git_index_entrycount(index_new); i++) {
+ e = git_index_get_byindex(index_new, i);
+
+ if (git_index_entry_stage(e) != 0 &&
+ (error = git_index_add(index_repo, e)) < 0)
+ goto done;
+ }
+
+ /* Add name entries */
+ git_index_name_clear(index_repo);
+
+ for (i = 0; i < git_index_name_entrycount(index_new); i++) {
+ name = git_index_name_get_byindex(index_new, i);
+
+ if ((error = git_index_name_add(index_repo,
+ name->ancestor, name->ours, name->theirs)) < 0)
+ goto done;
+ }
+
+ /* Add the reuc */
+ git_index_reuc_clear(index_repo);
+
+ for (i = 0; i < git_index_reuc_entrycount(index_new); i++) {
+ reuc = (git_index_reuc_entry *)git_index_reuc_get_byindex(index_new, i);
+
+ if ((error = git_index_reuc_add(index_repo, reuc->path,
+ reuc->mode[0], &reuc->oid[0],
+ reuc->mode[1], &reuc->oid[1],
+ reuc->mode[2], &reuc->oid[2])) < 0)
+ goto done;
+ }
+
+done:
+ if (index_repo != NULL)
+ git_index_set_caps(index_repo, index_repo_caps);
+
+ git_index_free(index_repo);
+ git_vector_free_deep(&paths);
+
+ return error;
+}
+
+int git_merge__append_conflicts_to_merge_msg(
+ git_repository *repo,
+ git_index *index)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ const char *last = NULL;
+ size_t i;
+ int error;
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 ||
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_APPEND, GIT_MERGE_FILE_MODE)) < 0)
+ goto cleanup;
+
+ if (git_index_has_conflicts(index))
+ git_filebuf_printf(&file, "\nConflicts:\n");
+
+ for (i = 0; i < git_index_entrycount(index); i++) {
+ const git_index_entry *e = git_index_get_byindex(index, i);
+
+ if (git_index_entry_stage(e) == 0)
+ continue;
+
+ if (last == NULL || strcmp(e->path, last) != 0)
+ git_filebuf_printf(&file, "\t%s\n", e->path);
+
+ last = e->path;
+ }
+
+ error = git_filebuf_commit(&file);
+
+cleanup:
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ return error;
+}
+
+
+static int merge_state_cleanup(git_repository *repo)
+{
+ const char *state_files[] = {
+ GIT_MERGE_HEAD_FILE,
+ GIT_MERGE_MODE_FILE,
+ GIT_MERGE_MSG_FILE,
+ };
+
+ return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files));
+}
+
+static int merge_heads(
+ git_merge_head **ancestor_head_out,
+ git_merge_head **our_head_out,
+ git_repository *repo,
+ const git_merge_head **their_heads,
+ size_t their_heads_len)
+{
+ git_merge_head *ancestor_head = NULL, *our_head = NULL;
+ git_reference *our_ref = NULL;
+ int error = 0;
+
+ *ancestor_head_out = NULL;
+ *our_head_out = NULL;
+
+ if ((error = git_repository__ensure_not_bare(repo, "merge")) < 0)
+ goto done;
+
+ if ((error = git_reference_lookup(&our_ref, repo, GIT_HEAD_FILE)) < 0 ||
+ (error = git_merge_head_from_ref(&our_head, repo, our_ref)) < 0)
+ goto done;
+
+ if ((error = merge_ancestor_head(&ancestor_head, repo, our_head, their_heads, their_heads_len)) < 0) {
+ if (error != GIT_ENOTFOUND)
+ goto done;
+
+ giterr_clear();
+ error = 0;
+ }
+
+ *ancestor_head_out = ancestor_head;
+ *our_head_out = our_head;
+
+done:
+ if (error < 0) {
+ git_merge_head_free(ancestor_head);
+ git_merge_head_free(our_head);
+ }
+
+ git_reference_free(our_ref);
+
+ return error;
+}
+
+static int merge_preference(git_merge_preference_t *out, git_repository *repo)
+{
+ git_config *config;
+ const char *value;
+ int bool_value, error = 0;
+
+ *out = GIT_MERGE_PREFERENCE_NONE;
+
+ if ((error = git_repository_config_snapshot(&config, repo)) < 0)
+ goto done;
+
+ if ((error = git_config_get_string(&value, config, "merge.ff")) < 0) {
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
+ }
+
+ goto done;
+ }
+
+ if (git_config_parse_bool(&bool_value, value) == 0) {
+ if (!bool_value)
+ *out |= GIT_MERGE_PREFERENCE_NO_FASTFORWARD;
+ } else {
+ if (strcasecmp(value, "only") == 0)
+ *out |= GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY;
+ }
+
+done:
+ git_config_free(config);
+ return error;
+}
+
+int git_merge_analysis(
+ git_merge_analysis_t *analysis_out,
+ git_merge_preference_t *preference_out,
+ git_repository *repo,
+ const git_merge_head **their_heads,
+ size_t their_heads_len)
+{
+ git_merge_head *ancestor_head = NULL, *our_head = NULL;
+ int error = 0;
+
+ assert(analysis_out && preference_out && repo && their_heads);
+
+ if (their_heads_len != 1) {
+ giterr_set(GITERR_MERGE, "Can only merge a single branch");
+ error = -1;
+ goto done;
+ }
+
+ *analysis_out = GIT_MERGE_ANALYSIS_NONE;
+
+ if ((error = merge_preference(preference_out, repo)) < 0)
+ goto done;
+
+ if (git_repository_head_unborn(repo)) {
+ *analysis_out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_UNBORN;
+ goto done;
+ }
+
+ if ((error = merge_heads(&ancestor_head, &our_head, repo, their_heads, their_heads_len)) < 0)
+ goto done;
+
+ /* We're up-to-date if we're trying to merge our own common ancestor. */
+ if (ancestor_head && git_oid_equal(&ancestor_head->oid, &their_heads[0]->oid))
+ *analysis_out |= GIT_MERGE_ANALYSIS_UP_TO_DATE;
+
+ /* We're fastforwardable if we're our own common ancestor. */
+ else if (ancestor_head && git_oid_equal(&ancestor_head->oid, &our_head->oid))
+ *analysis_out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_NORMAL;
+
+ /* Otherwise, just a normal merge is possible. */
+ else
+ *analysis_out |= GIT_MERGE_ANALYSIS_NORMAL;
+
+done:
+ git_merge_head_free(ancestor_head);
+ git_merge_head_free(our_head);
+ return error;
+}
+
+int git_merge(
+ git_repository *repo,
+ const git_merge_head **their_heads,
+ size_t their_heads_len,
+ const git_merge_options *merge_opts,
+ const git_checkout_options *given_checkout_opts)
+{
+ git_reference *our_ref = NULL;
+ git_checkout_options checkout_opts;
+ git_merge_head *ancestor_head = NULL, *our_head = NULL;
+ git_tree *ancestor_tree = NULL, *our_tree = NULL, **their_trees = NULL;
+ git_index *index_new = NULL, *index_repo = NULL;
+ size_t i;
+ int error = 0;
+
+ assert(repo && their_heads);
+
+ if (their_heads_len != 1) {
+ giterr_set(GITERR_MERGE, "Can only merge a single branch");
+ return -1;
+ }
+
+ their_trees = git__calloc(their_heads_len, sizeof(git_tree *));
+ GITERR_CHECK_ALLOC(their_trees);
+
+ if ((error = merge_heads(&ancestor_head, &our_head, repo, their_heads, their_heads_len)) < 0)
+ goto on_error;
+
+ if ((error = merge_normalize_checkout_opts(repo, &checkout_opts, given_checkout_opts,
+ ancestor_head, our_head, their_heads_len, their_heads)) < 0)
+ goto on_error;
+
+ /* Write the merge files to the repository. */
+ if ((error = git_merge__setup(repo, our_head, their_heads, their_heads_len)) < 0)
+ goto on_error;
+
+ if (ancestor_head != NULL &&
+ (error = git_commit_tree(&ancestor_tree, ancestor_head->commit)) < 0)
+ goto on_error;
+
+ if ((error = git_commit_tree(&our_tree, our_head->commit)) < 0)
+ goto on_error;
+
+ for (i = 0; i < their_heads_len; i++) {
+ if ((error = git_commit_tree(&their_trees[i], their_heads[i]->commit)) < 0)
+ goto on_error;
+ }
+
+ /* TODO: recursive, octopus, etc... */
+
+ if ((error = git_merge_trees(&index_new, repo, ancestor_tree, our_tree, their_trees[0], merge_opts)) < 0 ||
+ (error = git_merge__indexes(repo, index_new)) < 0 ||
+ (error = git_repository_index(&index_repo, repo)) < 0 ||
+ (error = git_merge__append_conflicts_to_merge_msg(repo, index_repo)) < 0 ||
+ (error = git_checkout_index(repo, index_repo, &checkout_opts)) < 0)
+ goto on_error;
+
+ goto done;
+
+on_error:
+ merge_state_cleanup(repo);
+
+done:
+ git_index_free(index_new);
+ git_index_free(index_repo);
+
+ git_tree_free(ancestor_tree);
+ git_tree_free(our_tree);
+
+ for (i = 0; i < their_heads_len; i++)
+ git_tree_free(their_trees[i]);
+
+ git__free(their_trees);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(ancestor_head);
+
+ git_reference_free(our_ref);
+
+ return error;
+}
+
+/* Merge heads are the input to merge */
+
+static int merge_head_init(
+ git_merge_head **out,
+ git_repository *repo,
+ const char *ref_name,
+ const char *remote_url,
+ const git_oid *oid)
+{
+ git_merge_head *head;
+ int error = 0;
+
+ assert(out && oid);
+
+ *out = NULL;
+
+ head = git__calloc(1, sizeof(git_merge_head));
+ GITERR_CHECK_ALLOC(head);
+
+ if (ref_name) {
+ head->ref_name = git__strdup(ref_name);
+ GITERR_CHECK_ALLOC(head->ref_name);
+ }
+
+ if (remote_url) {
+ head->remote_url = git__strdup(remote_url);
+ GITERR_CHECK_ALLOC(head->remote_url);
+ }
+
+ git_oid_cpy(&head->oid, oid);
+
+ git_oid_fmt(head->oid_str, oid);
+ head->oid_str[GIT_OID_HEXSZ] = '\0';
+
+ if ((error = git_commit_lookup(&head->commit, repo, &head->oid)) < 0) {
+ git_merge_head_free(head);
+ return error;
+ }
+
+ *out = head;
+ return error;
+}
+
+int git_merge_head_from_ref(
+ git_merge_head **out,
+ git_repository *repo,
+ const git_reference *ref)
+{
+ git_reference *resolved;
+ int error = 0;
+
+ assert(out && repo && ref);
+
+ *out = NULL;
+
+ if ((error = git_reference_resolve(&resolved, ref)) < 0)
+ return error;
+
+ error = merge_head_init(out, repo, git_reference_name(ref), NULL,
+ git_reference_target(resolved));
+
+ git_reference_free(resolved);
+ return error;
+}
+
+int git_merge_head_from_id(
+ git_merge_head **out,
+ git_repository *repo,
+ const git_oid *oid)
+{
+ assert(out && repo && oid);
+
+ return merge_head_init(out, repo, NULL, NULL, oid);
+}
+
+int git_merge_head_from_fetchhead(
+ git_merge_head **out,
+ git_repository *repo,
+ const char *branch_name,
+ const char *remote_url,
+ const git_oid *oid)
+{
+ assert(repo && branch_name && remote_url && oid);
+
+ return merge_head_init(out, repo, branch_name, remote_url, oid);
+}
+
+const git_oid *git_merge_head_id(
+ const git_merge_head *head)
+{
+ assert(head);
+
+ return &head->oid;
+}
+
+void git_merge_head_free(git_merge_head *head)
+{
+ if (head == NULL)
+ return;
+
+ if (head->commit != NULL)
+ git_object_free((git_object *)head->commit);
+
+ if (head->ref_name != NULL)
+ git__free(head->ref_name);
+
+ if (head->remote_url != NULL)
+ git__free(head->remote_url);
+
+ git__free(head);
+}
+
+int git_merge_init_options(git_merge_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_merge_options, GIT_MERGE_OPTIONS_INIT);
+ return 0;
+}
+
+int git_merge_file_init_input(git_merge_file_input *input, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ input, version, git_merge_file_input, GIT_MERGE_FILE_INPUT_INIT);
+ return 0;
+}
+
+int git_merge_file_init_options(
+ git_merge_file_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_merge_file_options, GIT_MERGE_FILE_OPTIONS_INIT);
+ return 0;
+}
diff --git a/src/merge.h b/src/merge.h
new file mode 100644
index 000000000..00f6197bf
--- /dev/null
+++ b/src/merge.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "vector.h"
+#include "commit_list.h"
+#include "pool.h"
+
+#include "git2/merge.h"
+#include "git2/types.h"
+
+#define GIT_MERGE_MSG_FILE "MERGE_MSG"
+#define GIT_MERGE_MODE_FILE "MERGE_MODE"
+#define GIT_MERGE_FILE_MODE 0666
+
+#define GIT_MERGE_TREE_RENAME_THRESHOLD 50
+#define GIT_MERGE_TREE_TARGET_LIMIT 1000
+
+/** Types of changes when files are merged from branch to branch. */
+typedef enum {
+ /* No conflict - a change only occurs in one branch. */
+ GIT_MERGE_DIFF_NONE = 0,
+
+ /* Occurs when a file is modified in both branches. */
+ GIT_MERGE_DIFF_BOTH_MODIFIED = (1 << 0),
+
+ /* Occurs when a file is added in both branches. */
+ GIT_MERGE_DIFF_BOTH_ADDED = (1 << 1),
+
+ /* Occurs when a file is deleted in both branches. */
+ GIT_MERGE_DIFF_BOTH_DELETED = (1 << 2),
+
+ /* Occurs when a file is modified in one branch and deleted in the other. */
+ GIT_MERGE_DIFF_MODIFIED_DELETED = (1 << 3),
+
+ /* Occurs when a file is renamed in one branch and modified in the other. */
+ GIT_MERGE_DIFF_RENAMED_MODIFIED = (1 << 4),
+
+ /* Occurs when a file is renamed in one branch and deleted in the other. */
+ GIT_MERGE_DIFF_RENAMED_DELETED = (1 << 5),
+
+ /* Occurs when a file is renamed in one branch and a file with the same
+ * name is added in the other. Eg, A->B and new file B. Core git calls
+ * this a "rename/delete". */
+ GIT_MERGE_DIFF_RENAMED_ADDED = (1 << 6),
+
+ /* Occurs when both a file is renamed to the same name in the ours and
+ * theirs branches. Eg, A->B and A->B in both. Automergeable. */
+ GIT_MERGE_DIFF_BOTH_RENAMED = (1 << 7),
+
+ /* Occurs when a file is renamed to different names in the ours and theirs
+ * branches. Eg, A->B and A->C. */
+ GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2 = (1 << 8),
+
+ /* Occurs when two files are renamed to the same name in the ours and
+ * theirs branches. Eg, A->C and B->C. */
+ GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 = (1 << 9),
+
+ /* Occurs when an item at a path in one branch is a directory, and an
+ * item at the same path in a different branch is a file. */
+ GIT_MERGE_DIFF_DIRECTORY_FILE = (1 << 10),
+
+ /* The child of a folder that is in a directory/file conflict. */
+ GIT_MERGE_DIFF_DF_CHILD = (1 << 11),
+} git_merge_diff_type_t;
+
+
+typedef struct {
+ git_repository *repo;
+ git_pool pool;
+
+ /* Vector of git_index_entry that represent the merged items that
+ * have been staged, either because only one side changed, or because
+ * the two changes were non-conflicting and mergeable. These items
+ * will be written as staged entries in the main index.
+ */
+ git_vector staged;
+
+ /* Vector of git_merge_diff entries that represent the conflicts that
+ * have not been automerged. These items will be written to high-stage
+ * entries in the main index.
+ */
+ git_vector conflicts;
+
+ /* Vector of git_merge_diff that have been automerged. These items
+ * will be written to the REUC when the index is produced.
+ */
+ git_vector resolved;
+} git_merge_diff_list;
+
+/**
+ * Description of changes to one file across three trees.
+ */
+typedef struct {
+ git_merge_diff_type_t type;
+
+ git_index_entry ancestor_entry;
+
+ git_index_entry our_entry;
+ git_delta_t our_status;
+
+ git_index_entry their_entry;
+ git_delta_t their_status;
+
+ int binary:1;
+} git_merge_diff;
+
+/** Internal structure for merge inputs */
+struct git_merge_head {
+ char *ref_name;
+ char *remote_url;
+
+ git_oid oid;
+ char oid_str[GIT_OID_HEXSZ+1];
+ git_commit *commit;
+};
+
+int git_merge__bases_many(
+ git_commit_list **out,
+ git_revwalk *walk,
+ git_commit_list_node *one,
+ git_vector *twos);
+
+/*
+ * Three-way tree differencing
+ */
+
+git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo);
+
+int git_merge_diff_list__find_differences(git_merge_diff_list *merge_diff_list,
+ const git_tree *ancestor_tree,
+ const git_tree *ours_tree,
+ const git_tree *theirs_tree);
+
+int git_merge_diff_list__find_renames(git_repository *repo, git_merge_diff_list *merge_diff_list, const git_merge_options *opts);
+
+void git_merge_diff_list__free(git_merge_diff_list *diff_list);
+
+/* Merge metadata setup */
+
+int git_merge__setup(
+ git_repository *repo,
+ const git_merge_head *our_head,
+ const git_merge_head *heads[],
+ size_t heads_len);
+
+int git_merge__indexes(git_repository *repo, git_index *index_new);
+
+int git_merge__append_conflicts_to_merge_msg(git_repository *repo, git_index *index);
+
+#endif
diff --git a/src/merge_file.c b/src/merge_file.c
new file mode 100644
index 000000000..ff0364432
--- /dev/null
+++ b/src/merge_file.c
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "merge_file.h"
+#include "posix.h"
+#include "fileops.h"
+#include "index.h"
+
+#include "git2/repository.h"
+#include "git2/object.h"
+#include "git2/index.h"
+
+#include "xdiff/xdiff.h"
+
+#define GIT_MERGE_FILE_SIDE_EXISTS(X) ((X)->mode != 0)
+
+GIT_INLINE(const char *) merge_file_best_path(
+ const git_merge_file_input *ancestor,
+ const git_merge_file_input *ours,
+ const git_merge_file_input *theirs)
+{
+ if (!ancestor) {
+ if (ours && theirs && strcmp(ours->path, theirs->path) == 0)
+ return ours->path;
+
+ return NULL;
+ }
+
+ if (ours && strcmp(ancestor->path, ours->path) == 0)
+ return theirs ? theirs->path : NULL;
+ else if(theirs && strcmp(ancestor->path, theirs->path) == 0)
+ return ours ? ours->path : NULL;
+
+ return NULL;
+}
+
+GIT_INLINE(int) merge_file_best_mode(
+ const git_merge_file_input *ancestor,
+ const git_merge_file_input *ours,
+ const git_merge_file_input *theirs)
+{
+ /*
+ * If ancestor didn't exist and either ours or theirs is executable,
+ * assume executable. Otherwise, if any mode changed from the ancestor,
+ * use that one.
+ */
+ if (!ancestor) {
+ if ((ours && ours->mode == GIT_FILEMODE_BLOB_EXECUTABLE) ||
+ (theirs && theirs->mode == GIT_FILEMODE_BLOB_EXECUTABLE))
+ return GIT_FILEMODE_BLOB_EXECUTABLE;
+
+ return GIT_FILEMODE_BLOB;
+ } else if (ours && theirs) {
+ if (ancestor->mode == ours->mode)
+ return theirs->mode;
+
+ return ours->mode;
+ }
+
+ return 0;
+}
+
+int git_merge_file__input_from_index(
+ git_merge_file_input *input_out,
+ git_odb_object **odb_object_out,
+ git_odb *odb,
+ const git_index_entry *entry)
+{
+ int error = 0;
+
+ assert(input_out && odb_object_out && odb && entry);
+
+ if ((error = git_odb_read(odb_object_out, odb, &entry->id)) < 0)
+ goto done;
+
+ input_out->path = entry->path;
+ input_out->mode = entry->mode;
+ input_out->ptr = (char *)git_odb_object_data(*odb_object_out);
+ input_out->size = git_odb_object_size(*odb_object_out);
+
+done:
+ return error;
+}
+
+static void merge_file_normalize_opts(
+ git_merge_file_options *out,
+ const git_merge_file_options *given_opts)
+{
+ if (given_opts)
+ memcpy(out, given_opts, sizeof(git_merge_file_options));
+ else {
+ git_merge_file_options default_opts = GIT_MERGE_FILE_OPTIONS_INIT;
+ memcpy(out, &default_opts, sizeof(git_merge_file_options));
+ }
+}
+
+static int git_merge_file__from_inputs(
+ git_merge_file_result *out,
+ const git_merge_file_input *ancestor,
+ const git_merge_file_input *ours,
+ const git_merge_file_input *theirs,
+ const git_merge_file_options *given_opts)
+{
+ xmparam_t xmparam;
+ mmfile_t ancestor_mmfile = {0}, our_mmfile = {0}, their_mmfile = {0};
+ mmbuffer_t mmbuffer;
+ git_merge_file_options options = GIT_MERGE_FILE_OPTIONS_INIT;
+ const char *path;
+ int xdl_result;
+ int error = 0;
+
+ memset(out, 0x0, sizeof(git_merge_file_result));
+
+ merge_file_normalize_opts(&options, given_opts);
+
+ memset(&xmparam, 0x0, sizeof(xmparam_t));
+
+ if (ancestor) {
+ xmparam.ancestor = (options.ancestor_label) ?
+ options.ancestor_label : ancestor->path;
+ ancestor_mmfile.ptr = (char *)ancestor->ptr;
+ ancestor_mmfile.size = ancestor->size;
+ }
+
+ xmparam.file1 = (options.our_label) ?
+ options.our_label : ours->path;
+ our_mmfile.ptr = (char *)ours->ptr;
+ our_mmfile.size = ours->size;
+
+ xmparam.file2 = (options.their_label) ?
+ options.their_label : theirs->path;
+ their_mmfile.ptr = (char *)theirs->ptr;
+ their_mmfile.size = theirs->size;
+
+ if (options.favor == GIT_MERGE_FILE_FAVOR_OURS)
+ xmparam.favor = XDL_MERGE_FAVOR_OURS;
+ else if (options.favor == GIT_MERGE_FILE_FAVOR_THEIRS)
+ xmparam.favor = XDL_MERGE_FAVOR_THEIRS;
+ else if (options.favor == GIT_MERGE_FILE_FAVOR_UNION)
+ xmparam.favor = XDL_MERGE_FAVOR_UNION;
+
+ xmparam.level = (options.flags & GIT_MERGE_FILE_SIMPLIFY_ALNUM) ?
+ XDL_MERGE_ZEALOUS_ALNUM : XDL_MERGE_ZEALOUS;
+
+ if (options.flags & GIT_MERGE_FILE_STYLE_DIFF3)
+ xmparam.style = XDL_MERGE_DIFF3;
+
+ if ((xdl_result = xdl_merge(&ancestor_mmfile, &our_mmfile,
+ &their_mmfile, &xmparam, &mmbuffer)) < 0) {
+ giterr_set(GITERR_MERGE, "Failed to merge files.");
+ error = -1;
+ goto done;
+ }
+
+ if ((path = merge_file_best_path(ancestor, ours, theirs)) != NULL &&
+ (out->path = strdup(path)) == NULL) {
+ error = -1;
+ goto done;
+ }
+
+ out->automergeable = (xdl_result == 0);
+ out->ptr = (const char *)mmbuffer.ptr;
+ out->len = mmbuffer.size;
+ out->mode = merge_file_best_mode(ancestor, ours, theirs);
+
+done:
+ if (error < 0)
+ git_merge_file_result_free(out);
+
+ return error;
+}
+
+static git_merge_file_input *git_merge_file__normalize_inputs(
+ git_merge_file_input *out,
+ const git_merge_file_input *given)
+{
+ memcpy(out, given, sizeof(git_merge_file_input));
+
+ if (!out->path)
+ out->path = "file.txt";
+
+ if (!out->mode)
+ out->mode = 0100644;
+
+ return out;
+}
+
+int git_merge_file(
+ git_merge_file_result *out,
+ const git_merge_file_input *ancestor,
+ const git_merge_file_input *ours,
+ const git_merge_file_input *theirs,
+ const git_merge_file_options *options)
+{
+ git_merge_file_input inputs[3] = { {0} };
+
+ assert(out && ours && theirs);
+
+ memset(out, 0x0, sizeof(git_merge_file_result));
+
+ if (ancestor)
+ ancestor = git_merge_file__normalize_inputs(&inputs[0], ancestor);
+
+ ours = git_merge_file__normalize_inputs(&inputs[1], ours);
+ theirs = git_merge_file__normalize_inputs(&inputs[2], theirs);
+
+ return git_merge_file__from_inputs(out, ancestor, ours, theirs, options);
+}
+
+int git_merge_file_from_index(
+ git_merge_file_result *out,
+ git_repository *repo,
+ const git_index_entry *ancestor,
+ const git_index_entry *ours,
+ const git_index_entry *theirs,
+ const git_merge_file_options *options)
+{
+ git_merge_file_input inputs[3] = { {0} },
+ *ancestor_input = NULL, *our_input = NULL, *their_input = NULL;
+ git_odb *odb = NULL;
+ git_odb_object *odb_object[3] = { 0 };
+ int error = 0;
+
+ assert(out && repo && ours && theirs);
+
+ memset(out, 0x0, sizeof(git_merge_file_result));
+
+ if ((error = git_repository_odb(&odb, repo)) < 0)
+ goto done;
+
+ if (ancestor) {
+ if ((error = git_merge_file__input_from_index(
+ &inputs[0], &odb_object[0], odb, ancestor)) < 0)
+ goto done;
+
+ ancestor_input = &inputs[0];
+ }
+
+ if ((error = git_merge_file__input_from_index(
+ &inputs[1], &odb_object[1], odb, ours)) < 0)
+ goto done;
+
+ our_input = &inputs[1];
+
+ if ((error = git_merge_file__input_from_index(
+ &inputs[2], &odb_object[2], odb, theirs)) < 0)
+ goto done;
+
+ their_input = &inputs[2];
+
+ if ((error = git_merge_file__from_inputs(out,
+ ancestor_input, our_input, their_input, options)) < 0)
+ goto done;
+
+done:
+ git_odb_object_free(odb_object[0]);
+ git_odb_object_free(odb_object[1]);
+ git_odb_object_free(odb_object[2]);
+ git_odb_free(odb);
+
+ return error;
+}
+
+void git_merge_file_result_free(git_merge_file_result *result)
+{
+ if (result == NULL)
+ return;
+
+ git__free((char *)result->path);
+
+ /* xdiff uses malloc() not git_malloc, so we use free(), not git_free() */
+ free((char *)result->ptr);
+}
diff --git a/src/merge_file.h b/src/merge_file.h
new file mode 100644
index 000000000..263391ee3
--- /dev/null
+++ b/src/merge_file.h
@@ -0,0 +1,14 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_filediff_h__
+#define INCLUDE_filediff_h__
+
+#include "xdiff/xdiff.h"
+
+#include "git2/merge.h"
+
+#endif
diff --git a/src/message.c b/src/message.c
new file mode 100644
index 000000000..6c5a2379f
--- /dev/null
+++ b/src/message.c
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "message.h"
+
+static size_t line_length_without_trailing_spaces(const char *line, size_t len)
+{
+ while (len) {
+ unsigned char c = line[len - 1];
+ if (!git__isspace(c))
+ break;
+ len--;
+ }
+
+ return len;
+}
+
+/* Greatly inspired from git.git "stripspace" */
+/* see https://github.com/git/git/blob/497215d8811ac7b8955693ceaad0899ecd894ed2/builtin/stripspace.c#L4-67 */
+int git_message_prettify(git_buf *message_out, const char *message, int strip_comments, char comment_char)
+{
+ const size_t message_len = strlen(message);
+
+ int consecutive_empty_lines = 0;
+ size_t i, line_length, rtrimmed_line_length;
+ char *next_newline;
+
+ git_buf_sanitize(message_out);
+
+ for (i = 0; i < strlen(message); i += line_length) {
+ next_newline = memchr(message + i, '\n', message_len - i);
+
+ if (next_newline != NULL) {
+ line_length = next_newline - (message + i) + 1;
+ } else {
+ line_length = message_len - i;
+ }
+
+ if (strip_comments && line_length && message[i] == comment_char)
+ continue;
+
+ rtrimmed_line_length = line_length_without_trailing_spaces(message + i, line_length);
+
+ if (!rtrimmed_line_length) {
+ consecutive_empty_lines++;
+ continue;
+ }
+
+ if (consecutive_empty_lines > 0 && message_out->size > 0)
+ git_buf_putc(message_out, '\n');
+
+ consecutive_empty_lines = 0;
+ git_buf_put(message_out, message + i, rtrimmed_line_length);
+ git_buf_putc(message_out, '\n');
+ }
+
+ return git_buf_oom(message_out) ? -1 : 0;
+}
diff --git a/src/message.h b/src/message.h
new file mode 100644
index 000000000..3c4b8dc45
--- /dev/null
+++ b/src/message.h
@@ -0,0 +1,15 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_message_h__
+#define INCLUDE_message_h__
+
+#include "git2/message.h"
+#include "buffer.h"
+
+int git_message__prettify(git_buf *message_out, const char *message, int strip_comments);
+
+#endif /* INCLUDE_message_h__ */
diff --git a/src/mwindow.c b/src/mwindow.c
index e9dbfb325..7e5fcdfbc 100644
--- a/src/mwindow.c
+++ b/src/mwindow.c
@@ -1,26 +1,8 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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"
@@ -28,27 +10,21 @@
#include "vector.h"
#include "fileops.h"
#include "map.h"
+#include "global.h"
#define DEFAULT_WINDOW_SIZE \
(sizeof(void*) >= 8 \
- ? 1 * 1024 * 1024 * 1024 \
+ ? 1 * 1024 * 1024 * 1024 \
: 32 * 1024 * 1024)
#define DEFAULT_MAPPED_LIMIT \
- ((1024L * 1024L) * (sizeof(void*) >= 8 ? 8192 : 256))
+ ((1024 * 1024) * (sizeof(void*) >= 8 ? 8192ULL : 256UL))
-/*
- * We need this because each process is only allowed a specific amount
- * of memory. Making it writable should generate one instance per
- * process, but we still need to set a couple of variables.
- */
+size_t git_mwindow__window_size = DEFAULT_WINDOW_SIZE;
+size_t git_mwindow__mapped_limit = DEFAULT_MAPPED_LIMIT;
-static git_mwindow_ctl ctl = {
- 0,
- 0,
- DEFAULT_WINDOW_SIZE,
- DEFAULT_MAPPED_LIMIT
-};
+/* Whenever you want to read or modify this, grab git__mwindow_mutex */
+static git_mwindow_ctl mem_ctl;
/*
* Free all the windows in a sequence, typically because we're done
@@ -56,34 +32,43 @@ static git_mwindow_ctl ctl = {
*/
void git_mwindow_free_all(git_mwindow_file *mwf)
{
- unsigned int i;
+ git_mwindow_ctl *ctl = &mem_ctl;
+ size_t i;
+
+ if (git_mutex_lock(&git__mwindow_mutex)) {
+ giterr_set(GITERR_THREAD, "unable to lock mwindow mutex");
+ return;
+ }
+
/*
* Remove these windows from the global list
*/
- for (i = 0; i < ctl.windowfiles.length; ++i){
- if (git_vector_get(&ctl.windowfiles, i) == mwf) {
- git_vector_remove(&ctl.windowfiles, i);
+ for (i = 0; i < ctl->windowfiles.length; ++i){
+ if (git_vector_get(&ctl->windowfiles, i) == mwf) {
+ git_vector_remove(&ctl->windowfiles, i);
break;
}
}
- if (ctl.windowfiles.length == 0) {
- git_vector_free(&ctl.windowfiles);
- ctl.windowfiles.contents = NULL;
+ if (ctl->windowfiles.length == 0) {
+ git_vector_free(&ctl->windowfiles);
+ ctl->windowfiles.contents = NULL;
}
while (mwf->windows) {
git_mwindow *w = mwf->windows;
assert(w->inuse_cnt == 0);
- ctl.mapped -= w->window_map.len;
- ctl.open_windows--;
+ ctl->mapped -= w->window_map.len;
+ ctl->open_windows--;
git_futils_mmap_free(&w->window_map);
mwf->windows = w->next;
- free(w);
+ git__free(w);
}
+
+ git_mutex_unlock(&git__mwindow_mutex);
}
/*
@@ -99,7 +84,7 @@ int git_mwindow_contains(git_mwindow *win, git_off_t offset)
/*
* Find the least-recently-used window in a file
*/
-void git_mwindow_scan_lru(
+static void git_mwindow_scan_lru(
git_mwindow_file *mwf,
git_mwindow **lru_w,
git_mwindow **lru_l)
@@ -124,99 +109,125 @@ void git_mwindow_scan_lru(
/*
* Close the least recently used window. You should check to see if
- * the file descriptors need closing from time to time.
+ * the file descriptors need closing from time to time. Called under
+ * lock from new_window.
*/
-int git_mwindow_close_lru(git_mwindow_file *mwf)
+static int git_mwindow_close_lru(git_mwindow_file *mwf)
{
- unsigned int i;
- git_mwindow *lru_w = NULL, *lru_l = NULL;
+ git_mwindow_ctl *ctl = &mem_ctl;
+ size_t i;
+ git_mwindow *lru_w = NULL, *lru_l = NULL, **list = &mwf->windows;
- /* FIMXE: Does this give us any advantage? */
+ /* FIXME: Does this give us any advantage? */
if(mwf->windows)
git_mwindow_scan_lru(mwf, &lru_w, &lru_l);
- for (i = 0; i < ctl.windowfiles.length; ++i) {
- git_mwindow_scan_lru(git_vector_get(&ctl.windowfiles, i), &lru_w, &lru_l);
+ for (i = 0; i < ctl->windowfiles.length; ++i) {
+ git_mwindow *last = lru_w;
+ git_mwindow_file *cur = git_vector_get(&ctl->windowfiles, i);
+ git_mwindow_scan_lru(cur, &lru_w, &lru_l);
+ if (lru_w != last)
+ list = &cur->windows;
}
- if (lru_w) {
- git_mwindow_close(&lru_w);
- ctl.mapped -= lru_w->window_map.len;
- git_futils_mmap_free(&lru_w->window_map);
+ if (!lru_w) {
+ giterr_set(GITERR_OS, "Failed to close memory window. Couldn't find LRU");
+ return -1;
+ }
- if (lru_l)
- lru_l->next = lru_w->next;
- else
- mwf->windows = lru_w->next;
+ ctl->mapped -= lru_w->window_map.len;
+ git_futils_mmap_free(&lru_w->window_map);
- free(lru_w);
- ctl.open_windows--;
+ if (lru_l)
+ lru_l->next = lru_w->next;
+ else
+ *list = lru_w->next;
- return GIT_SUCCESS;
- }
+ git__free(lru_w);
+ ctl->open_windows--;
- return git__throw(GIT_ERROR, "Failed to close memory window. Couln't find LRU");
+ return 0;
}
-static git_mwindow *new_window(git_mwindow_file *mwf, git_file fd, git_off_t size, git_off_t offset)
+/* This gets called under lock from git_mwindow_open */
+static git_mwindow *new_window(
+ git_mwindow_file *mwf,
+ git_file fd,
+ git_off_t size,
+ git_off_t offset)
{
- size_t walign = ctl.window_size / 2;
+ git_mwindow_ctl *ctl = &mem_ctl;
+ size_t walign = git_mwindow__window_size / 2;
git_off_t len;
git_mwindow *w;
w = git__malloc(sizeof(*w));
+
if (w == NULL)
- return w;
+ return NULL;
memset(w, 0x0, sizeof(*w));
w->offset = (offset / walign) * walign;
len = size - w->offset;
- if (len > (git_off_t)ctl.window_size)
- len = (git_off_t)ctl.window_size;
+ if (len > (git_off_t)git_mwindow__window_size)
+ len = (git_off_t)git_mwindow__window_size;
- ctl.mapped += (size_t)len;
+ ctl->mapped += (size_t)len;
- while(ctl.mapped_limit < ctl.mapped &&
- git_mwindow_close_lru(mwf) == GIT_SUCCESS) {}
+ while (git_mwindow__mapped_limit < ctl->mapped &&
+ git_mwindow_close_lru(mwf) == 0) /* nop */;
- /* FIXME: Shouldn't we error out if there's an error in closing lru? */
+ /*
+ * We treat `mapped_limit` as a soft limit. If we can't find a
+ * window to close and are above the limit, we still mmap the new
+ * window.
+ */
- if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < GIT_SUCCESS)
- goto cleanup;
+ if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < 0) {
+ git__free(w);
+ return NULL;
+ }
- ctl.mmap_calls++;
- ctl.open_windows++;
+ ctl->mmap_calls++;
+ ctl->open_windows++;
- if (ctl.mapped > ctl.peak_mapped)
- ctl.peak_mapped = ctl.mapped;
+ if (ctl->mapped > ctl->peak_mapped)
+ ctl->peak_mapped = ctl->mapped;
- if (ctl.open_windows > ctl.peak_open_windows)
- ctl.peak_open_windows = ctl.open_windows;
+ if (ctl->open_windows > ctl->peak_open_windows)
+ ctl->peak_open_windows = ctl->open_windows;
return w;
-
-cleanup:
- free(w);
- return NULL;
}
/*
* Open a new window, closing the least recenty used until we have
* enough space. Don't forget to add it to your list
*/
-unsigned char *git_mwindow_open(git_mwindow_file *mwf, git_mwindow **cursor,
- git_off_t offset, int extra, unsigned int *left)
+unsigned char *git_mwindow_open(
+ git_mwindow_file *mwf,
+ git_mwindow **cursor,
+ git_off_t offset,
+ size_t extra,
+ unsigned int *left)
{
+ git_mwindow_ctl *ctl = &mem_ctl;
git_mwindow *w = *cursor;
- if (!w || !git_mwindow_contains(w, offset + extra)) {
+ if (git_mutex_lock(&git__mwindow_mutex)) {
+ giterr_set(GITERR_THREAD, "unable to lock mwindow mutex");
+ return NULL;
+ }
+
+ if (!w || !(git_mwindow_contains(w, offset) && git_mwindow_contains(w, offset + extra))) {
if (w) {
w->inuse_cnt--;
}
for (w = mwf->windows; w; w = w->next) {
- if (git_mwindow_contains(w, offset + extra))
+ if (git_mwindow_contains(w, offset) &&
+ git_mwindow_contains(w, offset + extra))
break;
}
@@ -226,8 +237,10 @@ unsigned char *git_mwindow_open(git_mwindow_file *mwf, git_mwindow **cursor,
*/
if (!w) {
w = new_window(mwf, mwf->fd, mwf->size, offset);
- if (w == NULL)
+ if (w == NULL) {
+ git_mutex_unlock(&git__mwindow_mutex);
return NULL;
+ }
w->next = mwf->windows;
mwf->windows = w;
}
@@ -235,36 +248,72 @@ unsigned char *git_mwindow_open(git_mwindow_file *mwf, git_mwindow **cursor,
/* If we changed w, store it in the cursor */
if (w != *cursor) {
- w->last_used = ctl.used_ctr++;
+ w->last_used = ctl->used_ctr++;
w->inuse_cnt++;
*cursor = w;
}
offset -= w->offset;
- assert(git__is_sizet(offset));
if (left)
*left = (unsigned int)(w->window_map.len - offset);
+ git_mutex_unlock(&git__mwindow_mutex);
return (unsigned char *) w->window_map.data + offset;
}
int git_mwindow_file_register(git_mwindow_file *mwf)
{
- int error;
+ git_mwindow_ctl *ctl = &mem_ctl;
+ int ret;
+
+ if (git_mutex_lock(&git__mwindow_mutex)) {
+ giterr_set(GITERR_THREAD, "unable to lock mwindow mutex");
+ return -1;
+ }
+
+ if (ctl->windowfiles.length == 0 &&
+ git_vector_init(&ctl->windowfiles, 8, NULL) < 0) {
+ git_mutex_unlock(&git__mwindow_mutex);
+ return -1;
+ }
- if (ctl.windowfiles.length == 0 &&
- (error = git_vector_init(&ctl.windowfiles, 8, NULL)) < GIT_SUCCESS)
- return error;
+ ret = git_vector_insert(&ctl->windowfiles, mwf);
+ git_mutex_unlock(&git__mwindow_mutex);
- return git_vector_insert(&ctl.windowfiles, mwf);
+ return ret;
+}
+
+void git_mwindow_file_deregister(git_mwindow_file *mwf)
+{
+ git_mwindow_ctl *ctl = &mem_ctl;
+ git_mwindow_file *cur;
+ size_t i;
+
+ if (git_mutex_lock(&git__mwindow_mutex))
+ return;
+
+ git_vector_foreach(&ctl->windowfiles, i, cur) {
+ if (cur == mwf) {
+ git_vector_remove(&ctl->windowfiles, i);
+ git_mutex_unlock(&git__mwindow_mutex);
+ return;
+ }
+ }
+ git_mutex_unlock(&git__mwindow_mutex);
}
void git_mwindow_close(git_mwindow **window)
{
git_mwindow *w = *window;
if (w) {
+ if (git_mutex_lock(&git__mwindow_mutex)) {
+ giterr_set(GITERR_THREAD, "unable to lock mwindow mutex");
+ return;
+ }
+
w->inuse_cnt--;
+ git_mutex_unlock(&git__mwindow_mutex);
*window = NULL;
}
}
diff --git a/src/mwindow.h b/src/mwindow.h
index 1d4a58453..0018ebbf0 100644
--- a/src/mwindow.h
+++ b/src/mwindow.h
@@ -1,26 +1,8 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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_mwindow__
@@ -28,14 +10,13 @@
#include "map.h"
#include "vector.h"
-#include "fileops.h"
typedef struct git_mwindow {
struct git_mwindow *next;
git_map window_map;
git_off_t offset;
- unsigned int last_used;
- unsigned int inuse_cnt;
+ size_t last_used;
+ size_t inuse_cnt;
} git_mwindow;
typedef struct git_mwindow_file {
@@ -47,8 +28,6 @@ typedef struct git_mwindow_file {
typedef struct git_mwindow_ctl {
size_t mapped;
unsigned int open_windows;
- size_t window_size; /* needs default value */
- size_t mapped_limit; /* needs default value */
unsigned int mmap_calls;
unsigned int peak_open_windows;
size_t peak_mapped;
@@ -58,9 +37,9 @@ typedef struct git_mwindow_ctl {
int git_mwindow_contains(git_mwindow *win, git_off_t offset);
void git_mwindow_free_all(git_mwindow_file *mwf);
-unsigned char *git_mwindow_open(git_mwindow_file *mwf, git_mwindow **cursor, git_off_t offset, int extra, unsigned int *left);
-void git_mwindow_scan_lru(git_mwindow_file *mwf, git_mwindow **lru_w, git_mwindow **lru_l);
+unsigned char *git_mwindow_open(git_mwindow_file *mwf, git_mwindow **cursor, git_off_t offset, size_t extra, unsigned int *left);
int git_mwindow_file_register(git_mwindow_file *mwf);
+void git_mwindow_file_deregister(git_mwindow_file *mwf);
void git_mwindow_close(git_mwindow **w_cursor);
#endif
diff --git a/src/netops.c b/src/netops.c
index 55cb7e45e..a0193a022 100644
--- a/src/netops.c
+++ b/src/netops.c
@@ -1,74 +1,177 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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 _WIN32
-# include <sys/types.h>
-# include <sys/socket.h>
-# include <netdb.h>
+# include <sys/types.h>
+# include <sys/socket.h>
+# include <sys/select.h>
+# include <sys/time.h>
+# include <netdb.h>
+# include <netinet/in.h>
+# include <arpa/inet.h>
#else
-# define _WIN32_WINNT 0x0501
-# include <winsock2.h>
-# include <Ws2tcpip.h>
-# pragma comment(lib, "Ws2_32.lib")
+# include <ws2tcpip.h>
+# ifdef _MSC_VER
+# pragma comment(lib, "ws2_32")
+# endif
+#endif
+
+#ifdef GIT_SSL
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+# include <openssl/x509v3.h>
#endif
+#include <ctype.h>
#include "git2/errors.h"
#include "common.h"
#include "netops.h"
+#include "posix.h"
+#include "buffer.h"
+#include "http_parser.h"
-void gitno_buffer_setup(gitno_buffer *buf, char *data, unsigned int len, int fd)
+#ifdef GIT_WIN32
+static void net_set_error(const char *str)
{
- memset(buf, 0x0, sizeof(gitno_buffer));
- memset(data, 0x0, len);
- buf->data = data;
- buf->len = len - 1;
- buf->offset = 0;
- buf->fd = fd;
+ int error = WSAGetLastError();
+ char * win32_error = git_win32_get_error_message(error);
+
+ if (win32_error) {
+ giterr_set(GITERR_NET, "%s: %s", str, win32_error);
+ git__free(win32_error);
+ } else {
+ giterr_set(GITERR_NET, str);
+ }
}
+#else
+static void net_set_error(const char *str)
+{
+ giterr_set(GITERR_NET, "%s: %s", str, strerror(errno));
+}
+#endif
+
+#ifdef GIT_SSL
+static int ssl_set_error(gitno_ssl *ssl, int error)
+{
+ int err;
+ unsigned long e;
+
+ err = SSL_get_error(ssl->ssl, error);
+
+ assert(err != SSL_ERROR_WANT_READ);
+ assert(err != SSL_ERROR_WANT_WRITE);
+
+ switch (err) {
+ case SSL_ERROR_WANT_CONNECT:
+ case SSL_ERROR_WANT_ACCEPT:
+ giterr_set(GITERR_NET, "SSL error: connection failure\n");
+ break;
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ giterr_set(GITERR_NET, "SSL error: x509 error\n");
+ break;
+ case SSL_ERROR_SYSCALL:
+ e = ERR_get_error();
+ if (e > 0) {
+ giterr_set(GITERR_NET, "SSL error: %s",
+ ERR_error_string(e, NULL));
+ break;
+ } else if (error < 0) {
+ giterr_set(GITERR_OS, "SSL error: syscall failure");
+ break;
+ }
+ giterr_set(GITERR_NET, "SSL error: received early EOF");
+ break;
+ case SSL_ERROR_SSL:
+ e = ERR_get_error();
+ giterr_set(GITERR_NET, "SSL error: %s",
+ ERR_error_string(e, NULL));
+ break;
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ default:
+ giterr_set(GITERR_NET, "SSL error: unknown error");
+ break;
+ }
+ return -1;
+}
+#endif
int gitno_recv(gitno_buffer *buf)
{
+ return buf->recv(buf);
+}
+
+#ifdef GIT_SSL
+static int gitno__recv_ssl(gitno_buffer *buf)
+{
int ret;
- ret = recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0);
- if (ret < 0)
- return git__throw(GIT_EOSERR, "Failed to receive data");
- if (ret == 0) /* Orderly shutdown, so exit */
- return GIT_SUCCESS;
+ do {
+ ret = SSL_read(buf->socket->ssl.ssl, buf->data + buf->offset, buf->len - buf->offset);
+ } while (SSL_get_error(buf->socket->ssl.ssl, ret) == SSL_ERROR_WANT_READ);
+
+ if (ret < 0) {
+ net_set_error("Error receiving socket data");
+ return -1;
+ }
buf->offset += ret;
+ return ret;
+}
+#endif
+
+static int gitno__recv(gitno_buffer *buf)
+{
+ int ret;
+
+ ret = p_recv(buf->socket->socket, buf->data + buf->offset, buf->len - buf->offset, 0);
+ if (ret < 0) {
+ net_set_error("Error receiving socket data");
+ return -1;
+ }
+ buf->offset += ret;
return ret;
}
+void gitno_buffer_setup_callback(
+ gitno_socket *socket,
+ gitno_buffer *buf,
+ char *data,
+ size_t len,
+ int (*recv)(gitno_buffer *buf), void *cb_data)
+{
+ memset(data, 0x0, len);
+ buf->data = data;
+ buf->len = len;
+ buf->offset = 0;
+ buf->socket = socket;
+ buf->recv = recv;
+ buf->cb_data = cb_data;
+}
+
+void gitno_buffer_setup(gitno_socket *socket, gitno_buffer *buf, char *data, size_t len)
+{
+#ifdef GIT_SSL
+ if (socket->ssl.ctx) {
+ gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv_ssl, NULL);
+ return;
+ }
+#endif
+
+ gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv, NULL);
+}
+
/* Consume up to ptr and move the rest of the buffer to the beginning */
void gitno_consume(gitno_buffer *buf, const char *ptr)
{
- int consumed;
+ size_t consumed;
+ assert(ptr - buf->data >= 0);
assert(ptr - buf->data <= (int) buf->len);
consumed = ptr - buf->data;
@@ -79,67 +182,561 @@ void gitno_consume(gitno_buffer *buf, const char *ptr)
}
/* Consume const bytes and move the rest of the buffer to the beginning */
-void gitno_consume_n(gitno_buffer *buf, unsigned int cons)
+void gitno_consume_n(gitno_buffer *buf, size_t cons)
{
memmove(buf->data, buf->data + cons, buf->len - buf->offset);
memset(buf->data + cons, 0x0, buf->len - buf->offset);
buf->offset -= cons;
}
-int gitno_connect(const char *host, const char *port)
+#ifdef GIT_SSL
+
+static int gitno_ssl_teardown(gitno_ssl *ssl)
+{
+ int ret;
+
+ ret = SSL_shutdown(ssl->ssl);
+ if (ret < 0)
+ ret = ssl_set_error(ssl, ret);
+ else
+ ret = 0;
+
+ SSL_free(ssl->ssl);
+ SSL_CTX_free(ssl->ctx);
+ return ret;
+}
+
+#endif
+
+/* Match host names according to RFC 2818 rules */
+int gitno__match_host(const char *pattern, const char *host)
+{
+ for (;;) {
+ char c = tolower(*pattern++);
+
+ if (c == '\0')
+ return *host ? -1 : 0;
+
+ if (c == '*') {
+ c = *pattern;
+ /* '*' at the end matches everything left */
+ if (c == '\0')
+ return 0;
+
+ /*
+ * We've found a pattern, so move towards the next matching
+ * char. The '.' is handled specially because wildcards aren't
+ * allowed to cross subdomains.
+ */
+
+ while(*host) {
+ char h = tolower(*host);
+ if (c == h)
+ return gitno__match_host(pattern, host++);
+ if (h == '.')
+ return gitno__match_host(pattern, host);
+ host++;
+ }
+ return -1;
+ }
+
+ if (c != tolower(*host++))
+ return -1;
+ }
+
+ return -1;
+}
+
+static int check_host_name(const char *name, const char *host)
+{
+ if (!strcasecmp(name, host))
+ return 0;
+
+ if (gitno__match_host(name, host) < 0)
+ return -1;
+
+ return 0;
+}
+
+#ifdef GIT_SSL
+
+static int verify_server_cert(gitno_ssl *ssl, const char *host)
+{
+ X509 *cert;
+ X509_NAME *peer_name;
+ ASN1_STRING *str;
+ unsigned char *peer_cn = NULL;
+ int matched = -1, type = GEN_DNS;
+ GENERAL_NAMES *alts;
+ struct in6_addr addr6;
+ struct in_addr addr4;
+ void *addr;
+ int i = -1,j;
+
+ if (SSL_get_verify_result(ssl->ssl) != X509_V_OK) {
+ giterr_set(GITERR_SSL, "The SSL certificate is invalid");
+ return -1;
+ }
+
+ /* Try to parse the host as an IP address to see if it is */
+ if (p_inet_pton(AF_INET, host, &addr4)) {
+ type = GEN_IPADD;
+ addr = &addr4;
+ } else {
+ if(p_inet_pton(AF_INET6, host, &addr6)) {
+ type = GEN_IPADD;
+ addr = &addr6;
+ }
+ }
+
+
+ cert = SSL_get_peer_certificate(ssl->ssl);
+ if (!cert) {
+ giterr_set(GITERR_SSL, "the server did not provide a certificate");
+ return -1;
+ }
+
+ /* Check the alternative names */
+ alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
+ if (alts) {
+ int num;
+
+ num = sk_GENERAL_NAME_num(alts);
+ for (i = 0; i < num && matched != 1; i++) {
+ const GENERAL_NAME *gn = sk_GENERAL_NAME_value(alts, i);
+ const char *name = (char *) ASN1_STRING_data(gn->d.ia5);
+ size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5);
+
+ /* Skip any names of a type we're not looking for */
+ if (gn->type != type)
+ continue;
+
+ if (type == GEN_DNS) {
+ /* If it contains embedded NULs, don't even try */
+ if (memchr(name, '\0', namelen))
+ continue;
+
+ if (check_host_name(name, host) < 0)
+ matched = 0;
+ else
+ matched = 1;
+ } else if (type == GEN_IPADD) {
+ /* Here name isn't so much a name but a binary representation of the IP */
+ matched = !!memcmp(name, addr, namelen);
+ }
+ }
+ }
+ GENERAL_NAMES_free(alts);
+
+ if (matched == 0)
+ goto cert_fail_name;
+
+ if (matched == 1)
+ return 0;
+
+ /* If no alternative names are available, check the common name */
+ peer_name = X509_get_subject_name(cert);
+ if (peer_name == NULL)
+ goto on_error;
+
+ if (peer_name) {
+ /* Get the index of the last CN entry */
+ while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0)
+ i = j;
+ }
+
+ if (i < 0)
+ goto on_error;
+
+ str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i));
+ if (str == NULL)
+ goto on_error;
+
+ /* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */
+ if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) {
+ int size = ASN1_STRING_length(str);
+
+ if (size > 0) {
+ peer_cn = OPENSSL_malloc(size + 1);
+ GITERR_CHECK_ALLOC(peer_cn);
+ memcpy(peer_cn, ASN1_STRING_data(str), size);
+ peer_cn[size] = '\0';
+ }
+ } else {
+ int size = ASN1_STRING_to_UTF8(&peer_cn, str);
+ GITERR_CHECK_ALLOC(peer_cn);
+ if (memchr(peer_cn, '\0', size))
+ goto cert_fail_name;
+ }
+
+ if (check_host_name((char *)peer_cn, host) < 0)
+ goto cert_fail_name;
+
+ OPENSSL_free(peer_cn);
+
+ return 0;
+
+on_error:
+ OPENSSL_free(peer_cn);
+ return ssl_set_error(ssl, 0);
+
+cert_fail_name:
+ OPENSSL_free(peer_cn);
+ giterr_set(GITERR_SSL, "hostname does not match certificate");
+ return -1;
+}
+
+static int ssl_setup(gitno_socket *socket, const char *host, int flags)
{
- struct addrinfo *info, *p;
+ int ret;
+
+ SSL_library_init();
+ SSL_load_error_strings();
+ socket->ssl.ctx = SSL_CTX_new(SSLv23_method());
+ if (socket->ssl.ctx == NULL)
+ return ssl_set_error(&socket->ssl, 0);
+
+ SSL_CTX_set_mode(socket->ssl.ctx, SSL_MODE_AUTO_RETRY);
+ SSL_CTX_set_verify(socket->ssl.ctx, SSL_VERIFY_NONE, NULL);
+ if (!SSL_CTX_set_default_verify_paths(socket->ssl.ctx))
+ return ssl_set_error(&socket->ssl, 0);
+
+ socket->ssl.ssl = SSL_new(socket->ssl.ctx);
+ if (socket->ssl.ssl == NULL)
+ return ssl_set_error(&socket->ssl, 0);
+
+ if((ret = SSL_set_fd(socket->ssl.ssl, socket->socket)) == 0)
+ return ssl_set_error(&socket->ssl, ret);
+
+ if ((ret = SSL_connect(socket->ssl.ssl)) <= 0)
+ return ssl_set_error(&socket->ssl, ret);
+
+ if (GITNO_CONNECT_SSL_NO_CHECK_CERT & flags)
+ return 0;
+
+ return verify_server_cert(&socket->ssl, host);
+}
+#endif
+
+static int gitno__close(GIT_SOCKET s)
+{
+#ifdef GIT_WIN32
+ if (SOCKET_ERROR == closesocket(s))
+ return -1;
+
+ if (0 != WSACleanup()) {
+ giterr_set(GITERR_OS, "Winsock cleanup failed");
+ return -1;
+ }
+
+ return 0;
+#else
+ return close(s);
+#endif
+}
+
+int gitno_connect(gitno_socket *s_out, const char *host, const char *port, int flags)
+{
+ struct addrinfo *info = NULL, *p;
struct addrinfo hints;
- int ret, error = GIT_SUCCESS;
- int s;
+ GIT_SOCKET s = INVALID_SOCKET;
+ int ret;
+
+#ifdef GIT_WIN32
+ /* on win32, the WSA context needs to be initialized
+ * before any socket calls can be performed */
+ WSADATA wsd;
+
+ if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) {
+ giterr_set(GITERR_OS, "Winsock init failed");
+ return -1;
+ }
+
+ if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) {
+ WSACleanup();
+ giterr_set(GITERR_OS, "Winsock init failed");
+ return -1;
+ }
+#endif
+
+ /* Zero the socket structure provided */
+ memset(s_out, 0x0, sizeof(gitno_socket));
memset(&hints, 0x0, sizeof(struct addrinfo));
- hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
+ hints.ai_family = AF_UNSPEC;
- ret = getaddrinfo(host, port, &hints, &info);
- if (ret != 0) {
- error = GIT_EOSERR;
- goto cleanup;
+ if ((ret = p_getaddrinfo(host, port, &hints, &info)) < 0) {
+ giterr_set(GITERR_NET,
+ "Failed to resolve address for %s: %s", host, p_gai_strerror(ret));
+ return -1;
}
for (p = info; p != NULL; p = p->ai_next) {
s = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
- if (s < 0) {
- error = GIT_EOSERR;
- goto cleanup;
+
+ if (s == INVALID_SOCKET) {
+ net_set_error("error creating socket");
+ break;
}
- ret = connect(s, p->ai_addr, p->ai_addrlen);
+ if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0)
+ break;
+
/* If we can't connect, try the next one */
+ gitno__close(s);
+ s = INVALID_SOCKET;
+ }
+
+ /* Oops, we couldn't connect to any address */
+ if (s == INVALID_SOCKET && p == NULL) {
+ giterr_set(GITERR_OS, "Failed to connect to %s", host);
+ p_freeaddrinfo(info);
+ return -1;
+ }
+
+ s_out->socket = s;
+ p_freeaddrinfo(info);
+
+#ifdef GIT_SSL
+ if ((flags & GITNO_CONNECT_SSL) && ssl_setup(s_out, host, flags) < 0)
+ return -1;
+#else
+ /* SSL is not supported */
+ if (flags & GITNO_CONNECT_SSL) {
+ giterr_set(GITERR_OS, "SSL is not supported by this copy of libgit2.");
+ return -1;
+ }
+#endif
+
+ return 0;
+}
+
+#ifdef GIT_SSL
+static int gitno_send_ssl(gitno_ssl *ssl, const char *msg, size_t len, int flags)
+{
+ int ret;
+ size_t off = 0;
+
+ GIT_UNUSED(flags);
+
+ while (off < len) {
+ 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(gitno_socket *socket, const char *msg, size_t len, int flags)
+{
+ int ret;
+ size_t off = 0;
+
+#ifdef GIT_SSL
+ if (socket->ssl.ctx)
+ return gitno_send_ssl(&socket->ssl, msg, len, flags);
+#endif
+
+ while (off < len) {
+ errno = 0;
+ ret = p_send(socket->socket, msg + off, len - off, flags);
if (ret < 0) {
- continue;
+ net_set_error("Error sending data");
+ return -1;
}
- /* Return the socket */
- error = s;
+ off += ret;
+ }
+
+ return (int)off;
+}
+
+int gitno_close(gitno_socket *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;
+ struct timeval tv;
+
+ tv.tv_sec = sec;
+ tv.tv_usec = usec;
+
+ FD_ZERO(&fds);
+ FD_SET(buf->socket->socket, &fds);
+
+ /* The select(2) interface is silly */
+ return select((int)buf->socket->socket + 1, &fds, NULL, NULL, &tv);
+}
+
+static const char *prefix_http = "http://";
+static const char *prefix_https = "https://";
+
+int gitno_connection_data_from_url(
+ gitno_connection_data *data,
+ const char *url,
+ const char *service_suffix)
+{
+ int error = -1;
+ const char *default_port = NULL, *path_search_start = NULL;
+ char *original_host = NULL;
+
+ /* service_suffix is optional */
+ assert(data && url);
+
+ /* Save these for comparison later */
+ original_host = data->host;
+ data->host = NULL;
+ gitno_connection_data_free_ptrs(data);
+
+ if (!git__prefixcmp(url, prefix_http)) {
+ path_search_start = url + strlen(prefix_http);
+ default_port = "80";
+
+ if (data->use_ssl) {
+ giterr_set(GITERR_NET, "Redirect from HTTPS to HTTP is not allowed");
+ goto cleanup;
+ }
+ } else if (!git__prefixcmp(url, prefix_https)) {
+ path_search_start = url + strlen(prefix_https);
+ default_port = "443";
+ data->use_ssl = true;
+ } else if (url[0] == '/')
+ default_port = data->use_ssl ? "443" : "80";
+
+ if (!default_port) {
+ giterr_set(GITERR_NET, "Unrecognized URL prefix");
goto cleanup;
}
- /* Oops, we couldn't connect to any address */
- error = GIT_EOSERR;
+ error = gitno_extract_url_parts(
+ &data->host, &data->port, &data->path, &data->user, &data->pass,
+ url, default_port);
+
+ if (url[0] == '/') {
+ /* Relative redirect; reuse original host name and port */
+ path_search_start = url;
+ git__free(data->host);
+ data->host = original_host;
+ original_host = NULL;
+ }
+
+ if (!error) {
+ const char *path = strchr(path_search_start, '/');
+ size_t pathlen = strlen(path);
+ size_t suffixlen = service_suffix ? strlen(service_suffix) : 0;
+
+ if (suffixlen &&
+ !memcmp(path + pathlen - suffixlen, service_suffix, suffixlen)) {
+ git__free(data->path);
+ data->path = git__strndup(path, pathlen - suffixlen);
+ } else {
+ git__free(data->path);
+ data->path = git__strdup(path);
+ }
+
+ /* Check for errors in the resulting data */
+ if (original_host && url[0] != '/' && strcmp(original_host, data->host)) {
+ giterr_set(GITERR_NET, "Cross host redirect not allowed");
+ error = -1;
+ }
+ }
cleanup:
- freeaddrinfo(info);
+ if (original_host) git__free(original_host);
return error;
}
-int gitno_send(int s, const char *msg, int len, int flags)
+void gitno_connection_data_free_ptrs(gitno_connection_data *d)
{
- int ret, off = 0;
+ git__free(d->host); d->host = NULL;
+ git__free(d->port); d->port = NULL;
+ git__free(d->path); d->path = NULL;
+ git__free(d->user); d->user = NULL;
+ git__free(d->pass); d->pass = NULL;
+}
- while (off < len) {
- ret = send(s, msg + off, len - off, flags);
- if (ret < 0)
- return GIT_EOSERR;
+#define hex2c(c) ((c | 32) % 39 - 9)
+static char* unescape(char *str)
+{
+ int x, y;
+ int len = (int)strlen(str);
- off += ret;
+ for (x=y=0; str[y]; ++x, ++y) {
+ if ((str[x] = str[y]) == '%') {
+ if (y < len-2 && isxdigit(str[y+1]) && isxdigit(str[y+2])) {
+ str[x] = (hex2c(str[y+1]) << 4) + hex2c(str[y+2]);
+ y += 2;
+ }
+ }
}
+ str[x] = '\0';
+ return str;
+}
- return off;
+int gitno_extract_url_parts(
+ char **host,
+ char **port,
+ char **path,
+ char **username,
+ char **password,
+ const char *url,
+ const char *default_port)
+{
+ struct http_parser_url u = {0};
+ const char *_host, *_port, *_path, *_userinfo;
+
+ if (http_parser_parse_url(url, strlen(url), false, &u)) {
+ giterr_set(GITERR_NET, "Malformed URL '%s'", url);
+ return GIT_EINVALIDSPEC;
+ }
+
+ _host = url+u.field_data[UF_HOST].off;
+ _port = url+u.field_data[UF_PORT].off;
+ _path = url+u.field_data[UF_PATH].off;
+ _userinfo = url+u.field_data[UF_USERINFO].off;
+
+ if (u.field_set & (1 << UF_HOST)) {
+ *host = git__substrdup(_host, u.field_data[UF_HOST].len);
+ GITERR_CHECK_ALLOC(*host);
+ }
+
+ if (u.field_set & (1 << UF_PORT))
+ *port = git__substrdup(_port, u.field_data[UF_PORT].len);
+ else
+ *port = git__strdup(default_port);
+ GITERR_CHECK_ALLOC(*port);
+
+ if (u.field_set & (1 << UF_PATH)) {
+ *path = git__substrdup(_path, u.field_data[UF_PATH].len);
+ GITERR_CHECK_ALLOC(*path);
+ }
+
+ if (u.field_set & (1 << UF_USERINFO)) {
+ const char *colon = memchr(_userinfo, ':', u.field_data[UF_USERINFO].len);
+ if (colon) {
+ *username = unescape(git__substrdup(_userinfo, colon - _userinfo));
+ *password = unescape(git__substrdup(colon+1, u.field_data[UF_USERINFO].len - (colon+1-_userinfo)));
+ GITERR_CHECK_ALLOC(*password);
+ } else {
+ *username = git__substrdup(_userinfo, u.field_data[UF_USERINFO].len);
+ }
+ GITERR_CHECK_ALLOC(*username);
+
+ }
+
+ return 0;
}
diff --git a/src/netops.h b/src/netops.h
index c828ed9f3..8e3a2524f 100644
--- a/src/netops.h
+++ b/src/netops.h
@@ -1,22 +1,114 @@
/*
- * netops.h - convencience functions for networking
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_netops_h__
#define INCLUDE_netops_h__
-typedef struct gitno_buffer {
+#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;
- unsigned int len;
- unsigned int offset;
- int fd;
-} gitno_buffer;
+ size_t len;
+ size_t offset;
+ gitno_socket *socket;
+ int (*recv)(struct gitno_buffer *buffer);
+ 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_buffer *buf, char *data, unsigned int len, int fd);
+/**
+ * Check if the name in a cert matches the wanted hostname
+ *
+ * Check if a pattern from a certificate matches the hostname we
+ * wanted to connect to according to RFC2818 rules (which specifies
+ * HTTP over TLS). Mainly, an asterisk matches anything, but is
+ * limited to a single url component.
+ *
+ * Note that this does not set an error message. It expects the user
+ * to provide the message for the user.
+ */
+int gitno__match_host(const char *pattern, const char *host);
+
+void gitno_buffer_setup(gitno_socket *t, gitno_buffer *buf, char *data, size_t len);
+void gitno_buffer_setup_callback(gitno_socket *t, gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data);
int gitno_recv(gitno_buffer *buf);
+
void gitno_consume(gitno_buffer *buf, const char *ptr);
-void gitno_consume_n(gitno_buffer *buf, unsigned int cons);
+void gitno_consume_n(gitno_buffer *buf, size_t cons);
+
+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);
+
+typedef struct gitno_connection_data {
+ char *host;
+ char *port;
+ char *path;
+ char *user;
+ char *pass;
+ bool use_ssl;
+} gitno_connection_data;
+
+/*
+ * This replaces all the pointers in `data` with freshly-allocated strings,
+ * that the caller is responsible for freeing.
+ * `gitno_connection_data_free_ptrs` is good for this.
+ */
+
+int gitno_connection_data_from_url(
+ gitno_connection_data *data,
+ const char *url,
+ const char *service_suffix);
+
+/* This frees all the pointers IN the struct, but not the struct itself. */
+void gitno_connection_data_free_ptrs(gitno_connection_data *data);
-int gitno_connect(const char *host, const char *port);
-int gitno_send(int s, const char *msg, int len, int flags);
+int gitno_extract_url_parts(
+ char **host,
+ char **port,
+ char **path,
+ char **username,
+ char **password,
+ const char *url,
+ const char *default_port);
#endif
diff --git a/src/notes.c b/src/notes.c
new file mode 100644
index 000000000..ffe5d345a
--- /dev/null
+++ b/src/notes.c
@@ -0,0 +1,649 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "notes.h"
+
+#include "git2.h"
+#include "refs.h"
+#include "config.h"
+#include "iterator.h"
+#include "signature.h"
+
+static int note_error_notfound(void)
+{
+ giterr_set(GITERR_INVALID, "Note could not be found");
+ return GIT_ENOTFOUND;
+}
+
+static int find_subtree_in_current_level(
+ git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ const char *annotated_object_sha,
+ int fanout)
+{
+ size_t i;
+ const git_tree_entry *entry;
+
+ *out = NULL;
+
+ if (parent == NULL)
+ return note_error_notfound();
+
+ for (i = 0; i < git_tree_entrycount(parent); i++) {
+ entry = git_tree_entry_byindex(parent, i);
+
+ if (!git__ishex(git_tree_entry_name(entry)))
+ continue;
+
+ if (S_ISDIR(git_tree_entry_filemode(entry))
+ && strlen(git_tree_entry_name(entry)) == 2
+ && !strncmp(git_tree_entry_name(entry), annotated_object_sha + fanout, 2))
+ return git_tree_lookup(out, repo, git_tree_entry_id(entry));
+
+ /* Not a DIR, so do we have an already existing blob? */
+ if (!strcmp(git_tree_entry_name(entry), annotated_object_sha + fanout))
+ return GIT_EEXISTS;
+ }
+
+ return note_error_notfound();
+}
+
+static int find_subtree_r(git_tree **out, git_tree *root,
+ git_repository *repo, const char *target, int *fanout)
+{
+ int error;
+ git_tree *subtree = NULL;
+
+ *out = NULL;
+
+ error = find_subtree_in_current_level(&subtree, repo, root, target, *fanout);
+ if (error == GIT_EEXISTS)
+ return git_tree_lookup(out, repo, git_tree_id(root));
+
+ if (error < 0)
+ return error;
+
+ *fanout += 2;
+ error = find_subtree_r(out, subtree, repo, target, fanout);
+ git_tree_free(subtree);
+
+ return error;
+}
+
+static int find_blob(git_oid *blob, git_tree *tree, const char *target)
+{
+ size_t i;
+ const git_tree_entry *entry;
+
+ for (i=0; i<git_tree_entrycount(tree); i++) {
+ entry = git_tree_entry_byindex(tree, i);
+
+ if (!strcmp(git_tree_entry_name(entry), target)) {
+ /* found matching note object - return */
+
+ git_oid_cpy(blob, git_tree_entry_id(entry));
+ return 0;
+ }
+ }
+
+ return note_error_notfound();
+}
+
+static int tree_write(
+ git_tree **out,
+ git_repository *repo,
+ git_tree *source_tree,
+ const git_oid *object_oid,
+ const char *treeentry_name,
+ unsigned int attributes)
+{
+ int error;
+ git_treebuilder *tb = NULL;
+ const git_tree_entry *entry;
+ git_oid tree_oid;
+
+ if ((error = git_treebuilder_create(&tb, source_tree)) < 0)
+ goto cleanup;
+
+ if (object_oid) {
+ if ((error = git_treebuilder_insert(
+ &entry, tb, treeentry_name, object_oid, attributes)) < 0)
+ goto cleanup;
+ } else {
+ if ((error = git_treebuilder_remove(tb, treeentry_name)) < 0)
+ goto cleanup;
+ }
+
+ if ((error = git_treebuilder_write(&tree_oid, repo, tb)) < 0)
+ goto cleanup;
+
+ error = git_tree_lookup(out, repo, &tree_oid);
+
+cleanup:
+ git_treebuilder_free(tb);
+ return error;
+}
+
+static int manipulate_note_in_tree_r(
+ git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ git_oid *note_oid,
+ const char *annotated_object_sha,
+ int fanout,
+ int (*note_exists_cb)(
+ git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ git_oid *note_oid,
+ const char *annotated_object_sha,
+ int fanout,
+ int current_error),
+ int (*note_notfound_cb)(
+ git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ git_oid *note_oid,
+ const char *annotated_object_sha,
+ int fanout,
+ int current_error))
+{
+ int error;
+ git_tree *subtree = NULL, *new = NULL;
+ char subtree_name[3];
+
+ error = find_subtree_in_current_level(
+ &subtree, repo, parent, annotated_object_sha, fanout);
+
+ if (error == GIT_EEXISTS) {
+ error = note_exists_cb(
+ out, repo, parent, note_oid, annotated_object_sha, fanout, error);
+ goto cleanup;
+ }
+
+ if (error == GIT_ENOTFOUND) {
+ error = note_notfound_cb(
+ out, repo, parent, note_oid, annotated_object_sha, fanout, error);
+ goto cleanup;
+ }
+
+ if (error < 0)
+ goto cleanup;
+
+ /* An existing fanout has been found, let's dig deeper */
+ error = manipulate_note_in_tree_r(
+ &new, repo, subtree, note_oid, annotated_object_sha,
+ fanout + 2, note_exists_cb, note_notfound_cb);
+
+ if (error < 0)
+ goto cleanup;
+
+ strncpy(subtree_name, annotated_object_sha + fanout, 2);
+ subtree_name[2] = '\0';
+
+ error = tree_write(out, repo, parent, git_tree_id(new),
+ subtree_name, GIT_FILEMODE_TREE);
+
+
+cleanup:
+ git_tree_free(new);
+ git_tree_free(subtree);
+ return error;
+}
+
+static int remove_note_in_tree_eexists_cb(
+ git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ git_oid *note_oid,
+ const char *annotated_object_sha,
+ int fanout,
+ int current_error)
+{
+ GIT_UNUSED(note_oid);
+ GIT_UNUSED(current_error);
+
+ return tree_write(out, repo, parent, NULL, annotated_object_sha + fanout, 0);
+}
+
+static int remove_note_in_tree_enotfound_cb(
+ git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ git_oid *note_oid,
+ const char *annotated_object_sha,
+ int fanout,
+ int current_error)
+{
+ GIT_UNUSED(out);
+ GIT_UNUSED(repo);
+ GIT_UNUSED(parent);
+ GIT_UNUSED(note_oid);
+ GIT_UNUSED(fanout);
+
+ giterr_set(GITERR_REPOSITORY, "Object '%s' has no note", annotated_object_sha);
+ return current_error;
+}
+
+static int insert_note_in_tree_eexists_cb(git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ git_oid *note_oid,
+ const char *annotated_object_sha,
+ int fanout,
+ int current_error)
+{
+ GIT_UNUSED(out);
+ GIT_UNUSED(repo);
+ GIT_UNUSED(parent);
+ GIT_UNUSED(note_oid);
+ GIT_UNUSED(fanout);
+
+ giterr_set(GITERR_REPOSITORY, "Note for '%s' exists already", annotated_object_sha);
+ return current_error;
+}
+
+static int insert_note_in_tree_enotfound_cb(git_tree **out,
+ git_repository *repo,
+ git_tree *parent,
+ git_oid *note_oid,
+ const char *annotated_object_sha,
+ int fanout,
+ int current_error)
+{
+ GIT_UNUSED(current_error);
+
+ /* No existing fanout at this level, insert in place */
+ return tree_write(
+ out,
+ repo,
+ parent,
+ note_oid,
+ annotated_object_sha + fanout,
+ GIT_FILEMODE_BLOB);
+}
+
+static int note_write(git_oid *out,
+ git_repository *repo,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *notes_ref,
+ const char *note,
+ git_tree *commit_tree,
+ const char *target,
+ git_commit **parents,
+ int allow_note_overwrite)
+{
+ int error;
+ git_oid oid;
+ git_tree *tree = NULL;
+
+ // TODO: should we apply filters?
+ /* create note object */
+ if ((error = git_blob_create_frombuffer(&oid, repo, note, strlen(note))) < 0)
+ goto cleanup;
+
+ if ((error = manipulate_note_in_tree_r(
+ &tree, repo, commit_tree, &oid, target, 0,
+ allow_note_overwrite ? insert_note_in_tree_enotfound_cb : insert_note_in_tree_eexists_cb,
+ insert_note_in_tree_enotfound_cb)) < 0)
+ goto cleanup;
+
+ if (out)
+ git_oid_cpy(out, &oid);
+
+ error = git_commit_create(&oid, repo, notes_ref, author, committer,
+ NULL, GIT_NOTES_DEFAULT_MSG_ADD,
+ tree, *parents == NULL ? 0 : 1, (const git_commit **) parents);
+
+cleanup:
+ git_tree_free(tree);
+ return error;
+}
+
+static int note_new(git_note **out, git_oid *note_oid, git_blob *blob)
+{
+ git_note *note = NULL;
+
+ note = (git_note *)git__malloc(sizeof(git_note));
+ GITERR_CHECK_ALLOC(note);
+
+ git_oid_cpy(&note->id, note_oid);
+ note->message = git__strdup((char *)git_blob_rawcontent(blob));
+ GITERR_CHECK_ALLOC(note->message);
+
+ *out = note;
+
+ return 0;
+}
+
+static int note_lookup(
+ git_note **out, git_repository *repo, git_tree *tree, const char *target)
+{
+ int error, fanout = 0;
+ git_oid oid;
+ git_blob *blob = NULL;
+ git_note *note = NULL;
+ git_tree *subtree = NULL;
+
+ if ((error = find_subtree_r(&subtree, tree, repo, target, &fanout)) < 0)
+ goto cleanup;
+
+ if ((error = find_blob(&oid, subtree, target + fanout)) < 0)
+ goto cleanup;
+
+ if ((error = git_blob_lookup(&blob, repo, &oid)) < 0)
+ goto cleanup;
+
+ if ((error = note_new(&note, &oid, blob)) < 0)
+ goto cleanup;
+
+ *out = note;
+
+cleanup:
+ git_tree_free(subtree);
+ git_blob_free(blob);
+ return error;
+}
+
+static int note_remove(git_repository *repo,
+ const git_signature *author, const git_signature *committer,
+ const char *notes_ref, git_tree *tree,
+ const char *target, git_commit **parents)
+{
+ int error;
+ git_tree *tree_after_removal = NULL;
+ git_oid oid;
+
+ if ((error = manipulate_note_in_tree_r(
+ &tree_after_removal, repo, tree, NULL, target, 0,
+ remove_note_in_tree_eexists_cb, remove_note_in_tree_enotfound_cb)) < 0)
+ goto cleanup;
+
+ error = git_commit_create(&oid, repo, notes_ref, author, committer,
+ NULL, GIT_NOTES_DEFAULT_MSG_RM,
+ tree_after_removal,
+ *parents == NULL ? 0 : 1,
+ (const git_commit **) parents);
+
+cleanup:
+ git_tree_free(tree_after_removal);
+ return error;
+}
+
+static int note_get_default_ref(const char **out, git_repository *repo)
+{
+ git_config *cfg;
+ int ret = git_repository_config__weakptr(&cfg, repo);
+
+ *out = (ret != 0) ? NULL : git_config__get_string_force(
+ cfg, "core.notesref", GIT_NOTES_DEFAULT_REF);
+
+ return ret;
+}
+
+static int normalize_namespace(const char **notes_ref, git_repository *repo)
+{
+ if (*notes_ref)
+ return 0;
+
+ return note_get_default_ref(notes_ref, repo);
+}
+
+static int retrieve_note_tree_and_commit(
+ git_tree **tree_out,
+ git_commit **commit_out,
+ git_repository *repo,
+ const char **notes_ref)
+{
+ int error;
+ git_oid oid;
+
+ if ((error = normalize_namespace(notes_ref, repo)) < 0)
+ return error;
+
+ if ((error = git_reference_name_to_id(&oid, repo, *notes_ref)) < 0)
+ return error;
+
+ if (git_commit_lookup(commit_out, repo, &oid) < 0)
+ return error;
+
+ if ((error = git_commit_tree(tree_out, *commit_out)) < 0)
+ return error;
+
+ return 0;
+}
+
+int git_note_read(git_note **out, git_repository *repo,
+ const char *notes_ref, const git_oid *oid)
+{
+ int error;
+ char *target = NULL;
+ git_tree *tree = NULL;
+ git_commit *commit = NULL;
+
+ target = git_oid_allocfmt(oid);
+ GITERR_CHECK_ALLOC(target);
+
+ if (!(error = retrieve_note_tree_and_commit(
+ &tree, &commit, repo, &notes_ref)))
+ error = note_lookup(out, repo, tree, target);
+
+ git__free(target);
+ git_tree_free(tree);
+ git_commit_free(commit);
+ return error;
+}
+
+int git_note_create(
+ git_oid *out,
+ git_repository *repo,
+ const git_signature *author,
+ const git_signature *committer,
+ const char *notes_ref,
+ const git_oid *oid,
+ const char *note,
+ int allow_note_overwrite)
+{
+ int error;
+ char *target = NULL;
+ git_commit *commit = NULL;
+ git_tree *tree = NULL;
+
+ target = git_oid_allocfmt(oid);
+ GITERR_CHECK_ALLOC(target);
+
+ error = retrieve_note_tree_and_commit(&tree, &commit, repo, &notes_ref);
+
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto cleanup;
+
+ error = note_write(out, repo, author, committer, notes_ref,
+ note, tree, target, &commit, allow_note_overwrite);
+
+cleanup:
+ git__free(target);
+ git_commit_free(commit);
+ git_tree_free(tree);
+ return error;
+}
+
+int git_note_remove(git_repository *repo, const char *notes_ref,
+ const git_signature *author, const git_signature *committer,
+ const git_oid *oid)
+{
+ int error;
+ char *target = NULL;
+ git_commit *commit = NULL;
+ git_tree *tree = NULL;
+
+ target = git_oid_allocfmt(oid);
+ GITERR_CHECK_ALLOC(target);
+
+ if (!(error = retrieve_note_tree_and_commit(
+ &tree, &commit, repo, &notes_ref)))
+ error = note_remove(
+ repo, author, committer, notes_ref, tree, target, &commit);
+
+ git__free(target);
+ git_commit_free(commit);
+ git_tree_free(tree);
+ return error;
+}
+
+int git_note_default_ref(const char **out, git_repository *repo)
+{
+ assert(repo);
+ return note_get_default_ref(out, repo);
+}
+
+const char * git_note_message(const git_note *note)
+{
+ assert(note);
+ return note->message;
+}
+
+const git_oid * git_note_id(const git_note *note)
+{
+ assert(note);
+ return &note->id;
+}
+
+void git_note_free(git_note *note)
+{
+ if (note == NULL)
+ return;
+
+ git__free(note->message);
+ git__free(note);
+}
+
+static int process_entry_path(
+ const char* entry_path,
+ git_oid *annotated_object_id)
+{
+ int error = 0;
+ size_t i = 0, j = 0, len;
+ git_buf buf = GIT_BUF_INIT;
+
+ if ((error = git_buf_puts(&buf, entry_path)) < 0)
+ goto cleanup;
+
+ len = git_buf_len(&buf);
+
+ while (i < len) {
+ if (buf.ptr[i] == '/') {
+ i++;
+ continue;
+ }
+
+ if (git__fromhex(buf.ptr[i]) < 0) {
+ /* This is not a note entry */
+ goto cleanup;
+ }
+
+ if (i != j)
+ buf.ptr[j] = buf.ptr[i];
+
+ i++;
+ j++;
+ }
+
+ buf.ptr[j] = '\0';
+ buf.size = j;
+
+ if (j != GIT_OID_HEXSZ) {
+ /* This is not a note entry */
+ goto cleanup;
+ }
+
+ error = git_oid_fromstr(annotated_object_id, buf.ptr);
+
+cleanup:
+ git_buf_free(&buf);
+ return error;
+}
+
+int git_note_foreach(
+ git_repository *repo,
+ const char *notes_ref,
+ git_note_foreach_cb note_cb,
+ void *payload)
+{
+ int error;
+ git_note_iterator *iter = NULL;
+ git_oid note_id, annotated_id;
+
+ if ((error = git_note_iterator_new(&iter, repo, notes_ref)) < 0)
+ return error;
+
+ while (!(error = git_note_next(&note_id, &annotated_id, iter))) {
+ if ((error = note_cb(&note_id, &annotated_id, payload)) != 0) {
+ giterr_set_after_callback(error);
+ break;
+ }
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ git_note_iterator_free(iter);
+ return error;
+}
+
+
+void git_note_iterator_free(git_note_iterator *it)
+{
+ if (it == NULL)
+ return;
+
+ git_iterator_free(it);
+}
+
+
+int git_note_iterator_new(
+ git_note_iterator **it,
+ git_repository *repo,
+ const char *notes_ref)
+{
+ int error;
+ git_commit *commit = NULL;
+ git_tree *tree = NULL;
+
+ error = retrieve_note_tree_and_commit(&tree, &commit, repo, &notes_ref);
+ if (error < 0)
+ goto cleanup;
+
+ if ((error = git_iterator_for_tree(it, tree, 0, NULL, NULL)) < 0)
+ git_iterator_free(*it);
+
+cleanup:
+ git_tree_free(tree);
+ git_commit_free(commit);
+
+ return error;
+}
+
+int git_note_next(
+ git_oid* note_id,
+ git_oid* annotated_id,
+ git_note_iterator *it)
+{
+ int error;
+ const git_index_entry *item;
+
+ if ((error = git_iterator_current(&item, it)) < 0)
+ return error;
+
+ git_oid_cpy(note_id, &item->id);
+
+ if (!(error = process_entry_path(item->path, annotated_id)))
+ git_iterator_advance(NULL, it);
+
+ return error;
+}
diff --git a/src/notes.h b/src/notes.h
new file mode 100644
index 000000000..e9cfa00fa
--- /dev/null
+++ b/src/notes.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_note_h__
+#define INCLUDE_note_h__
+
+#include "common.h"
+
+#include "git2/oid.h"
+#include "git2/types.h"
+
+#define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
+
+#define GIT_NOTES_DEFAULT_MSG_ADD \
+ "Notes added by 'git_note_create' from libgit2"
+
+#define GIT_NOTES_DEFAULT_MSG_RM \
+ "Notes removed by 'git_note_remove' from libgit2"
+
+struct git_note {
+ git_oid id;
+
+ char *message;
+};
+
+#endif /* INCLUDE_notes_h__ */
diff --git a/src/object.c b/src/object.c
index 8b07197f1..93068b85f 100644
--- a/src/object.c
+++ b/src/object.c
@@ -1,29 +1,9 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
*/
-#include <stdarg.h>
-
#include "git2/object.h"
#include "common.h"
@@ -36,102 +16,155 @@
static const int OBJECT_BASE_SIZE = 4096;
-static struct {
- const char *str; /* type name string */
- int loose; /* valid loose object type flag */
+typedef struct {
+ const char *str; /* type name string */
size_t size; /* size in bytes of the object structure */
-} git_objects_table[] = {
+
+ int (*parse)(void *self, git_odb_object *obj);
+ void (*free)(void *self);
+} git_object_def;
+
+static git_object_def git_objects_table[] = {
/* 0 = GIT_OBJ__EXT1 */
- { "", 0, 0},
+ { "", 0, NULL, NULL },
/* 1 = GIT_OBJ_COMMIT */
- { "commit", 1, sizeof(struct git_commit)},
+ { "commit", sizeof(git_commit), git_commit__parse, git_commit__free },
/* 2 = GIT_OBJ_TREE */
- { "tree", 1, sizeof(struct git_tree) },
+ { "tree", sizeof(git_tree), git_tree__parse, git_tree__free },
/* 3 = GIT_OBJ_BLOB */
- { "blob", 1, sizeof(struct git_blob) },
+ { "blob", sizeof(git_blob), git_blob__parse, git_blob__free },
/* 4 = GIT_OBJ_TAG */
- { "tag", 1, sizeof(struct git_tag) },
+ { "tag", sizeof(git_tag), git_tag__parse, git_tag__free },
/* 5 = GIT_OBJ__EXT2 */
- { "", 0, 0 },
-
+ { "", 0, NULL, NULL },
/* 6 = GIT_OBJ_OFS_DELTA */
- { "OFS_DELTA", 0, 0 },
-
+ { "OFS_DELTA", 0, NULL, NULL },
/* 7 = GIT_OBJ_REF_DELTA */
- { "REF_DELTA", 0, 0 }
+ { "REF_DELTA", 0, NULL, NULL },
};
-static int create_object(git_object **object_out, git_otype type)
+int git_object__from_odb_object(
+ git_object **object_out,
+ git_repository *repo,
+ git_odb_object *odb_obj,
+ git_otype type)
{
+ int error;
+ size_t object_size;
+ git_object_def *def;
git_object *object = NULL;
assert(object_out);
-
*object_out = NULL;
- switch (type) {
- case GIT_OBJ_COMMIT:
- case GIT_OBJ_TAG:
- case GIT_OBJ_BLOB:
- case GIT_OBJ_TREE:
- object = git__malloc(git_object__size(type));
- if (object == NULL)
- return GIT_ENOMEM;
- memset(object, 0x0, git_object__size(type));
- break;
+ /* Validate type match */
+ if (type != GIT_OBJ_ANY && type != odb_obj->cached.type) {
+ giterr_set(GITERR_INVALID,
+ "The requested type does not match the type in the ODB");
+ return GIT_ENOTFOUND;
+ }
- default:
- return git__throw(GIT_EINVALIDTYPE, "The given type is invalid");
+ if ((object_size = git_object__size(odb_obj->cached.type)) == 0) {
+ giterr_set(GITERR_INVALID, "The requested type is invalid");
+ return GIT_ENOTFOUND;
}
- object->type = type;
+ /* Allocate and initialize base object */
+ object = git__calloc(1, object_size);
+ GITERR_CHECK_ALLOC(object);
+
+ git_oid_cpy(&object->cached.oid, &odb_obj->cached.oid);
+ object->cached.type = odb_obj->cached.type;
+ object->cached.size = odb_obj->cached.size;
+ object->repo = repo;
+
+ /* Parse raw object data */
+ def = &git_objects_table[odb_obj->cached.type];
+ assert(def->free && def->parse);
+
+ if ((error = def->parse(object, odb_obj)) < 0)
+ def->free(object);
+ else
+ *object_out = git_cache_store_parsed(&repo->objects, object);
- *object_out = object;
- return GIT_SUCCESS;
+ return error;
}
-int git_object_lookup_prefix(git_object **object_out, git_repository *repo, const git_oid *id, unsigned int len, git_otype type)
+void git_object__free(void *obj)
+{
+ git_otype type = ((git_object *)obj)->cached.type;
+
+ if (type < 0 || ((size_t)type) >= ARRAY_SIZE(git_objects_table) ||
+ !git_objects_table[type].free)
+ git__free(obj);
+ else
+ git_objects_table[type].free(obj);
+}
+
+int git_object_lookup_prefix(
+ git_object **object_out,
+ git_repository *repo,
+ const git_oid *id,
+ size_t len,
+ git_otype type)
{
git_object *object = NULL;
- git_odb_object *odb_obj;
- int error = GIT_SUCCESS;
+ git_odb *odb = NULL;
+ git_odb_object *odb_obj = NULL;
+ int error = 0;
assert(repo && object_out && id);
- if (len < GIT_OID_MINPREFIXLEN)
- return git__throw(GIT_EAMBIGUOUSOIDPREFIX,
- "Failed to lookup object. Prefix length is lower than %d.", GIT_OID_MINPREFIXLEN);
+ if (len < GIT_OID_MINPREFIXLEN) {
+ giterr_set(GITERR_OBJECT, "Ambiguous lookup - OID prefix is too short");
+ return GIT_EAMBIGUOUS;
+ }
+
+ error = git_repository_odb__weakptr(&odb, repo);
+ if (error < 0)
+ return error;
if (len > GIT_OID_HEXSZ)
len = GIT_OID_HEXSZ;
- if (len == GIT_OID_HEXSZ) {
+ if (len == GIT_OID_HEXSZ) {
+ git_cached_obj *cached = NULL;
+
/* We want to match the full id : we can first look up in the cache,
* since there is no need to check for non ambiguousity
*/
- object = git_cache_get(&repo->objects, id);
- if (object != NULL) {
- if (type != GIT_OBJ_ANY && type != object->type)
- {
- git_object_close(object);
- return git__throw(GIT_EINVALIDTYPE, "Failed to lookup object. The given type does not match the type on the ODB");
+ cached = git_cache_get_any(&repo->objects, id);
+ if (cached != NULL) {
+ if (cached->flags == GIT_CACHE_STORE_PARSED) {
+ object = (git_object *)cached;
+
+ if (type != GIT_OBJ_ANY && type != object->cached.type) {
+ git_object_free(object);
+ giterr_set(GITERR_INVALID,
+ "The requested type does not match the type in ODB");
+ return GIT_ENOTFOUND;
+ }
+
+ *object_out = object;
+ return 0;
+ } else if (cached->flags == GIT_CACHE_STORE_RAW) {
+ odb_obj = (git_odb_object *)cached;
+ } else {
+ assert(!"Wrong caching type in the global object cache");
}
-
- *object_out = object;
- return GIT_SUCCESS;
+ } else {
+ /* Object was not found in the cache, let's explore the backends.
+ * We could just use git_odb_read_unique_short_oid,
+ * it is the same cost for packed and loose object backends,
+ * but it may be much more costly for sqlite and hiredis.
+ */
+ error = git_odb_read(&odb_obj, odb, id);
}
-
- /* Object was not found in the cache, let's explore the backends.
- * We could just use git_odb_read_unique_short_oid,
- * it is the same cost for packed and loose object backends,
- * but it may be much more costly for sqlite and hiredis.
- */
- error = git_odb_read(&odb_obj, repo->db, id);
} else {
git_oid short_oid;
@@ -144,104 +177,36 @@ int git_object_lookup_prefix(git_object **object_out, git_repository *repo, cons
/* If len < GIT_OID_HEXSZ (a strict short oid was given), we have
* 2 options :
* - We always search in the cache first. If we find that short oid is
- * ambiguous, we can stop. But in all the other cases, we must then
- * explore all the backends (to find an object if there was match,
- * or to check that oid is not ambiguous if we have found 1 match in
- * the cache)
+ * ambiguous, we can stop. But in all the other cases, we must then
+ * explore all the backends (to find an object if there was match,
+ * or to check that oid is not ambiguous if we have found 1 match in
+ * the cache)
* - We never explore the cache, go right to exploring the backends
* We chose the latter : we explore directly the backends.
*/
- error = git_odb_read_prefix(&odb_obj, repo->db, &short_oid, len);
+ error = git_odb_read_prefix(&odb_obj, odb, &short_oid, len);
}
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to lookup object");
+ if (error < 0)
+ return error;
- if (type != GIT_OBJ_ANY && type != odb_obj->raw.type) {
- git_odb_object_close(odb_obj);
- return git__throw(GIT_EINVALIDTYPE, "Failed to lookup object. The given type does not match the type on the ODB");
- }
-
- type = odb_obj->raw.type;
+ error = git_object__from_odb_object(object_out, repo, odb_obj, type);
- if ((error = create_object(&object, type)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to lookup object");
+ git_odb_object_free(odb_obj);
- /* Initialize parent object */
- git_oid_cpy(&object->cached.oid, &odb_obj->cached.oid);
- object->repo = repo;
-
- switch (type) {
- case GIT_OBJ_COMMIT:
- error = git_commit__parse((git_commit *)object, odb_obj);
- break;
-
- case GIT_OBJ_TREE:
- error = git_tree__parse((git_tree *)object, odb_obj);
- break;
-
- case GIT_OBJ_TAG:
- error = git_tag__parse((git_tag *)object, odb_obj);
- break;
-
- case GIT_OBJ_BLOB:
- error = git_blob__parse((git_blob *)object, odb_obj);
- break;
-
- default:
- break;
- }
-
- git_odb_object_close(odb_obj);
-
- if (error < GIT_SUCCESS) {
- git_object__free(object);
- return git__rethrow(error, "Failed to lookup object");
- }
-
- *object_out = git_cache_try_store(&repo->objects, object);
- return GIT_SUCCESS;
+ return error;
}
int git_object_lookup(git_object **object_out, git_repository *repo, const git_oid *id, git_otype type) {
return git_object_lookup_prefix(object_out, repo, id, GIT_OID_HEXSZ, type);
}
-void git_object__free(void *_obj)
-{
- git_object *object = (git_object *)_obj;
-
- assert(object);
-
- switch (object->type) {
- case GIT_OBJ_COMMIT:
- git_commit__free((git_commit *)object);
- break;
-
- case GIT_OBJ_TREE:
- git_tree__free((git_tree *)object);
- break;
-
- case GIT_OBJ_TAG:
- git_tag__free((git_tag *)object);
- break;
-
- case GIT_OBJ_BLOB:
- git_blob__free((git_blob *)object);
- break;
-
- default:
- free(object);
- break;
- }
-}
-
-void git_object_close(git_object *object)
+void git_object_free(git_object *object)
{
if (object == NULL)
return;
- git_cached_obj_decref((git_cached_obj *)object, git_object__free);
+ git_cached_obj_decref(object);
}
const git_oid *git_object_id(const git_object *obj)
@@ -253,7 +218,7 @@ const git_oid *git_object_id(const git_object *obj)
git_otype git_object_type(const git_object *obj)
{
assert(obj);
- return obj->type;
+ return obj->cached.type;
}
git_repository *git_object_owner(const git_object *obj)
@@ -289,7 +254,7 @@ int git_object_typeisloose(git_otype type)
if (type < 0 || ((size_t) type) >= ARRAY_SIZE(git_objects_table))
return 0;
- return git_objects_table[type].loose;
+ return (git_objects_table[type].size > 0) ? 1 : 0;
}
size_t git_object__size(git_otype type)
@@ -300,3 +265,178 @@ size_t git_object__size(git_otype type)
return git_objects_table[type].size;
}
+static int dereference_object(git_object **dereferenced, git_object *obj)
+{
+ git_otype type = git_object_type(obj);
+
+ switch (type) {
+ case GIT_OBJ_COMMIT:
+ return git_commit_tree((git_tree **)dereferenced, (git_commit*)obj);
+
+ case GIT_OBJ_TAG:
+ return git_tag_target(dereferenced, (git_tag*)obj);
+
+ case GIT_OBJ_BLOB:
+ return GIT_ENOTFOUND;
+
+ case GIT_OBJ_TREE:
+ return GIT_EAMBIGUOUS;
+
+ default:
+ return GIT_EINVALIDSPEC;
+ }
+}
+
+static int peel_error(int error, const git_oid *oid, git_otype type)
+{
+ const char *type_name;
+ char hex_oid[GIT_OID_HEXSZ + 1];
+
+ type_name = git_object_type2string(type);
+
+ git_oid_fmt(hex_oid, oid);
+ hex_oid[GIT_OID_HEXSZ] = '\0';
+
+ giterr_set(GITERR_OBJECT, "The git_object of id '%s' can not be "
+ "successfully peeled into a %s (git_otype=%i).", hex_oid, type_name, type);
+
+ return error;
+}
+
+int git_object_peel(
+ git_object **peeled,
+ const git_object *object,
+ git_otype target_type)
+{
+ git_object *source, *deref = NULL;
+ int error;
+
+ assert(object && peeled);
+
+ if (git_object_type(object) == target_type)
+ return git_object_dup(peeled, (git_object *)object);
+
+ assert(target_type == GIT_OBJ_TAG ||
+ target_type == GIT_OBJ_COMMIT ||
+ target_type == GIT_OBJ_TREE ||
+ target_type == GIT_OBJ_BLOB ||
+ target_type == GIT_OBJ_ANY);
+
+ source = (git_object *)object;
+
+ while (!(error = dereference_object(&deref, source))) {
+
+ if (source != object)
+ git_object_free(source);
+
+ if (git_object_type(deref) == target_type) {
+ *peeled = deref;
+ return 0;
+ }
+
+ if (target_type == GIT_OBJ_ANY &&
+ git_object_type(deref) != git_object_type(object))
+ {
+ *peeled = deref;
+ return 0;
+ }
+
+ source = deref;
+ deref = NULL;
+ }
+
+ if (source != object)
+ git_object_free(source);
+
+ git_object_free(deref);
+
+ if (error)
+ error = peel_error(error, git_object_id(object), target_type);
+
+ return error;
+}
+
+int git_object_dup(git_object **dest, git_object *source)
+{
+ git_cached_obj_incref(source);
+ *dest = source;
+ return 0;
+}
+
+int git_object_lookup_bypath(
+ git_object **out,
+ const git_object *treeish,
+ const char *path,
+ git_otype type)
+{
+ int error = -1;
+ git_tree *tree = NULL;
+ git_tree_entry *entry = NULL;
+
+ assert(out && treeish && path);
+
+ if ((error = git_object_peel((git_object**)&tree, treeish, GIT_OBJ_TREE)) < 0 ||
+ (error = git_tree_entry_bypath(&entry, tree, path)) < 0)
+ {
+ goto cleanup;
+ }
+
+ if (type != GIT_OBJ_ANY && git_tree_entry_type(entry) != type)
+ {
+ giterr_set(GITERR_OBJECT,
+ "object at path '%s' is not of the asked-for type %d",
+ path, type);
+ error = GIT_EINVALIDSPEC;
+ goto cleanup;
+ }
+
+ error = git_tree_entry_to_object(out, git_object_owner(treeish), entry);
+
+cleanup:
+ git_tree_entry_free(entry);
+ git_tree_free(tree);
+ return error;
+}
+
+int git_object_short_id(git_buf *out, const git_object *obj)
+{
+ git_repository *repo;
+ int len = GIT_ABBREV_DEFAULT, error;
+ git_oid id = {{0}};
+ git_odb *odb;
+
+ assert(out && obj);
+
+ git_buf_sanitize(out);
+ repo = git_object_owner(obj);
+
+ if ((error = git_repository__cvar(&len, repo, GIT_CVAR_ABBREV)) < 0)
+ return error;
+
+ if ((error = git_repository_odb(&odb, repo)) < 0)
+ return error;
+
+ while (len < GIT_OID_HEXSZ) {
+ /* set up short oid */
+ memcpy(&id.id, &obj->cached.oid.id, (len + 1) / 2);
+ if (len & 1)
+ id.id[len / 2] &= 0xf0;
+
+ error = git_odb_exists_prefix(NULL, odb, &id, len);
+ if (error != GIT_EAMBIGUOUS)
+ break;
+
+ giterr_clear();
+ len++;
+ }
+
+ if (!error && !(error = git_buf_grow(out, len + 1))) {
+ git_oid_tostr(out->ptr, len + 1, &id);
+ out->size = len;
+ }
+
+ git_odb_free(odb);
+
+ return error;
+}
+
diff --git a/src/object.h b/src/object.h
new file mode 100644
index 000000000..d187c55b7
--- /dev/null
+++ b/src/object.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_object_h__
+#define INCLUDE_object_h__
+
+/** Base git object for inheritance */
+struct git_object {
+ git_cached_obj cached;
+ git_repository *repo;
+};
+
+/* fully free the object; internal method, DO NOT EXPORT */
+void git_object__free(void *object);
+
+int git_object__from_odb_object(
+ git_object **object_out,
+ git_repository *repo,
+ git_odb_object *odb_obj,
+ git_otype type);
+
+int git_object__resolve_to_type(git_object **obj, git_otype type);
+
+int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header);
+
+void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid);
+
+#endif
diff --git a/src/object_api.c b/src/object_api.c
new file mode 100644
index 000000000..838bba323
--- /dev/null
+++ b/src/object_api.c
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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/object.h"
+
+#include "common.h"
+#include "repository.h"
+
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "tag.h"
+
+/**
+ * Blob
+ */
+int git_commit_lookup(git_commit **out, git_repository *repo, const git_oid *id)
+{
+ return git_object_lookup((git_object **)out, repo, id, GIT_OBJ_COMMIT);
+}
+
+int git_commit_lookup_prefix(git_commit **out, git_repository *repo, const git_oid *id, size_t len)
+{
+ return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJ_COMMIT);
+}
+
+void git_commit_free(git_commit *obj)
+{
+ git_object_free((git_object *)obj);
+}
+
+const git_oid *git_commit_id(const git_commit *obj)
+{
+ return git_object_id((const git_object *)obj);
+}
+
+git_repository *git_commit_owner(const git_commit *obj)
+{
+ return git_object_owner((const git_object *)obj);
+}
+
+
+/**
+ * Tree
+ */
+int git_tree_lookup(git_tree **out, git_repository *repo, const git_oid *id)
+{
+ return git_object_lookup((git_object **)out, repo, id, GIT_OBJ_TREE);
+}
+
+int git_tree_lookup_prefix(git_tree **out, git_repository *repo, const git_oid *id, size_t len)
+{
+ return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJ_TREE);
+}
+
+void git_tree_free(git_tree *obj)
+{
+ git_object_free((git_object *)obj);
+}
+
+const git_oid *git_tree_id(const git_tree *obj)
+{
+ return git_object_id((const git_object *)obj);
+}
+
+git_repository *git_tree_owner(const git_tree *obj)
+{
+ return git_object_owner((const git_object *)obj);
+}
+
+
+/**
+ * Tag
+ */
+int git_tag_lookup(git_tag **out, git_repository *repo, const git_oid *id)
+{
+ return git_object_lookup((git_object **)out, repo, id, GIT_OBJ_TAG);
+}
+
+int git_tag_lookup_prefix(git_tag **out, git_repository *repo, const git_oid *id, size_t len)
+{
+ return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJ_TAG);
+}
+
+void git_tag_free(git_tag *obj)
+{
+ git_object_free((git_object *)obj);
+}
+
+const git_oid *git_tag_id(const git_tag *obj)
+{
+ return git_object_id((const git_object *)obj);
+}
+
+git_repository *git_tag_owner(const git_tag *obj)
+{
+ return git_object_owner((const git_object *)obj);
+}
+
+/**
+ * Blob
+ */
+int git_blob_lookup(git_blob **out, git_repository *repo, const git_oid *id)
+{
+ return git_object_lookup((git_object **)out, repo, id, GIT_OBJ_BLOB);
+}
+
+int git_blob_lookup_prefix(git_blob **out, git_repository *repo, const git_oid *id, size_t len)
+{
+ return git_object_lookup_prefix((git_object **)out, repo, id, len, GIT_OBJ_BLOB);
+}
+
+void git_blob_free(git_blob *obj)
+{
+ git_object_free((git_object *)obj);
+}
+
+const git_oid *git_blob_id(const git_blob *obj)
+{
+ return git_object_id((const git_object *)obj);
+}
+
+git_repository *git_blob_owner(const git_blob *obj)
+{
+ return git_object_owner((const git_object *)obj);
+}
diff --git a/src/odb.c b/src/odb.c
index a3045f716..a4fc02686 100644
--- a/src/odb.c
+++ b/src/odb.c
@@ -1,37 +1,23 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
-#include "git2/zlib.h"
+#include <zlib.h>
#include "git2/object.h"
+#include "git2/sys/odb_backend.h"
#include "fileops.h"
#include "hash.h"
#include "odb.h"
#include "delta-apply.h"
+#include "filter.h"
+#include "repository.h"
#include "git2/odb_backend.h"
+#include "git2/oid.h"
#define GIT_ALTERNATES_FILE "info/alternates"
@@ -39,73 +25,82 @@
#define GIT_LOOSE_PRIORITY 2
#define GIT_PACKED_PRIORITY 1
+#define GIT_ALTERNATES_MAX_DEPTH 5
+
typedef struct
{
git_odb_backend *backend;
int priority;
- int is_alternate;
+ bool is_alternate;
+ ino_t disk_inode;
} backend_internal;
-static int format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type)
+static git_cache *odb_cache(git_odb *odb)
{
- const char *type_str = git_object_type2string(obj_type);
- int len = snprintf(hdr, n, "%s %"PRIuZ, type_str, obj_len);
+ if (odb->rc.owner != NULL) {
+ git_repository *owner = odb->rc.owner;
+ return &owner->objects;
+ }
- assert(len > 0); /* otherwise snprintf() is broken */
- assert(((size_t) len) < n); /* otherwise the caller is broken! */
+ return &odb->own_cache;
+}
+
+static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth);
- if (len < 0 || ((size_t) len) >= n)
- return git__throw(GIT_ERROR, "Cannot format object header. Length is out of bounds");
+int git_odb__format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type)
+{
+ const char *type_str = git_object_type2string(obj_type);
+ int len = p_snprintf(hdr, n, "%s %"PRIuZ, type_str, obj_len);
+ assert(len > 0 && len <= (int)n);
return len+1;
}
-int git_odb__hash_obj(git_oid *id, char *hdr, size_t n, int *len, git_rawobj *obj)
+int git_odb__hashobj(git_oid *id, git_rawobj *obj)
{
git_buf_vec vec[2];
- int hdrlen;
+ char header[64];
+ int hdrlen;
- assert(id && hdr && len && obj);
+ assert(id && obj);
if (!git_object_typeisloose(obj->type))
- return git__throw(GIT_ERROR, "Failed to hash object. Wrong object type");
+ return -1;
if (!obj->data && obj->len != 0)
- return git__throw(GIT_ERROR, "Failed to hash object. No data given");
-
- if ((hdrlen = format_object_header(hdr, n, obj->len, obj->type)) < 0)
- return git__rethrow(hdrlen, "Failed to hash object");
+ return -1;
- *len = hdrlen;
+ hdrlen = git_odb__format_object_header(header, sizeof(header), obj->len, obj->type);
- vec[0].data = hdr;
- vec[0].len = hdrlen;
+ vec[0].data = header;
+ vec[0].len = hdrlen;
vec[1].data = obj->data;
- vec[1].len = obj->len;
+ vec[1].len = obj->len;
git_hash_vec(id, vec, 2);
- return GIT_SUCCESS;
+ return 0;
}
-static git_odb_object *new_odb_object(const git_oid *oid, git_rawobj *source)
+static git_odb_object *odb_object__alloc(const git_oid *oid, git_rawobj *source)
{
- git_odb_object *object = git__malloc(sizeof(git_odb_object));
- memset(object, 0x0, sizeof(git_odb_object));
+ git_odb_object *object = git__calloc(1, sizeof(git_odb_object));
- git_oid_cpy(&object->cached.oid, oid);
- memcpy(&object->raw, source, sizeof(git_rawobj));
+ if (object != NULL) {
+ git_oid_cpy(&object->cached.oid, oid);
+ object->cached.type = source->type;
+ object->cached.size = source->len;
+ object->buffer = source->data;
+ }
return object;
}
-static void free_odb_object(void *o)
+void git_odb_object__free(void *object)
{
- git_odb_object *object = (git_odb_object *)o;
-
if (object != NULL) {
- free(object->raw.data);
- free(object);
+ git__free(((git_odb_object *)object)->buffer);
+ git__free(object);
}
}
@@ -116,74 +111,172 @@ const git_oid *git_odb_object_id(git_odb_object *object)
const void *git_odb_object_data(git_odb_object *object)
{
- return object->raw.data;
+ return object->buffer;
}
size_t git_odb_object_size(git_odb_object *object)
{
- return object->raw.len;
+ return object->cached.size;
}
git_otype git_odb_object_type(git_odb_object *object)
{
- return object->raw.type;
+ return object->cached.type;
}
-void git_odb_object_close(git_odb_object *object)
+int git_odb_object_dup(git_odb_object **dest, git_odb_object *source)
{
- git_cached_obj_decref((git_cached_obj *)object, &free_odb_object);
+ git_cached_obj_incref(source);
+ *dest = source;
+ return 0;
}
-int git_odb_hashfile(git_oid *out, const char *path, git_otype type)
+void git_odb_object_free(git_odb_object *object)
+{
+ if (object == NULL)
+ return;
+
+ git_cached_obj_decref(object);
+}
+
+int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type)
{
- int fd, hdr_len;
+ int hdr_len;
char hdr[64], buffer[2048];
- git_off_t size;
- git_hash_ctx *ctx;
+ git_hash_ctx ctx;
+ ssize_t read_len = 0;
+ int error = 0;
- if ((fd = p_open(path, O_RDONLY)) < 0)
- return git__throw(GIT_ENOTFOUND, "Could not open '%s'", path);
+ if (!git_object_typeisloose(type)) {
+ giterr_set(GITERR_INVALID, "Invalid object type for hash");
+ return -1;
+ }
- if ((size = git_futils_filesize(fd)) < 0 || !git__is_sizet(size)) {
- p_close(fd);
- return git__throw(GIT_EOSERR, "'%s' appears to be corrupted", path);
+ if ((error = git_hash_ctx_init(&ctx)) < 0)
+ return -1;
+
+ hdr_len = git_odb__format_object_header(hdr, sizeof(hdr), size, type);
+
+ if ((error = git_hash_update(&ctx, hdr, hdr_len)) < 0)
+ goto done;
+
+ while (size > 0 && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) {
+ if ((error = git_hash_update(&ctx, buffer, read_len)) < 0)
+ goto done;
+
+ size -= read_len;
}
- hdr_len = format_object_header(hdr, sizeof(hdr), (size_t)size, type);
- if (hdr_len < 0)
- return git__throw(GIT_ERROR, "Failed to format blob header. Length is out of bounds");
+ /* If p_read returned an error code, the read obviously failed.
+ * If size is not zero, the file was truncated after we originally
+ * stat'd it, so we consider this a read failure too */
+ if (read_len < 0 || size > 0) {
+ giterr_set(GITERR_OS, "Error reading file for hashing");
+ error = -1;
- ctx = git_hash_new_ctx();
+ goto done;
+ }
- git_hash_update(ctx, hdr, hdr_len);
+ error = git_hash_final(out, &ctx);
- while (size > 0) {
+done:
+ git_hash_ctx_cleanup(&ctx);
+ return error;
+}
+
+int git_odb__hashfd_filtered(
+ git_oid *out, git_file fd, size_t size, git_otype type, git_filter_list *fl)
+{
+ int error;
+ git_buf raw = GIT_BUF_INIT;
+
+ if (!fl)
+ return git_odb__hashfd(out, fd, size, type);
+
+ /* size of data is used in header, so we have to read the whole file
+ * into memory to apply filters before beginning to calculate the hash
+ */
+
+ if (!(error = git_futils_readbuffer_fd(&raw, fd, size))) {
+ git_buf post = GIT_BUF_INIT;
+
+ error = git_filter_list_apply_to_data(&post, fl, &raw);
+
+ git_buf_free(&raw);
+
+ if (!error)
+ error = git_odb_hash(out, post.ptr, post.size, type);
+
+ git_buf_free(&post);
+ }
+
+ return error;
+}
+
+int git_odb__hashlink(git_oid *out, const char *path)
+{
+ struct stat st;
+ git_off_t size;
+ int result;
+
+ if (git_path_lstat(path, &st) < 0)
+ return -1;
+
+ size = st.st_size;
+
+ if (!git__is_sizet(size)) {
+ giterr_set(GITERR_OS, "File size overflow for 32-bit systems");
+ return -1;
+ }
+
+ if (S_ISLNK(st.st_mode)) {
+ char *link_data;
ssize_t read_len;
- read_len = read(fd, buffer, sizeof(buffer));
+ link_data = git__malloc((size_t)(size + 1));
+ GITERR_CHECK_ALLOC(link_data);
- if (read_len < 0) {
- p_close(fd);
- git_hash_free_ctx(ctx);
- return git__throw(GIT_EOSERR, "Can't read full file '%s'", path);
+ read_len = p_readlink(path, link_data, (size_t)size);
+ link_data[size] = '\0';
+ if (read_len != (ssize_t)size) {
+ giterr_set(GITERR_OS, "Failed to read symlink data for '%s'", path);
+ git__free(link_data);
+ return -1;
}
- git_hash_update(ctx, buffer, read_len);
- size -= read_len;
+ result = git_odb_hash(out, link_data, (size_t)size, GIT_OBJ_BLOB);
+ git__free(link_data);
+ } else {
+ int fd = git_futils_open_ro(path);
+ if (fd < 0)
+ return -1;
+ result = git_odb__hashfd(out, fd, (size_t)size, GIT_OBJ_BLOB);
+ p_close(fd);
}
- p_close(fd);
+ return result;
+}
- git_hash_final(out, ctx);
- git_hash_free_ctx(ctx);
+int git_odb_hashfile(git_oid *out, const char *path, git_otype type)
+{
+ git_off_t size;
+ int result, fd = git_futils_open_ro(path);
+ if (fd < 0)
+ return fd;
+
+ if ((size = git_futils_filesize(fd)) < 0 || !git__is_sizet(size)) {
+ giterr_set(GITERR_OS, "File size overflow for 32-bit systems");
+ p_close(fd);
+ return -1;
+ }
- return GIT_SUCCESS;
+ result = git_odb__hashfd(out, fd, (size_t)size, type);
+ p_close(fd);
+ return result;
}
int git_odb_hash(git_oid *id, const void *data, size_t len, git_otype type)
{
- char hdr[64];
- int hdrlen;
git_rawobj raw;
assert(id);
@@ -192,7 +285,7 @@ int git_odb_hash(git_oid *id, const void *data, size_t len, git_otype type)
raw.len = len;
raw.type = type;
- return git_odb__hash_obj(id, hdr, sizeof(hdr), &hdrlen, &raw);
+ return git_odb__hashobj(id, &raw);
}
/**
@@ -206,10 +299,10 @@ typedef struct {
git_otype type;
} fake_wstream;
-static int fake_wstream__fwrite(git_oid *oid, git_odb_stream *_stream)
+static int fake_wstream__fwrite(git_odb_stream *_stream, const git_oid *oid)
{
fake_wstream *stream = (fake_wstream *)_stream;
- return _stream->backend->write(oid, _stream->backend, stream->buffer, stream->size, stream->type);
+ return _stream->backend->write(_stream->backend, oid, stream->buffer, stream->size, stream->type);
}
static int fake_wstream__write(git_odb_stream *_stream, const char *data, size_t len)
@@ -217,19 +310,19 @@ static int fake_wstream__write(git_odb_stream *_stream, const char *data, size_t
fake_wstream *stream = (fake_wstream *)_stream;
if (stream->written + len > stream->size)
- return GIT_ENOMEM;
+ return -1;
memcpy(stream->buffer + stream->written, data, len);
stream->written += len;
- return GIT_SUCCESS;
+ return 0;
}
static void fake_wstream__free(git_odb_stream *_stream)
{
fake_wstream *stream = (fake_wstream *)_stream;
- free(stream->buffer);
- free(stream);
+ git__free(stream->buffer);
+ git__free(stream);
}
static int init_fake_wstream(git_odb_stream **stream_p, git_odb_backend *backend, size_t size, git_otype type)
@@ -237,15 +330,14 @@ static int init_fake_wstream(git_odb_stream **stream_p, git_odb_backend *backend
fake_wstream *stream;
stream = git__calloc(1, sizeof(fake_wstream));
- if (stream == NULL)
- return GIT_ENOMEM;
+ GITERR_CHECK_ALLOC(stream);
stream->size = size;
stream->type = type;
stream->buffer = git__malloc(size);
if (stream->buffer == NULL) {
- free(stream);
- return GIT_ENOMEM;
+ git__free(stream);
+ return -1;
}
stream->stream.backend = backend;
@@ -256,7 +348,7 @@ static int init_fake_wstream(git_odb_stream **stream_p, git_odb_backend *backend
stream->stream.mode = GIT_STREAM_WRONLY;
*stream_p = (git_odb_stream *)stream;
- return GIT_SUCCESS;
+ return 0;
}
/***********************************************************
@@ -280,191 +372,256 @@ static int backend_sort_cmp(const void *a, const void *b)
int git_odb_new(git_odb **out)
{
- int error;
-
git_odb *db = git__calloc(1, sizeof(*db));
- if (!db)
- return GIT_ENOMEM;
-
- error = git_cache_init(&db->cache, GIT_DEFAULT_CACHE_SIZE, &free_odb_object);
- if (error < GIT_SUCCESS) {
- free(db);
- return git__rethrow(error, "Failed to create object database");
- }
+ GITERR_CHECK_ALLOC(db);
- if ((error = git_vector_init(&db->backends, 4, backend_sort_cmp)) < GIT_SUCCESS) {
- free(db);
- return git__rethrow(error, "Failed to create object database");
+ if (git_cache_init(&db->own_cache) < 0 ||
+ git_vector_init(&db->backends, 4, backend_sort_cmp) < 0) {
+ git__free(db);
+ return -1;
}
*out = db;
- return GIT_SUCCESS;
+ GIT_REFCOUNT_INC(db);
+ return 0;
}
-static int add_backend_internal(git_odb *odb, git_odb_backend *backend, int priority, int is_alternate)
+static int add_backend_internal(
+ git_odb *odb, git_odb_backend *backend,
+ int priority, bool is_alternate, ino_t disk_inode)
{
backend_internal *internal;
assert(odb && backend);
- if (backend->odb != NULL && backend->odb != odb)
- return git__throw(GIT_EBUSY, "The backend is already owned by another ODB");
+ GITERR_CHECK_VERSION(backend, GIT_ODB_BACKEND_VERSION, "git_odb_backend");
+
+ /* Check if the backend is already owned by another ODB */
+ assert(!backend->odb || backend->odb == odb);
internal = git__malloc(sizeof(backend_internal));
- if (internal == NULL)
- return GIT_ENOMEM;
+ GITERR_CHECK_ALLOC(internal);
internal->backend = backend;
internal->priority = priority;
internal->is_alternate = is_alternate;
+ internal->disk_inode = disk_inode;
if (git_vector_insert(&odb->backends, internal) < 0) {
- free(internal);
- return GIT_ENOMEM;
+ git__free(internal);
+ return -1;
}
git_vector_sort(&odb->backends);
internal->backend->odb = odb;
- return GIT_SUCCESS;
+ return 0;
}
int git_odb_add_backend(git_odb *odb, git_odb_backend *backend, int priority)
{
- return add_backend_internal(odb, backend, priority, 0);
+ return add_backend_internal(odb, backend, priority, false, 0);
}
int git_odb_add_alternate(git_odb *odb, git_odb_backend *backend, int priority)
{
- return add_backend_internal(odb, backend, priority, 1);
+ return add_backend_internal(odb, backend, priority, true, 0);
}
-static int add_default_backends(git_odb *db, const char *objects_dir, int as_alternates)
+size_t git_odb_num_backends(git_odb *odb)
{
+ assert(odb);
+ return odb->backends.length;
+}
+
+static int git_odb__error_unsupported_in_backend(const char *action)
+{
+ giterr_set(GITERR_ODB,
+ "Cannot %s - unsupported in the loaded odb backends", action);
+ return -1;
+}
+
+
+int git_odb_get_backend(git_odb_backend **out, git_odb *odb, size_t pos)
+{
+ backend_internal *internal;
+
+ assert(out && odb);
+ internal = git_vector_get(&odb->backends, pos);
+
+ if (internal && internal->backend) {
+ *out = internal->backend;
+ return 0;
+ }
+
+ giterr_set(GITERR_ODB, "No ODB backend loaded at index %" PRIuZ, pos);
+ return GIT_ENOTFOUND;
+}
+
+static int add_default_backends(
+ git_odb *db, const char *objects_dir,
+ bool as_alternates, int alternate_depth)
+{
+ size_t i;
+ struct stat st;
+ ino_t inode;
git_odb_backend *loose, *packed;
- int error;
- /* add the loose object backend */
- error = git_odb_backend_loose(&loose, objects_dir);
- if (error < GIT_SUCCESS)
- return error;
+ /* TODO: inodes are not really relevant on Win32, so we need to find
+ * a cross-platform workaround for this */
+#ifdef GIT_WIN32
+ GIT_UNUSED(i);
+ GIT_UNUSED(st);
- error = add_backend_internal(db, loose, GIT_LOOSE_PRIORITY, as_alternates);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to add backend");
+ inode = 0;
+#else
+ if (p_stat(objects_dir, &st) < 0) {
+ if (as_alternates)
+ return 0;
- /* add the packed file backend */
- error = git_odb_backend_pack(&packed, objects_dir);
- if (error < GIT_SUCCESS)
- return error;
+ giterr_set(GITERR_ODB, "Failed to load object database in '%s'", objects_dir);
+ return -1;
+ }
+
+ inode = st.st_ino;
+
+ for (i = 0; i < db->backends.length; ++i) {
+ backend_internal *backend = git_vector_get(&db->backends, i);
+ if (backend->disk_inode == inode)
+ return 0;
+ }
+#endif
+
+ /* add the loose object backend */
+ if (git_odb_backend_loose(&loose, objects_dir, -1, 0, 0, 0) < 0 ||
+ add_backend_internal(db, loose, GIT_LOOSE_PRIORITY, as_alternates, inode) < 0)
+ return -1;
- error = add_backend_internal(db, packed, GIT_PACKED_PRIORITY, as_alternates);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to add backend");
+ /* add the packed file backend */
+ if (git_odb_backend_pack(&packed, objects_dir) < 0 ||
+ add_backend_internal(db, packed, GIT_PACKED_PRIORITY, as_alternates, inode) < 0)
+ return -1;
- return GIT_SUCCESS;
+ 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)
{
- char alternates_path[GIT_PATH_MAX];
- char *buffer, *alternate;
+ git_buf alternates_path = GIT_BUF_INIT;
+ git_buf alternates_buf = GIT_BUF_INIT;
+ char *buffer;
+ const char *alternate;
+ int result = 0;
- git_fbuffer alternates_buf = GIT_FBUFFER_INIT;
- int error;
+ /* Git reports an error, we just ignore anything deeper */
+ if (alternate_depth > GIT_ALTERNATES_MAX_DEPTH)
+ return 0;
- git_path_join(alternates_path, objects_dir, GIT_ALTERNATES_FILE);
+ if (git_buf_joinpath(&alternates_path, objects_dir, GIT_ALTERNATES_FILE) < 0)
+ return -1;
- if (git_futils_exists(alternates_path) < GIT_SUCCESS)
- return GIT_SUCCESS;
+ if (git_path_exists(alternates_path.ptr) == false) {
+ git_buf_free(&alternates_path);
+ return 0;
+ }
- if (git_futils_readbuffer(&alternates_buf, alternates_path) < GIT_SUCCESS)
- return git__throw(GIT_EOSERR, "Failed to add backend. Can't read alternates");
+ if (git_futils_readbuffer(&alternates_buf, alternates_path.ptr) < 0) {
+ git_buf_free(&alternates_path);
+ return -1;
+ }
- buffer = (char *)alternates_buf.data;
- error = GIT_SUCCESS;
+ buffer = (char *)alternates_buf.ptr;
/* add each alternate as a new backend; one alternate per line */
while ((alternate = git__strtok(&buffer, "\r\n")) != NULL) {
- char full_path[GIT_PATH_MAX];
-
if (*alternate == '\0' || *alternate == '#')
continue;
- /* relative path: build based on the current `objects` folder */
- if (*alternate == '.') {
- git_path_join(full_path, objects_dir, alternate);
- alternate = full_path;
+ /*
+ * 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 ((error = add_default_backends(odb, alternate, 1)) < GIT_SUCCESS)
+ if ((result = add_default_backends(odb, alternate, true, alternate_depth + 1)) < 0)
break;
}
- git_futils_freebuffer(&alternates_buf);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to load alternates");
- return error;
+ git_buf_free(&alternates_path);
+ git_buf_free(&alternates_buf);
+
+ return result;
+}
+
+int git_odb_add_disk_alternate(git_odb *odb, const char *path)
+{
+ return add_default_backends(odb, path, true, 0);
}
int git_odb_open(git_odb **out, const char *objects_dir)
{
git_odb *db;
- int error;
assert(out && objects_dir);
*out = NULL;
- if ((error = git_odb_new(&db)) < 0)
- return git__rethrow(error, "Failed to open ODB");
-
- if ((error = add_default_backends(db, objects_dir, 0)) < GIT_SUCCESS)
- goto cleanup;
+ if (git_odb_new(&db) < 0)
+ return -1;
- if ((error = load_alternates(db, objects_dir)) < GIT_SUCCESS)
- goto cleanup;
+ if (add_default_backends(db, objects_dir, 0, 0) < 0) {
+ git_odb_free(db);
+ return -1;
+ }
*out = db;
- return GIT_SUCCESS;
-
-cleanup:
- git_odb_close(db);
- return error; /* error already set - pass through */
+ return 0;
}
-void git_odb_close(git_odb *db)
+static void odb_free(git_odb *db)
{
- unsigned int i;
-
- if (db == NULL)
- return;
+ size_t i;
for (i = 0; i < db->backends.length; ++i) {
backend_internal *internal = git_vector_get(&db->backends, i);
git_odb_backend *backend = internal->backend;
if (backend->free) backend->free(backend);
- else free(backend);
+ else git__free(backend);
- free(internal);
+ git__free(internal);
}
git_vector_free(&db->backends);
- git_cache_free(&db->cache);
- free(db);
+ git_cache_free(&db->own_cache);
+
+ git__memzero(db, sizeof(*db));
+ git__free(db);
+}
+
+void git_odb_free(git_odb *db)
+{
+ if (db == NULL)
+ return;
+
+ GIT_REFCOUNT_DEC(db, odb_free);
}
int git_odb_exists(git_odb *db, const git_oid *id)
{
git_odb_object *object;
- unsigned int i;
- int found = 0;
+ size_t i;
+ bool found = false;
assert(db && id);
- if ((object = git_cache_get(&db->cache, id)) != NULL) {
- git_odb_object_close(object);
- return 1;
+ if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) {
+ git_odb_object_free(object);
+ return (int)true;
}
for (i = 0; i < db->backends.length && !found; ++i) {
@@ -472,27 +629,104 @@ int git_odb_exists(git_odb *db, const git_oid *id)
git_odb_backend *b = internal->backend;
if (b->exists != NULL)
- found = b->exists(b, id);
+ found = (bool)b->exists(b, id);
}
- return found;
+ return (int)found;
+}
+
+int git_odb_exists_prefix(
+ git_oid *out, git_odb *db, const git_oid *short_id, size_t len)
+{
+ int error = GIT_ENOTFOUND, num_found = 0;
+ size_t i;
+ git_oid key = {{0}}, last_found = {{0}}, found;
+
+ assert(db && short_id);
+
+ if (len < GIT_OID_MINPREFIXLEN)
+ return git_odb__error_ambiguous("prefix length too short");
+ if (len > GIT_OID_HEXSZ)
+ len = GIT_OID_HEXSZ;
+
+ if (len == GIT_OID_HEXSZ) {
+ if (git_odb_exists(db, short_id)) {
+ if (out)
+ git_oid_cpy(out, short_id);
+ return 0;
+ } else {
+ return git_odb__error_notfound("no match for id prefix", short_id);
+ }
+ }
+
+ /* just copy valid part of short_id */
+ memcpy(&key.id, short_id->id, (len + 1) / 2);
+ if (len & 1)
+ key.id[len / 2] &= 0xF0;
+
+ for (i = 0; i < db->backends.length; ++i) {
+ backend_internal *internal = git_vector_get(&db->backends, i);
+ git_odb_backend *b = internal->backend;
+
+ if (!b->exists_prefix)
+ continue;
+
+ error = b->exists_prefix(&found, b, &key, len);
+ if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH)
+ continue;
+ if (error)
+ return error;
+
+ /* make sure found item doesn't introduce ambiguity */
+ if (num_found) {
+ if (git_oid__cmp(&last_found, &found))
+ return git_odb__error_ambiguous("multiple matches for prefix");
+ } else {
+ git_oid_cpy(&last_found, &found);
+ num_found++;
+ }
+ }
+
+ if (!num_found)
+ return git_odb__error_notfound("no match for id prefix", &key);
+ if (out)
+ git_oid_cpy(out, &last_found);
+
+ return 0;
}
int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git_oid *id)
{
- unsigned int i;
+ int error;
+ git_odb_object *object;
+
+ error = git_odb__read_header_or_object(&object, len_p, type_p, db, id);
+
+ if (object)
+ git_odb_object_free(object);
+
+ return error;
+}
+
+int git_odb__read_header_or_object(
+ git_odb_object **out, size_t *len_p, git_otype *type_p,
+ git_odb *db, const git_oid *id)
+{
+ size_t i;
int error = GIT_ENOTFOUND;
git_odb_object *object;
- assert(db && id);
+ assert(db && id && out && len_p && type_p);
- if ((object = git_cache_get(&db->cache, id)) != NULL) {
- *len_p = object->raw.len;
- *type_p = object->raw.type;
- git_odb_object_close(object);
- return GIT_SUCCESS;
+ if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) {
+ *len_p = object->cached.size;
+ *type_p = object->cached.type;
+ *out = object;
+ return 0;
}
+ *out = NULL;
+
for (i = 0; i < db->backends.length && error < 0; ++i) {
backend_internal *internal = git_vector_get(&db->backends, i);
git_odb_backend *b = internal->backend;
@@ -501,115 +735,155 @@ int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git
error = b->read_header(len_p, type_p, b, id);
}
- if (error == GIT_EPASSTHROUGH)
- return GIT_SUCCESS;
+ if (!error || error == GIT_PASSTHROUGH)
+ return 0;
/*
* no backend could read only the header.
* try reading the whole object and freeing the contents
*/
- if (error < 0) {
- if ((error = git_odb_read(&object, db, id)) < GIT_SUCCESS)
- return error; /* error already set - pass through */
+ if ((error = git_odb_read(&object, db, id)) < 0)
+ return error; /* error already set - pass along */
- *len_p = object->raw.len;
- *type_p = object->raw.type;
- git_odb_object_close(object);
- }
+ *len_p = object->cached.size;
+ *type_p = object->cached.type;
+ *out = object;
- return GIT_SUCCESS;
+ return 0;
}
int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id)
{
- unsigned int i;
- int error = GIT_ENOTFOUND;
+ size_t i, reads = 0;
+ int error;
git_rawobj raw;
+ git_odb_object *object;
assert(out && db && id);
- *out = git_cache_get(&db->cache, id);
+ *out = git_cache_get_raw(odb_cache(db), id);
if (*out != NULL)
- return GIT_SUCCESS;
+ return 0;
+
+ error = GIT_ENOTFOUND;
for (i = 0; i < db->backends.length && error < 0; ++i) {
backend_internal *internal = git_vector_get(&db->backends, i);
git_odb_backend *b = internal->backend;
- if (b->read != NULL)
+ if (b->read != NULL) {
+ ++reads;
error = b->read(&raw.data, &raw.len, &raw.type, b, id);
+ }
}
- if (error == GIT_EPASSTHROUGH || error == GIT_SUCCESS) {
- *out = git_cache_try_store(&db->cache, new_odb_object(id, &raw));
- return GIT_SUCCESS;
+ if (error && error != GIT_PASSTHROUGH) {
+ if (!reads)
+ return git_odb__error_notfound("no match for id", id);
+ return error;
}
- return git__rethrow(error, "Failed to read object");
+ giterr_clear();
+ if ((object = odb_object__alloc(id, &raw)) == NULL)
+ return -1;
+
+ *out = git_cache_store_raw(odb_cache(db), object);
+ return 0;
}
-int git_odb_read_prefix(git_odb_object **out, git_odb *db, const git_oid *short_id, unsigned int len)
+int git_odb_read_prefix(
+ git_odb_object **out, git_odb *db, const git_oid *short_id, size_t len)
{
- unsigned int i;
+ size_t i;
int error = GIT_ENOTFOUND;
- git_oid full_oid;
+ git_oid key = {{0}}, found_full_oid = {{0}};
git_rawobj raw;
- int found = 0;
+ void *data = NULL;
+ bool found = false;
+ git_odb_object *object;
assert(out && db);
if (len < GIT_OID_MINPREFIXLEN)
- return git__throw(GIT_EAMBIGUOUSOIDPREFIX, "Failed to lookup object. Prefix length is lower than %d.", GIT_OID_MINPREFIXLEN);
-
+ return git_odb__error_ambiguous("prefix length too short");
if (len > GIT_OID_HEXSZ)
len = GIT_OID_HEXSZ;
if (len == GIT_OID_HEXSZ) {
- *out = git_cache_get(&db->cache, short_id);
+ *out = git_cache_get_raw(odb_cache(db), short_id);
if (*out != NULL)
- return GIT_SUCCESS;
+ return 0;
}
- for (i = 0; i < db->backends.length && found < 2; ++i) {
+ /* just copy valid part of short_id */
+ memcpy(&key.id, short_id->id, (len + 1) / 2);
+ if (len & 1)
+ key.id[len / 2] &= 0xF0;
+
+ for (i = 0; i < db->backends.length; ++i) {
backend_internal *internal = git_vector_get(&db->backends, i);
git_odb_backend *b = internal->backend;
- if (b->read != NULL) {
- error = b->read_prefix(&full_oid, &raw.data, &raw.len, &raw.type, b, short_id, len);
- switch (error) {
- case GIT_SUCCESS:
- found++;
- break;
- case GIT_ENOTFOUND:
- case GIT_EPASSTHROUGH:
- break;
- case GIT_EAMBIGUOUSOIDPREFIX:
- return git__rethrow(error, "Failed to read object. Ambiguous sha1 prefix");
- default:
- return git__rethrow(error, "Failed to read object");
+ if (b->read_prefix != NULL) {
+ git_oid full_oid;
+ error = b->read_prefix(&full_oid, &raw.data, &raw.len, &raw.type, b, &key, len);
+ if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH)
+ continue;
+
+ if (error)
+ return error;
+
+ git__free(data);
+ data = raw.data;
+
+ if (found && git_oid__cmp(&full_oid, &found_full_oid)) {
+ git__free(raw.data);
+ return git_odb__error_ambiguous("multiple matches for prefix");
}
+
+ found_full_oid = full_oid;
+ found = true;
}
}
- if (found == 1) {
- *out = git_cache_try_store(&db->cache, new_odb_object(&full_oid, &raw));
- } else if (found > 1) {
- return git__throw(GIT_EAMBIGUOUSOIDPREFIX, "Failed to read object. Ambiguous sha1 prefix");
- } else {
- return git__throw(GIT_ENOTFOUND, "Failed to read object. Object not found");
- }
+ if (!found)
+ return git_odb__error_notfound("no match for prefix", &key);
+
+ if ((object = odb_object__alloc(&found_full_oid, &raw)) == NULL)
+ return -1;
- return GIT_SUCCESS;
+ *out = git_cache_store_raw(odb_cache(db), object);
+ return 0;
}
-int git_odb_write(git_oid *oid, git_odb *db, const void *data, size_t len, git_otype type)
+int git_odb_foreach(git_odb *db, git_odb_foreach_cb cb, void *payload)
{
unsigned int i;
+ backend_internal *internal;
+
+ git_vector_foreach(&db->backends, i, internal) {
+ git_odb_backend *b = internal->backend;
+ int error = b->foreach(b, cb, payload);
+ if (error < 0)
+ return error;
+ }
+
+ return 0;
+}
+
+int git_odb_write(
+ git_oid *oid, git_odb *db, const void *data, size_t len, git_otype type)
+{
+ size_t i;
int error = GIT_ERROR;
git_odb_stream *stream;
assert(oid && db);
+ git_odb_hash(oid, data, len, type);
+ if (git_odb_exists(db, oid))
+ return 0;
+
for (i = 0; i < db->backends.length && error < 0; ++i) {
backend_internal *internal = git_vector_get(&db->backends, i);
git_odb_backend *b = internal->backend;
@@ -619,30 +893,41 @@ int git_odb_write(git_oid *oid, git_odb *db, const void *data, size_t len, git_o
continue;
if (b->write != NULL)
- error = b->write(oid, b, data, len, type);
+ error = b->write(b, oid, data, len, type);
}
- if (error == GIT_EPASSTHROUGH || error == GIT_SUCCESS)
- return GIT_SUCCESS;
+ if (!error || error == GIT_PASSTHROUGH)
+ return 0;
- /* if no backends were able to write the object directly, we try a streaming
- * write to the backends; just write the whole object into the stream in one
- * push */
+ /* if no backends were able to write the object directly, we try a
+ * streaming write to the backends; just write the whole object into the
+ * stream in one push
+ */
+ if ((error = git_odb_open_wstream(&stream, db, len, type)) != 0)
+ return error;
- if ((error = git_odb_open_wstream(&stream, db, len, type)) == GIT_SUCCESS) {
- stream->write(stream, data, len);
- error = stream->finalize_write(oid, stream);
- stream->free(stream);
- return GIT_SUCCESS;
- }
+ stream->write(stream, data, len);
+ error = stream->finalize_write(stream, oid);
+ git_odb_stream_free(stream);
- return git__rethrow(error, "Failed to write object");
+ return error;
}
-int git_odb_open_wstream(git_odb_stream **stream, git_odb *db, size_t size, git_otype type)
+static void hash_header(git_hash_ctx *ctx, size_t size, git_otype type)
{
- unsigned int i;
+ char header[64];
+ int hdrlen;
+
+ hdrlen = git_odb__format_object_header(header, sizeof(header), size, type);
+ git_hash_update(ctx, header, hdrlen);
+}
+
+int git_odb_open_wstream(
+ git_odb_stream **stream, git_odb *db, size_t size, git_otype type)
+{
+ size_t i, writes = 0;
int error = GIT_ERROR;
+ git_hash_ctx *ctx = NULL;
assert(stream && db);
@@ -654,21 +939,97 @@ int git_odb_open_wstream(git_odb_stream **stream, git_odb *db, size_t size, git_
if (internal->is_alternate)
continue;
- if (b->writestream != NULL)
+ if (b->writestream != NULL) {
+ ++writes;
error = b->writestream(stream, b, size, type);
- else if (b->write != NULL)
+ } else if (b->write != NULL) {
+ ++writes;
error = init_fake_wstream(stream, b, size, type);
+ }
}
- if (error == GIT_EPASSTHROUGH || error == GIT_SUCCESS)
- return GIT_SUCCESS;
+ if (error < 0) {
+ if (error == GIT_PASSTHROUGH)
+ error = 0;
+ else if (!writes)
+ error = git_odb__error_unsupported_in_backend("write object");
+
+ goto done;
+ }
+
+ ctx = git__malloc(sizeof(git_hash_ctx));
+ GITERR_CHECK_ALLOC(ctx);
+
+ if ((error = git_hash_ctx_init(ctx)) < 0)
+ goto done;
+
+ hash_header(ctx, size, type);
+ (*stream)->hash_ctx = ctx;
- return git__rethrow(error, "Failed to open write stream");
+ (*stream)->declared_size = size;
+ (*stream)->received_bytes = 0;
+
+done:
+ return error;
+}
+
+static int git_odb_stream__invalid_length(
+ const git_odb_stream *stream,
+ const char *action)
+{
+ giterr_set(GITERR_ODB,
+ "Cannot %s - "
+ "Invalid length. %"PRIuZ" was expected. The "
+ "total size of the received chunks amounts to %"PRIuZ".",
+ action, stream->declared_size, stream->received_bytes);
+
+ return -1;
+}
+
+int git_odb_stream_write(git_odb_stream *stream, const char *buffer, size_t len)
+{
+ git_hash_update(stream->hash_ctx, buffer, len);
+
+ stream->received_bytes += len;
+
+ if (stream->received_bytes > stream->declared_size)
+ return git_odb_stream__invalid_length(stream,
+ "stream_write()");
+
+ return stream->write(stream, buffer, len);
+}
+
+int git_odb_stream_finalize_write(git_oid *out, git_odb_stream *stream)
+{
+ if (stream->received_bytes != stream->declared_size)
+ return git_odb_stream__invalid_length(stream,
+ "stream_finalize_write()");
+
+ git_hash_final(out, stream->hash_ctx);
+
+ if (git_odb_exists(stream->backend->odb, out))
+ return 0;
+
+ return stream->finalize_write(stream, out);
+}
+
+int git_odb_stream_read(git_odb_stream *stream, char *buffer, size_t len)
+{
+ return stream->read(stream, buffer, len);
+}
+
+void git_odb_stream_free(git_odb_stream *stream)
+{
+ if (stream == NULL)
+ return;
+
+ git__free(stream->hash_ctx);
+ stream->free(stream);
}
int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oid)
{
- unsigned int i;
+ size_t i, reads = 0;
int error = GIT_ERROR;
assert(stream && db);
@@ -677,13 +1038,95 @@ int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oi
backend_internal *internal = git_vector_get(&db->backends, i);
git_odb_backend *b = internal->backend;
- if (b->readstream != NULL)
+ if (b->readstream != NULL) {
+ ++reads;
error = b->readstream(stream, b, oid);
+ }
+ }
+
+ if (error == GIT_PASSTHROUGH)
+ error = 0;
+ if (error < 0 && !reads)
+ error = git_odb__error_unsupported_in_backend("read object streamed");
+
+ return error;
+}
+
+int git_odb_write_pack(struct git_odb_writepack **out, git_odb *db, git_transfer_progress_cb progress_cb, void *progress_payload)
+{
+ size_t i, writes = 0;
+ 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) {
+ ++writes;
+ error = b->writepack(out, b, db, progress_cb, progress_payload);
+ }
+ }
+
+ if (error == GIT_PASSTHROUGH)
+ error = 0;
+ if (error < 0 && !writes)
+ error = git_odb__error_unsupported_in_backend("write pack");
+
+ return error;
+}
+
+void *git_odb_backend_malloc(git_odb_backend *backend, size_t len)
+{
+ GIT_UNUSED(backend);
+ return git__malloc(len);
+}
+
+int git_odb_refresh(struct git_odb *db)
+{
+ size_t i;
+ assert(db);
+
+ for (i = 0; i < db->backends.length; ++i) {
+ backend_internal *internal = git_vector_get(&db->backends, i);
+ git_odb_backend *b = internal->backend;
+
+ if (b->refresh != NULL) {
+ int error = b->refresh(b);
+ if (error < 0)
+ return error;
+ }
}
- if (error == GIT_EPASSTHROUGH || error == GIT_SUCCESS)
- return GIT_SUCCESS;
+ return 0;
+}
- return git__rethrow(error, "Failed to open read stream");
+int git_odb__error_notfound(const char *message, const git_oid *oid)
+{
+ if (oid != NULL) {
+ char oid_str[GIT_OID_HEXSZ + 1];
+ git_oid_tostr(oid_str, sizeof(oid_str), oid);
+ giterr_set(GITERR_ODB, "Object not found - %s (%s)", message, oid_str);
+ } else
+ giterr_set(GITERR_ODB, "Object not found - %s", message);
+
+ return GIT_ENOTFOUND;
}
+int git_odb__error_ambiguous(const char *message)
+{
+ giterr_set(GITERR_ODB, "Ambiguous SHA1 prefix - %s", message);
+ return GIT_EAMBIGUOUS;
+}
+
+int git_odb_init_backend(git_odb_backend *backend, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ backend, version, git_odb_backend, GIT_ODB_BACKEND_INIT);
+ return 0;
+}
diff --git a/src/odb.h b/src/odb.h
index f3685834e..61dd9a7fd 100644
--- a/src/odb.h
+++ b/src/odb.h
@@ -1,3 +1,9 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_odb_h__
#define INCLUDE_odb_h__
@@ -7,27 +13,91 @@
#include "vector.h"
#include "cache.h"
+#include "posix.h"
+#include "filter.h"
+
+#define GIT_OBJECTS_DIR "objects/"
+#define GIT_OBJECT_DIR_MODE 0777
+#define GIT_OBJECT_FILE_MODE 0444
/* DO NOT EXPORT */
typedef struct {
- void *data; /**< Raw, decompressed object data. */
- size_t len; /**< Total number of bytes in data. */
- git_otype type; /**< Type of this object. */
+ void *data; /**< Raw, decompressed object data. */
+ size_t len; /**< Total number of bytes in data. */
+ git_otype type; /**< Type of this object. */
} git_rawobj;
/* EXPORT */
struct git_odb_object {
git_cached_obj cached;
- git_rawobj raw;
+ void *buffer;
};
/* EXPORT */
struct git_odb {
- void *_internal;
+ git_refcount rc;
git_vector backends;
- git_cache cache;
+ git_cache own_cache;
};
-int git_odb__hash_obj(git_oid *id, char *hdr, size_t n, int *len, git_rawobj *obj);
+/*
+ * Hash a git_rawobj internally.
+ * The `git_rawobj` is supposed to be previously initialized
+ */
+int git_odb__hashobj(git_oid *id, git_rawobj *obj);
+
+/*
+ * Format the object header such as it would appear in the on-disk object
+ */
+int git_odb__format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type);
+/*
+ * Hash an open file descriptor.
+ * This is a performance call when the contents of a fd need to be hashed,
+ * but the fd is already open and we have the size of the contents.
+ *
+ * Saves us some `stat` calls.
+ *
+ * The fd is never closed, not even on error. It must be opened and closed
+ * by the caller
+ */
+int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type);
+
+/*
+ * Hash an open file descriptor applying an array of filters
+ * Acts just like git_odb__hashfd with the addition of filters...
+ */
+int git_odb__hashfd_filtered(
+ git_oid *out, git_file fd, size_t len, git_otype type, git_filter_list *fl);
+
+/*
+ * Hash a `path`, assuming it could be a POSIX symlink: if the path is a
+ * symlink, then the raw contents of the symlink will be hashed. Otherwise,
+ * this will fallback to `git_odb__hashfd`.
+ *
+ * The hash type for this call is always `GIT_OBJ_BLOB` because symlinks may
+ * only point to blobs.
+ */
+int git_odb__hashlink(git_oid *out, const char *path);
+
+/*
+ * Generate a GIT_ENOTFOUND error for the ODB.
+ */
+int git_odb__error_notfound(const char *message, const git_oid *oid);
+
+/*
+ * Generate a GIT_EAMBIGUOUS error for the ODB.
+ */
+int git_odb__error_ambiguous(const char *message);
+
+/*
+ * Attempt to read object header or just return whole object if it could
+ * not be read.
+ */
+int git_odb__read_header_or_object(
+ git_odb_object **out, size_t *len_p, git_otype *type_p,
+ git_odb *db, const git_oid *id);
+
+/* fully free the object; internal method, DO NOT EXPORT */
+void git_odb_object__free(void *object);
#endif
diff --git a/src/odb_loose.c b/src/odb_loose.c
index a3a4e5b56..b2e8bed4d 100644
--- a/src/odb_loose.c
+++ b/src/odb_loose.c
@@ -1,32 +1,14 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
-#include "git2/zlib.h"
+#include <zlib.h>
#include "git2/object.h"
-#include "git2/oid.h"
+#include "git2/sys/odb_backend.h"
#include "fileops.h"
#include "hash.h"
#include "odb.h"
@@ -36,15 +18,14 @@
#include "git2/odb_backend.h"
#include "git2/types.h"
-typedef struct { /* object header data */
- git_otype type; /* object type */
- size_t size; /* object size */
+typedef struct { /* object header data */
+ git_otype type; /* object type */
+ size_t size; /* object size */
} obj_hdr;
typedef struct {
git_odb_stream stream;
git_filebuf fbuf;
- int finished;
} loose_writestream;
typedef struct loose_backend {
@@ -52,7 +33,11 @@ typedef struct loose_backend {
int object_zlib_level; /** loose object zlib compression level. */
int fsync_object_files; /** loose object file fsync flag. */
- char *objects_dir;
+ mode_t object_file_mode;
+ mode_t object_dir_mode;
+
+ size_t objects_dirlen;
+ char objects_dir[GIT_FLEX_ARRAY];
} loose_backend;
/* State structure for exploring directories,
@@ -61,7 +46,7 @@ typedef struct loose_backend {
typedef struct {
size_t dir_len;
unsigned char short_oid[GIT_OID_HEXSZ]; /* hex formatted oid to match */
- unsigned int short_oid_len;
+ size_t short_oid_len;
int found; /* number of matching
* objects already found */
unsigned char res_oid[GIT_OID_HEXSZ]; /* hex formatted oid of
@@ -69,41 +54,44 @@ typedef struct {
} loose_locate_object_state;
-
/***********************************************************
*
* MISCELANEOUS HELPER FUNCTIONS
*
***********************************************************/
-static size_t object_file_name(char *name, size_t n, char *dir, const git_oid *id)
+static int object_file_name(
+ git_buf *name, const loose_backend *be, const git_oid *id)
{
- size_t len = strlen(dir);
-
- /* check length: 43 = 40 hex sha1 chars + 2 * '/' + '\0' */
- if (len+43 > n)
- return len+43;
+ /* expand length for object root + 40 hex sha1 chars + 2 * '/' + '\0' */
+ if (git_buf_grow(name, be->objects_dirlen + GIT_OID_HEXSZ + 3) < 0)
+ return -1;
- /* the object dir: eg $GIT_DIR/objects */
- strcpy(name, dir);
- if (name[len-1] != '/')
- name[len++] = '/';
+ git_buf_set(name, be->objects_dir, be->objects_dirlen);
+ git_path_to_dir(name);
/* loose object filename: aa/aaa... (41 bytes) */
- git_oid_pathfmt(&name[len], id);
- name[len+41] = '\0';
+ git_oid_pathfmt(name->ptr + name->size, id);
+ name->size += GIT_OID_HEXSZ + 1;
+ name->ptr[name->size] = '\0';
return 0;
}
+static int object_mkdir(const git_buf *name, const loose_backend *be)
+{
+ return git_futils_mkdir(
+ name->ptr + be->objects_dirlen, be->objects_dir, be->object_dir_mode,
+ GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR);
+}
-static size_t get_binary_object_header(obj_hdr *hdr, git_fbuffer *obj)
+static size_t get_binary_object_header(obj_hdr *hdr, git_buf *obj)
{
unsigned char c;
- unsigned char *data = obj->data;
+ unsigned char *data = (unsigned char *)obj->ptr;
size_t shift, size, used = 0;
- if (obj->len == 0)
+ if (git_buf_len(obj) == 0)
return 0;
c = data[used++];
@@ -112,7 +100,7 @@ static size_t get_binary_object_header(obj_hdr *hdr, git_fbuffer *obj)
size = c & 15;
shift = 4;
while (c & 0x80) {
- if (obj->len <= used)
+ if (git_buf_len(obj) <= used)
return 0;
if (sizeof(size_t) * 8 <= shift)
return 0;
@@ -142,7 +130,7 @@ static size_t get_object_header(obj_hdr *hdr, unsigned char *data)
if (used == 0)
return 0;
hdr->type = git_object_string2type(typename);
- used++; /* consume the space */
+ used++; /* consume the space */
/*
* length follows immediately in decimal (without
@@ -182,29 +170,29 @@ static size_t get_object_header(obj_hdr *hdr, unsigned char *data)
static void init_stream(z_stream *s, void *out, size_t len)
{
memset(s, 0, sizeof(*s));
- s->next_out = out;
- s->avail_out = len;
+ s->next_out = out;
+ s->avail_out = (uInt)len;
}
static void set_stream_input(z_stream *s, void *in, size_t len)
{
- s->next_in = in;
- s->avail_in = len;
+ s->next_in = in;
+ s->avail_in = (uInt)len;
}
static void set_stream_output(z_stream *s, void *out, size_t len)
{
- s->next_out = out;
- s->avail_out = len;
+ s->next_out = out;
+ s->avail_out = (uInt)len;
}
-static int start_inflate(z_stream *s, git_fbuffer *obj, void *out, size_t len)
+static int start_inflate(z_stream *s, git_buf *obj, void *out, size_t len)
{
int status;
init_stream(s, out, len);
- set_stream_input(s, obj->data, obj->len);
+ set_stream_input(s, obj->ptr, git_buf_len(obj));
if ((status = inflateInit(s)) < Z_OK)
return status;
@@ -221,10 +209,12 @@ static int finish_inflate(z_stream *s)
inflateEnd(s);
- if ((status != Z_STREAM_END) || (s->avail_in != 0))
- return git__throw(GIT_ERROR, "Failed to finish inflation. Stream aborted prematurely");
+ if ((status != Z_STREAM_END) || (s->avail_in != 0)) {
+ giterr_set(GITERR_ZLIB, "Failed to finish ZLib inflation. Stream aborted prematurely");
+ return -1;
+ }
- return GIT_SUCCESS;
+ return 0;
}
static int is_zlib_compressed_data(unsigned char *data)
@@ -232,7 +222,7 @@ static int is_zlib_compressed_data(unsigned char *data)
unsigned int w;
w = ((unsigned int)(data[0]) << 8) + data[1];
- return data[0] == 0x78 && !(w % 31);
+ return (data[0] & 0x8F) == 0x08 && !(w % 31);
}
static int inflate_buffer(void *in, size_t inlen, void *out, size_t outlen)
@@ -242,27 +232,30 @@ static int inflate_buffer(void *in, size_t inlen, void *out, size_t outlen)
memset(&zs, 0x0, sizeof(zs));
- zs.next_out = out;
- zs.avail_out = outlen;
+ zs.next_out = out;
+ zs.avail_out = (uInt)outlen;
- zs.next_in = in;
- zs.avail_in = inlen;
+ zs.next_in = in;
+ zs.avail_in = (uInt)inlen;
- if (inflateInit(&zs) < Z_OK)
- return git__throw(GIT_ERROR, "Failed to inflate buffer");
+ if (inflateInit(&zs) < Z_OK) {
+ giterr_set(GITERR_ZLIB, "Failed to inflate buffer");
+ return -1;
+ }
while (status == Z_OK)
status = inflate(&zs, Z_FINISH);
inflateEnd(&zs);
- if ((status != Z_STREAM_END) /*|| (zs.avail_in != 0) */)
- return git__throw(GIT_ERROR, "Failed to inflate buffer. Stream aborted prematurely");
-
- if (zs.total_out != outlen)
- return git__throw(GIT_ERROR, "Failed to inflate buffer. Stream aborted prematurely");
+ if (status != Z_STREAM_END /* || zs.avail_in != 0 */ ||
+ zs.total_out != outlen)
+ {
+ giterr_set(GITERR_ZLIB, "Failed to inflate buffer. Stream aborted prematurely");
+ return -1;
+ }
- return GIT_SUCCESS;
+ return 0;
}
static void *inflate_tail(z_stream *s, void *hb, size_t used, obj_hdr *hdr)
@@ -295,7 +288,7 @@ static void *inflate_tail(z_stream *s, void *hb, size_t used, obj_hdr *hdr)
else {
set_stream_output(s, buf + used, hdr->size - used);
if (finish_inflate(s)) {
- free(buf);
+ git__free(buf);
return NULL;
}
}
@@ -309,7 +302,7 @@ static void *inflate_tail(z_stream *s, void *hb, size_t used, obj_hdr *hdr)
* of loose object data into packs. This format is no longer used, but
* we must still read it.
*/
-static int inflate_packlike_loose_disk_obj(git_rawobj *out, git_fbuffer *obj)
+static int inflate_packlike_loose_disk_obj(git_rawobj *out, git_buf *obj)
{
unsigned char *in, *buf;
obj_hdr hdr;
@@ -319,35 +312,34 @@ static int inflate_packlike_loose_disk_obj(git_rawobj *out, git_fbuffer *obj)
* read the object header, which is an (uncompressed)
* binary encoding of the object type and size.
*/
- if ((used = get_binary_object_header(&hdr, obj)) == 0)
- return git__throw(GIT_ERROR, "Failed to inflate loose object. Object has no header");
-
- if (!git_object_typeisloose(hdr.type))
- return git__throw(GIT_ERROR, "Failed to inflate loose object. Wrong object type");
+ if ((used = get_binary_object_header(&hdr, obj)) == 0 ||
+ !git_object_typeisloose(hdr.type)) {
+ giterr_set(GITERR_ODB, "Failed to inflate loose object.");
+ return -1;
+ }
/*
* allocate a buffer and inflate the data into it
*/
buf = git__malloc(hdr.size + 1);
- if (!buf)
- return GIT_ENOMEM;
-
- in = ((unsigned char *)obj->data) + used;
- len = obj->len - used;
- if (inflate_buffer(in, len, buf, hdr.size)) {
- free(buf);
- return git__throw(GIT_ERROR, "Failed to inflate loose object. Could not inflate buffer");
+ GITERR_CHECK_ALLOC(buf);
+
+ in = ((unsigned char *)obj->ptr) + used;
+ len = obj->size - used;
+ if (inflate_buffer(in, len, buf, hdr.size) < 0) {
+ git__free(buf);
+ return -1;
}
buf[hdr.size] = '\0';
out->data = buf;
- out->len = hdr.size;
+ out->len = hdr.size;
out->type = hdr.type;
- return GIT_SUCCESS;
+ return 0;
}
-static int inflate_disk_obj(git_rawobj *out, git_fbuffer *obj)
+static int inflate_disk_obj(git_rawobj *out, git_buf *obj)
{
unsigned char head[64], *buf;
z_stream zs;
@@ -357,35 +349,34 @@ static int inflate_disk_obj(git_rawobj *out, git_fbuffer *obj)
/*
* check for a pack-like loose object
*/
- if (!is_zlib_compressed_data(obj->data))
+ if (!is_zlib_compressed_data((unsigned char *)obj->ptr))
return inflate_packlike_loose_disk_obj(out, obj);
/*
* inflate the initial part of the io buffer in order
* to parse the object header (type and size).
*/
- if (start_inflate(&zs, obj, head, sizeof(head)) < Z_OK)
- return git__throw(GIT_ERROR, "Failed to inflate disk object. Could not inflate buffer");
-
- if ((used = get_object_header(&hdr, head)) == 0)
- return git__throw(GIT_ERROR, "Failed to inflate disk object. Object has no header");
-
- if (!git_object_typeisloose(hdr.type))
- return git__throw(GIT_ERROR, "Failed to inflate disk object. Wrong object type");
+ if (start_inflate(&zs, obj, head, sizeof(head)) < Z_OK ||
+ (used = get_object_header(&hdr, head)) == 0 ||
+ !git_object_typeisloose(hdr.type))
+ {
+ giterr_set(GITERR_ODB, "Failed to inflate disk object.");
+ return -1;
+ }
/*
* allocate a buffer and inflate the object data into it
* (including the initial sequence in the head buffer).
*/
if ((buf = inflate_tail(&zs, head, used, &hdr)) == NULL)
- return GIT_ENOMEM;
+ return -1;
buf[hdr.size] = '\0';
out->data = buf;
- out->len = hdr.size;
+ out->len = hdr.size;
out->type = hdr.type;
- return GIT_SUCCESS;
+ return 0;
}
@@ -402,29 +393,31 @@ static int inflate_disk_obj(git_rawobj *out, git_fbuffer *obj)
*
***********************************************************/
-static int read_loose(git_rawobj *out, const char *loc)
+static int read_loose(git_rawobj *out, git_buf *loc)
{
int error;
- git_fbuffer obj = GIT_FBUFFER_INIT;
+ git_buf obj = GIT_BUF_INIT;
assert(out && loc);
+ if (git_buf_oom(loc))
+ return -1;
+
out->data = NULL;
- out->len = 0;
+ out->len = 0;
out->type = GIT_OBJ_BAD;
- if (git_futils_readbuffer(&obj, loc) < 0)
- return git__throw(GIT_ENOTFOUND, "Failed to read loose object. File not found");
+ if (!(error = git_futils_readbuffer(&obj, loc->ptr)))
+ error = inflate_disk_obj(out, &obj);
- error = inflate_disk_obj(out, &obj);
- git_futils_freebuffer(&obj);
+ git_buf_free(&obj);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to read loose object");
+ return error;
}
-static int read_header_loose(git_rawobj *out, const char *loc)
+static int read_header_loose(git_rawobj *out, git_buf *loc)
{
- int error = GIT_SUCCESS, z_return = Z_ERRNO, read_bytes;
+ int error = 0, z_return = Z_ERRNO, read_bytes;
git_file fd;
z_stream zs;
obj_hdr header_obj;
@@ -432,133 +425,155 @@ static int read_header_loose(git_rawobj *out, const char *loc)
assert(out && loc);
+ if (git_buf_oom(loc))
+ return -1;
+
out->data = NULL;
- if ((fd = p_open(loc, O_RDONLY)) < 0)
- return git__throw(GIT_ENOTFOUND, "Failed to read loose object header. File not found");
+ if ((fd = git_futils_open_ro(loc->ptr)) < 0)
+ return fd;
init_stream(&zs, inflated_buffer, sizeof(inflated_buffer));
- if (inflateInit(&zs) < Z_OK) {
- error = GIT_EZLIB;
- goto cleanup;
- }
+ z_return = inflateInit(&zs);
- do {
- if ((read_bytes = read(fd, raw_buffer, sizeof(raw_buffer))) > 0) {
+ while (z_return == Z_OK) {
+ if ((read_bytes = p_read(fd, raw_buffer, sizeof(raw_buffer))) > 0) {
set_stream_input(&zs, raw_buffer, read_bytes);
z_return = inflate(&zs, 0);
- } else {
+ } else
z_return = Z_STREAM_END;
- break;
- }
- } while (z_return == Z_OK);
+ }
if ((z_return != Z_STREAM_END && z_return != Z_BUF_ERROR)
|| get_object_header(&header_obj, inflated_buffer) == 0
- || git_object_typeisloose(header_obj.type) == 0) {
- error = GIT_EOBJCORRUPTED;
- goto cleanup;
+ || git_object_typeisloose(header_obj.type) == 0)
+ {
+ giterr_set(GITERR_ZLIB, "Failed to read loose object header");
+ error = -1;
+ } else {
+ out->len = header_obj.size;
+ out->type = header_obj.type;
}
- out->len = header_obj.size;
- out->type = header_obj.type;
-
-cleanup:
finish_inflate(&zs);
p_close(fd);
- if (error < GIT_SUCCESS)
- return git__throw(error, "Failed to read loose object header. Header is corrupted");
-
- return GIT_SUCCESS;
+ return error;
}
-static int locate_object(char *object_location, loose_backend *backend, const git_oid *oid)
+static int locate_object(
+ git_buf *object_location,
+ loose_backend *backend,
+ const git_oid *oid)
{
- object_file_name(object_location, GIT_PATH_MAX, backend->objects_dir, oid);
- return git_futils_exists(object_location);
+ int error = object_file_name(object_location, backend, oid);
+
+ if (!error && !git_path_exists(object_location->ptr))
+ return GIT_ENOTFOUND;
+
+ return error;
}
/* Explore an entry of a directory and see if it matches a short oid */
-int fn_locate_object_short_oid(void *state, char *pathbuf) {
+static int fn_locate_object_short_oid(void *state, git_buf *pathbuf) {
loose_locate_object_state *sstate = (loose_locate_object_state *)state;
- size_t pathbuf_len = strlen(pathbuf);
- if (pathbuf_len - sstate->dir_len != GIT_OID_HEXSZ - 2) {
+ if (git_buf_len(pathbuf) - sstate->dir_len != GIT_OID_HEXSZ - 2) {
/* Entry cannot be an object. Continue to next entry */
- return GIT_SUCCESS;
+ return 0;
}
- if (!git_futils_exists(pathbuf) && git_futils_isdir(pathbuf)) {
+ if (git_path_isdir(pathbuf->ptr) == false) {
/* We are already in the directory matching the 2 first hex characters,
* compare the first ncmp characters of the oids */
if (!memcmp(sstate->short_oid + 2,
- (unsigned char *)pathbuf + sstate->dir_len,
+ (unsigned char *)pathbuf->ptr + sstate->dir_len,
sstate->short_oid_len - 2)) {
if (!sstate->found) {
sstate->res_oid[0] = sstate->short_oid[0];
sstate->res_oid[1] = sstate->short_oid[1];
- memcpy(sstate->res_oid+2, pathbuf+sstate->dir_len, GIT_OID_HEXSZ-2);
+ memcpy(sstate->res_oid+2, pathbuf->ptr+sstate->dir_len, GIT_OID_HEXSZ-2);
}
sstate->found++;
}
}
+
if (sstate->found > 1)
- return git__throw(GIT_EAMBIGUOUSOIDPREFIX, "Ambiguous sha1 prefix within loose objects");
+ return GIT_EAMBIGUOUS;
- return GIT_SUCCESS;
+ return 0;
}
/* Locate an object matching a given short oid */
-static int locate_object_short_oid(char *object_location, git_oid *res_oid, loose_backend *backend, const git_oid *short_oid, unsigned int len)
+static int locate_object_short_oid(
+ git_buf *object_location,
+ git_oid *res_oid,
+ loose_backend *backend,
+ const git_oid *short_oid,
+ size_t len)
{
char *objects_dir = backend->objects_dir;
size_t dir_len = strlen(objects_dir);
loose_locate_object_state state;
int error;
- if (dir_len+43 > GIT_PATH_MAX)
- return git__throw(GIT_ERROR, "Failed to locate object from short oid. Object path too long");
+ /* prealloc memory for OBJ_DIR/xx/xx..38x..xx */
+ if (git_buf_grow(object_location, dir_len + 3 + GIT_OID_HEXSZ) < 0)
+ return -1;
- strcpy(object_location, objects_dir);
+ git_buf_set(object_location, objects_dir, dir_len);
+ git_path_to_dir(object_location);
- /* Add a separator if not already there */
- if (object_location[dir_len-1] != '/')
- object_location[dir_len++] = '/';
+ /* save adjusted position at end of dir so it can be restored later */
+ dir_len = git_buf_len(object_location);
/* Convert raw oid to hex formatted oid */
git_oid_fmt((char *)state.short_oid, short_oid);
+
/* Explore OBJ_DIR/xx/ where xx is the beginning of hex formatted short oid */
- sprintf(object_location+dir_len, "%.2s/", state.short_oid);
+ if (git_buf_put(object_location, (char *)state.short_oid, 3) < 0)
+ return -1;
+ object_location->ptr[object_location->size - 1] = '/';
/* Check that directory exists */
- if (git_futils_exists(object_location) || git_futils_isdir(object_location))
- return git__throw(GIT_ENOTFOUND, "Failed to locate object from short oid. Object not found");
+ if (git_path_isdir(object_location->ptr) == false)
+ return git_odb__error_notfound("no matching loose object for prefix", short_oid);
- state.dir_len = dir_len+3;
+ state.dir_len = git_buf_len(object_location);
state.short_oid_len = len;
state.found = 0;
+
/* Explore directory to find a unique object matching short_oid */
- error = git_futils_direach(object_location, GIT_PATH_MAX, fn_locate_object_short_oid, &state);
- if (error) {
- return git__rethrow(error, "Failed to locate object from short oid");
- }
- if (!state.found) {
- return git__throw(GIT_ENOTFOUND, "Failed to locate object from short oid. Object not found");
- }
+ error = git_path_direach(
+ object_location, 0, fn_locate_object_short_oid, &state);
+ if (error < 0 && error != GIT_EAMBIGUOUS)
+ return error;
+
+ if (!state.found)
+ return git_odb__error_notfound("no matching loose object for prefix", short_oid);
+
+ if (state.found > 1)
+ return git_odb__error_ambiguous("multiple matches in loose objects");
/* Convert obtained hex formatted oid to raw */
error = git_oid_fromstr(res_oid, (char *)state.res_oid);
- if (error) {
- return git__rethrow(error, "Failed to locate object from short oid");
- }
+ if (error)
+ return error;
/* Update the location according to the oid obtained */
- git_oid_pathfmt(object_location+dir_len, res_oid);
- return GIT_SUCCESS;
+ git_buf_truncate(object_location, dir_len);
+ if (git_buf_grow(object_location, dir_len + GIT_OID_HEXSZ + 2) < 0)
+ return -1;
+
+ git_oid_pathfmt(object_location->ptr + dir_len, res_oid);
+
+ object_location->size += GIT_OID_HEXSZ + 1;
+ object_location->ptr[object_location->size] = '\0';
+
+ return 0;
}
@@ -577,9 +592,9 @@ static int locate_object_short_oid(char *object_location, git_oid *res_oid, loos
*
***********************************************************/
-int loose_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid)
+static int loose_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid)
{
- char object_path[GIT_PATH_MAX];
+ git_buf object_path = GIT_BUF_INIT;
git_rawobj raw;
int error;
@@ -588,159 +603,244 @@ int loose_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend
raw.len = 0;
raw.type = GIT_OBJ_BAD;
- if (locate_object(object_path, (loose_backend *)backend, oid) < 0)
- return git__throw(GIT_ENOTFOUND, "Failed to read loose backend header. Object not found");
+ if (locate_object(&object_path, (loose_backend *)backend, oid) < 0)
+ error = git_odb__error_notfound("no matching loose object", oid);
+ else if ((error = read_header_loose(&raw, &object_path)) == 0) {
+ *len_p = raw.len;
+ *type_p = raw.type;
+ }
- if ((error = read_header_loose(&raw, object_path)) < GIT_SUCCESS)
- return error;
+ git_buf_free(&object_path);
- *len_p = raw.len;
- *type_p = raw.type;
- return GIT_SUCCESS;
+ return error;
}
-int loose_backend__read(void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid)
+static int loose_backend__read(void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid)
{
- char object_path[GIT_PATH_MAX];
+ git_buf object_path = GIT_BUF_INIT;
git_rawobj raw;
- int error;
+ int error = 0;
assert(backend && oid);
- if (locate_object(object_path, (loose_backend *)backend, oid) < 0)
- return git__throw(GIT_ENOTFOUND, "Failed to read loose backend. Object not found");
-
- if ((error = read_loose(&raw, object_path)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to read loose backend");
+ if (locate_object(&object_path, (loose_backend *)backend, oid) < 0)
+ error = git_odb__error_notfound("no matching loose object", oid);
+ else if ((error = read_loose(&raw, &object_path)) == 0) {
+ *buffer_p = raw.data;
+ *len_p = raw.len;
+ *type_p = raw.type;
+ }
- *buffer_p = raw.data;
- *len_p = raw.len;
- *type_p = raw.type;
+ git_buf_free(&object_path);
- return GIT_SUCCESS;
+ return error;
}
-int loose_backend__read_prefix(
+static int loose_backend__read_prefix(
git_oid *out_oid,
void **buffer_p,
size_t *len_p,
git_otype *type_p,
git_odb_backend *backend,
const git_oid *short_oid,
- unsigned int len)
+ size_t len)
{
- if (len < GIT_OID_MINPREFIXLEN)
- return git__throw(GIT_EAMBIGUOUSOIDPREFIX, "Failed to read loose backend. Prefix length is lower than %d.", GIT_OID_MINPREFIXLEN);
+ int error = 0;
+
+ assert(len >= GIT_OID_MINPREFIXLEN && len <= GIT_OID_HEXSZ);
- if (len >= GIT_OID_HEXSZ) {
+ if (len == GIT_OID_HEXSZ) {
/* We can fall back to regular read method */
- int error = loose_backend__read(buffer_p, len_p, type_p, backend, short_oid);
- if (error == GIT_SUCCESS)
+ error = loose_backend__read(buffer_p, len_p, type_p, backend, short_oid);
+ if (!error)
git_oid_cpy(out_oid, short_oid);
-
- return error;
} else {
- char object_path[GIT_PATH_MAX];
+ git_buf object_path = GIT_BUF_INIT;
git_rawobj raw;
- int error;
assert(backend && short_oid);
- if ((error = locate_object_short_oid(object_path, out_oid, (loose_backend *)backend, short_oid, len)) < 0) {
- return git__rethrow(error, "Failed to read loose backend");
+ if ((error = locate_object_short_oid(&object_path, out_oid,
+ (loose_backend *)backend, short_oid, len)) == 0 &&
+ (error = read_loose(&raw, &object_path)) == 0)
+ {
+ *buffer_p = raw.data;
+ *len_p = raw.len;
+ *type_p = raw.type;
}
- if ((error = read_loose(&raw, object_path)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to read loose backend");
-
- *buffer_p = raw.data;
- *len_p = raw.len;
- *type_p = raw.type;
+ git_buf_free(&object_path);
}
- return GIT_SUCCESS;
+ return error;
}
-int loose_backend__exists(git_odb_backend *backend, const git_oid *oid)
+static int loose_backend__exists(git_odb_backend *backend, const git_oid *oid)
{
- char object_path[GIT_PATH_MAX];
+ git_buf object_path = GIT_BUF_INIT;
+ int error;
assert(backend && oid);
- return locate_object(object_path, (loose_backend *)backend, oid) == GIT_SUCCESS;
+ error = locate_object(&object_path, (loose_backend *)backend, oid);
+
+ git_buf_free(&object_path);
+
+ return !error;
}
-int loose_backend__stream_fwrite(git_oid *oid, git_odb_stream *_stream)
+static int loose_backend__exists_prefix(
+ git_oid *out, git_odb_backend *backend, const git_oid *short_id, size_t len)
{
- loose_writestream *stream = (loose_writestream *)_stream;
- loose_backend *backend = (loose_backend *)_stream->backend;
-
+ git_buf object_path = GIT_BUF_INIT;
int error;
- char final_path[GIT_PATH_MAX];
- if ((error = git_filebuf_hash(oid, &stream->fbuf)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to write loose backend");
+ assert(backend && out && short_id && len >= GIT_OID_MINPREFIXLEN);
- if (object_file_name(final_path, sizeof(final_path), backend->objects_dir, oid))
- return GIT_ENOMEM;
+ error = locate_object_short_oid(
+ &object_path, out, (loose_backend *)backend, short_id, len);
- if ((error = git_futils_mkpath2file(final_path)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to write loose backend");
+ git_buf_free(&object_path);
- stream->finished = 1;
- return git_filebuf_commit_at(&stream->fbuf, final_path);
+ return error;
}
-int loose_backend__stream_write(git_odb_stream *_stream, const char *data, size_t len)
+struct foreach_state {
+ size_t dir_len;
+ git_odb_foreach_cb cb;
+ void *data;
+};
+
+GIT_INLINE(int) filename_to_oid(git_oid *oid, const char *ptr)
{
- loose_writestream *stream = (loose_writestream *)_stream;
- return git_filebuf_write(&stream->fbuf, data, len);
+ int v, i = 0;
+ if (strlen(ptr) != 41)
+ return -1;
+
+ if (ptr[2] != '/') {
+ return -1;
+ }
+
+ v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i+1]);
+ if (v < 0)
+ return -1;
+
+ oid->id[0] = (unsigned char) v;
+
+ ptr += 3;
+ for (i = 0; i < 38; i += 2) {
+ v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i + 1]);
+ if (v < 0)
+ return -1;
+
+ oid->id[1 + i/2] = (unsigned char) v;
+ }
+
+ return 0;
+}
+
+static int foreach_object_dir_cb(void *_state, git_buf *path)
+{
+ git_oid oid;
+ struct foreach_state *state = (struct foreach_state *) _state;
+
+ if (filename_to_oid(&oid, path->ptr + state->dir_len) < 0)
+ return 0;
+
+ return giterr_set_after_callback_function(
+ state->cb(&oid, state->data), "git_odb_foreach");
+}
+
+static int foreach_cb(void *_state, git_buf *path)
+{
+ struct foreach_state *state = (struct foreach_state *) _state;
+
+ /* non-dir is some stray file, ignore it */
+ if (!git_path_isdir(git_buf_cstr(path)))
+ return 0;
+
+ return git_path_direach(path, 0, foreach_object_dir_cb, state);
+}
+
+static int loose_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data)
+{
+ char *objects_dir;
+ int error;
+ git_buf buf = GIT_BUF_INIT;
+ struct foreach_state state;
+ loose_backend *backend = (loose_backend *) _backend;
+
+ assert(backend && cb);
+
+ objects_dir = backend->objects_dir;
+
+ git_buf_sets(&buf, objects_dir);
+ git_path_to_dir(&buf);
+ if (git_buf_oom(&buf))
+ return -1;
+
+ memset(&state, 0, sizeof(state));
+ state.cb = cb;
+ state.data = data;
+ state.dir_len = git_buf_len(&buf);
+
+ error = git_path_direach(&buf, 0, foreach_cb, &state);
+
+ git_buf_free(&buf);
+
+ return error;
}
-void loose_backend__stream_free(git_odb_stream *_stream)
+static int loose_backend__stream_fwrite(git_odb_stream *_stream, const git_oid *oid)
{
loose_writestream *stream = (loose_writestream *)_stream;
+ loose_backend *backend = (loose_backend *)_stream->backend;
+ git_buf final_path = GIT_BUF_INIT;
+ int error = 0;
- if (!stream->finished)
- git_filebuf_cleanup(&stream->fbuf);
+ if (object_file_name(&final_path, backend, oid) < 0 ||
+ object_mkdir(&final_path, backend) < 0)
+ error = -1;
+ else
+ error = git_filebuf_commit_at(
+ &stream->fbuf, final_path.ptr);
+
+ git_buf_free(&final_path);
- free(stream);
+ return error;
}
-static int format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type)
+static int loose_backend__stream_write(git_odb_stream *_stream, const char *data, size_t len)
{
- const char *type_str = git_object_type2string(obj_type);
- int len = snprintf(hdr, n, "%s %"PRIuZ, type_str, obj_len);
+ loose_writestream *stream = (loose_writestream *)_stream;
+ return git_filebuf_write(&stream->fbuf, data, len);
+}
- assert(len > 0); /* otherwise snprintf() is broken */
- assert(((size_t) len) < n); /* otherwise the caller is broken! */
+static void loose_backend__stream_free(git_odb_stream *_stream)
+{
+ loose_writestream *stream = (loose_writestream *)_stream;
- if (len < 0 || ((size_t) len) >= n)
- return git__throw(GIT_ERROR, "Failed to format object header. Length is out of bounds");
- return len+1;
+ git_filebuf_cleanup(&stream->fbuf);
+ git__free(stream);
}
-int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_backend, size_t length, git_otype type)
+static int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_backend, size_t length, git_otype type)
{
loose_backend *backend;
- loose_writestream *stream;
-
- char hdr[64], tmp_path[GIT_PATH_MAX];
- int hdrlen;
- int error;
+ loose_writestream *stream = NULL;
+ char hdr[64];
+ git_buf tmp_path = GIT_BUF_INIT;
+ int hdrlen;
assert(_backend);
backend = (loose_backend *)_backend;
*stream_out = NULL;
- hdrlen = format_object_header(hdr, sizeof(hdr), length, type);
- if (hdrlen < GIT_SUCCESS)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to create loose backend stream. Object is corrupted");
+ hdrlen = git_odb__format_object_header(hdr, sizeof(hdr), length, type);
stream = git__calloc(1, sizeof(loose_writestream));
- if (stream == NULL)
- return GIT_ENOMEM;
+ GITERR_CHECK_ALLOC(stream);
stream->stream.backend = _backend;
stream->stream.read = NULL; /* read only */
@@ -749,98 +849,107 @@ int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_backend
stream->stream.free = &loose_backend__stream_free;
stream->stream.mode = GIT_STREAM_WRONLY;
- git_path_join(tmp_path, backend->objects_dir, "tmp_object");
-
- error = git_filebuf_open(&stream->fbuf, tmp_path,
- GIT_FILEBUF_HASH_CONTENTS |
- GIT_FILEBUF_DEFLATE_CONTENTS |
- GIT_FILEBUF_TEMPORARY);
-
- if (error < GIT_SUCCESS) {
- free(stream);
- return git__rethrow(error, "Failed to create loose backend stream");
- }
-
- error = stream->stream.write((git_odb_stream *)stream, hdr, hdrlen);
- if (error < GIT_SUCCESS) {
+ if (git_buf_joinpath(&tmp_path, backend->objects_dir, "tmp_object") < 0 ||
+ git_filebuf_open(&stream->fbuf, tmp_path.ptr,
+ GIT_FILEBUF_TEMPORARY |
+ (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT),
+ backend->object_file_mode) < 0 ||
+ stream->stream.write((git_odb_stream *)stream, hdr, hdrlen) < 0)
+ {
git_filebuf_cleanup(&stream->fbuf);
- free(stream);
- return git__rethrow(error, "Failed to create loose backend stream");
+ git__free(stream);
+ stream = NULL;
}
-
+ git_buf_free(&tmp_path);
*stream_out = (git_odb_stream *)stream;
- return GIT_SUCCESS;
+
+ return !stream ? -1 : 0;
}
-int loose_backend__write(git_oid *oid, git_odb_backend *_backend, const void *data, size_t len, git_otype type)
+static int loose_backend__write(git_odb_backend *_backend, const git_oid *oid, const void *data, size_t len, git_otype type)
{
- int error, header_len;
- char final_path[GIT_PATH_MAX], header[64];
- git_filebuf fbuf;
+ int error = 0, header_len;
+ git_buf final_path = GIT_BUF_INIT;
+ char header[64];
+ git_filebuf fbuf = GIT_FILEBUF_INIT;
loose_backend *backend;
backend = (loose_backend *)_backend;
/* prepare the header for the file */
+ header_len = git_odb__format_object_header(header, sizeof(header), len, type);
+
+ if (git_buf_joinpath(&final_path, backend->objects_dir, "tmp_object") < 0 ||
+ git_filebuf_open(&fbuf, final_path.ptr,
+ GIT_FILEBUF_TEMPORARY |
+ (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT),
+ backend->object_file_mode) < 0)
{
- header_len = format_object_header(header, sizeof(header), len, type);
- if (header_len < GIT_SUCCESS)
- return GIT_EOBJCORRUPTED;
+ error = -1;
+ goto cleanup;
}
- git_path_join(final_path, backend->objects_dir, "tmp_object");
-
- error = git_filebuf_open(&fbuf, final_path,
- GIT_FILEBUF_HASH_CONTENTS |
- GIT_FILEBUF_DEFLATE_CONTENTS |
- GIT_FILEBUF_TEMPORARY);
-
- if (error < GIT_SUCCESS)
- return error;
-
git_filebuf_write(&fbuf, header, header_len);
git_filebuf_write(&fbuf, data, len);
- git_filebuf_hash(oid, &fbuf);
-
- if ((error = object_file_name(final_path, sizeof(final_path), backend->objects_dir, oid)) < GIT_SUCCESS)
- goto cleanup;
-
- if ((error = git_futils_mkpath2file(final_path)) < GIT_SUCCESS)
- goto cleanup;
- return git_filebuf_commit_at(&fbuf, final_path);
+ if (object_file_name(&final_path, backend, oid) < 0 ||
+ object_mkdir(&final_path, backend) < 0 ||
+ git_filebuf_commit_at(&fbuf, final_path.ptr) < 0)
+ error = -1;
cleanup:
- git_filebuf_cleanup(&fbuf);
+ if (error < 0)
+ git_filebuf_cleanup(&fbuf);
+ git_buf_free(&final_path);
return error;
}
-void loose_backend__free(git_odb_backend *_backend)
+static void loose_backend__free(git_odb_backend *_backend)
{
loose_backend *backend;
assert(_backend);
backend = (loose_backend *)_backend;
- free(backend->objects_dir);
- free(backend);
+ git__free(backend);
}
-int git_odb_backend_loose(git_odb_backend **backend_out, const char *objects_dir)
+int git_odb_backend_loose(
+ git_odb_backend **backend_out,
+ const char *objects_dir,
+ int compression_level,
+ int do_fsync,
+ unsigned int dir_mode,
+ unsigned int file_mode)
{
loose_backend *backend;
+ size_t objects_dirlen;
- backend = git__calloc(1, sizeof(loose_backend));
- if (backend == NULL)
- return GIT_ENOMEM;
+ assert(backend_out && objects_dir);
- backend->objects_dir = git__strdup(objects_dir);
- if (backend->objects_dir == NULL) {
- free(backend);
- return GIT_ENOMEM;
- }
+ objects_dirlen = strlen(objects_dir);
+
+ backend = git__calloc(1, sizeof(loose_backend) + objects_dirlen + 2);
+ GITERR_CHECK_ALLOC(backend);
- backend->object_zlib_level = Z_BEST_SPEED;
- backend->fsync_object_files = 0;
+ backend->parent.version = GIT_ODB_BACKEND_VERSION;
+ backend->objects_dirlen = objects_dirlen;
+ memcpy(backend->objects_dir, objects_dir, objects_dirlen);
+ if (backend->objects_dir[backend->objects_dirlen - 1] != '/')
+ backend->objects_dir[backend->objects_dirlen++] = '/';
+
+ if (compression_level < 0)
+ compression_level = Z_BEST_SPEED;
+
+ if (dir_mode == 0)
+ dir_mode = GIT_OBJECT_DIR_MODE;
+
+ if (file_mode == 0)
+ file_mode = GIT_OBJECT_FILE_MODE;
+
+ backend->object_zlib_level = compression_level;
+ backend->fsync_object_files = do_fsync;
+ backend->object_dir_mode = dir_mode;
+ backend->object_file_mode = file_mode;
backend->parent.read = &loose_backend__read;
backend->parent.write = &loose_backend__write;
@@ -848,8 +957,10 @@ int git_odb_backend_loose(git_odb_backend **backend_out, const char *objects_dir
backend->parent.read_header = &loose_backend__read_header;
backend->parent.writestream = &loose_backend__stream;
backend->parent.exists = &loose_backend__exists;
+ backend->parent.exists_prefix = &loose_backend__exists_prefix;
+ backend->parent.foreach = &loose_backend__foreach;
backend->parent.free = &loose_backend__free;
*backend_out = (git_odb_backend *)backend;
- return GIT_SUCCESS;
+ return 0;
}
diff --git a/src/odb_mempack.c b/src/odb_mempack.c
new file mode 100644
index 000000000..d9b3a1824
--- /dev/null
+++ b/src/odb_mempack.c
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "git2/object.h"
+#include "git2/sys/odb_backend.h"
+#include "fileops.h"
+#include "hash.h"
+#include "odb.h"
+#include "array.h"
+#include "oidmap.h"
+
+#include "git2/odb_backend.h"
+#include "git2/types.h"
+#include "git2/pack.h"
+
+GIT__USE_OIDMAP;
+
+struct memobject {
+ git_oid oid;
+ size_t len;
+ git_otype type;
+ char data[];
+};
+
+struct memory_packer_db {
+ git_odb_backend parent;
+ git_oidmap *objects;
+ git_array_t(struct memobject *) commits;
+};
+
+static int impl__write(git_odb_backend *_backend, const git_oid *oid, const void *data, size_t len, git_otype type)
+{
+ struct memory_packer_db *db = (struct memory_packer_db *)_backend;
+ struct memobject *obj = NULL;
+ khiter_t pos;
+ int rval;
+
+ pos = kh_put(oid, db->objects, oid, &rval);
+ if (rval < 0)
+ return -1;
+
+ if (rval == 0)
+ return 0;
+
+ obj = git__malloc(sizeof(struct memobject) + len);
+ GITERR_CHECK_ALLOC(obj);
+
+ memcpy(obj->data, data, len);
+ git_oid_cpy(&obj->oid, oid);
+ obj->len = len;
+ obj->type = type;
+
+ kh_key(db->objects, pos) = &obj->oid;
+ kh_val(db->objects, pos) = obj;
+
+ if (type == GIT_OBJ_COMMIT) {
+ struct memobject **store = git_array_alloc(db->commits);
+ GITERR_CHECK_ALLOC(store);
+ *store = obj;
+ }
+
+ return 0;
+}
+
+static int impl__exists(git_odb_backend *backend, const git_oid *oid)
+{
+ struct memory_packer_db *db = (struct memory_packer_db *)backend;
+ khiter_t pos;
+
+ pos = kh_get(oid, db->objects, oid);
+ if (pos != kh_end(db->objects))
+ return 1;
+
+ return 0;
+}
+
+static int impl__read(void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid)
+{
+ struct memory_packer_db *db = (struct memory_packer_db *)backend;
+ struct memobject *obj = NULL;
+ khiter_t pos;
+
+ pos = kh_get(oid, db->objects, oid);
+ if (pos == kh_end(db->objects))
+ return GIT_ENOTFOUND;
+
+ obj = kh_val(db->objects, pos);
+
+ *len_p = obj->len;
+ *type_p = obj->type;
+ *buffer_p = git__malloc(obj->len);
+ GITERR_CHECK_ALLOC(*buffer_p);
+
+ memcpy(*buffer_p, obj->data, obj->len);
+ return 0;
+}
+
+static int impl__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid)
+{
+ struct memory_packer_db *db = (struct memory_packer_db *)backend;
+ struct memobject *obj = NULL;
+ khiter_t pos;
+
+ pos = kh_get(oid, db->objects, oid);
+ if (pos == kh_end(db->objects))
+ return GIT_ENOTFOUND;
+
+ obj = kh_val(db->objects, pos);
+
+ *len_p = obj->len;
+ *type_p = obj->type;
+ return 0;
+}
+
+int git_mempack_dump(git_buf *pack, git_repository *repo, git_odb_backend *_backend)
+{
+ struct memory_packer_db *db = (struct memory_packer_db *)_backend;
+ git_packbuilder *packbuilder;
+ uint32_t i;
+ int err = -1;
+
+ if (git_packbuilder_new(&packbuilder, repo) < 0)
+ return -1;
+
+ for (i = 0; i < db->commits.size; ++i) {
+ struct memobject *commit = db->commits.ptr[i];
+
+ err = git_packbuilder_insert_commit(packbuilder, &commit->oid);
+ if (err < 0)
+ goto cleanup;
+ }
+
+ err = git_packbuilder_write_buf(pack, packbuilder);
+
+cleanup:
+ git_packbuilder_free(packbuilder);
+ return err;
+}
+
+void git_mempack_reset(git_odb_backend *_backend)
+{
+ struct memory_packer_db *db = (struct memory_packer_db *)_backend;
+ struct memobject *object = NULL;
+
+ kh_foreach_value(db->objects, object, {
+ git__free(object);
+ });
+
+ git_array_clear(db->commits);
+}
+
+static void impl__free(git_odb_backend *_backend)
+{
+ git_mempack_reset(_backend);
+ git__free(_backend);
+}
+
+int git_mempack_new(git_odb_backend **out)
+{
+ struct memory_packer_db *db;
+
+ assert(out);
+
+ db = git__calloc(1, sizeof(struct memory_packer_db));
+ GITERR_CHECK_ALLOC(db);
+
+ db->objects = git_oidmap_alloc();
+
+ db->parent.read = &impl__read;
+ db->parent.write = &impl__write;
+ db->parent.read_header = &impl__read_header;
+ db->parent.exists = &impl__exists;
+ db->parent.free = &impl__free;
+
+ *out = (git_odb_backend *)db;
+ return 0;
+}
diff --git a/src/odb_pack.c b/src/odb_pack.c
index fc2408e76..3750da37f 100644
--- a/src/odb_pack.c
+++ b/src/odb_pack.c
@@ -1,32 +1,15 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
-#include "git2/zlib.h"
+#include <zlib.h>
#include "git2/repository.h"
-#include "git2/oid.h"
+#include "git2/indexer.h"
+#include "git2/sys/odb_backend.h"
#include "fileops.h"
#include "hash.h"
#include "odb.h"
@@ -42,15 +25,19 @@ struct pack_backend {
git_vector packs;
struct git_pack_file *last_found;
char *pack_folder;
- time_t pack_folder_mtime;
+};
+
+struct pack_writepack {
+ struct git_odb_writepack parent;
+ git_indexer *indexer;
};
/**
* The wonderful tale of a Packed Object lookup query
* ===================================================
- * A riveting and epic story of epicness and ASCII
- * art, presented by yours truly,
- * Sir Vicent of Marti
+ * A riveting and epic story of epicness and ASCII
+ * art, presented by yours truly,
+ * Sir Vicent of Marti
*
*
* Chapter 1: Once upon a time...
@@ -61,32 +48,32 @@ struct pack_backend {
* | Creates the pack backend structure, initializes the
* | callback pointers to our default read() and exist() methods,
* | and tries to preload all the known packfiles in the ODB.
- * |
+ * |
* |-# packfile_load_all
- * | Tries to find the `pack` folder, if it exists. ODBs without
- * | a pack folder are ignored altogether. If there's a `pack` folder
- * | we run a `dirent` callback through every file in the pack folder
- * | to find our packfiles. The packfiles are then sorted according
- * | to a sorting callback.
- * |
- * |-# packfile_load__cb
- * | | This callback is called from `dirent` with every single file
- * | | inside the pack folder. We find the packs by actually locating
- * | | their index (ends in ".idx"). From that index, we verify that
- * | | the corresponding packfile exists and is valid, and if so, we
- * | | add it to the pack list.
- * | |
- * | |-# packfile_check
- * | Make sure that there's a packfile to back this index, and store
- * | some very basic information regarding the packfile itself,
- * | such as the full path, the size, and the modification time.
- * | We don't actually open the packfile to check for internal consistency.
- * |
- * |-# packfile_sort__cb
- * Sort all the preloaded packs according to some specific criteria:
- * we prioritize the "newer" packs because it's more likely they
- * contain the objects we are looking for, and we prioritize local
- * packs over remote ones.
+ * | Tries to find the `pack` folder, if it exists. ODBs without
+ * | a pack folder are ignored altogether. If there's a `pack` folder
+ * | we run a `dirent` callback through every file in the pack folder
+ * | to find our packfiles. The packfiles are then sorted according
+ * | to a sorting callback.
+ * |
+ * |-# packfile_load__cb
+ * | | This callback is called from `dirent` with every single file
+ * | | inside the pack folder. We find the packs by actually locating
+ * | | their index (ends in ".idx"). From that index, we verify that
+ * | | the corresponding packfile exists and is valid, and if so, we
+ * | | add it to the pack list.
+ * | |
+ * | |-# packfile_check
+ * | Make sure that there's a packfile to back this index, and store
+ * | some very basic information regarding the packfile itself,
+ * | such as the full path, the size, and the modification time.
+ * | We don't actually open the packfile to check for internal consistency.
+ * |
+ * |-# packfile_sort__cb
+ * Sort all the preloaded packs according to some specific criteria:
+ * we prioritize the "newer" packs because it's more likely they
+ * contain the objects we are looking for, and we prioritize local
+ * packs over remote ones.
*
*
*
@@ -94,41 +81,41 @@ struct pack_backend {
* A standard packed `exist` query for an OID
* --------------------------------------------------
*
- * # pack_backend__exists
- * | Check if the given SHA1 oid exists in any of the packs
- * | that have been loaded for our ODB.
- * |
- * |-# pack_entry_find
- * | Iterate through all the packs that have been preloaded
- * | (starting by the pack where the latest object was found)
- * | to try to find the OID in one of them.
- * |
- * |-# pack_entry_find1
- * | Check the index of an individual pack to see if the SHA1
- * | OID can be found. If we can find the offset to that SHA1
- * | inside of the index, that means the object is contained
- * | inside of the packfile and we can stop searching.
- * | Before returning, we verify that the packfile behing the
- * | index we are searching still exists on disk.
- * |
- * |-# pack_entry_find_offset
- * | | Mmap the actual index file to disk if it hasn't been opened
- * | | yet, and run a binary search through it to find the OID.
- * | | See <http://book.git-scm.com/7_the_packfile.html> for specifics
- * | | on the Packfile Index format and how do we find entries in it.
- * | |
- * | |-# pack_index_open
- * | | Guess the name of the index based on the full path to the
- * | | packfile, open it and verify its contents. Only if the index
- * | | has not been opened already.
- * | |
- * | |-# pack_index_check
- * | Mmap the index file and do a quick run through the header
- * | to guess the index version (right now we support v1 and v2),
- * | and to verify that the size of the index makes sense.
- * |
- * |-# packfile_open
- * See `packfile_open` in Chapter 3
+ * # pack_backend__exists
+ * | Check if the given SHA1 oid exists in any of the packs
+ * | that have been loaded for our ODB.
+ * |
+ * |-# pack_entry_find
+ * | Iterate through all the packs that have been preloaded
+ * | (starting by the pack where the latest object was found)
+ * | to try to find the OID in one of them.
+ * |
+ * |-# pack_entry_find1
+ * | Check the index of an individual pack to see if the SHA1
+ * | OID can be found. If we can find the offset to that SHA1
+ * | inside of the index, that means the object is contained
+ * | inside of the packfile and we can stop searching.
+ * | Before returning, we verify that the packfile behing the
+ * | index we are searching still exists on disk.
+ * |
+ * |-# pack_entry_find_offset
+ * | | Mmap the actual index file to disk if it hasn't been opened
+ * | | yet, and run a binary search through it to find the OID.
+ * | | See <http://book.git-scm.com/7_the_packfile.html> for specifics
+ * | | on the Packfile Index format and how do we find entries in it.
+ * | |
+ * | |-# pack_index_open
+ * | | Guess the name of the index based on the full path to the
+ * | | packfile, open it and verify its contents. Only if the index
+ * | | has not been opened already.
+ * | |
+ * | |-# pack_index_check
+ * | Mmap the index file and do a quick run through the header
+ * | to guess the index version (right now we support v1 and v2),
+ * | and to verify that the size of the index makes sense.
+ * |
+ * |-# packfile_open
+ * See `packfile_open` in Chapter 3
*
*
*
@@ -146,28 +133,24 @@ struct pack_backend {
*
***********************************************************/
-static void pack_window_free_all(struct pack_backend *backend, struct git_pack_file *p);
-static int pack_window_contains(git_mwindow *win, off_t offset);
-
static int packfile_sort__cb(const void *a_, const void *b_);
-static int packfile_load__cb(void *_data, char *path);
-static int packfile_refresh_all(struct pack_backend *backend);
+static int packfile_load__cb(void *_data, git_buf *path);
static int pack_entry_find(struct git_pack_entry *e,
- struct pack_backend *backend, const git_oid *oid);
+ struct pack_backend *backend, const git_oid *oid);
/* Can find the offset of an object given
* a prefix of an identifier.
- * Throws GIT_EAMBIGUOUSOIDPREFIX if short oid
- * is ambiguous.
+ * Sets GIT_EAMBIGUOUS if short oid is ambiguous.
* This method assumes that len is between
* GIT_OID_MINPREFIXLEN and GIT_OID_HEXSZ.
*/
-static int pack_entry_find_prefix(struct git_pack_entry *e,
- struct pack_backend *backend,
- const git_oid *short_oid,
- unsigned int len);
+static int pack_entry_find_prefix(
+ struct git_pack_entry *e,
+ struct pack_backend *backend,
+ const git_oid *short_oid,
+ size_t len);
@@ -177,23 +160,6 @@ static int pack_entry_find_prefix(struct git_pack_entry *e,
*
***********************************************************/
-GIT_INLINE(void) pack_window_free_all(struct pack_backend *GIT_UNUSED(backend), struct git_pack_file *p)
-{
- GIT_UNUSED_ARG(backend);
- git_mwindow_free_all(&p->mwf);
-}
-
-GIT_INLINE(int) pack_window_contains(git_mwindow *win, off_t offset)
-{
- /* We must promise at least 20 bytes (one hash) after the
- * offset is available from this window, otherwise the offset
- * is not actually in this window and a different window (which
- * has that one hash excess) must be used. This is to support
- * the object header and delta base parsing routines below.
- */
- return git_mwindow_contains(win, offset + 20);
-}
-
static int packfile_sort__cb(const void *a_, const void *b_)
{
const struct git_pack_file *a = a_;
@@ -202,7 +168,7 @@ static int packfile_sort__cb(const void *a_, const void *b_)
/*
* Local packs tend to contain objects specific to our
- * variant of the project than remote ones. In addition,
+ * variant of the project than remote ones. In addition,
* remote ones could be on a network mounted filesystem.
* Favor local ones for these reasons.
*/
@@ -224,109 +190,102 @@ static int packfile_sort__cb(const void *a_, const void *b_)
}
-
-static int packfile_load__cb(void *_data, char *path)
+static int packfile_load__cb(void *data, git_buf *path)
{
- struct pack_backend *backend = (struct pack_backend *)_data;
+ struct pack_backend *backend = data;
struct git_pack_file *pack;
+ const char *path_str = git_buf_cstr(path);
+ size_t i, cmp_len = git_buf_len(path);
int error;
- size_t i;
- if (git__suffixcmp(path, ".idx") != 0)
- return GIT_SUCCESS; /* not an index */
+ if (cmp_len <= strlen(".idx") || git__suffixcmp(path_str, ".idx") != 0)
+ return 0; /* not an index */
+
+ cmp_len -= strlen(".idx");
for (i = 0; i < backend->packs.length; ++i) {
struct git_pack_file *p = git_vector_get(&backend->packs, i);
- if (memcmp(p->pack_name, path, strlen(path) - STRLEN(".idx")) == 0)
- return GIT_SUCCESS;
- }
-
- error = git_packfile_check(&pack, path);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to load packfile");
- if (git_vector_insert(&backend->packs, pack) < GIT_SUCCESS) {
- free(pack);
- return GIT_ENOMEM;
+ if (memcmp(p->pack_name, path_str, cmp_len) == 0)
+ return 0;
}
- return GIT_SUCCESS;
-}
-
-static int packfile_refresh_all(struct pack_backend *backend)
-{
- int error;
- struct stat st;
-
- if (backend->pack_folder == NULL)
- return GIT_SUCCESS;
+ error = git_packfile_alloc(&pack, path->ptr);
- if (p_stat(backend->pack_folder, &st) < 0 || !S_ISDIR(st.st_mode))
- return git__throw(GIT_ENOTFOUND, "Failed to refresh packfiles. Backend not found");
+ /* ignore missing .pack file as git does */
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ return 0;
+ }
- if (st.st_mtime != backend->pack_folder_mtime) {
- char path[GIT_PATH_MAX];
- strcpy(path, backend->pack_folder);
+ if (!error)
+ error = git_vector_insert(&backend->packs, pack);
- /* reload all packs */
- error = git_futils_direach(path, GIT_PATH_MAX, packfile_load__cb, (void *)backend);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to refresh packfiles");
+ return error;
- git_vector_sort(&backend->packs);
- backend->pack_folder_mtime = st.st_mtime;
- }
-
- return GIT_SUCCESS;
}
-static int pack_entry_find(struct git_pack_entry *e, struct pack_backend *backend, const git_oid *oid)
+static int pack_entry_find_inner(
+ struct git_pack_entry *e,
+ struct pack_backend *backend,
+ const git_oid *oid,
+ struct git_pack_file *last_found)
{
- int error;
size_t i;
- if ((error = packfile_refresh_all(backend)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to find pack entry");
-
- if (backend->last_found &&
- git_pack_entry_find(e, backend->last_found, oid, GIT_OID_HEXSZ) == GIT_SUCCESS)
- return GIT_SUCCESS;
+ if (last_found &&
+ git_pack_entry_find(e, last_found, oid, GIT_OID_HEXSZ) == 0)
+ return 0;
for (i = 0; i < backend->packs.length; ++i) {
struct git_pack_file *p;
p = git_vector_get(&backend->packs, i);
- if (p == backend->last_found)
+ if (p == last_found)
continue;
- if (git_pack_entry_find(e, p, oid, GIT_OID_HEXSZ) == GIT_SUCCESS) {
+ if (git_pack_entry_find(e, p, oid, GIT_OID_HEXSZ) == 0) {
backend->last_found = p;
- return GIT_SUCCESS;
+ return 0;
}
}
- return git__throw(GIT_ENOTFOUND, "Failed to find pack entry");
+ return -1;
+}
+
+static int pack_entry_find(struct git_pack_entry *e, struct pack_backend *backend, const git_oid *oid)
+{
+ struct git_pack_file *last_found = backend->last_found;
+
+ if (backend->last_found &&
+ git_pack_entry_find(e, backend->last_found, oid, GIT_OID_HEXSZ) == 0)
+ return 0;
+
+ if (!pack_entry_find_inner(e, backend, oid, last_found))
+ return 0;
+
+ return git_odb__error_notfound("failed to find pack entry", oid);
}
static int pack_entry_find_prefix(
struct git_pack_entry *e,
struct pack_backend *backend,
const git_oid *short_oid,
- unsigned int len)
+ size_t len)
{
int error;
size_t i;
- unsigned found = 0;
-
- if ((error = packfile_refresh_all(backend)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to find pack entry");
-
- if (backend->last_found) {
- error = git_pack_entry_find(e, backend->last_found, short_oid, len);
- if (error == GIT_EAMBIGUOUSOIDPREFIX) {
- return git__rethrow(error, "Failed to find pack entry. Ambiguous sha1 prefix");
- } else if (error == GIT_SUCCESS) {
- found = 1;
+ git_oid found_full_oid = {{0}};
+ bool found = false;
+ struct git_pack_file *last_found = backend->last_found;
+
+ if (last_found) {
+ error = git_pack_entry_find(e, last_found, short_oid, len);
+ if (error == GIT_EAMBIGUOUS)
+ return error;
+ if (!error) {
+ git_oid_cpy(&found_full_oid, &e->sha1);
+ found = true;
}
}
@@ -334,28 +293,25 @@ static int pack_entry_find_prefix(
struct git_pack_file *p;
p = git_vector_get(&backend->packs, i);
- if (p == backend->last_found)
+ if (p == last_found)
continue;
error = git_pack_entry_find(e, p, short_oid, len);
- if (error == GIT_EAMBIGUOUSOIDPREFIX) {
- return git__rethrow(error, "Failed to find pack entry. Ambiguous sha1 prefix");
- } else if (error == GIT_SUCCESS) {
- found++;
- if (found > 1)
- break;
+ if (error == GIT_EAMBIGUOUS)
+ return error;
+ if (!error) {
+ if (found && git_oid_cmp(&e->sha1, &found_full_oid))
+ return git_odb__error_ambiguous("found multiple pack entries");
+ git_oid_cpy(&found_full_oid, &e->sha1);
+ found = true;
backend->last_found = p;
}
}
- if (!found) {
- return git__rethrow(GIT_ENOTFOUND, "Failed to find pack entry");
- } else if (found > 1) {
- return git__rethrow(GIT_EAMBIGUOUSOIDPREFIX, "Failed to find pack entry. Ambiguous sha1 prefix");
- } else {
- return GIT_SUCCESS;
- }
-
+ if (!found)
+ return git_odb__error_notfound("no matching pack entry for prefix", short_oid);
+ else
+ return 0;
}
@@ -366,86 +322,279 @@ static int pack_entry_find_prefix(
* Implement the git_odb_backend API calls
*
***********************************************************/
+static int pack_backend__refresh(git_odb_backend *backend_)
+{
+ int error;
+ struct stat st;
+ git_buf path = GIT_BUF_INIT;
+ struct pack_backend *backend = (struct pack_backend *)backend_;
-/*
-int pack_backend__read_header(git_rawobj *obj, git_odb_backend *backend, const git_oid *oid)
+ if (backend->pack_folder == NULL)
+ return 0;
+
+ if (p_stat(backend->pack_folder, &st) < 0 || !S_ISDIR(st.st_mode))
+ return git_odb__error_notfound("failed to refresh packfiles", NULL);
+
+ git_buf_sets(&path, backend->pack_folder);
+
+ /* reload all packs */
+ error = git_path_direach(&path, 0, packfile_load__cb, backend);
+
+ git_buf_free(&path);
+ git_vector_sort(&backend->packs);
+
+ return error;
+}
+
+static int pack_backend__read_header_internal(
+ size_t *len_p, git_otype *type_p,
+ struct git_odb_backend *backend, const git_oid *oid)
{
- pack_location location;
+ struct git_pack_entry e;
+ int error;
- assert(obj && backend && oid);
+ assert(len_p && type_p && backend && oid);
- if (locate_packfile(&location, (struct pack_backend *)backend, oid) < 0)
- return GIT_ENOTFOUND;
+ if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0)
+ return error;
- return read_header_packed(obj, &location);
+ return git_packfile_resolve_header(len_p, type_p, e.p, e.offset);
}
-*/
-int pack_backend__read(void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid)
+static int pack_backend__read_header(
+ size_t *len_p, git_otype *type_p,
+ struct git_odb_backend *backend, const git_oid *oid)
+{
+ int error;
+
+ error = pack_backend__read_header_internal(len_p, type_p, backend, oid);
+
+ if (error != GIT_ENOTFOUND)
+ return error;
+
+ if ((error = pack_backend__refresh(backend)) < 0)
+ return error;
+
+ return pack_backend__read_header_internal(len_p, type_p, backend, oid);
+}
+
+static int pack_backend__read_internal(
+ void **buffer_p, size_t *len_p, git_otype *type_p,
+ git_odb_backend *backend, const git_oid *oid)
{
struct git_pack_entry e;
git_rawobj raw;
int error;
- if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to read pack backend");
-
- if ((error = git_packfile_unpack(&raw, e.p, &e.offset)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to read pack backend");
+ if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0 ||
+ (error = git_packfile_unpack(&raw, e.p, &e.offset)) < 0)
+ return error;
*buffer_p = raw.data;
*len_p = raw.len;
*type_p = raw.type;
- return GIT_SUCCESS;
+ return 0;
+}
+
+static int pack_backend__read(
+ void **buffer_p, size_t *len_p, git_otype *type_p,
+ git_odb_backend *backend, const git_oid *oid)
+{
+ int error;
+
+ error = pack_backend__read_internal(buffer_p, len_p, type_p, backend, oid);
+
+ if (error != GIT_ENOTFOUND)
+ return error;
+
+ if ((error = pack_backend__refresh(backend)) < 0)
+ return error;
+
+ return pack_backend__read_internal(buffer_p, len_p, type_p, backend, oid);
}
-int pack_backend__read_prefix(
+static int pack_backend__read_prefix_internal(
git_oid *out_oid,
void **buffer_p,
size_t *len_p,
git_otype *type_p,
git_odb_backend *backend,
const git_oid *short_oid,
- unsigned int len)
+ size_t len)
{
+ int error = 0;
+
if (len < GIT_OID_MINPREFIXLEN)
- return git__throw(GIT_EAMBIGUOUSOIDPREFIX, "Failed to read pack backend. Prefix length is lower than %d.", GIT_OID_MINPREFIXLEN);
+ error = git_odb__error_ambiguous("prefix length too short");
- if (len >= GIT_OID_HEXSZ) {
+ else if (len >= GIT_OID_HEXSZ) {
/* We can fall back to regular read method */
- int error = pack_backend__read(buffer_p, len_p, type_p, backend, short_oid);
- if (error == GIT_SUCCESS)
+ error = pack_backend__read(buffer_p, len_p, type_p, backend, short_oid);
+ if (!error)
git_oid_cpy(out_oid, short_oid);
-
- return error;
} else {
struct git_pack_entry e;
git_rawobj raw;
- int error;
- if ((error = pack_entry_find_prefix(&e, (struct pack_backend *)backend, short_oid, len)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to read pack backend");
+ if ((error = pack_entry_find_prefix(
+ &e, (struct pack_backend *)backend, short_oid, len)) == 0 &&
+ (error = git_packfile_unpack(&raw, e.p, &e.offset)) == 0)
+ {
+ *buffer_p = raw.data;
+ *len_p = raw.len;
+ *type_p = raw.type;
+ git_oid_cpy(out_oid, &e.sha1);
+ }
+ }
- if ((error = git_packfile_unpack(&raw, e.p, &e.offset)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to read pack backend");
+ return error;
+}
- *buffer_p = raw.data;
- *len_p = raw.len;
- *type_p = raw.type;
- git_oid_cpy(out_oid, &e.sha1);
- }
+static int pack_backend__read_prefix(
+ git_oid *out_oid,
+ void **buffer_p,
+ size_t *len_p,
+ git_otype *type_p,
+ git_odb_backend *backend,
+ const git_oid *short_oid,
+ size_t len)
+{
+ int error;
+
+ error = pack_backend__read_prefix_internal(
+ out_oid, buffer_p, len_p, type_p, backend, short_oid, len);
- return GIT_SUCCESS;
+ if (error != GIT_ENOTFOUND)
+ return error;
+
+ if ((error = pack_backend__refresh(backend)) < 0)
+ return error;
+
+ return pack_backend__read_prefix_internal(
+ out_oid, buffer_p, len_p, type_p, backend, short_oid, len);
}
-int pack_backend__exists(git_odb_backend *backend, const git_oid *oid)
+static int pack_backend__exists(git_odb_backend *backend, const git_oid *oid)
{
struct git_pack_entry e;
- return pack_entry_find(&e, (struct pack_backend *)backend, oid) == GIT_SUCCESS;
+ int error;
+
+ error = pack_entry_find(&e, (struct pack_backend *)backend, oid);
+
+ if (error != GIT_ENOTFOUND)
+ return error == 0;
+
+ if ((error = pack_backend__refresh(backend)) < 0) {
+ giterr_clear();
+ return (int)false;
+ }
+
+ return pack_entry_find(&e, (struct pack_backend *)backend, oid) == 0;
+}
+
+static int pack_backend__exists_prefix(
+ git_oid *out, git_odb_backend *backend, const git_oid *short_id, size_t len)
+{
+ int error;
+ struct pack_backend *pb = (struct pack_backend *)backend;
+ struct git_pack_entry e = {0};
+
+ error = pack_entry_find_prefix(&e, pb, short_id, len);
+
+ if (error == GIT_ENOTFOUND && !(error = pack_backend__refresh(backend)))
+ error = pack_entry_find_prefix(&e, pb, short_id, len);
+
+ git_oid_cpy(out, &e.sha1);
+
+ return error;
}
-void pack_backend__free(git_odb_backend *_backend)
+static int pack_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data)
+{
+ int error;
+ struct git_pack_file *p;
+ struct pack_backend *backend;
+ unsigned int i;
+
+ assert(_backend && cb);
+ backend = (struct pack_backend *)_backend;
+
+ /* Make sure we know about the packfiles */
+ if ((error = pack_backend__refresh(_backend)) < 0)
+ return error;
+
+ git_vector_foreach(&backend->packs, i, p) {
+ if ((error = git_pack_foreach_entry(p, cb, data)) < 0)
+ return error;
+ }
+
+ return 0;
+}
+
+static int pack_backend__writepack_append(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_append(writepack->indexer, 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_commit(writepack->indexer, stats);
+}
+
+static void pack_backend__writepack_free(struct git_odb_writepack *_writepack)
+{
+ struct pack_writepack *writepack = (struct pack_writepack *)_writepack;
+
+ assert(writepack);
+
+ git_indexer_free(writepack->indexer);
+ git__free(writepack);
+}
+
+static int pack_backend__writepack(struct git_odb_writepack **out,
+ git_odb_backend *_backend,
+ git_odb *odb,
+ git_transfer_progress_cb 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_new(&writepack->indexer,
+ backend->pack_folder, 0, odb, progress_cb, progress_payload) < 0) {
+ git__free(writepack);
+ return -1;
+ }
+
+ writepack->parent.backend = _backend;
+ writepack->parent.append = pack_backend__writepack_append;
+ writepack->parent.commit = pack_backend__writepack_commit;
+ writepack->parent.free = pack_backend__writepack_free;
+
+ *out = (git_odb_writepack *)writepack;
+
+ return 0;
+}
+
+static void pack_backend__free(git_odb_backend *_backend)
{
struct pack_backend *backend;
size_t i;
@@ -456,45 +605,84 @@ void pack_backend__free(git_odb_backend *_backend)
for (i = 0; i < backend->packs.length; ++i) {
struct git_pack_file *p = git_vector_get(&backend->packs, i);
- packfile_free(p);
+ git_packfile_free(p);
}
git_vector_free(&backend->packs);
- free(backend->pack_folder);
- free(backend);
+ git__free(backend->pack_folder);
+ git__free(backend);
}
-int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir)
+static int pack_backend__alloc(struct pack_backend **out, size_t initial_size)
{
- struct pack_backend *backend;
- char path[GIT_PATH_MAX];
-
- backend = git__calloc(1, sizeof(struct pack_backend));
- if (backend == NULL)
- return GIT_ENOMEM;
+ struct pack_backend *backend = git__calloc(1, sizeof(struct pack_backend));
+ GITERR_CHECK_ALLOC(backend);
- if (git_vector_init(&backend->packs, 8, packfile_sort__cb) < GIT_SUCCESS) {
- free(backend);
- return GIT_ENOMEM;
+ if (git_vector_init(&backend->packs, initial_size, packfile_sort__cb) < 0) {
+ git__free(backend);
+ return -1;
}
- git_path_join(path, objects_dir, "pack");
- if (git_futils_isdir(path) == GIT_SUCCESS) {
- backend->pack_folder = git__strdup(path);
- backend->pack_folder_mtime = 0;
-
- if (backend->pack_folder == NULL) {
- free(backend);
- return GIT_ENOMEM;
- }
- }
+ backend->parent.version = GIT_ODB_BACKEND_VERSION;
backend->parent.read = &pack_backend__read;
backend->parent.read_prefix = &pack_backend__read_prefix;
- backend->parent.read_header = NULL;
+ backend->parent.read_header = &pack_backend__read_header;
backend->parent.exists = &pack_backend__exists;
+ backend->parent.exists_prefix = &pack_backend__exists_prefix;
+ backend->parent.refresh = &pack_backend__refresh;
+ backend->parent.foreach = &pack_backend__foreach;
+ backend->parent.writepack = &pack_backend__writepack;
backend->parent.free = &pack_backend__free;
+ *out = backend;
+ return 0;
+}
+
+int git_odb_backend_one_pack(git_odb_backend **backend_out, const char *idx)
+{
+ struct pack_backend *backend = NULL;
+ struct git_pack_file *packfile = NULL;
+
+ if (pack_backend__alloc(&backend, 1) < 0)
+ return -1;
+
+ if (git_packfile_alloc(&packfile, idx) < 0 ||
+ git_vector_insert(&backend->packs, packfile) < 0)
+ {
+ pack_backend__free((git_odb_backend *)backend);
+ return -1;
+ }
+
+ *backend_out = (git_odb_backend *)backend;
+ return 0;
+}
+
+int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir)
+{
+ int error = 0;
+ struct pack_backend *backend = NULL;
+ git_buf path = GIT_BUF_INIT;
+
+ if (pack_backend__alloc(&backend, 8) < 0)
+ return -1;
+
+ if (!(error = git_buf_joinpath(&path, objects_dir, "pack")) &&
+ git_path_isdir(git_buf_cstr(&path)))
+ {
+ backend->pack_folder = git_buf_detach(&path);
+
+ error = pack_backend__refresh((git_odb_backend *)backend);
+ }
+
+ if (error < 0) {
+ pack_backend__free((git_odb_backend *)backend);
+ backend = NULL;
+ }
+
*backend_out = (git_odb_backend *)backend;
- return GIT_SUCCESS;
+
+ git_buf_free(&path);
+
+ return error;
}
diff --git a/src/offmap.h b/src/offmap.h
new file mode 100644
index 000000000..cd46fd687
--- /dev/null
+++ b/src/offmap.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2012 the libgit2 contributors
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_offmap_h__
+#define INCLUDE_offmap_h__
+
+#include "common.h"
+#include "git2/types.h"
+
+#define kmalloc git__malloc
+#define kcalloc git__calloc
+#define krealloc git__realloc
+#define kfree git__free
+#include "khash.h"
+
+__KHASH_TYPE(off, git_off_t, void *);
+typedef khash_t(off) git_offmap;
+
+#define GIT__USE_OFFMAP \
+ __KHASH_IMPL(off, static kh_inline, git_off_t, void *, 1, kh_int64_hash_func, kh_int64_hash_equal);
+
+#define git_offmap_alloc() kh_init(off)
+#define git_offmap_free(h) kh_destroy(off, h), h = NULL
+#define git_offmap_clear(h) kh_clear(off, h)
+
+#define git_offmap_num_entries(h) kh_size(h)
+
+#define git_offmap_lookup_index(h, k) kh_get(off, h, k)
+#define git_offmap_valid_index(h, idx) (idx != kh_end(h))
+
+#define git_offmap_exists(h, k) (kh_get(off, h, k) != kh_end(h))
+
+#define git_offmap_value_at(h, idx) kh_val(h, idx)
+#define git_offmap_set_value_at(h, idx, v) kh_val(h, idx) = v
+#define git_offmap_delete_at(h, idx) kh_del(off, h, idx)
+
+#define git_offmap_insert(h, key, val, rval) do { \
+ khiter_t __pos = kh_put(off, h, key, &rval); \
+ if (rval >= 0) { \
+ if (rval == 0) kh_key(h, __pos) = key; \
+ kh_val(h, __pos) = val; \
+ } } while (0)
+
+#define git_offmap_insert2(h, key, val, oldv, rval) do { \
+ khiter_t __pos = kh_put(off, h, key, &rval); \
+ if (rval >= 0) { \
+ if (rval == 0) { \
+ oldv = kh_val(h, __pos); \
+ kh_key(h, __pos) = key; \
+ } else { oldv = NULL; } \
+ kh_val(h, __pos) = val; \
+ } } while (0)
+
+#define git_offmap_delete(h, key) do { \
+ khiter_t __pos = git_offmap_lookup_index(h, key); \
+ if (git_offmap_valid_index(h, __pos)) \
+ git_offmap_delete_at(h, __pos); } while (0)
+
+#define git_offmap_foreach kh_foreach
+#define git_offmap_foreach_value kh_foreach_value
+
+#endif
diff --git a/src/oid.c b/src/oid.c
index f12ba30b9..b640cadd1 100644
--- a/src/oid.c
+++ b/src/oid.c
@@ -1,26 +1,8 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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"
@@ -29,50 +11,43 @@
#include <string.h>
#include <limits.h>
-static signed char from_hex[] = {
--1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00 */
--1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 10 */
--1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 20 */
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, /* 30 */
--1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 40 */
--1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 50 */
--1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 60 */
--1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 70 */
--1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80 */
--1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 90 */
--1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* a0 */
--1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* b0 */
--1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* c0 */
--1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* d0 */
--1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* e0 */
--1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* f0 */
-};
static char to_hex[] = "0123456789abcdef";
+static int oid_error_invalid(const char *msg)
+{
+ giterr_set(GITERR_INVALID, "Unable to parse OID - %s", msg);
+ return -1;
+}
+
int git_oid_fromstrn(git_oid *out, const char *str, size_t length)
{
size_t p;
+ int v;
- if (length > GIT_OID_HEXSZ)
- length = GIT_OID_HEXSZ;
+ assert(out && str);
- if (length % 2)
- length--;
+ if (!length)
+ return oid_error_invalid("too short");
- for (p = 0; p < length; p += 2) {
- int v = (from_hex[(unsigned char)str[p + 0]] << 4)
- | from_hex[(unsigned char)str[p + 1]];
+ if (length > GIT_OID_HEXSZ)
+ return oid_error_invalid("too long");
+
+ memset(out->id, 0, GIT_OID_RAWSZ);
+ for (p = 0; p < length; p++) {
+ v = git__fromhex(str[p]);
if (v < 0)
- return git__throw(GIT_ENOTOID, "Failed to generate sha1. Given string is not a valid sha1 hash");
+ return oid_error_invalid("contains invalid characters");
- out->id[p / 2] = (unsigned char)v;
+ out->id[p / 2] |= (unsigned char)(v << (p % 2 ? 0 : 4));
}
- for (; p < GIT_OID_HEXSZ; p += 2)
- out->id[p / 2] = 0x0;
+ return 0;
+}
- return GIT_SUCCESS;
+int git_oid_fromstrp(git_oid *out, const char *str)
+{
+ return git_oid_fromstrn(out, str, strlen(str));
}
int git_oid_fromstr(git_oid *out, const char *str)
@@ -87,12 +62,31 @@ GIT_INLINE(char) *fmt_one(char *str, unsigned int val)
return str;
}
-void git_oid_fmt(char *str, const git_oid *oid)
+void git_oid_nfmt(char *str, size_t n, const git_oid *oid)
{
- size_t i;
+ size_t i, max_i;
- for (i = 0; i < sizeof(oid->id); i++)
+ if (!oid) {
+ memset(str, 0, n);
+ return;
+ }
+ if (n > GIT_OID_HEXSZ) {
+ memset(&str[GIT_OID_HEXSZ], 0, n - GIT_OID_HEXSZ);
+ n = GIT_OID_HEXSZ;
+ }
+
+ max_i = n / 2;
+
+ for (i = 0; i < max_i; i++)
str = fmt_one(str, oid->id[i]);
+
+ if (n & 1)
+ *str++ = to_hex[oid->id[i] >> 4];
+}
+
+void git_oid_fmt(char *str, const git_oid *oid)
+{
+ git_oid_nfmt(str, GIT_OID_HEXSZ, oid);
}
void git_oid_pathfmt(char *str, const git_oid *oid)
@@ -110,34 +104,27 @@ char *git_oid_allocfmt(const git_oid *oid)
char *str = git__malloc(GIT_OID_HEXSZ + 1);
if (!str)
return NULL;
- git_oid_fmt(str, oid);
- str[GIT_OID_HEXSZ] = '\0';
+ git_oid_nfmt(str, GIT_OID_HEXSZ + 1, oid);
return str;
}
-char *git_oid_to_string(char *out, size_t n, const git_oid *oid)
+char *git_oid_tostr(char *out, size_t n, const git_oid *oid)
{
- char str[GIT_OID_HEXSZ];
-
- if (!out || n == 0 || !oid)
+ if (!out || n == 0)
return "";
- n--; /* allow room for terminating NUL */
+ if (n > GIT_OID_HEXSZ + 1)
+ n = GIT_OID_HEXSZ + 1;
- if (n > 0) {
- git_oid_fmt(str, oid);
- if (n > GIT_OID_HEXSZ)
- n = GIT_OID_HEXSZ;
- memcpy(out, str, n);
- }
-
- out[n] = '\0';
+ git_oid_nfmt(out, n - 1, oid); /* allow room for terminating NUL */
+ out[n - 1] = '\0';
return out;
}
-int git_oid__parse(git_oid *oid, const char **buffer_out,
- const char *buffer_end, const char *header)
+int git_oid__parse(
+ git_oid *oid, const char **buffer_out,
+ const char *buffer_end, const char *header)
{
const size_t sha_len = GIT_OID_HEXSZ;
const size_t header_len = strlen(header);
@@ -145,20 +132,20 @@ int git_oid__parse(git_oid *oid, const char **buffer_out,
const char *buffer = *buffer_out;
if (buffer + (header_len + sha_len + 1) > buffer_end)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse OID. Buffer too small");
+ return -1;
if (memcmp(buffer, header, header_len) != 0)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse OID. Buffer and header do not match");
+ return -1;
if (buffer[header_len + sha_len] != '\n')
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse OID. Buffer not terminated correctly");
+ return -1;
- if (git_oid_fromstr(oid, buffer + header_len) < GIT_SUCCESS)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse OID. Failed to generate sha1");
+ if (git_oid_fromstr(oid, buffer + header_len) < 0)
+ return -1;
*buffer_out = buffer + (header_len + sha_len + 1);
- return GIT_SUCCESS;
+ return 0;
}
void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid)
@@ -183,21 +170,29 @@ void git_oid_cpy(git_oid *out, const git_oid *src)
int git_oid_cmp(const git_oid *a, const git_oid *b)
{
- return memcmp(a->id, b->id, sizeof(a->id));
+ return git_oid__cmp(a, b);
}
-int git_oid_ncmp(const git_oid *oid_a, const git_oid *oid_b, unsigned int len)
+int git_oid_equal(const git_oid *a, const git_oid *b)
+{
+ return (git_oid__cmp(a, b) == 0);
+}
+
+int git_oid_ncmp(const git_oid *oid_a, const git_oid *oid_b, size_t len)
{
const unsigned char *a = oid_a->id;
const unsigned char *b = oid_b->id;
- do {
+ if (len > GIT_OID_HEXSZ)
+ len = GIT_OID_HEXSZ;
+
+ while (len > 1) {
if (*a != *b)
return 1;
a++;
b++;
len -= 2;
- } while (len > 1);
+ };
if (len)
if ((*a ^ *b) & 0xf0)
@@ -206,6 +201,43 @@ int git_oid_ncmp(const git_oid *oid_a, const git_oid *oid_b, unsigned int len)
return 0;
}
+int git_oid_strcmp(const git_oid *oid_a, const char *str)
+{
+ const unsigned char *a = oid_a->id;
+ unsigned char strval;
+ int hexval;
+
+ for (a = oid_a->id; *str && (a - oid_a->id) < GIT_OID_RAWSZ; ++a) {
+ if ((hexval = git__fromhex(*str++)) < 0)
+ return -1;
+ strval = (unsigned char)(hexval << 4);
+ if (*str) {
+ if ((hexval = git__fromhex(*str++)) < 0)
+ return -1;
+ strval |= hexval;
+ }
+ if (*a != strval)
+ return (*a - strval);
+ }
+
+ return 0;
+}
+
+int git_oid_streq(const git_oid *oid_a, const char *str)
+{
+ return git_oid_strcmp(oid_a, str) == 0 ? 0 : -1;
+}
+
+int git_oid_iszero(const git_oid *oid_a)
+{
+ const unsigned char *a = oid_a->id;
+ unsigned int i;
+ for (i = 0; i < GIT_OID_RAWSZ; ++i, ++a)
+ if (*a != 0)
+ return 0;
+ return 1;
+}
+
typedef short node_index;
typedef union {
@@ -221,16 +253,15 @@ struct git_oid_shorten {
static int resize_trie(git_oid_shorten *self, size_t new_size)
{
- self->nodes = realloc(self->nodes, new_size * sizeof(trie_node));
- if (self->nodes == NULL)
- return GIT_ENOMEM;
+ self->nodes = git__realloc(self->nodes, new_size * sizeof(trie_node));
+ GITERR_CHECK_ALLOC(self->nodes);
if (new_size > self->size) {
memset(&self->nodes[self->size], 0x0, (new_size - self->size) * sizeof(trie_node));
}
self->size = new_size;
- return GIT_SUCCESS;
+ return 0;
}
static trie_node *push_leaf(git_oid_shorten *os, node_index idx, int push_at, const char *oid)
@@ -239,14 +270,16 @@ static trie_node *push_leaf(git_oid_shorten *os, node_index idx, int push_at, co
node_index idx_leaf;
if (os->node_count >= os->size) {
- if (resize_trie(os, os->size * 2) < GIT_SUCCESS)
+ if (resize_trie(os, os->size * 2) < 0)
return NULL;
}
idx_leaf = (node_index)os->node_count++;
- if (os->node_count == SHRT_MAX)
+ if (os->node_count == SHRT_MAX) {
os->full = 1;
+ return NULL;
+ }
node = &os->nodes[idx];
node->children[push_at] = -idx_leaf;
@@ -261,27 +294,30 @@ git_oid_shorten *git_oid_shorten_new(size_t min_length)
{
git_oid_shorten *os;
- os = git__malloc(sizeof(git_oid_shorten));
+ assert((size_t)((int)min_length) == min_length);
+
+ os = git__calloc(1, sizeof(git_oid_shorten));
if (os == NULL)
return NULL;
- memset(os, 0x0, sizeof(git_oid_shorten));
-
- if (resize_trie(os, 16) < GIT_SUCCESS) {
- free(os);
+ if (resize_trie(os, 16) < 0) {
+ git__free(os);
return NULL;
}
os->node_count = 1;
- os->min_length = min_length;
+ os->min_length = (int)min_length;
return os;
}
void git_oid_shorten_free(git_oid_shorten *os)
{
- free(os->nodes);
- free(os);
+ if (os == NULL)
+ return;
+
+ git__free(os->nodes);
+ git__free(os);
}
@@ -302,7 +338,7 @@ void git_oid_shorten_free(git_oid_shorten *os)
* - Each normal node points to 16 children (one for each possible
* character in the oid). This is *not* stored in an array of
* pointers, because in a 64-bit arch this would be sucking
- * 16*sizeof(void*) = 128 bytes of memory per node, which is fucking
+ * 16*sizeof(void*) = 128 bytes of memory per node, which is
* insane. What we do is store Node Indexes, and use these indexes
* to look up each node in the om->index array. These indexes are
* signed shorts, so this limits the amount of unique OIDs that
@@ -331,24 +367,29 @@ void git_oid_shorten_free(git_oid_shorten *os)
*/
int git_oid_shorten_add(git_oid_shorten *os, const char *text_oid)
{
- int i, is_leaf;
+ int i;
+ bool is_leaf;
node_index idx;
- if (os->full)
- return GIT_ENOMEM;
+ if (os->full) {
+ giterr_set(GITERR_INVALID, "Unable to shorten OID - OID set full");
+ return -1;
+ }
if (text_oid == NULL)
return os->min_length;
idx = 0;
- is_leaf = 0;
+ is_leaf = false;
for (i = 0; i < GIT_OID_HEXSZ; ++i) {
- int c = from_hex[(int)text_oid[i]];
+ int c = git__fromhex(text_oid[i]);
trie_node *node;
- if (c == -1)
- return git__throw(GIT_ENOTOID, "Failed to shorten OID. Invalid hex value");
+ if (c == -1) {
+ giterr_set(GITERR_INVALID, "Unable to shorten OID - invalid hex value");
+ return -1;
+ }
node = &os->nodes[idx];
@@ -358,23 +399,29 @@ int git_oid_shorten_add(git_oid_shorten *os, const char *text_oid)
tail = node->tail;
node->tail = NULL;
- node = push_leaf(os, idx, from_hex[(int)tail[0]], &tail[1]);
- if (node == NULL)
- return GIT_ENOMEM;
+ node = push_leaf(os, idx, git__fromhex(tail[0]), &tail[1]);
+ if (node == NULL) {
+ if (os->full)
+ giterr_set(GITERR_INVALID, "Unable to shorten OID - OID set full");
+ return -1;
+ }
}
if (node->children[c] == 0) {
- if (push_leaf(os, idx, c, &text_oid[i + 1]) == NULL)
- return GIT_ENOMEM;
+ if (push_leaf(os, idx, c, &text_oid[i + 1]) == NULL) {
+ if (os->full)
+ giterr_set(GITERR_INVALID, "Unable to shorten OID - OID set full");
+ return -1;
+ }
break;
}
idx = node->children[c];
- is_leaf = 0;
+ is_leaf = false;
if (idx < 0) {
node->children[c] = idx = -idx;
- is_leaf = 1;
+ is_leaf = true;
}
}
diff --git a/src/oid.h b/src/oid.h
new file mode 100644
index 000000000..cfe7ca1b2
--- /dev/null
+++ b/src/oid.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_oid_h__
+#define INCLUDE_oid_h__
+
+#include "git2/oid.h"
+
+GIT_INLINE(int) git_oid__hashcmp(const unsigned char *sha1, const unsigned char *sha2)
+{
+ int i;
+
+ for (i = 0; i < GIT_OID_RAWSZ; i++, sha1++, sha2++) {
+ if (*sha1 != *sha2)
+ return *sha1 - *sha2;
+ }
+
+ return 0;
+}
+
+/*
+ * Compare two oid structures.
+ *
+ * @param a first oid structure.
+ * @param b second oid structure.
+ * @return <0, 0, >0 if a < b, a == b, a > b.
+ */
+GIT_INLINE(int) git_oid__cmp(const git_oid *a, const git_oid *b)
+{
+ return git_oid__hashcmp(a->id, b->id);
+}
+
+#endif
diff --git a/src/oidmap.h b/src/oidmap.h
new file mode 100644
index 000000000..a29c7cd35
--- /dev/null
+++ b/src/oidmap.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_oidmap_h__
+#define INCLUDE_oidmap_h__
+
+#include "common.h"
+#include "git2/oid.h"
+
+#define kmalloc git__malloc
+#define kcalloc git__calloc
+#define krealloc git__realloc
+#define kfree git__free
+#include "khash.h"
+
+__KHASH_TYPE(oid, const git_oid *, void *);
+typedef khash_t(oid) git_oidmap;
+
+GIT_INLINE(khint_t) git_oidmap_hash(const git_oid *oid)
+{
+ khint_t h;
+ memcpy(&h, oid, sizeof(khint_t));
+ return h;
+}
+
+#define GIT__USE_OIDMAP \
+ __KHASH_IMPL(oid, static kh_inline, const git_oid *, void *, 1, git_oidmap_hash, git_oid_equal)
+
+#define git_oidmap_alloc() kh_init(oid)
+#define git_oidmap_free(h) kh_destroy(oid,h), h = NULL
+
+#endif
diff --git a/src/pack-objects.c b/src/pack-objects.c
new file mode 100644
index 000000000..0040a826b
--- /dev/null
+++ b/src/pack-objects.c
@@ -0,0 +1,1425 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "pack-objects.h"
+
+#include "zstream.h"
+#include "delta.h"
+#include "iterator.h"
+#include "netops.h"
+#include "pack.h"
+#include "thread-utils.h"
+#include "tree.h"
+#include "util.h"
+
+#include "git2/pack.h"
+#include "git2/commit.h"
+#include "git2/tag.h"
+#include "git2/indexer.h"
+#include "git2/config.h"
+
+struct unpacked {
+ git_pobject *object;
+ void *data;
+ struct git_delta_index *index;
+ int depth;
+};
+
+struct tree_walk_context {
+ git_packbuilder *pb;
+ git_buf buf;
+};
+
+struct pack_write_context {
+ git_indexer *indexer;
+ git_transfer_progress *stats;
+};
+
+#ifdef GIT_THREADS
+
+#define GIT_PACKBUILDER__MUTEX_OP(pb, mtx, op) do { \
+ int result = git_mutex_##op(&(pb)->mtx); \
+ assert(!result); \
+ GIT_UNUSED(result); \
+ } while (0)
+
+#else
+
+#define GIT_PACKBUILDER__MUTEX_OP(pb,mtx,op) GIT_UNUSED(pb)
+
+#endif /* GIT_THREADS */
+
+#define git_packbuilder__cache_lock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, cache_mutex, lock)
+#define git_packbuilder__cache_unlock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, cache_mutex, unlock)
+#define git_packbuilder__progress_lock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, progress_mutex, lock)
+#define git_packbuilder__progress_unlock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, progress_mutex, unlock)
+
+/* The minimal interval between progress updates (in seconds). */
+#define MIN_PROGRESS_UPDATE_INTERVAL 0.5
+
+/* Size of the buffer to feed to zlib */
+#define COMPRESS_BUFLEN (1024 * 1024)
+
+static unsigned name_hash(const char *name)
+{
+ unsigned c, hash = 0;
+
+ if (!name)
+ return 0;
+
+ /*
+ * This effectively just creates a sortable number from the
+ * last sixteen non-whitespace characters. Last characters
+ * count "most", so things that end in ".c" sort together.
+ */
+ while ((c = *name++) != 0) {
+ if (git__isspace(c))
+ continue;
+ hash = (hash >> 2) + (c << 24);
+ }
+ return hash;
+}
+
+static int packbuilder_config(git_packbuilder *pb)
+{
+ git_config *config;
+ int ret;
+ int64_t val;
+
+ if ((ret = git_repository_config_snapshot(&config, pb->repo)) < 0)
+ return ret;
+
+#define config_get(KEY,DST,DFLT) do { \
+ ret = git_config_get_int64(&val, config, KEY); \
+ if (!ret) (DST) = val; \
+ else if (ret == GIT_ENOTFOUND) (DST) = (DFLT); \
+ else if (ret < 0) return -1; } while (0)
+
+ config_get("pack.deltaCacheSize", pb->max_delta_cache_size,
+ GIT_PACK_DELTA_CACHE_SIZE);
+ config_get("pack.deltaCacheLimit", pb->cache_max_small_delta_size,
+ GIT_PACK_DELTA_CACHE_LIMIT);
+ config_get("pack.deltaCacheSize", pb->big_file_threshold,
+ GIT_PACK_BIG_FILE_THRESHOLD);
+ config_get("pack.windowMemory", pb->window_memory_limit, 0);
+
+#undef config_get
+
+ git_config_free(config);
+
+ return 0;
+}
+
+int git_packbuilder_new(git_packbuilder **out, git_repository *repo)
+{
+ git_packbuilder *pb;
+
+ *out = NULL;
+
+ pb = git__calloc(1, sizeof(*pb));
+ GITERR_CHECK_ALLOC(pb);
+
+ pb->object_ix = git_oidmap_alloc();
+
+ if (!pb->object_ix)
+ goto on_error;
+
+ pb->repo = repo;
+ pb->nr_threads = 1; /* do not spawn any thread by default */
+
+ if (git_hash_ctx_init(&pb->ctx) < 0 ||
+ git_zstream_init(&pb->zstream) < 0 ||
+ git_repository_odb(&pb->odb, repo) < 0 ||
+ packbuilder_config(pb) < 0)
+ goto on_error;
+
+#ifdef GIT_THREADS
+
+ if (git_mutex_init(&pb->cache_mutex) ||
+ git_mutex_init(&pb->progress_mutex) ||
+ git_cond_init(&pb->progress_cond))
+ {
+ giterr_set(GITERR_OS, "Failed to initialize packbuilder mutex");
+ goto on_error;
+ }
+
+#endif
+
+ *out = pb;
+ return 0;
+
+on_error:
+ git_packbuilder_free(pb);
+ return -1;
+}
+
+unsigned int git_packbuilder_set_threads(git_packbuilder *pb, unsigned int n)
+{
+ assert(pb);
+
+#ifdef GIT_THREADS
+ pb->nr_threads = n;
+#else
+ GIT_UNUSED(n);
+ assert(1 == pb->nr_threads);
+#endif
+
+ return pb->nr_threads;
+}
+
+static void rehash(git_packbuilder *pb)
+{
+ git_pobject *po;
+ khiter_t pos;
+ unsigned int i;
+ int ret;
+
+ kh_clear(oid, pb->object_ix);
+ for (i = 0, po = pb->object_list; i < pb->nr_objects; i++, po++) {
+ pos = kh_put(oid, pb->object_ix, &po->id, &ret);
+ kh_value(pb->object_ix, pos) = po;
+ }
+}
+
+int git_packbuilder_insert(git_packbuilder *pb, const git_oid *oid,
+ const char *name)
+{
+ git_pobject *po;
+ khiter_t pos;
+ int ret;
+
+ assert(pb && oid);
+
+ /* If the object already exists in the hash table, then we don't
+ * have any work to do */
+ pos = kh_get(oid, pb->object_ix, oid);
+ if (pos != kh_end(pb->object_ix))
+ return 0;
+
+ if (pb->nr_objects >= pb->nr_alloc) {
+ pb->nr_alloc = (pb->nr_alloc + 1024) * 3 / 2;
+ pb->object_list = git__realloc(pb->object_list,
+ pb->nr_alloc * sizeof(*po));
+ GITERR_CHECK_ALLOC(pb->object_list);
+ rehash(pb);
+ }
+
+ po = pb->object_list + pb->nr_objects;
+ memset(po, 0x0, sizeof(*po));
+
+ if ((ret = git_odb_read_header(&po->size, &po->type, pb->odb, oid)) < 0)
+ return ret;
+
+ pb->nr_objects++;
+ git_oid_cpy(&po->id, oid);
+ po->hash = name_hash(name);
+
+ pos = kh_put(oid, pb->object_ix, &po->id, &ret);
+ if (ret < 0) {
+ giterr_set_oom();
+ return ret;
+ }
+ assert(ret != 0);
+ kh_value(pb->object_ix, pos) = po;
+
+ pb->done = false;
+
+ if (pb->progress_cb) {
+ double current_time = git__timer();
+ double elapsed = current_time - pb->last_progress_report_time;
+
+ if (elapsed >= MIN_PROGRESS_UPDATE_INTERVAL) {
+ pb->last_progress_report_time = current_time;
+
+ ret = pb->progress_cb(
+ GIT_PACKBUILDER_ADDING_OBJECTS,
+ pb->nr_objects, 0, pb->progress_cb_payload);
+
+ if (ret)
+ return giterr_set_after_callback(ret);
+ }
+ }
+
+ return 0;
+}
+
+static int get_delta(void **out, git_odb *odb, git_pobject *po)
+{
+ git_odb_object *src = NULL, *trg = NULL;
+ unsigned long delta_size;
+ void *delta_buf;
+
+ *out = NULL;
+
+ if (git_odb_read(&src, odb, &po->delta->id) < 0 ||
+ git_odb_read(&trg, odb, &po->id) < 0)
+ goto on_error;
+
+ delta_buf = git_delta(
+ git_odb_object_data(src), (unsigned long)git_odb_object_size(src),
+ git_odb_object_data(trg), (unsigned long)git_odb_object_size(trg),
+ &delta_size, 0);
+
+ if (!delta_buf || delta_size != po->delta_size) {
+ giterr_set(GITERR_INVALID, "Delta size changed");
+ goto on_error;
+ }
+
+ *out = delta_buf;
+
+ git_odb_object_free(src);
+ git_odb_object_free(trg);
+ return 0;
+
+on_error:
+ git_odb_object_free(src);
+ git_odb_object_free(trg);
+ return -1;
+}
+
+static int write_object(
+ git_packbuilder *pb,
+ git_pobject *po,
+ int (*write_cb)(void *buf, size_t size, void *cb_data),
+ void *cb_data)
+{
+ git_odb_object *obj = NULL;
+ git_otype type;
+ unsigned char hdr[10], *zbuf = NULL;
+ void *data = NULL;
+ size_t hdr_len, zbuf_len = COMPRESS_BUFLEN, data_len;
+ int error;
+
+ /*
+ * If we have a delta base, let's use the delta to save space.
+ * Otherwise load the whole object. 'data' ends up pointing to
+ * whatever data we want to put into the packfile.
+ */
+ if (po->delta) {
+ if (po->delta_data)
+ data = po->delta_data;
+ else if ((error = get_delta(&data, pb->odb, po)) < 0)
+ goto done;
+
+ data_len = po->delta_size;
+ type = GIT_OBJ_REF_DELTA;
+ } else {
+ if ((error = git_odb_read(&obj, pb->odb, &po->id)) < 0)
+ goto done;
+
+ data = (void *)git_odb_object_data(obj);
+ data_len = git_odb_object_size(obj);
+ type = git_odb_object_type(obj);
+ }
+
+ /* Write header */
+ hdr_len = git_packfile__object_header(hdr, data_len, type);
+
+ if ((error = write_cb(hdr, hdr_len, cb_data)) < 0 ||
+ (error = git_hash_update(&pb->ctx, hdr, hdr_len)) < 0)
+ goto done;
+
+ if (type == GIT_OBJ_REF_DELTA) {
+ if ((error = write_cb(po->delta->id.id, GIT_OID_RAWSZ, cb_data)) < 0 ||
+ (error = git_hash_update(&pb->ctx, po->delta->id.id, GIT_OID_RAWSZ)) < 0)
+ goto done;
+ }
+
+ /* Write data */
+ if (po->z_delta_size) {
+ data_len = po->z_delta_size;
+
+ if ((error = write_cb(data, data_len, cb_data)) < 0 ||
+ (error = git_hash_update(&pb->ctx, data, data_len)) < 0)
+ goto done;
+ } else {
+ zbuf = git__malloc(zbuf_len);
+ GITERR_CHECK_ALLOC(zbuf);
+
+ git_zstream_reset(&pb->zstream);
+ git_zstream_set_input(&pb->zstream, data, data_len);
+
+ while (!git_zstream_done(&pb->zstream)) {
+ if ((error = git_zstream_get_output(zbuf, &zbuf_len, &pb->zstream)) < 0 ||
+ (error = write_cb(zbuf, zbuf_len, cb_data)) < 0 ||
+ (error = git_hash_update(&pb->ctx, zbuf, zbuf_len)) < 0)
+ goto done;
+
+ zbuf_len = COMPRESS_BUFLEN; /* reuse buffer */
+ }
+ }
+
+ /*
+ * If po->delta is true, data is a delta and it is our
+ * responsibility to free it (otherwise it's a git_object's
+ * data). We set po->delta_data to NULL in case we got the
+ * data from there instead of get_delta(). If we didn't,
+ * there's no harm.
+ */
+ if (po->delta) {
+ git__free(data);
+ po->delta_data = NULL;
+ }
+
+ pb->nr_written++;
+
+done:
+ git__free(zbuf);
+ git_odb_object_free(obj);
+ return error;
+}
+
+enum write_one_status {
+ WRITE_ONE_SKIP = -1, /* already written */
+ WRITE_ONE_BREAK = 0, /* writing this will bust the limit; not written */
+ WRITE_ONE_WRITTEN = 1, /* normal */
+ WRITE_ONE_RECURSIVE = 2 /* already scheduled to be written */
+};
+
+static int write_one(
+ enum write_one_status *status,
+ git_packbuilder *pb,
+ git_pobject *po,
+ int (*write_cb)(void *buf, size_t size, void *cb_data),
+ void *cb_data)
+{
+ int error;
+
+ if (po->recursing) {
+ *status = WRITE_ONE_RECURSIVE;
+ return 0;
+ } else if (po->written) {
+ *status = WRITE_ONE_SKIP;
+ return 0;
+ }
+
+ if (po->delta) {
+ po->recursing = 1;
+
+ if ((error = write_one(status, pb, po->delta, write_cb, cb_data)) < 0)
+ return error;
+
+ /* we cannot depend on this one */
+ if (*status == WRITE_ONE_RECURSIVE)
+ po->delta = NULL;
+ }
+
+ *status = WRITE_ONE_WRITTEN;
+ po->written = 1;
+ po->recursing = 0;
+
+ return write_object(pb, po, write_cb, cb_data);
+}
+
+GIT_INLINE(void) add_to_write_order(git_pobject **wo, unsigned int *endp,
+ git_pobject *po)
+{
+ if (po->filled)
+ return;
+ wo[(*endp)++] = po;
+ po->filled = 1;
+}
+
+static void add_descendants_to_write_order(git_pobject **wo, unsigned int *endp,
+ git_pobject *po)
+{
+ int add_to_order = 1;
+ while (po) {
+ if (add_to_order) {
+ git_pobject *s;
+ /* add this node... */
+ add_to_write_order(wo, endp, po);
+ /* all its siblings... */
+ for (s = po->delta_sibling; s; s = s->delta_sibling) {
+ add_to_write_order(wo, endp, s);
+ }
+ }
+ /* drop down a level to add left subtree nodes if possible */
+ if (po->delta_child) {
+ add_to_order = 1;
+ po = po->delta_child;
+ } else {
+ add_to_order = 0;
+ /* our sibling might have some children, it is next */
+ if (po->delta_sibling) {
+ po = po->delta_sibling;
+ continue;
+ }
+ /* go back to our parent node */
+ po = po->delta;
+ while (po && !po->delta_sibling) {
+ /* we're on the right side of a subtree, keep
+ * going up until we can go right again */
+ po = po->delta;
+ }
+ if (!po) {
+ /* done- we hit our original root node */
+ return;
+ }
+ /* pass it off to sibling at this level */
+ po = po->delta_sibling;
+ }
+ };
+}
+
+static void add_family_to_write_order(git_pobject **wo, unsigned int *endp,
+ git_pobject *po)
+{
+ git_pobject *root;
+
+ for (root = po; root->delta; root = root->delta)
+ ; /* nothing */
+ add_descendants_to_write_order(wo, endp, root);
+}
+
+static int cb_tag_foreach(const char *name, git_oid *oid, void *data)
+{
+ git_packbuilder *pb = data;
+ git_pobject *po;
+ khiter_t pos;
+
+ GIT_UNUSED(name);
+
+ pos = kh_get(oid, pb->object_ix, oid);
+ if (pos == kh_end(pb->object_ix))
+ return 0;
+
+ po = kh_value(pb->object_ix, pos);
+ po->tagged = 1;
+
+ /* TODO: peel objects */
+
+ return 0;
+}
+
+static git_pobject **compute_write_order(git_packbuilder *pb)
+{
+ unsigned int i, wo_end, last_untagged;
+
+ git_pobject **wo = git__malloc(sizeof(*wo) * pb->nr_objects);
+
+ for (i = 0; i < pb->nr_objects; i++) {
+ git_pobject *po = pb->object_list + i;
+ po->tagged = 0;
+ po->filled = 0;
+ po->delta_child = NULL;
+ po->delta_sibling = NULL;
+ }
+
+ /*
+ * Fully connect delta_child/delta_sibling network.
+ * Make sure delta_sibling is sorted in the original
+ * recency order.
+ */
+ for (i = pb->nr_objects; i > 0;) {
+ git_pobject *po = &pb->object_list[--i];
+ if (!po->delta)
+ continue;
+ /* Mark me as the first child */
+ po->delta_sibling = po->delta->delta_child;
+ po->delta->delta_child = po;
+ }
+
+ /*
+ * Mark objects that are at the tip of tags.
+ */
+ if (git_tag_foreach(pb->repo, &cb_tag_foreach, pb) < 0) {
+ git__free(wo);
+ return NULL;
+ }
+
+ /*
+ * Give the objects in the original recency order until
+ * we see a tagged tip.
+ */
+ for (i = wo_end = 0; i < pb->nr_objects; i++) {
+ git_pobject *po = pb->object_list + i;
+ if (po->tagged)
+ break;
+ add_to_write_order(wo, &wo_end, po);
+ }
+ last_untagged = i;
+
+ /*
+ * Then fill all the tagged tips.
+ */
+ for (; i < pb->nr_objects; i++) {
+ git_pobject *po = pb->object_list + i;
+ if (po->tagged)
+ add_to_write_order(wo, &wo_end, po);
+ }
+
+ /*
+ * And then all remaining commits and tags.
+ */
+ for (i = last_untagged; i < pb->nr_objects; i++) {
+ git_pobject *po = pb->object_list + i;
+ if (po->type != GIT_OBJ_COMMIT &&
+ po->type != GIT_OBJ_TAG)
+ continue;
+ add_to_write_order(wo, &wo_end, po);
+ }
+
+ /*
+ * And then all the trees.
+ */
+ for (i = last_untagged; i < pb->nr_objects; i++) {
+ git_pobject *po = pb->object_list + i;
+ if (po->type != GIT_OBJ_TREE)
+ continue;
+ add_to_write_order(wo, &wo_end, po);
+ }
+
+ /*
+ * Finally all the rest in really tight order
+ */
+ for (i = last_untagged; i < pb->nr_objects; i++) {
+ git_pobject *po = pb->object_list + i;
+ if (!po->filled)
+ add_family_to_write_order(wo, &wo_end, po);
+ }
+
+ if (wo_end != pb->nr_objects) {
+ giterr_set(GITERR_INVALID, "invalid write order");
+ return NULL;
+ }
+
+ return wo;
+}
+
+static int write_pack(git_packbuilder *pb,
+ int (*write_cb)(void *buf, size_t size, void *cb_data),
+ void *cb_data)
+{
+ git_pobject **write_order;
+ git_pobject *po;
+ enum write_one_status status;
+ struct git_pack_header ph;
+ git_oid entry_oid;
+ unsigned int i = 0;
+ int error = 0;
+
+ write_order = compute_write_order(pb);
+ if (write_order == NULL) {
+ error = -1;
+ goto done;
+ }
+
+ /* Write pack header */
+ ph.hdr_signature = htonl(PACK_SIGNATURE);
+ ph.hdr_version = htonl(PACK_VERSION);
+ ph.hdr_entries = htonl(pb->nr_objects);
+
+ if ((error = write_cb(&ph, sizeof(ph), cb_data)) < 0 ||
+ (error = git_hash_update(&pb->ctx, &ph, sizeof(ph))) < 0)
+ goto done;
+
+ pb->nr_remaining = pb->nr_objects;
+ do {
+ pb->nr_written = 0;
+ for ( ; i < pb->nr_objects; ++i) {
+ po = write_order[i];
+
+ if ((error = write_one(&status, pb, po, write_cb, cb_data)) < 0)
+ goto done;
+ }
+
+ pb->nr_remaining -= pb->nr_written;
+ } while (pb->nr_remaining && i < pb->nr_objects);
+
+ if ((error = git_hash_final(&entry_oid, &pb->ctx)) < 0)
+ goto done;
+
+ error = write_cb(entry_oid.id, GIT_OID_RAWSZ, cb_data);
+
+done:
+ /* if callback cancelled writing, we must still free delta_data */
+ for ( ; i < pb->nr_objects; ++i) {
+ po = write_order[i];
+ if (po->delta_data) {
+ git__free(po->delta_data);
+ po->delta_data = NULL;
+ }
+ }
+
+ git__free(write_order);
+ return error;
+}
+
+static int write_pack_buf(void *buf, size_t size, void *data)
+{
+ git_buf *b = (git_buf *)data;
+ return git_buf_put(b, buf, size);
+}
+
+static int type_size_sort(const void *_a, const void *_b)
+{
+ const git_pobject *a = (git_pobject *)_a;
+ const git_pobject *b = (git_pobject *)_b;
+
+ if (a->type > b->type)
+ return -1;
+ if (a->type < b->type)
+ return 1;
+ if (a->hash > b->hash)
+ return -1;
+ if (a->hash < b->hash)
+ return 1;
+ /*
+ * TODO
+ *
+ if (a->preferred_base > b->preferred_base)
+ return -1;
+ if (a->preferred_base < b->preferred_base)
+ return 1;
+ */
+ if (a->size > b->size)
+ return -1;
+ if (a->size < b->size)
+ return 1;
+ return a < b ? -1 : (a > b); /* newest first */
+}
+
+static int delta_cacheable(git_packbuilder *pb, unsigned long src_size,
+ unsigned long trg_size, unsigned long delta_size)
+{
+ if (pb->max_delta_cache_size &&
+ pb->delta_cache_size + delta_size > pb->max_delta_cache_size)
+ return 0;
+
+ if (delta_size < pb->cache_max_small_delta_size)
+ return 1;
+
+ /* cache delta, if objects are large enough compared to delta size */
+ if ((src_size >> 20) + (trg_size >> 21) > (delta_size >> 10))
+ return 1;
+
+ return 0;
+}
+
+static int try_delta(git_packbuilder *pb, struct unpacked *trg,
+ struct unpacked *src, int max_depth,
+ unsigned long *mem_usage, int *ret)
+{
+ git_pobject *trg_object = trg->object;
+ git_pobject *src_object = src->object;
+ git_odb_object *obj;
+ unsigned long trg_size, src_size, delta_size,
+ sizediff, max_size, sz;
+ unsigned int ref_depth;
+ void *delta_buf;
+
+ /* Don't bother doing diffs between different types */
+ if (trg_object->type != src_object->type) {
+ *ret = -1;
+ return 0;
+ }
+
+ *ret = 0;
+
+ /* TODO: support reuse-delta */
+
+ /* Let's not bust the allowed depth. */
+ if (src->depth >= max_depth)
+ return 0;
+
+ /* Now some size filtering heuristics. */
+ trg_size = (unsigned long)trg_object->size;
+ if (!trg_object->delta) {
+ max_size = trg_size/2 - 20;
+ ref_depth = 1;
+ } else {
+ max_size = trg_object->delta_size;
+ ref_depth = trg->depth;
+ }
+
+ max_size = (uint64_t)max_size * (max_depth - src->depth) /
+ (max_depth - ref_depth + 1);
+ if (max_size == 0)
+ return 0;
+
+ src_size = (unsigned long)src_object->size;
+ sizediff = src_size < trg_size ? trg_size - src_size : 0;
+ if (sizediff >= max_size)
+ return 0;
+ if (trg_size < src_size / 32)
+ return 0;
+
+ /* Load data if not already done */
+ if (!trg->data) {
+ if (git_odb_read(&obj, pb->odb, &trg_object->id) < 0)
+ return -1;
+
+ sz = (unsigned long)git_odb_object_size(obj);
+ trg->data = git__malloc(sz);
+ GITERR_CHECK_ALLOC(trg->data);
+ memcpy(trg->data, git_odb_object_data(obj), sz);
+
+ git_odb_object_free(obj);
+
+ if (sz != trg_size) {
+ giterr_set(GITERR_INVALID,
+ "Inconsistent target object length");
+ return -1;
+ }
+
+ *mem_usage += sz;
+ }
+ if (!src->data) {
+ if (git_odb_read(&obj, pb->odb, &src_object->id) < 0)
+ return -1;
+
+ sz = (unsigned long)git_odb_object_size(obj);
+ src->data = git__malloc(sz);
+ GITERR_CHECK_ALLOC(src->data);
+ memcpy(src->data, git_odb_object_data(obj), sz);
+
+ git_odb_object_free(obj);
+
+ if (sz != src_size) {
+ giterr_set(GITERR_INVALID,
+ "Inconsistent source object length");
+ return -1;
+ }
+
+ *mem_usage += sz;
+ }
+ if (!src->index) {
+ src->index = git_delta_create_index(src->data, src_size);
+ if (!src->index)
+ return 0; /* suboptimal pack - out of memory */
+
+ *mem_usage += git_delta_sizeof_index(src->index);
+ }
+
+ delta_buf = git_delta_create(src->index, trg->data, trg_size,
+ &delta_size, max_size);
+ if (!delta_buf)
+ return 0;
+
+ if (trg_object->delta) {
+ /* Prefer only shallower same-sized deltas. */
+ if (delta_size == trg_object->delta_size &&
+ src->depth + 1 >= trg->depth) {
+ git__free(delta_buf);
+ return 0;
+ }
+ }
+
+ git_packbuilder__cache_lock(pb);
+ if (trg_object->delta_data) {
+ git__free(trg_object->delta_data);
+ pb->delta_cache_size -= trg_object->delta_size;
+ trg_object->delta_data = NULL;
+ }
+ if (delta_cacheable(pb, src_size, trg_size, delta_size)) {
+ pb->delta_cache_size += delta_size;
+ git_packbuilder__cache_unlock(pb);
+
+ trg_object->delta_data = git__realloc(delta_buf, delta_size);
+ GITERR_CHECK_ALLOC(trg_object->delta_data);
+ } else {
+ /* create delta when writing the pack */
+ git_packbuilder__cache_unlock(pb);
+ git__free(delta_buf);
+ }
+
+ trg_object->delta = src_object;
+ trg_object->delta_size = delta_size;
+ trg->depth = src->depth + 1;
+
+ *ret = 1;
+ return 0;
+}
+
+static unsigned int check_delta_limit(git_pobject *me, unsigned int n)
+{
+ git_pobject *child = me->delta_child;
+ unsigned int m = n;
+
+ while (child) {
+ unsigned int c = check_delta_limit(child, n + 1);
+ if (m < c)
+ m = c;
+ child = child->delta_sibling;
+ }
+ return m;
+}
+
+static unsigned long free_unpacked(struct unpacked *n)
+{
+ unsigned long freed_mem = git_delta_sizeof_index(n->index);
+ git_delta_free_index(n->index);
+ n->index = NULL;
+ if (n->data) {
+ freed_mem += (unsigned long)n->object->size;
+ git__free(n->data);
+ n->data = NULL;
+ }
+ n->object = NULL;
+ n->depth = 0;
+ return freed_mem;
+}
+
+static int find_deltas(git_packbuilder *pb, git_pobject **list,
+ unsigned int *list_size, unsigned int window,
+ int depth)
+{
+ git_pobject *po;
+ git_buf zbuf = GIT_BUF_INIT;
+ struct unpacked *array;
+ uint32_t idx = 0, count = 0;
+ unsigned long mem_usage = 0;
+ unsigned int i;
+ int error = -1;
+
+ array = git__calloc(window, sizeof(struct unpacked));
+ GITERR_CHECK_ALLOC(array);
+
+ for (;;) {
+ struct unpacked *n = array + idx;
+ int max_depth, j, best_base = -1;
+
+ git_packbuilder__progress_lock(pb);
+ if (!*list_size) {
+ git_packbuilder__progress_unlock(pb);
+ break;
+ }
+
+ po = *list++;
+ (*list_size)--;
+ git_packbuilder__progress_unlock(pb);
+
+ mem_usage -= free_unpacked(n);
+ n->object = po;
+
+ while (pb->window_memory_limit &&
+ mem_usage > pb->window_memory_limit &&
+ count > 1) {
+ uint32_t tail = (idx + window - count) % window;
+ mem_usage -= free_unpacked(array + tail);
+ count--;
+ }
+
+ /*
+ * If the current object is at pack edge, take the depth the
+ * objects that depend on the current object into account
+ * otherwise they would become too deep.
+ */
+ max_depth = depth;
+ if (po->delta_child) {
+ max_depth -= check_delta_limit(po, 0);
+ if (max_depth <= 0)
+ goto next;
+ }
+
+ j = window;
+ while (--j > 0) {
+ int ret;
+ uint32_t other_idx = idx + j;
+ struct unpacked *m;
+
+ if (other_idx >= window)
+ other_idx -= window;
+
+ m = array + other_idx;
+ if (!m->object)
+ break;
+
+ if (try_delta(pb, n, m, max_depth, &mem_usage, &ret) < 0)
+ goto on_error;
+ if (ret < 0)
+ break;
+ else if (ret > 0)
+ best_base = other_idx;
+ }
+
+ /*
+ * If we decided to cache the delta data, then it is best
+ * to compress it right away. First because we have to do
+ * it anyway, and doing it here while we're threaded will
+ * save a lot of time in the non threaded write phase,
+ * as well as allow for caching more deltas within
+ * the same cache size limit.
+ * ...
+ * But only if not writing to stdout, since in that case
+ * the network is most likely throttling writes anyway,
+ * and therefore it is best to go to the write phase ASAP
+ * instead, as we can afford spending more time compressing
+ * between writes at that moment.
+ */
+ if (po->delta_data) {
+ if (git_zstream_deflatebuf(&zbuf, po->delta_data, po->delta_size) < 0)
+ goto on_error;
+
+ git__free(po->delta_data);
+ po->delta_data = git__malloc(zbuf.size);
+ GITERR_CHECK_ALLOC(po->delta_data);
+
+ memcpy(po->delta_data, zbuf.ptr, zbuf.size);
+ po->z_delta_size = (unsigned long)zbuf.size;
+ git_buf_clear(&zbuf);
+
+ git_packbuilder__cache_lock(pb);
+ pb->delta_cache_size -= po->delta_size;
+ pb->delta_cache_size += po->z_delta_size;
+ git_packbuilder__cache_unlock(pb);
+ }
+
+ /*
+ * If we made n a delta, and if n is already at max
+ * depth, leaving it in the window is pointless. we
+ * should evict it first.
+ */
+ if (po->delta && max_depth <= n->depth)
+ continue;
+
+ /*
+ * Move the best delta base up in the window, after the
+ * currently deltified object, to keep it longer. It will
+ * be the first base object to be attempted next.
+ */
+ if (po->delta) {
+ struct unpacked swap = array[best_base];
+ int dist = (window + idx - best_base) % window;
+ int dst = best_base;
+ while (dist--) {
+ int src = (dst + 1) % window;
+ array[dst] = array[src];
+ dst = src;
+ }
+ array[dst] = swap;
+ }
+
+ next:
+ idx++;
+ if (count + 1 < window)
+ count++;
+ if (idx >= window)
+ idx = 0;
+ }
+ error = 0;
+
+on_error:
+ for (i = 0; i < window; ++i) {
+ git__free(array[i].index);
+ git__free(array[i].data);
+ }
+ git__free(array);
+ git_buf_free(&zbuf);
+
+ return error;
+}
+
+#ifdef GIT_THREADS
+
+struct thread_params {
+ git_thread thread;
+ git_packbuilder *pb;
+
+ git_pobject **list;
+
+ git_cond cond;
+ git_mutex mutex;
+
+ unsigned int list_size;
+ unsigned int remaining;
+
+ int window;
+ int depth;
+ int working;
+ int data_ready;
+};
+
+static void *threaded_find_deltas(void *arg)
+{
+ struct thread_params *me = arg;
+
+ while (me->remaining) {
+ if (find_deltas(me->pb, me->list, &me->remaining,
+ me->window, me->depth) < 0) {
+ ; /* TODO */
+ }
+
+ git_packbuilder__progress_lock(me->pb);
+ me->working = 0;
+ git_cond_signal(&me->pb->progress_cond);
+ git_packbuilder__progress_unlock(me->pb);
+
+ if (git_mutex_lock(&me->mutex)) {
+ giterr_set(GITERR_THREAD, "unable to lock packfile condition mutex");
+ return NULL;
+ }
+
+ while (!me->data_ready)
+ git_cond_wait(&me->cond, &me->mutex);
+
+ /*
+ * We must not set ->data_ready before we wait on the
+ * condition because the main thread may have set it to 1
+ * before we get here. In order to be sure that new
+ * work is available if we see 1 in ->data_ready, it
+ * was initialized to 0 before this thread was spawned
+ * and we reset it to 0 right away.
+ */
+ me->data_ready = 0;
+ git_mutex_unlock(&me->mutex);
+ }
+ /* leave ->working 1 so that this doesn't get more work assigned */
+ return NULL;
+}
+
+static int ll_find_deltas(git_packbuilder *pb, git_pobject **list,
+ unsigned int list_size, unsigned int window,
+ int depth)
+{
+ struct thread_params *p;
+ int i, ret, active_threads = 0;
+
+ if (!pb->nr_threads)
+ pb->nr_threads = git_online_cpus();
+
+ if (pb->nr_threads <= 1) {
+ find_deltas(pb, list, &list_size, window, depth);
+ return 0;
+ }
+
+ p = git__malloc(pb->nr_threads * sizeof(*p));
+ GITERR_CHECK_ALLOC(p);
+
+ /* Partition the work among the threads */
+ for (i = 0; i < pb->nr_threads; ++i) {
+ unsigned sub_size = list_size / (pb->nr_threads - i);
+
+ /* don't use too small segments or no deltas will be found */
+ if (sub_size < 2*window && i+1 < pb->nr_threads)
+ sub_size = 0;
+
+ p[i].pb = pb;
+ p[i].window = window;
+ p[i].depth = depth;
+ p[i].working = 1;
+ p[i].data_ready = 0;
+
+ /* try to split chunks on "path" boundaries */
+ while (sub_size && sub_size < list_size &&
+ list[sub_size]->hash &&
+ list[sub_size]->hash == list[sub_size-1]->hash)
+ sub_size++;
+
+ p[i].list = list;
+ p[i].list_size = sub_size;
+ p[i].remaining = sub_size;
+
+ list += sub_size;
+ list_size -= sub_size;
+ }
+
+ /* Start work threads */
+ for (i = 0; i < pb->nr_threads; ++i) {
+ if (!p[i].list_size)
+ continue;
+
+ git_mutex_init(&p[i].mutex);
+ git_cond_init(&p[i].cond);
+
+ ret = git_thread_create(&p[i].thread, NULL,
+ threaded_find_deltas, &p[i]);
+ if (ret) {
+ giterr_set(GITERR_THREAD, "unable to create thread");
+ return -1;
+ }
+ active_threads++;
+ }
+
+ /*
+ * Now let's wait for work completion. Each time a thread is done
+ * with its work, we steal half of the remaining work from the
+ * thread with the largest number of unprocessed objects and give
+ * it to that newly idle thread. This ensure good load balancing
+ * until the remaining object list segments are simply too short
+ * to be worth splitting anymore.
+ */
+ while (active_threads) {
+ struct thread_params *target = NULL;
+ struct thread_params *victim = NULL;
+ unsigned sub_size = 0;
+
+ /* Start by locating a thread that has transitioned its
+ * 'working' flag from 1 -> 0. This indicates that it is
+ * ready to receive more work using our work-stealing
+ * algorithm. */
+ git_packbuilder__progress_lock(pb);
+ for (;;) {
+ for (i = 0; !target && i < pb->nr_threads; i++)
+ if (!p[i].working)
+ target = &p[i];
+ if (target)
+ break;
+ git_cond_wait(&pb->progress_cond, &pb->progress_mutex);
+ }
+
+ /* At this point we hold the progress lock and have located
+ * a thread to receive more work. We still need to locate a
+ * thread from which to steal work (the victim). */
+ for (i = 0; i < pb->nr_threads; i++)
+ if (p[i].remaining > 2*window &&
+ (!victim || victim->remaining < p[i].remaining))
+ victim = &p[i];
+
+ if (victim) {
+ sub_size = victim->remaining / 2;
+ list = victim->list + victim->list_size - sub_size;
+ while (sub_size && list[0]->hash &&
+ list[0]->hash == list[-1]->hash) {
+ list++;
+ sub_size--;
+ }
+ if (!sub_size) {
+ /*
+ * It is possible for some "paths" to have
+ * so many objects that no hash boundary
+ * might be found. Let's just steal the
+ * exact half in that case.
+ */
+ sub_size = victim->remaining / 2;
+ list -= sub_size;
+ }
+ target->list = list;
+ victim->list_size -= sub_size;
+ victim->remaining -= sub_size;
+ }
+ target->list_size = sub_size;
+ target->remaining = sub_size;
+ target->working = 1;
+ git_packbuilder__progress_unlock(pb);
+
+ if (git_mutex_lock(&target->mutex)) {
+ giterr_set(GITERR_THREAD, "unable to lock packfile condition mutex");
+ git__free(p);
+ return -1;
+ }
+
+ target->data_ready = 1;
+ git_cond_signal(&target->cond);
+ git_mutex_unlock(&target->mutex);
+
+ if (!sub_size) {
+ git_thread_join(&target->thread, NULL);
+ git_cond_free(&target->cond);
+ git_mutex_free(&target->mutex);
+ active_threads--;
+ }
+ }
+
+ git__free(p);
+ return 0;
+}
+
+#else
+#define ll_find_deltas(pb, l, ls, w, d) find_deltas(pb, l, &ls, w, d)
+#endif
+
+static int prepare_pack(git_packbuilder *pb)
+{
+ git_pobject **delta_list;
+ unsigned int i, n = 0;
+
+ if (pb->nr_objects == 0 || pb->done)
+ return 0; /* nothing to do */
+
+ /*
+ * Although we do not report progress during deltafication, we
+ * at least report that we are in the deltafication stage
+ */
+ if (pb->progress_cb)
+ pb->progress_cb(GIT_PACKBUILDER_DELTAFICATION, 0, pb->nr_objects, pb->progress_cb_payload);
+
+ delta_list = git__malloc(pb->nr_objects * sizeof(*delta_list));
+ GITERR_CHECK_ALLOC(delta_list);
+
+ for (i = 0; i < pb->nr_objects; ++i) {
+ git_pobject *po = pb->object_list + i;
+
+ /* Make sure the item is within our size limits */
+ if (po->size < 50 || po->size > pb->big_file_threshold)
+ continue;
+
+ delta_list[n++] = po;
+ }
+
+ if (n > 1) {
+ git__tsort((void **)delta_list, n, type_size_sort);
+ if (ll_find_deltas(pb, delta_list, n,
+ GIT_PACK_WINDOW + 1,
+ GIT_PACK_DEPTH) < 0) {
+ git__free(delta_list);
+ return -1;
+ }
+ }
+
+ pb->done = true;
+ git__free(delta_list);
+ return 0;
+}
+
+#define PREPARE_PACK if (prepare_pack(pb) < 0) { return -1; }
+
+int git_packbuilder_foreach(git_packbuilder *pb, int (*cb)(void *buf, size_t size, void *payload), void *payload)
+{
+ PREPARE_PACK;
+ return write_pack(pb, cb, payload);
+}
+
+int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb)
+{
+ PREPARE_PACK;
+ git_buf_sanitize(buf);
+ return write_pack(pb, &write_pack_buf, buf);
+}
+
+static int write_cb(void *buf, size_t len, void *payload)
+{
+ struct pack_write_context *ctx = payload;
+ return git_indexer_append(ctx->indexer, buf, len, ctx->stats);
+}
+
+int git_packbuilder_write(
+ git_packbuilder *pb,
+ const char *path,
+ unsigned int mode,
+ git_transfer_progress_cb progress_cb,
+ void *progress_cb_payload)
+{
+ git_indexer *indexer;
+ git_transfer_progress stats;
+ struct pack_write_context ctx;
+
+ PREPARE_PACK;
+
+ if (git_indexer_new(
+ &indexer, path, mode, pb->odb, progress_cb, progress_cb_payload) < 0)
+ return -1;
+
+ ctx.indexer = indexer;
+ ctx.stats = &stats;
+
+ if (git_packbuilder_foreach(pb, write_cb, &ctx) < 0 ||
+ git_indexer_commit(indexer, &stats) < 0) {
+ git_indexer_free(indexer);
+ return -1;
+ }
+
+ git_oid_cpy(&pb->pack_oid, git_indexer_hash(indexer));
+
+ git_indexer_free(indexer);
+ return 0;
+}
+
+#undef PREPARE_PACK
+
+const git_oid *git_packbuilder_hash(git_packbuilder *pb)
+{
+ return &pb->pack_oid;
+}
+
+static int cb_tree_walk(
+ const char *root, const git_tree_entry *entry, void *payload)
+{
+ int error;
+ struct tree_walk_context *ctx = payload;
+
+ /* A commit inside a tree represents a submodule commit and should be skipped. */
+ if (git_tree_entry_type(entry) == GIT_OBJ_COMMIT)
+ return 0;
+
+ if (!(error = git_buf_sets(&ctx->buf, root)) &&
+ !(error = git_buf_puts(&ctx->buf, git_tree_entry_name(entry))))
+ error = git_packbuilder_insert(
+ ctx->pb, git_tree_entry_id(entry), git_buf_cstr(&ctx->buf));
+
+ return error;
+}
+
+int git_packbuilder_insert_commit(git_packbuilder *pb, const git_oid *oid)
+{
+ git_commit *commit;
+
+ if (git_commit_lookup(&commit, pb->repo, oid) < 0 ||
+ git_packbuilder_insert(pb, oid, NULL) < 0)
+ return -1;
+
+ if (git_packbuilder_insert_tree(pb, git_commit_tree_id(commit)) < 0)
+ return -1;
+
+ git_commit_free(commit);
+ return 0;
+}
+
+int git_packbuilder_insert_tree(git_packbuilder *pb, const git_oid *oid)
+{
+ int error;
+ git_tree *tree = NULL;
+ struct tree_walk_context context = { pb, GIT_BUF_INIT };
+
+ if (!(error = git_tree_lookup(&tree, pb->repo, oid)) &&
+ !(error = git_packbuilder_insert(pb, oid, NULL)))
+ error = git_tree_walk(tree, GIT_TREEWALK_PRE, cb_tree_walk, &context);
+
+ git_tree_free(tree);
+ git_buf_free(&context.buf);
+ return error;
+}
+
+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;
+}
+
+int git_packbuilder_set_callbacks(git_packbuilder *pb, git_packbuilder_progress progress_cb, void *progress_cb_payload)
+{
+ if (!pb)
+ return -1;
+
+ pb->progress_cb = progress_cb;
+ pb->progress_cb_payload = progress_cb_payload;
+
+ return 0;
+}
+
+void git_packbuilder_free(git_packbuilder *pb)
+{
+ if (pb == NULL)
+ return;
+
+#ifdef GIT_THREADS
+
+ git_mutex_free(&pb->cache_mutex);
+ git_mutex_free(&pb->progress_mutex);
+ git_cond_free(&pb->progress_cond);
+
+#endif
+
+ if (pb->odb)
+ git_odb_free(pb->odb);
+
+ if (pb->object_ix)
+ git_oidmap_free(pb->object_ix);
+
+ if (pb->object_list)
+ git__free(pb->object_list);
+
+ git_hash_ctx_cleanup(&pb->ctx);
+ git_zstream_free(&pb->zstream);
+
+ git__free(pb);
+}
diff --git a/src/pack-objects.h b/src/pack-objects.h
new file mode 100644
index 000000000..4647df75a
--- /dev/null
+++ b/src/pack-objects.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_pack_objects_h__
+#define INCLUDE_pack_objects_h__
+
+#include "common.h"
+
+#include "buffer.h"
+#include "hash.h"
+#include "oidmap.h"
+#include "netops.h"
+#include "zstream.h"
+
+#include "git2/oid.h"
+#include "git2/pack.h"
+
+#define GIT_PACK_WINDOW 10 /* number of objects to possibly delta against */
+#define GIT_PACK_DEPTH 50 /* max delta depth */
+#define GIT_PACK_DELTA_CACHE_SIZE (256 * 1024 * 1024)
+#define GIT_PACK_DELTA_CACHE_LIMIT 1000
+#define GIT_PACK_BIG_FILE_THRESHOLD (512 * 1024 * 1024)
+
+typedef struct git_pobject {
+ git_oid id;
+ git_otype type;
+ git_off_t offset;
+
+ size_t size;
+
+ unsigned int hash; /* name hint hash */
+
+ struct git_pobject *delta; /* delta base object */
+ struct git_pobject *delta_child; /* deltified objects who bases me */
+ struct git_pobject *delta_sibling; /* other deltified objects
+ * who uses the same base as
+ * me */
+
+ void *delta_data;
+ unsigned long delta_size;
+ unsigned long z_delta_size;
+
+ int written:1,
+ recursing:1,
+ tagged:1,
+ filled:1;
+} git_pobject;
+
+struct git_packbuilder {
+ git_repository *repo; /* associated repository */
+ git_odb *odb; /* associated object database */
+
+ git_hash_ctx ctx;
+ git_zstream zstream;
+
+ uint32_t nr_objects,
+ nr_alloc,
+ nr_written,
+ nr_remaining;
+
+ git_pobject *object_list;
+
+ git_oidmap *object_ix;
+
+ git_oid pack_oid; /* hash of written pack */
+
+ /* synchronization objects */
+ git_mutex cache_mutex;
+ git_mutex progress_mutex;
+ git_cond progress_cond;
+
+ /* configs */
+ uint64_t delta_cache_size;
+ uint64_t max_delta_cache_size;
+ uint64_t cache_max_small_delta_size;
+ uint64_t big_file_threshold;
+ uint64_t window_memory_limit;
+
+ int nr_threads; /* nr of threads to use */
+
+ git_packbuilder_progress progress_cb;
+ void *progress_cb_payload;
+ double last_progress_report_time; /* the time progress was last reported */
+
+ bool done;
+};
+
+int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb);
+
+#endif /* INCLUDE_pack_objects_h__ */
diff --git a/src/pack.c b/src/pack.c
index c84f007bc..ace7abb58 100644
--- a/src/pack.c
+++ b/src/pack.c
@@ -1,44 +1,28 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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 "mwindow.h"
+#include "common.h"
#include "odb.h"
#include "pack.h"
#include "delta-apply.h"
#include "sha1_lookup.h"
+#include "mwindow.h"
+#include "fileops.h"
+#include "oid.h"
-#include "git2/oid.h"
-#include "git2/zlib.h"
+#include <zlib.h>
static int packfile_open(struct git_pack_file *p);
-static off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_t n);
+static git_off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_t n);
int packfile_unpack_compressed(
git_rawobj *obj,
struct git_pack_file *p,
git_mwindow **w_curs,
- off_t *curpos,
+ git_off_t *curpos,
size_t size,
git_otype type);
@@ -50,11 +34,153 @@ int packfile_unpack_compressed(
* GIT_OID_MINPREFIXLEN and GIT_OID_HEXSZ.
*/
static int pack_entry_find_offset(
- off_t *offset_out,
+ git_off_t *offset_out,
git_oid *found_oid,
struct git_pack_file *p,
const git_oid *short_oid,
- unsigned int len);
+ size_t len);
+
+static int packfile_error(const char *message)
+{
+ giterr_set(GITERR_ODB, "Invalid pack file - %s", message);
+ return -1;
+}
+
+/********************
+ * Delta base cache
+ ********************/
+
+static git_pack_cache_entry *new_cache_object(git_rawobj *source)
+{
+ git_pack_cache_entry *e = git__calloc(1, sizeof(git_pack_cache_entry));
+ if (!e)
+ return NULL;
+
+ memcpy(&e->raw, source, sizeof(git_rawobj));
+
+ return e;
+}
+
+static void free_cache_object(void *o)
+{
+ git_pack_cache_entry *e = (git_pack_cache_entry *)o;
+
+ if (e != NULL) {
+ assert(e->refcount.val == 0);
+ git__free(e->raw.data);
+ git__free(e);
+ }
+}
+
+static void cache_free(git_pack_cache *cache)
+{
+ khiter_t k;
+
+ if (cache->entries) {
+ for (k = kh_begin(cache->entries); k != kh_end(cache->entries); k++) {
+ if (kh_exist(cache->entries, k))
+ free_cache_object(kh_value(cache->entries, k));
+ }
+
+ git_offmap_free(cache->entries);
+ cache->entries = NULL;
+ }
+}
+
+static int cache_init(git_pack_cache *cache)
+{
+ cache->entries = git_offmap_alloc();
+ GITERR_CHECK_ALLOC(cache->entries);
+
+ cache->memory_limit = GIT_PACK_CACHE_MEMORY_LIMIT;
+
+ if (git_mutex_init(&cache->lock)) {
+ giterr_set(GITERR_OS, "Failed to initialize pack cache mutex");
+
+ git__free(cache->entries);
+ cache->entries = NULL;
+
+ return -1;
+ }
+
+ return 0;
+}
+
+static git_pack_cache_entry *cache_get(git_pack_cache *cache, git_off_t offset)
+{
+ khiter_t k;
+ git_pack_cache_entry *entry = NULL;
+
+ if (git_mutex_lock(&cache->lock) < 0)
+ return NULL;
+
+ k = kh_get(off, cache->entries, offset);
+ if (k != kh_end(cache->entries)) { /* found it */
+ entry = kh_value(cache->entries, k);
+ git_atomic_inc(&entry->refcount);
+ entry->last_usage = cache->use_ctr++;
+ }
+ git_mutex_unlock(&cache->lock);
+
+ return entry;
+}
+
+/* Run with the cache lock held */
+static void free_lowest_entry(git_pack_cache *cache)
+{
+ git_pack_cache_entry *entry;
+ khiter_t k;
+
+ for (k = kh_begin(cache->entries); k != kh_end(cache->entries); k++) {
+ if (!kh_exist(cache->entries, k))
+ continue;
+
+ entry = kh_value(cache->entries, k);
+
+ if (entry && entry->refcount.val == 0) {
+ cache->memory_used -= entry->raw.len;
+ kh_del(off, cache->entries, k);
+ free_cache_object(entry);
+ }
+ }
+}
+
+static int cache_add(git_pack_cache *cache, git_rawobj *base, git_off_t offset)
+{
+ git_pack_cache_entry *entry;
+ int error, exists = 0;
+ khiter_t k;
+
+ if (base->len > GIT_PACK_CACHE_SIZE_LIMIT)
+ return -1;
+
+ entry = new_cache_object(base);
+ if (entry) {
+ if (git_mutex_lock(&cache->lock) < 0) {
+ giterr_set(GITERR_OS, "failed to lock cache");
+ return -1;
+ }
+ /* Add it to the cache if nobody else has */
+ exists = kh_get(off, cache->entries, offset) != kh_end(cache->entries);
+ if (!exists) {
+ while (cache->memory_used + base->len > cache->memory_limit)
+ free_lowest_entry(cache);
+
+ k = kh_put(off, cache->entries, offset, &error);
+ assert(error != 0);
+ kh_value(cache->entries, k) = entry;
+ cache->memory_used += entry->raw.len;
+ }
+ git_mutex_unlock(&cache->lock);
+ /* Somebody beat us to adding it into the cache */
+ if (exists) {
+ git__free(entry);
+ return -1;
+ }
+ }
+
+ return 0;
+}
/***********************************************************
*
@@ -64,50 +190,50 @@ static int pack_entry_find_offset(
static void pack_index_free(struct git_pack_file *p)
{
+ if (p->oids) {
+ git__free(p->oids);
+ p->oids = NULL;
+ }
if (p->index_map.data) {
git_futils_mmap_free(&p->index_map);
p->index_map.data = NULL;
}
}
-static int pack_index_check(const char *path, struct git_pack_file *p)
+static int pack_index_check(const char *path, struct git_pack_file *p)
{
struct git_pack_idx_header *hdr;
uint32_t version, nr, i, *index;
-
void *idx_map;
size_t idx_size;
-
struct stat st;
-
- /* TODO: properly open the file without access time */
- git_file fd = p_open(path, O_RDONLY /*| O_NOATIME */);
-
int error;
-
+ /* TODO: properly open the file without access time using O_NOATIME */
+ git_file fd = git_futils_open_ro(path);
if (fd < 0)
- return git__throw(GIT_EOSERR, "Failed to check index. File missing or corrupted");
+ return fd;
- if (p_fstat(fd, &st) < GIT_SUCCESS) {
+ if (p_fstat(fd, &st) < 0) {
p_close(fd);
- return git__throw(GIT_EOSERR, "Failed to check index. File appears to be corrupted");
+ giterr_set(GITERR_OS, "Unable to stat pack index '%s'", path);
+ return -1;
}
- if (!git__is_sizet(st.st_size))
- return GIT_ENOMEM;
-
- idx_size = (size_t)st.st_size;
-
- if (idx_size < 4 * 256 + 20 + 20) {
+ if (!S_ISREG(st.st_mode) ||
+ !git__is_sizet(st.st_size) ||
+ (idx_size = (size_t)st.st_size) < 4 * 256 + 20 + 20)
+ {
p_close(fd);
- return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Object is corrupted");
+ giterr_set(GITERR_ODB, "Invalid pack index '%s'", path);
+ return -1;
}
error = git_futils_mmap_ro(&p->index_map, fd, 0, idx_size);
+
p_close(fd);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to check index");
+ if (error < 0)
+ return error;
hdr = idx_map = p->index_map.data;
@@ -116,7 +242,7 @@ static int pack_index_check(const char *path, struct git_pack_file *p)
if (version < 2 || version > 2) {
git_futils_mmap_free(&p->index_map);
- return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Unsupported index version");
+ return packfile_error("unsupported index version");
}
} else
@@ -126,13 +252,13 @@ static int pack_index_check(const char *path, struct git_pack_file *p)
index = idx_map;
if (version > 1)
- index += 2; /* skip index header */
+ index += 2; /* skip index header */
for (i = 0; i < 256; i++) {
uint32_t n = ntohl(index[i]);
if (n < nr) {
git_futils_mmap_free(&p->index_map);
- return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Index is non-monotonic");
+ return packfile_error("index is non-monotonic");
}
nr = n;
}
@@ -140,25 +266,25 @@ static int pack_index_check(const char *path, struct git_pack_file *p)
if (version == 1) {
/*
* Total size:
- * - 256 index entries 4 bytes each
- * - 24-byte entries * nr (20-byte sha1 + 4-byte offset)
- * - 20-byte SHA1 of the packfile
- * - 20-byte SHA1 file checksum
+ * - 256 index entries 4 bytes each
+ * - 24-byte entries * nr (20-byte sha1 + 4-byte offset)
+ * - 20-byte SHA1 of the packfile
+ * - 20-byte SHA1 file checksum
*/
if (idx_size != 4*256 + nr * 24 + 20 + 20) {
git_futils_mmap_free(&p->index_map);
- return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Object is corrupted");
+ return packfile_error("index is corrupted");
}
} else if (version == 2) {
/*
* Minimum size:
- * - 8 bytes of header
- * - 256 index entries 4 bytes each
- * - 20-byte sha1 entry * nr
- * - 4-byte crc entry * nr
- * - 4-byte offset entry * nr
- * - 20-byte SHA1 of the packfile
- * - 20-byte SHA1 file checksum
+ * - 8 bytes of header
+ * - 256 index entries 4 bytes each
+ * - 20-byte sha1 entry * nr
+ * - 4-byte crc entry * nr
+ * - 4-byte offset entry * nr
+ * - 20-byte SHA1 of the packfile
+ * - 20-byte SHA1 file checksum
* And after the 4-byte offset table might be a
* variable sized table containing 8-byte entries
* for offsets larger than 2^31.
@@ -171,46 +297,56 @@ static int pack_index_check(const char *path, struct git_pack_file *p)
if (idx_size < min_size || idx_size > max_size) {
git_futils_mmap_free(&p->index_map);
- return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Wrong index size");
- }
-
- /* Make sure that off_t is big enough to access the whole pack...
- * Is this an issue in libgit2? It shouldn't. */
- if (idx_size != min_size && (sizeof(off_t) <= 4)) {
- git_futils_mmap_free(&p->index_map);
- return git__throw(GIT_EOSERR, "Failed to check index. off_t not big enough to access the whole pack");
+ return packfile_error("wrong index size");
}
}
- p->index_version = version;
p->num_objects = nr;
- return GIT_SUCCESS;
+ p->index_version = version;
+ return 0;
}
static int pack_index_open(struct git_pack_file *p)
{
char *idx_name;
- int error;
+ int error = 0;
+ size_t name_len, base_len;
+
+ if (p->index_version > -1)
+ return 0;
+
+ name_len = strlen(p->pack_name);
+ assert(name_len > strlen(".pack")); /* checked by git_pack_file alloc */
+
+ if ((idx_name = git__malloc(name_len)) == NULL)
+ return -1;
+
+ base_len = name_len - strlen(".pack");
+ memcpy(idx_name, p->pack_name, base_len);
+ memcpy(idx_name + base_len, ".idx", sizeof(".idx"));
+
+ if ((error = git_mutex_lock(&p->lock)) < 0) {
+ git__free(idx_name);
+ return error;
+ }
- if (p->index_map.data)
- return GIT_SUCCESS;
+ if (p->index_version == -1)
+ error = pack_index_check(idx_name, p);
- idx_name = git__strdup(p->pack_name);
- strcpy(idx_name + strlen(idx_name) - STRLEN(".pack"), ".idx");
+ git__free(idx_name);
- error = pack_index_check(idx_name, p);
- free(idx_name);
+ git_mutex_unlock(&p->lock);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to open index");
+ return error;
}
static unsigned char *pack_window_open(
struct git_pack_file *p,
git_mwindow **w_cursor,
- off_t offset,
+ git_off_t offset,
unsigned int *left)
{
- if (p->mwf.fd == -1 && packfile_open(p) < GIT_SUCCESS)
+ if (p->mwf.fd == -1 && packfile_open(p) < 0)
return NULL;
/* Since packfiles end in a hash of their content and it's
@@ -224,7 +360,40 @@ static unsigned char *pack_window_open(
return git_mwindow_open(&p->mwf, w_cursor, offset, 20, left);
}
-static unsigned long packfile_unpack_header1(
+/*
+ * The per-object header is a pretty dense thing, which is
+ * - first byte: low four bits are "size",
+ * then three bits of "type",
+ * with the high bit being "size continues".
+ * - each byte afterwards: low seven bits are size continuation,
+ * with the high bit being "size continues"
+ */
+size_t git_packfile__object_header(unsigned char *hdr, size_t size, git_otype type)
+{
+ unsigned char *hdr_base;
+ unsigned char c;
+
+ assert(type >= GIT_OBJ_COMMIT && type <= GIT_OBJ_REF_DELTA);
+
+ /* TODO: add support for chunked objects; see git.git 6c0d19b1 */
+
+ c = (unsigned char)((type << 4) | (size & 15));
+ size >>= 4;
+ hdr_base = hdr;
+
+ while (size) {
+ *hdr++ = c | 0x80;
+ c = size & 0x7f;
+ size >>= 7;
+ }
+ *hdr++ = c;
+
+ return (hdr - hdr_base);
+}
+
+
+static int packfile_unpack_header1(
+ unsigned long *usedp,
size_t *sizep,
git_otype *type,
const unsigned char *buf,
@@ -239,8 +408,13 @@ static unsigned long packfile_unpack_header1(
size = c & 15;
shift = 4;
while (c & 0x80) {
- if (len <= used || bitsizeof(long) <= shift)
- return 0;
+ if (len <= used)
+ return GIT_EBUFS;
+
+ if (bitsizeof(long) <= shift) {
+ *usedp = 0;
+ return -1;
+ }
c = buf[used++];
size += (c & 0x7f) << shift;
@@ -248,7 +422,8 @@ static unsigned long packfile_unpack_header1(
}
*sizep = (size_t)size;
- return used;
+ *usedp = used;
+ return 0;
}
int git_packfile_unpack_header(
@@ -256,205 +431,488 @@ int git_packfile_unpack_header(
git_otype *type_p,
git_mwindow_file *mwf,
git_mwindow **w_curs,
- off_t *curpos)
+ git_off_t *curpos)
{
unsigned char *base;
unsigned int left;
unsigned long used;
+ int ret;
/* pack_window_open() assures us we have [base, base + 20) available
- * as a range that we can look at at. (Its actually the hash
- * size that is assured.) With our object header encoding
+ * as a range that we can look at at. (Its actually the hash
+ * size that is assured.) With our object header encoding
* the maximum deflated object size is 2^137, which is just
* insane, so we know won't exceed what we have been given.
*/
-// base = pack_window_open(p, w_curs, *curpos, &left);
+/* base = pack_window_open(p, w_curs, *curpos, &left); */
base = git_mwindow_open(mwf, w_curs, *curpos, 20, &left);
if (base == NULL)
- return GIT_ENOMEM;
-
- used = packfile_unpack_header1(size_p, type_p, base, left);
+ return GIT_EBUFS;
- if (used == 0)
- return git__throw(GIT_EOBJCORRUPTED, "Header length is zero");
+ ret = packfile_unpack_header1(&used, size_p, type_p, base, left);
+ git_mwindow_close(w_curs);
+ if (ret == GIT_EBUFS)
+ return ret;
+ else if (ret < 0)
+ return packfile_error("header length is zero");
*curpos += used;
- return GIT_SUCCESS;
+ return 0;
}
-static int packfile_unpack_delta(
- git_rawobj *obj,
+int git_packfile_resolve_header(
+ size_t *size_p,
+ git_otype *type_p,
struct git_pack_file *p,
- git_mwindow **w_curs,
- off_t *curpos,
- size_t delta_size,
- git_otype delta_type,
- off_t obj_offset)
+ git_off_t offset)
{
- off_t base_offset;
- git_rawobj base, delta;
+ git_mwindow *w_curs = NULL;
+ git_off_t curpos = offset;
+ size_t size;
+ git_otype type;
+ git_off_t base_offset;
int error;
- base_offset = get_delta_base(p, w_curs, curpos, delta_type, obj_offset);
- if (base_offset == 0)
- return git__throw(GIT_EOBJCORRUPTED, "Delta offset is zero");
- if (base_offset < 0)
- return git__rethrow(base_offset, "Failed to get delta base");
+ error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos);
+ git_mwindow_close(&w_curs);
+ if (error < 0)
+ return error;
+
+ if (type == GIT_OBJ_OFS_DELTA || type == GIT_OBJ_REF_DELTA) {
+ size_t base_size;
+ git_rawobj delta;
+ base_offset = get_delta_base(p, &w_curs, &curpos, type, offset);
+ git_mwindow_close(&w_curs);
+ error = packfile_unpack_compressed(&delta, p, &w_curs, &curpos, size, type);
+ git_mwindow_close(&w_curs);
+ if (error < 0)
+ return error;
+ error = git__delta_read_header(delta.data, delta.len, &base_size, size_p);
+ git__free(delta.data);
+ if (error < 0)
+ return error;
+ } else
+ *size_p = size;
+
+ while (type == GIT_OBJ_OFS_DELTA || type == GIT_OBJ_REF_DELTA) {
+ curpos = base_offset;
+ error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos);
+ git_mwindow_close(&w_curs);
+ if (error < 0)
+ return error;
+ if (type != GIT_OBJ_OFS_DELTA && type != GIT_OBJ_REF_DELTA)
+ break;
+ base_offset = get_delta_base(p, &w_curs, &curpos, type, base_offset);
+ git_mwindow_close(&w_curs);
+ }
+ *type_p = type;
- git_mwindow_close(w_curs);
- error = git_packfile_unpack(&base, p, &base_offset);
+ return error;
+}
- /*
- * TODO: git.git tries to load the base from other packfiles
- * or loose objects.
- *
- * We'll need to do this in order to support thin packs.
- */
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Corrupted delta");
+#define SMALL_STACK_SIZE 64
- error = packfile_unpack_compressed(&delta, p, w_curs, curpos, delta_size, delta_type);
- if (error < GIT_SUCCESS) {
- free(base.data);
- return git__rethrow(error, "Corrupted delta");
- }
+/**
+ * Generate the chain of dependencies which we need to get to the
+ * object at `off`. `chain` is used a stack, popping gives the right
+ * order to apply deltas on. If an object is found in the pack's base
+ * cache, we stop calculating there.
+ */
+static int pack_dependency_chain(git_dependency_chain *chain_out,
+ git_pack_cache_entry **cached_out, git_off_t *cached_off,
+ struct pack_chain_elem *small_stack, size_t *stack_sz,
+ struct git_pack_file *p, git_off_t obj_offset)
+{
+ git_dependency_chain chain = GIT_ARRAY_INIT;
+ git_mwindow *w_curs = NULL;
+ git_off_t curpos = obj_offset, base_offset;
+ int error = 0, use_heap = 0;
+ size_t size, elem_pos;
+ git_otype type;
+
+ elem_pos = 0;
+ while (true) {
+ struct pack_chain_elem *elem;
+ git_pack_cache_entry *cached = NULL;
+
+ /* if we have a base cached, we can stop here instead */
+ if ((cached = cache_get(&p->bases, obj_offset)) != NULL) {
+ *cached_out = cached;
+ *cached_off = obj_offset;
+ break;
+ }
+
+ /* if we run out of space on the small stack, use the array */
+ if (elem_pos == SMALL_STACK_SIZE) {
+ git_array_init_to_size(chain, elem_pos);
+ GITERR_CHECK_ARRAY(chain);
+ memcpy(chain.ptr, small_stack, elem_pos * sizeof(struct pack_chain_elem));
+ chain.size = elem_pos;
+ use_heap = 1;
+ }
- obj->type = base.type;
- error = git__delta_apply(obj,
- base.data, base.len,
- delta.data, delta.len);
+ curpos = obj_offset;
+ if (!use_heap) {
+ elem = &small_stack[elem_pos];
+ } else {
+ elem = git_array_alloc(chain);
+ if (!elem) {
+ error = -1;
+ goto on_error;
+ }
+ }
+
+ elem->base_key = obj_offset;
+
+ error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos);
+ git_mwindow_close(&w_curs);
+
+ if (error < 0)
+ goto on_error;
+
+ elem->offset = curpos;
+ elem->size = size;
+ elem->type = type;
+ elem->base_key = obj_offset;
+
+ if (type != GIT_OBJ_OFS_DELTA && type != GIT_OBJ_REF_DELTA)
+ break;
+
+ base_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset);
+ git_mwindow_close(&w_curs);
+
+ if (base_offset == 0) {
+ error = packfile_error("delta offset is zero");
+ goto on_error;
+ }
+ if (base_offset < 0) { /* must actually be an error code */
+ error = (int)base_offset;
+ goto on_error;
+ }
- free(base.data);
- free(delta.data);
+ /* we need to pass the pos *after* the delta-base bit */
+ elem->offset = curpos;
- /* TODO: we might want to cache this shit. eventually */
- //add_delta_base_cache(p, base_offset, base, base_size, *type);
- return error; /* error set by git__delta_apply */
+ /* go through the loop again, but with the new object */
+ obj_offset = base_offset;
+ elem_pos++;
+ }
+
+
+ *stack_sz = elem_pos + 1;
+ *chain_out = chain;
+ return error;
+
+on_error:
+ git_array_clear(chain);
+ return error;
}
int git_packfile_unpack(
- git_rawobj *obj,
- struct git_pack_file *p,
- off_t *obj_offset)
+ git_rawobj *obj,
+ struct git_pack_file *p,
+ git_off_t *obj_offset)
{
git_mwindow *w_curs = NULL;
- off_t curpos = *obj_offset;
- int error;
-
- size_t size = 0;
- git_otype type;
+ git_off_t curpos = *obj_offset;
+ int error, free_base = 0;
+ git_dependency_chain chain = GIT_ARRAY_INIT;
+ struct pack_chain_elem *elem = NULL, *stack;
+ git_pack_cache_entry *cached = NULL;
+ struct pack_chain_elem small_stack[SMALL_STACK_SIZE];
+ size_t stack_size, elem_pos;
+ git_otype base_type;
/*
* TODO: optionally check the CRC on the packfile
*/
+ error = pack_dependency_chain(&chain, &cached, obj_offset, small_stack, &stack_size, p, *obj_offset);
+ if (error < 0)
+ return error;
+
obj->data = NULL;
obj->len = 0;
obj->type = GIT_OBJ_BAD;
- error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to unpack packfile");
+ /* let's point to the right stack */
+ stack = chain.ptr ? chain.ptr : small_stack;
- switch (type) {
- case GIT_OBJ_OFS_DELTA:
- case GIT_OBJ_REF_DELTA:
- error = packfile_unpack_delta(
- obj, p, &w_curs, &curpos,
- size, type, *obj_offset);
- break;
+ elem_pos = stack_size;
+ if (cached) {
+ memcpy(obj, &cached->raw, sizeof(git_rawobj));
+ base_type = obj->type;
+ elem_pos--; /* stack_size includes the base, which isn't actually there */
+ } else {
+ elem = &stack[--elem_pos];
+ base_type = elem->type;
+ }
+
+ if (error < 0)
+ goto cleanup;
+ switch (base_type) {
case GIT_OBJ_COMMIT:
case GIT_OBJ_TREE:
case GIT_OBJ_BLOB:
case GIT_OBJ_TAG:
- error = packfile_unpack_compressed(
- obj, p, &w_curs, &curpos,
- size, type);
+ if (!cached) {
+ curpos = elem->offset;
+ error = packfile_unpack_compressed(obj, p, &w_curs, &curpos, elem->size, elem->type);
+ git_mwindow_close(&w_curs);
+ base_type = elem->type;
+ }
+ if (error < 0)
+ goto cleanup;
break;
-
+ case GIT_OBJ_OFS_DELTA:
+ case GIT_OBJ_REF_DELTA:
+ error = packfile_error("dependency chain ends in a delta");
+ goto cleanup;
default:
- error = GIT_EOBJCORRUPTED;
- break;
+ error = packfile_error("invalid packfile type in header");
+ goto cleanup;
}
- git_mwindow_close(&w_curs);
+ /*
+ * Finding the object we want a cached base element is
+ * problematic, as we need to make sure we don't accidentally
+ * give the caller the cached object, which it would then feel
+ * free to free, so we need to copy the data.
+ */
+ if (cached && stack_size == 1) {
+ void *data = obj->data;
+ obj->data = git__malloc(obj->len + 1);
+ GITERR_CHECK_ALLOC(obj->data);
+ memcpy(obj->data, data, obj->len + 1);
+ git_atomic_dec(&cached->refcount);
+ goto cleanup;
+ }
+
+ /* we now apply each consecutive delta until we run out */
+ while (elem_pos > 0 && !error) {
+ git_rawobj base, delta;
+
+ /*
+ * We can now try to add the base to the cache, as
+ * long as it's not already the cached one.
+ */
+ if (!cached)
+ free_base = !!cache_add(&p->bases, obj, elem->base_key);
+
+ elem = &stack[elem_pos - 1];
+ curpos = elem->offset;
+ error = packfile_unpack_compressed(&delta, p, &w_curs, &curpos, elem->size, elem->type);
+ git_mwindow_close(&w_curs);
+
+ if (error < 0)
+ break;
+
+ /* the current object becomes the new base, on which we apply the delta */
+ base = *obj;
+ obj->data = NULL;
+ obj->len = 0;
+ obj->type = GIT_OBJ_BAD;
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to unpack object");
+ error = git__delta_apply(obj, base.data, base.len, delta.data, delta.len);
+ obj->type = base_type;
+ /*
+ * We usually don't want to free the base at this
+ * point, as we put it into the cache in the previous
+ * iteration. free_base lets us know that we got the
+ * base object directly from the packfile, so we can free it.
+ */
+ git__free(delta.data);
+ if (free_base) {
+ free_base = 0;
+ git__free(base.data);
+ }
+
+ if (cached) {
+ git_atomic_dec(&cached->refcount);
+ cached = NULL;
+ }
+
+ if (error < 0)
+ break;
+
+ elem_pos--;
+ }
+
+cleanup:
+ if (error < 0)
+ git__free(obj->data);
- *obj_offset = curpos;
- return GIT_SUCCESS;
+ if (elem)
+ *obj_offset = elem->offset;
+
+ git_array_clear(chain);
+ return error;
+}
+
+static void *use_git_alloc(void *opaq, unsigned int count, unsigned int size)
+{
+ GIT_UNUSED(opaq);
+ return git__calloc(count, size);
+}
+
+static void use_git_free(void *opaq, void *ptr)
+{
+ GIT_UNUSED(opaq);
+ git__free(ptr);
+}
+
+int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, git_off_t curpos)
+{
+ int st;
+
+ memset(obj, 0, sizeof(git_packfile_stream));
+ obj->curpos = curpos;
+ obj->p = p;
+ obj->zstream.zalloc = use_git_alloc;
+ obj->zstream.zfree = use_git_free;
+ obj->zstream.next_in = Z_NULL;
+ obj->zstream.next_out = Z_NULL;
+ st = inflateInit(&obj->zstream);
+ if (st != Z_OK) {
+ git__free(obj);
+ giterr_set(GITERR_ZLIB, "failed to init packfile stream");
+ return -1;
+ }
+
+ return 0;
+}
+
+ssize_t git_packfile_stream_read(git_packfile_stream *obj, void *buffer, size_t len)
+{
+ unsigned char *in;
+ size_t written;
+ int st;
+
+ if (obj->done)
+ return 0;
+
+ in = pack_window_open(obj->p, &obj->mw, obj->curpos, &obj->zstream.avail_in);
+ if (in == NULL)
+ return GIT_EBUFS;
+
+ obj->zstream.next_out = buffer;
+ obj->zstream.avail_out = (unsigned int)len;
+ obj->zstream.next_in = in;
+
+ st = inflate(&obj->zstream, Z_SYNC_FLUSH);
+ git_mwindow_close(&obj->mw);
+
+ obj->curpos += obj->zstream.next_in - in;
+ written = len - obj->zstream.avail_out;
+
+ if (st != Z_OK && st != Z_STREAM_END) {
+ giterr_set(GITERR_ZLIB, "error reading from the zlib stream");
+ return -1;
+ }
+
+ if (st == Z_STREAM_END)
+ obj->done = 1;
+
+
+ /* If we didn't write anything out but we're not done, we need more data */
+ if (!written && st != Z_STREAM_END)
+ return GIT_EBUFS;
+
+ return written;
+
+}
+
+void git_packfile_stream_free(git_packfile_stream *obj)
+{
+ inflateEnd(&obj->zstream);
}
int packfile_unpack_compressed(
- git_rawobj *obj,
- struct git_pack_file *p,
- git_mwindow **w_curs,
- off_t *curpos,
- size_t size,
- git_otype type)
+ git_rawobj *obj,
+ struct git_pack_file *p,
+ git_mwindow **w_curs,
+ git_off_t *curpos,
+ size_t size,
+ git_otype type)
{
int st;
z_stream stream;
unsigned char *buffer, *in;
- buffer = git__malloc(size + 1);
- memset(buffer, 0x0, size + 1);
+ buffer = git__calloc(1, size + 1);
+ GITERR_CHECK_ALLOC(buffer);
memset(&stream, 0, sizeof(stream));
stream.next_out = buffer;
- stream.avail_out = size + 1;
+ stream.avail_out = (uInt)size + 1;
+ stream.zalloc = use_git_alloc;
+ stream.zfree = use_git_free;
st = inflateInit(&stream);
if (st != Z_OK) {
- free(buffer);
- return git__throw(GIT_EZLIB, "Error in zlib");
+ git__free(buffer);
+ giterr_set(GITERR_ZLIB, "failed to init zlib stream on unpack");
+
+ return -1;
}
do {
in = pack_window_open(p, w_curs, *curpos, &stream.avail_in);
stream.next_in = in;
st = inflate(&stream, Z_FINISH);
+ git_mwindow_close(w_curs);
if (!stream.avail_out)
break; /* the payload is larger than it should be */
+ if (st == Z_BUF_ERROR && in == NULL) {
+ inflateEnd(&stream);
+ git__free(buffer);
+ return GIT_EBUFS;
+ }
+
*curpos += stream.next_in - in;
} while (st == Z_OK || st == Z_BUF_ERROR);
inflateEnd(&stream);
if ((st != Z_STREAM_END) || stream.total_out != size) {
- free(buffer);
- return git__throw(GIT_EZLIB, "Error in zlib");
+ git__free(buffer);
+ giterr_set(GITERR_ZLIB, "error inflating zlib stream");
+ return -1;
}
obj->type = type;
obj->len = size;
obj->data = buffer;
- return GIT_SUCCESS;
+ return 0;
}
/*
* curpos is where the data starts, delta_obj_offset is the where the
* header starts
*/
-off_t get_delta_base(
- struct git_pack_file *p,
- git_mwindow **w_curs,
- off_t *curpos,
- git_otype type,
- off_t delta_obj_offset)
+git_off_t get_delta_base(
+ struct git_pack_file *p,
+ git_mwindow **w_curs,
+ git_off_t *curpos,
+ git_otype type,
+ git_off_t delta_obj_offset)
{
- unsigned char *base_info = pack_window_open(p, w_curs, *curpos, NULL);
- off_t base_offset;
+ unsigned int left = 0;
+ unsigned char *base_info;
+ git_off_t base_offset;
git_oid unused;
+ base_info = pack_window_open(p, w_curs, *curpos, &left);
+ /* Assumption: the only reason this would fail is because the file is too small */
+ if (base_info == NULL)
+ return GIT_EBUFS;
/* pack_window_open() assured us we have [base_info, base_info + 20)
* as a range that we can look at without walking off the
- * end of the mapped window. Its actually the hash size
- * that is assured. An OFS_DELTA longer than the hash size
+ * end of the mapped window. Its actually the hash size
+ * that is assured. An OFS_DELTA longer than the hash size
* is stupid, as then a REF_DELTA would be smaller to store.
*/
if (type == GIT_OBJ_OFS_DELTA) {
@@ -462,20 +920,34 @@ off_t get_delta_base(
unsigned char c = base_info[used++];
base_offset = c & 127;
while (c & 128) {
+ if (left <= used)
+ return GIT_EBUFS;
base_offset += 1;
if (!base_offset || MSB(base_offset, 7))
- return 0; /* overflow */
+ return 0; /* overflow */
c = base_info[used++];
base_offset = (base_offset << 7) + (c & 127);
}
base_offset = delta_obj_offset - base_offset;
if (base_offset <= 0 || base_offset >= delta_obj_offset)
- return 0; /* out of bound */
+ return 0; /* out of bound */
*curpos += used;
} else if (type == GIT_OBJ_REF_DELTA) {
+ /* If we have the cooperative cache, search in it first */
+ if (p->has_cache) {
+ khiter_t k;
+ git_oid oid;
+
+ git_oid_fromraw(&oid, base_info);
+ k = kh_get(oid, p->idx_cache, &oid);
+ if (k != kh_end(p->idx_cache)) {
+ *curpos += 20;
+ return ((struct git_pack_entry *)kh_value(p->idx_cache, k))->offset;
+ }
+ }
/* The base entry _must_ be in the same pack */
- if (pack_entry_find_offset(&base_offset, &unused, p, (git_oid *)base_info, GIT_OID_HEXSZ) < GIT_SUCCESS)
- return git__throw(GIT_EPACKCORRUPTED, "Base entry delta is not in the same pack");
+ if (pack_entry_find_offset(&base_offset, &unused, p, (git_oid *)base_info, GIT_OID_HEXSZ) < 0)
+ return packfile_error("base entry delta is not in the same pack");
*curpos += 20;
} else
return 0;
@@ -489,29 +961,25 @@ off_t get_delta_base(
*
***********************************************************/
-static struct git_pack_file *packfile_alloc(int extra)
+void git_packfile_free(struct git_pack_file *p)
{
- struct git_pack_file *p = git__malloc(sizeof(*p) + extra);
- memset(p, 0, sizeof(*p));
- p->mwf.fd = -1;
- return p;
-}
-
+ if (!p)
+ return;
-void packfile_free(struct git_pack_file *p)
-{
- assert(p);
+ cache_free(&p->bases);
- /* clear_delta_base_cache(); */
git_mwindow_free_all(&p->mwf);
- if (p->mwf.fd != -1)
+ if (p->mwf.fd >= 0)
p_close(p->mwf.fd);
pack_index_free(p);
- free(p->bad_object_sha1);
- free(p);
+ git__free(p->bad_object_sha1);
+
+ git_mutex_free(&p->lock);
+ git_mutex_free(&p->bases.lock);
+ git__free(p);
}
static int packfile_open(struct git_pack_file *p)
@@ -521,24 +989,32 @@ static int packfile_open(struct git_pack_file *p)
git_oid sha1;
unsigned char *idx_sha1;
- if (!p->index_map.data && pack_index_open(p) < GIT_SUCCESS)
- return git__throw(GIT_ENOTFOUND, "Failed to open packfile. File not found");
+ if (p->index_version == -1 && pack_index_open(p) < 0)
+ return git_odb__error_notfound("failed to open packfile", NULL);
- /* TODO: open with noatime */
- p->mwf.fd = p_open(p->pack_name, O_RDONLY);
- if (p->mwf.fd < 0 || p_fstat(p->mwf.fd, &st) < GIT_SUCCESS)
- return git__throw(GIT_EOSERR, "Failed to open packfile. File appears to be corrupted");
+ /* if mwf opened by another thread, return now */
+ if (git_mutex_lock(&p->lock) < 0)
+ return packfile_error("failed to get lock for open");
- if (git_mwindow_file_register(&p->mwf) < GIT_SUCCESS) {
- p_close(p->mwf.fd);
- return git__throw(GIT_ERROR, "Failed to register packfile windows");
+ if (p->mwf.fd >= 0) {
+ git_mutex_unlock(&p->lock);
+ return 0;
}
+ /* TODO: open with noatime */
+ p->mwf.fd = git_futils_open_ro(p->pack_name);
+ if (p->mwf.fd < 0)
+ goto cleanup;
+
+ if (p_fstat(p->mwf.fd, &st) < 0 ||
+ git_mwindow_file_register(&p->mwf) < 0)
+ goto cleanup;
+
/* If we created the struct before we had the pack we lack size. */
if (!p->mwf.size) {
if (!S_ISREG(st.st_mode))
goto cleanup;
- p->mwf.size = (off_t)st.st_size;
+ p->mwf.size = (git_off_t)st.st_size;
} else if (p->mwf.size != st.st_size)
goto cleanup;
@@ -548,92 +1024,104 @@ static int packfile_open(struct git_pack_file *p)
*/
fd_flag = fcntl(p->mwf.fd, F_GETFD, 0);
if (fd_flag < 0)
- return error("cannot determine file descriptor flags");
+ goto cleanup;
fd_flag |= FD_CLOEXEC;
if (fcntl(p->pack_fd, F_SETFD, fd_flag) == -1)
- return GIT_EOSERR;
+ goto cleanup;
#endif
/* Verify we recognize this pack file format. */
- if (p_read(p->mwf.fd, &hdr, sizeof(hdr)) < GIT_SUCCESS)
- goto cleanup;
-
- if (hdr.hdr_signature != htonl(PACK_SIGNATURE))
- goto cleanup;
-
- if (!pack_version_ok(hdr.hdr_version))
+ if (p_read(p->mwf.fd, &hdr, sizeof(hdr)) < 0 ||
+ hdr.hdr_signature != htonl(PACK_SIGNATURE) ||
+ !pack_version_ok(hdr.hdr_version))
goto cleanup;
/* Verify the pack matches its index. */
- if (p->num_objects != ntohl(hdr.hdr_entries))
- goto cleanup;
-
- if (p_lseek(p->mwf.fd, p->mwf.size - GIT_OID_RAWSZ, SEEK_SET) == -1)
- goto cleanup;
-
- if (p_read(p->mwf.fd, sha1.id, GIT_OID_RAWSZ) < GIT_SUCCESS)
+ if (p->num_objects != ntohl(hdr.hdr_entries) ||
+ p_lseek(p->mwf.fd, p->mwf.size - GIT_OID_RAWSZ, SEEK_SET) == -1 ||
+ p_read(p->mwf.fd, sha1.id, GIT_OID_RAWSZ) < 0)
goto cleanup;
idx_sha1 = ((unsigned char *)p->index_map.data) + p->index_map.len - 40;
- if (git_oid_cmp(&sha1, (git_oid *)idx_sha1) != 0)
+ if (git_oid__cmp(&sha1, (git_oid *)idx_sha1) != 0)
goto cleanup;
- return GIT_SUCCESS;
+ git_mutex_unlock(&p->lock);
+ return 0;
cleanup:
- p_close(p->mwf.fd);
+ giterr_set(GITERR_OS, "Invalid packfile '%s'", p->pack_name);
+
+ if (p->mwf.fd >= 0)
+ p_close(p->mwf.fd);
p->mwf.fd = -1;
- return git__throw(GIT_EPACKCORRUPTED, "Failed to open packfile. Pack is corrupted");
+
+ git_mutex_unlock(&p->lock);
+
+ return -1;
}
-int git_packfile_check(struct git_pack_file **pack_out, const char *path)
+int git_packfile_alloc(struct git_pack_file **pack_out, const char *path)
{
struct stat st;
struct git_pack_file *p;
- size_t path_len;
+ size_t path_len = path ? strlen(path) : 0;
*pack_out = NULL;
- path_len = strlen(path);
- p = packfile_alloc(path_len + 2);
+
+ if (path_len < strlen(".idx"))
+ return git_odb__error_notfound("invalid packfile path", NULL);
+
+ p = git__calloc(1, sizeof(*p) + path_len + 2);
+ GITERR_CHECK_ALLOC(p);
+
+ memcpy(p->pack_name, path, path_len + 1);
/*
* Make sure a corresponding .pack file exists and that
* the index looks sane.
*/
- path_len -= STRLEN(".idx");
- if (path_len < 1) {
- free(p);
- return git__throw(GIT_ENOTFOUND, "Failed to check packfile. Wrong path name");
- }
+ if (git__suffixcmp(path, ".idx") == 0) {
+ size_t root_len = path_len - strlen(".idx");
- memcpy(p->pack_name, path, path_len);
+ memcpy(p->pack_name + root_len, ".keep", sizeof(".keep"));
+ if (git_path_exists(p->pack_name) == true)
+ p->pack_keep = 1;
- strcpy(p->pack_name + path_len, ".keep");
- if (git_futils_exists(p->pack_name) == GIT_SUCCESS)
- p->pack_keep = 1;
+ memcpy(p->pack_name + root_len, ".pack", sizeof(".pack"));
+ path_len = path_len - strlen(".idx") + strlen(".pack");
+ }
- strcpy(p->pack_name + path_len, ".pack");
- if (p_stat(p->pack_name, &st) < GIT_SUCCESS || !S_ISREG(st.st_mode)) {
- free(p);
- return git__throw(GIT_ENOTFOUND, "Failed to check packfile. File not found");
+ if (p_stat(p->pack_name, &st) < 0 || !S_ISREG(st.st_mode)) {
+ git__free(p);
+ return git_odb__error_notfound("packfile not found", NULL);
}
/* ok, it looks sane as far as we can check without
* actually mapping the pack file.
*/
- p->mwf.size = (off_t)st.st_size;
+ p->mwf.fd = -1;
+ p->mwf.size = st.st_size;
p->pack_local = 1;
p->mtime = (git_time_t)st.st_mtime;
+ p->index_version = -1;
+
+ if (git_mutex_init(&p->lock)) {
+ giterr_set(GITERR_OS, "Failed to initialize packfile mutex");
+ git__free(p);
+ return -1;
+ }
- /* see if we can parse the sha1 oid in the packfile name */
- if (path_len < 40 ||
- git_oid_fromstr(&p->sha1, path + path_len - GIT_OID_HEXSZ) < GIT_SUCCESS)
- memset(&p->sha1, 0x0, GIT_OID_RAWSZ);
+ if (cache_init(&p->bases) < 0) {
+ git__free(p);
+ return -1;
+ }
*pack_out = p;
- return GIT_SUCCESS;
+
+ return 0;
}
/***********************************************************
@@ -642,7 +1130,7 @@ int git_packfile_check(struct git_pack_file **pack_out, const char *path)
*
***********************************************************/
-static off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_t n)
+static git_off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_t n)
{
const unsigned char *index = p->index_map.data;
index += 4 * 256;
@@ -656,16 +1144,79 @@ static off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_t n)
return off;
index += p->num_objects * 4 + (off & 0x7fffffff) * 8;
return (((uint64_t)ntohl(*((uint32_t *)(index + 0)))) << 32) |
- ntohl(*((uint32_t *)(index + 4)));
+ ntohl(*((uint32_t *)(index + 4)));
}
}
+static int git__memcmp4(const void *a, const void *b) {
+ return memcmp(a, b, 4);
+}
+
+int git_pack_foreach_entry(
+ struct git_pack_file *p,
+ git_odb_foreach_cb cb,
+ void *data)
+{
+ const unsigned char *index = p->index_map.data, *current;
+ uint32_t i;
+ int error = 0;
+
+ if (index == NULL) {
+ if ((error = pack_index_open(p)) < 0)
+ return error;
+
+ assert(p->index_map.data);
+
+ index = p->index_map.data;
+ }
+
+ if (p->index_version > 1) {
+ index += 8;
+ }
+
+ index += 4 * 256;
+
+ if (p->oids == NULL) {
+ git_vector offsets, oids;
+
+ if ((error = git_vector_init(&oids, p->num_objects, NULL)))
+ return error;
+
+ if ((error = git_vector_init(&offsets, p->num_objects, git__memcmp4)))
+ return error;
+
+ if (p->index_version > 1) {
+ const unsigned char *off = index + 24 * p->num_objects;
+ for (i = 0; i < p->num_objects; i++)
+ git_vector_insert(&offsets, (void*)&off[4 * i]);
+ git_vector_sort(&offsets);
+ git_vector_foreach(&offsets, i, current)
+ git_vector_insert(&oids, (void*)&index[5 * (current - off)]);
+ } else {
+ for (i = 0; i < p->num_objects; i++)
+ git_vector_insert(&offsets, (void*)&index[24 * i]);
+ git_vector_sort(&offsets);
+ git_vector_foreach(&offsets, i, current)
+ git_vector_insert(&oids, (void*)&current[4]);
+ }
+
+ git_vector_free(&offsets);
+ p->oids = (git_oid **)git_vector_detach(NULL, NULL, &oids);
+ }
+
+ for (i = 0; i < p->num_objects; i++)
+ if ((error = cb(p->oids[i], data)) != 0)
+ return giterr_set_after_callback(error);
+
+ return error;
+}
+
static int pack_entry_find_offset(
- off_t *offset_out,
- git_oid *found_oid,
- struct git_pack_file *p,
- const git_oid *short_oid,
- unsigned int len)
+ git_off_t *offset_out,
+ git_oid *found_oid,
+ struct git_pack_file *p,
+ const git_oid *short_oid,
+ size_t len)
{
const uint32_t *level1_ofs = p->index_map.data;
const unsigned char *index = p->index_map.data;
@@ -675,12 +1226,11 @@ static int pack_entry_find_offset(
*offset_out = 0;
- if (index == NULL) {
+ if (p->index_version == -1) {
int error;
- if ((error = pack_index_open(p)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to find offset for pack entry");
-
+ if ((error = pack_index_open(p)) < 0)
+ return error;
assert(p->index_map.data);
index = p->index_map.data;
@@ -708,8 +1258,11 @@ static int pack_entry_find_offset(
short_oid->id[0], short_oid->id[1], short_oid->id[2], lo, hi, p->num_objects);
#endif
- /* Use git.git lookup code */
- pos = sha1_entry_pos(index, stride, 0, lo, hi, p->num_objects, short_oid->id);
+#ifdef GIT_USE_LOOKUP
+ pos = sha1_entry_pos(index, stride, 0, lo, hi, p->num_objects, short_oid->id);
+#else
+ pos = sha1_position(index, stride, lo, hi, short_oid->id);
+#endif
if (pos >= 0) {
/* An object matching exactly the oid was found */
@@ -722,13 +1275,12 @@ static int pack_entry_find_offset(
if (pos < (int)p->num_objects) {
current = index + pos * stride;
- if (!git_oid_ncmp(short_oid, (const git_oid *)current, len)) {
+ if (!git_oid_ncmp(short_oid, (const git_oid *)current, len))
found = 1;
- }
}
}
- if (found && pos + 1 < (int)p->num_objects) {
+ if (found && len != GIT_OID_HEXSZ && pos + 1 < (int)p->num_objects) {
/* Check for ambiguousity */
const unsigned char *next = current + stride;
@@ -737,31 +1289,33 @@ static int pack_entry_find_offset(
}
}
- if (!found) {
- return git__throw(GIT_ENOTFOUND, "Failed to find offset for pack entry. Entry not found");
- } else if (found > 1) {
- return git__throw(GIT_EAMBIGUOUSOIDPREFIX, "Failed to find offset for pack entry. Ambiguous sha1 prefix within pack");
- } else {
- *offset_out = nth_packed_object_offset(p, pos);
- git_oid_fromraw(found_oid, current);
+ if (!found)
+ return git_odb__error_notfound("failed to find offset for pack entry", short_oid);
+ if (found > 1)
+ return git_odb__error_ambiguous("found multiple offsets for pack entry");
+
+ *offset_out = nth_packed_object_offset(p, pos);
+ git_oid_fromraw(found_oid, current);
#ifdef INDEX_DEBUG_LOOKUP
+ {
unsigned char hex_sha1[GIT_OID_HEXSZ + 1];
git_oid_fmt(hex_sha1, found_oid);
hex_sha1[GIT_OID_HEXSZ] = '\0';
printf("found lo=%d %s\n", lo, hex_sha1);
-#endif
- return GIT_SUCCESS;
}
+#endif
+
+ return 0;
}
int git_pack_entry_find(
struct git_pack_entry *e,
struct git_pack_file *p,
const git_oid *short_oid,
- unsigned int len)
+ size_t len)
{
- off_t offset;
+ git_off_t offset;
git_oid found_oid;
int error;
@@ -770,23 +1324,23 @@ int git_pack_entry_find(
if (len == GIT_OID_HEXSZ && p->num_bad_objects) {
unsigned i;
for (i = 0; i < p->num_bad_objects; i++)
- if (git_oid_cmp(short_oid, &p->bad_object_sha1[i]) == 0)
- return git__throw(GIT_ERROR, "Failed to find pack entry. Bad object found");
+ if (git_oid__cmp(short_oid, &p->bad_object_sha1[i]) == 0)
+ return packfile_error("bad object found in packfile");
}
error = pack_entry_find_offset(&offset, &found_oid, p, short_oid, len);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to find pack entry. Couldn't find offset");
+ if (error < 0)
+ return error;
/* we found a unique entry in the index;
* make sure the packfile backing the index
* still exists on disk */
- if (p->mwf.fd == -1 && packfile_open(p) < GIT_SUCCESS)
- return git__throw(GIT_EOSERR, "Failed to find pack entry. Packfile doesn't exist on disk");
+ if (p->mwf.fd == -1 && (error = packfile_open(p)) < 0)
+ return error;
e->offset = offset;
e->p = p;
git_oid_cpy(&e->sha1, &found_oid);
- return GIT_SUCCESS;
+ return 0;
}
diff --git a/src/pack.h b/src/pack.h
index a7112a6aa..610e70c18 100644
--- a/src/pack.h
+++ b/src/pack.h
@@ -1,37 +1,25 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_pack_h__
#define INCLUDE_pack_h__
+#include <zlib.h>
+
#include "git2/oid.h"
#include "common.h"
#include "map.h"
#include "mwindow.h"
#include "odb.h"
+#include "oidmap.h"
+#include "array.h"
+
+#define GIT_PACK_FILE_MODE 0444
#define PACK_SIGNATURE 0x5041434b /* "PACK" */
#define PACK_VERSION 2
@@ -56,7 +44,7 @@ struct git_pack_header {
* Very old git binaries will also compare the first 4 bytes to the
* next 4 bytes in the index and abort with a "non-monotonic index"
* error if the second 4 byte word is smaller than the first 4
- * byte word. This would be true in the proposed future index
+ * byte word. This would be true in the proposed future index
* format as idx_signature would be greater than idx_version.
*/
@@ -67,9 +55,41 @@ struct git_pack_idx_header {
uint32_t idx_version;
};
+typedef struct git_pack_cache_entry {
+ size_t last_usage; /* enough? */
+ git_atomic refcount;
+ git_rawobj raw;
+} git_pack_cache_entry;
+
+struct pack_chain_elem {
+ git_off_t base_key;
+ git_off_t offset;
+ size_t size;
+ git_otype type;
+};
+
+typedef git_array_t(struct pack_chain_elem) git_dependency_chain;
+
+#include "offmap.h"
+
+GIT__USE_OFFMAP;
+GIT__USE_OIDMAP;
+
+#define GIT_PACK_CACHE_MEMORY_LIMIT 16 * 1024 * 1024
+#define GIT_PACK_CACHE_SIZE_LIMIT 1024 * 1024 /* don't bother caching anything over 1MB */
+
+typedef struct {
+ size_t memory_used;
+ size_t memory_limit;
+ size_t use_ctr;
+ git_mutex lock;
+ git_offmap *entries;
+} git_pack_cache;
+
struct git_pack_file {
git_mwindow_file mwf;
git_map index_map;
+ git_mutex lock; /* protect updates to mwf and index_map */
uint32_t num_objects;
uint32_t num_bad_objects;
@@ -77,38 +97,73 @@ struct git_pack_file {
int index_version;
git_time_t mtime;
- unsigned pack_local:1, pack_keep:1;
- git_oid sha1;
+ unsigned pack_local:1, pack_keep:1, has_cache:1;
+ git_oidmap *idx_cache;
+ git_oid **oids;
+
+ git_pack_cache bases; /* delta base cache */
/* something like ".git/objects/pack/xxxxx.pack" */
char pack_name[GIT_FLEX_ARRAY]; /* more */
};
struct git_pack_entry {
- off_t offset;
+ git_off_t offset;
git_oid sha1;
struct git_pack_file *p;
};
+typedef struct git_packfile_stream {
+ git_off_t curpos;
+ int done;
+ z_stream zstream;
+ struct git_pack_file *p;
+ git_mwindow *mw;
+} git_packfile_stream;
+
+size_t git_packfile__object_header(unsigned char *hdr, size_t size, git_otype type);
+
int git_packfile_unpack_header(
size_t *size_p,
git_otype *type_p,
git_mwindow_file *mwf,
git_mwindow **w_curs,
- off_t *curpos);
+ git_off_t *curpos);
-int git_packfile_unpack(git_rawobj *obj, struct git_pack_file *p, off_t *obj_offset);
+int git_packfile_resolve_header(
+ size_t *size_p,
+ git_otype *type_p,
+ struct git_pack_file *p,
+ git_off_t offset);
+
+int git_packfile_unpack(git_rawobj *obj, struct git_pack_file *p, git_off_t *obj_offset);
+int packfile_unpack_compressed(
+ git_rawobj *obj,
+ struct git_pack_file *p,
+ git_mwindow **w_curs,
+ git_off_t *curpos,
+ size_t size,
+ git_otype type);
+
+int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, git_off_t curpos);
+ssize_t git_packfile_stream_read(git_packfile_stream *obj, void *buffer, size_t len);
+void git_packfile_stream_free(git_packfile_stream *obj);
-off_t get_delta_base(struct git_pack_file *p, git_mwindow **w_curs,
- off_t *curpos, git_otype type,
- off_t delta_obj_offset);
+git_off_t get_delta_base(struct git_pack_file *p, git_mwindow **w_curs,
+ git_off_t *curpos, git_otype type,
+ git_off_t delta_obj_offset);
+
+void git_packfile_free(struct git_pack_file *p);
+int git_packfile_alloc(struct git_pack_file **pack_out, const char *path);
-void packfile_free(struct git_pack_file *p);
-int git_packfile_check(struct git_pack_file **pack_out, const char *path);
int git_pack_entry_find(
struct git_pack_entry *e,
struct git_pack_file *p,
const git_oid *short_oid,
- unsigned int len);
+ size_t len);
+int git_pack_foreach_entry(
+ struct git_pack_file *p,
+ git_odb_foreach_cb cb,
+ void *data);
#endif
diff --git a/src/path.c b/src/path.c
index 1f7a16679..5beab97ed 100644
--- a/src/path.c
+++ b/src/path.c
@@ -1,24 +1,80 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
#include "common.h"
#include "path.h"
#include "posix.h"
-
-#include <stdarg.h>
+#ifdef GIT_WIN32
+#include "win32/posix.h"
+#include "win32/w32_util.h"
+#else
+#include <dirent.h>
+#endif
#include <stdio.h>
#include <ctype.h>
+#define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':')
+
+#ifdef GIT_WIN32
+static bool looks_like_network_computer_name(const char *path, int pos)
+{
+ if (pos < 3)
+ return false;
+
+ if (path[0] != '/' || path[1] != '/')
+ return false;
+
+ while (pos-- > 2) {
+ if (path[pos] == '/')
+ return false;
+ }
+
+ return true;
+}
+#endif
+
/*
* Based on the Android implementation, BSD licensed.
- * Check http://android.git.kernel.org/
+ * http://android.git.kernel.org/
+ *
+ * Copyright (C) 2008 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
*/
-int git_path_basename_r(char *buffer, size_t bufflen, const char *path)
+int git_path_basename_r(git_buf *buffer, const char *path)
{
const char *endp, *startp;
int len, result;
/* Empty or NULL string gets treated as "." */
if (path == NULL || *path == '\0') {
- startp = ".";
- len = 1;
+ startp = ".";
+ len = 1;
goto Exit;
}
@@ -30,7 +86,7 @@ int git_path_basename_r(char *buffer, size_t bufflen, const char *path)
/* All slashes becomes "/" */
if (endp == path && *endp == '/') {
startp = "/";
- len = 1;
+ len = 1;
goto Exit;
}
@@ -39,22 +95,15 @@ int git_path_basename_r(char *buffer, size_t bufflen, const char *path)
while (startp > path && *(startp - 1) != '/')
startp--;
- len = endp - startp +1;
+ /* Cast is safe because max path < max int */
+ len = (int)(endp - startp + 1);
Exit:
result = len;
- if (buffer == NULL) {
- return result;
- }
- if (len > (int)bufflen-1) {
- len = (int)bufflen-1;
- result = GIT_ENOMEM;
- }
- if (len >= 0) {
- memmove(buffer, startp, len);
- buffer[len] = 0;
- }
+ if (buffer != NULL && git_buf_set(buffer, startp, len) < 0)
+ return -1;
+
return result;
}
@@ -62,102 +111,113 @@ Exit:
* Based on the Android implementation, BSD licensed.
* Check http://android.git.kernel.org/
*/
-int git_path_dirname_r(char *buffer, size_t bufflen, const char *path)
+int git_path_dirname_r(git_buf *buffer, const char *path)
{
- const char *endp;
- int result, len;
+ const char *endp;
+ int result, len;
+
+ /* Empty or NULL string gets treated as "." */
+ if (path == NULL || *path == '\0') {
+ path = ".";
+ len = 1;
+ goto Exit;
+ }
+
+ /* Strip trailing slashes */
+ endp = path + strlen(path) - 1;
+ while (endp > path && *endp == '/')
+ endp--;
+
+ /* Find the start of the dir */
+ while (endp > path && *endp != '/')
+ endp--;
+
+ /* Either the dir is "/" or there are no slashes */
+ if (endp == path) {
+ path = (*endp == '/') ? "/" : ".";
+ len = 1;
+ goto Exit;
+ }
+
+ do {
+ endp--;
+ } while (endp > path && *endp == '/');
- /* Empty or NULL string gets treated as "." */
- if (path == NULL || *path == '\0') {
- path = ".";
- len = 1;
- goto Exit;
- }
+ /* Cast is safe because max path < max int */
+ len = (int)(endp - path + 1);
- /* Strip trailing slashes */
- endp = path + strlen(path) - 1;
- while (endp > path && *endp == '/')
- endp--;
+#ifdef GIT_WIN32
+ /* Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return
+ 'C:/' here */
- /* Find the start of the dir */
- while (endp > path && *endp != '/')
- endp--;
+ if (len == 2 && LOOKS_LIKE_DRIVE_PREFIX(path)) {
+ len = 3;
+ goto Exit;
+ }
- /* Either the dir is "/" or there are no slashes */
- if (endp == path) {
- path = (*endp == '/') ? "/" : ".";
- len = 1;
- goto Exit;
- }
+ /* Similarly checks if we're dealing with a network computer name
+ '//computername/.git' will return '//computername/' */
- do {
- endp--;
- } while (endp > path && *endp == '/');
+ if (looks_like_network_computer_name(path, len)) {
+ len++;
+ goto Exit;
+ }
- len = endp - path +1;
+#endif
Exit:
- result = len;
- if (len+1 > GIT_PATH_MAX) {
- return GIT_ENOMEM;
- }
- if (buffer == NULL)
- return result;
+ result = len;
- if (len > (int)bufflen-1) {
- len = (int)bufflen-1;
- result = GIT_ENOMEM;
- }
+ if (buffer != NULL && git_buf_set(buffer, path, len) < 0)
+ return -1;
- if (len >= 0) {
- memmove(buffer, path, len);
- buffer[len] = 0;
- }
- return result;
+ return result;
}
char *git_path_dirname(const char *path)
{
- char *dname = NULL;
- int len;
+ git_buf buf = GIT_BUF_INIT;
+ char *dirname;
- len = (path ? strlen(path) : 0) + 2;
- dname = (char *)git__malloc(len);
- if (dname == NULL)
- return NULL;
+ git_path_dirname_r(&buf, path);
+ dirname = git_buf_detach(&buf);
+ git_buf_free(&buf); /* avoid memleak if error occurs */
- if (git_path_dirname_r(dname, len, path) < GIT_SUCCESS) {
- free(dname);
- return NULL;
- }
-
- return dname;
+ return dirname;
}
char *git_path_basename(const char *path)
{
- char *bname = NULL;
- int len;
-
- len = (path ? strlen(path) : 0) + 2;
- bname = (char *)git__malloc(len);
- if (bname == NULL)
- return NULL;
+ git_buf buf = GIT_BUF_INIT;
+ char *basename;
- if (git_path_basename_r(bname, len, path) < GIT_SUCCESS) {
- free(bname);
- return NULL;
- }
+ git_path_basename_r(&buf, path);
+ basename = git_buf_detach(&buf);
+ git_buf_free(&buf); /* avoid memleak if error occurs */
- return bname;
+ return basename;
}
+size_t git_path_basename_offset(git_buf *buffer)
+{
+ ssize_t slash;
+
+ if (!buffer || buffer->size <= 0)
+ return 0;
+
+ slash = git_buf_rfind_next(buffer, '/');
+
+ if (slash >= 0 && buffer->ptr[slash] == '/')
+ return (size_t)(slash + 1);
+
+ return 0;
+}
const char *git_path_topdir(const char *path)
{
size_t len;
- int i;
+ ssize_t i;
assert(path);
len = strlen(path);
@@ -165,90 +225,923 @@ const char *git_path_topdir(const char *path)
if (!len || path[len - 1] != '/')
return NULL;
- for (i = len - 2; i >= 0; --i)
+ for (i = (ssize_t)len - 2; i >= 0; --i)
if (path[i] == '/')
break;
return &path[i + 1];
}
-void git_path_join_n(char *buffer_out, int count, ...)
+int git_path_root(const char *path)
{
- va_list ap;
- int i;
- char *buffer_start = buffer_out;
+ int offset = 0;
+
+ /* Does the root of the path look like a windows drive ? */
+ if (LOOKS_LIKE_DRIVE_PREFIX(path))
+ offset += 2;
- va_start(ap, count);
- for (i = 0; i < count; ++i) {
- const char *path;
- int len;
+#ifdef GIT_WIN32
+ /* Are we dealing with a windows network path? */
+ else if ((path[0] == '/' && path[1] == '/' && path[2] != '/') ||
+ (path[0] == '\\' && path[1] == '\\' && path[2] != '\\'))
+ {
+ offset += 2;
- path = va_arg(ap, const char *);
+ /* Skip the computer name segment */
+ while (path[offset] && path[offset] != '/' && path[offset] != '\\')
+ offset++;
+ }
+#endif
- assert((i == 0) || path != buffer_start);
+ if (path[offset] == '/' || path[offset] == '\\')
+ return offset;
- if (i > 0 && *path == '/' && buffer_out > buffer_start && buffer_out[-1] == '/')
- path++;
+ return -1; /* Not a real error - signals that path is not rooted */
+}
- if (!*path)
- continue;
+int git_path_join_unrooted(
+ git_buf *path_out, const char *path, const char *base, ssize_t *root_at)
+{
+ int error, root;
- len = strlen(path);
- memmove(buffer_out, path, len);
- buffer_out = buffer_out + len;
+ assert(path && path_out);
- if (i < count - 1 && buffer_out[-1] != '/')
- *buffer_out++ = '/';
+ root = git_path_root(path);
+
+ if (base != NULL && root < 0) {
+ error = git_buf_joinpath(path_out, base, path);
+
+ if (root_at)
+ *root_at = (ssize_t)strlen(base);
+ }
+ else {
+ error = git_buf_sets(path_out, path);
+
+ if (root_at)
+ *root_at = (root < 0) ? 0 : (ssize_t)root;
}
- va_end(ap);
- *buffer_out = '\0';
+ return error;
}
-int git_path_root(const char *path)
+int git_path_prettify(git_buf *path_out, const char *path, const char *base)
{
- int offset = 0;
+ char buf[GIT_PATH_MAX];
+
+ assert(path && path_out);
+
+ /* construct path if needed */
+ if (base != NULL && git_path_root(path) < 0) {
+ if (git_buf_joinpath(path_out, base, path) < 0)
+ return -1;
+ path = path_out->ptr;
+ }
+
+ if (p_realpath(path, buf) == NULL) {
+ /* giterr_set resets the errno when dealing with a GITERR_OS kind of error */
+ int error = (errno == ENOENT || errno == ENOTDIR) ? GIT_ENOTFOUND : -1;
+ giterr_set(GITERR_OS, "Failed to resolve path '%s'", path);
+
+ git_buf_clear(path_out);
+
+ return error;
+ }
+
+ return git_buf_sets(path_out, buf);
+}
+
+int git_path_prettify_dir(git_buf *path_out, const char *path, const char *base)
+{
+ int error = git_path_prettify(path_out, path, base);
+ return (error < 0) ? error : git_path_to_dir(path_out);
+}
+
+int git_path_to_dir(git_buf *path)
+{
+ if (path->asize > 0 &&
+ git_buf_len(path) > 0 &&
+ path->ptr[git_buf_len(path) - 1] != '/')
+ git_buf_putc(path, '/');
+
+ return git_buf_oom(path) ? -1 : 0;
+}
+
+void git_path_string_to_dir(char* path, size_t size)
+{
+ size_t end = strlen(path);
+
+ if (end && path[end - 1] != '/' && end < size) {
+ path[end] = '/';
+ path[end + 1] = '\0';
+ }
+}
+
+int git__percent_decode(git_buf *decoded_out, const char *input)
+{
+ int len, hi, lo, i;
+ assert(decoded_out && input);
+
+ len = (int)strlen(input);
+ git_buf_clear(decoded_out);
+
+ for(i = 0; i < len; i++)
+ {
+ char c = input[i];
+
+ if (c != '%')
+ goto append;
+
+ if (i >= len - 2)
+ goto append;
+
+ hi = git__fromhex(input[i + 1]);
+ lo = git__fromhex(input[i + 2]);
+
+ if (hi < 0 || lo < 0)
+ goto append;
+
+ c = (char)(hi << 4 | lo);
+ i += 2;
+
+append:
+ if (git_buf_putc(decoded_out, c) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int error_invalid_local_file_uri(const char *uri)
+{
+ giterr_set(GITERR_CONFIG, "'%s' is not a valid local file URI", uri);
+ return -1;
+}
+
+int git_path_fromurl(git_buf *local_path_out, const char *file_url)
+{
+ int offset = 0, len;
+
+ assert(local_path_out && file_url);
+
+ if (git__prefixcmp(file_url, "file://") != 0)
+ return error_invalid_local_file_uri(file_url);
+
+ offset += 7;
+ len = (int)strlen(file_url);
+
+ if (offset < len && file_url[offset] == '/')
+ offset++;
+ else if (offset < len && git__prefixcmp(file_url + offset, "localhost/") == 0)
+ offset += 10;
+ else
+ return error_invalid_local_file_uri(file_url);
+
+ if (offset >= len || file_url[offset] == '/')
+ return error_invalid_local_file_uri(file_url);
+
+#ifndef GIT_WIN32
+ offset--; /* A *nix absolute path starts with a forward slash */
+#endif
+
+ git_buf_clear(local_path_out);
+
+ return git__percent_decode(local_path_out, file_url + offset);
+}
+
+int git_path_walk_up(
+ git_buf *path,
+ const char *ceiling,
+ int (*cb)(void *data, git_buf *),
+ void *data)
+{
+ int error = 0;
+ git_buf iter;
+ ssize_t stop = 0, scan;
+ char oldc = '\0';
+
+ assert(path && cb);
+
+ if (ceiling != NULL) {
+ if (git__prefixcmp(path->ptr, ceiling) == 0)
+ stop = (ssize_t)strlen(ceiling);
+ else
+ stop = git_buf_len(path);
+ }
+ scan = git_buf_len(path);
+
+ iter.ptr = path->ptr;
+ iter.size = git_buf_len(path);
+ iter.asize = path->asize;
+
+ while (scan >= stop) {
+ error = cb(data, &iter);
+ iter.ptr[scan] = oldc;
+
+ if (error) {
+ giterr_set_after_callback(error);
+ break;
+ }
+
+ scan = git_buf_rfind_next(&iter, '/');
+ if (scan >= 0) {
+ scan++;
+ oldc = iter.ptr[scan];
+ iter.size = scan;
+ iter.ptr[scan] = '\0';
+ }
+ }
+
+ if (scan >= 0)
+ iter.ptr[scan] = oldc;
+
+ return error;
+}
+
+bool git_path_exists(const char *path)
+{
+ assert(path);
+ return p_access(path, F_OK) == 0;
+}
+
+bool git_path_isdir(const char *path)
+{
+ struct stat st;
+ if (p_stat(path, &st) < 0)
+ return false;
+
+ return S_ISDIR(st.st_mode) != 0;
+}
+
+bool git_path_isfile(const char *path)
+{
+ struct stat st;
+
+ assert(path);
+ if (p_stat(path, &st) < 0)
+ return false;
+
+ return S_ISREG(st.st_mode) != 0;
+}
#ifdef GIT_WIN32
- /* Does the root of the path look like a windows drive ? */
- if (isalpha(path[0]) && (path[1] == ':'))
- offset += 2;
+
+bool git_path_is_empty_dir(const char *path)
+{
+ git_win32_path filter_w;
+ bool empty = false;
+
+ if (git_win32__findfirstfile_filter(filter_w, path)) {
+ WIN32_FIND_DATAW findData;
+ HANDLE hFind = FindFirstFileW(filter_w, &findData);
+
+ /* If the find handle was created successfully, then it's a directory */
+ if (hFind != INVALID_HANDLE_VALUE) {
+ empty = true;
+
+ do {
+ /* Allow the enumeration to return . and .. and still be considered
+ * empty. In the special case of drive roots (i.e. C:\) where . and
+ * .. do not occur, we can still consider the path to be an empty
+ * directory if there's nothing there. */
+ if (!git_path_is_dot_or_dotdotW(findData.cFileName)) {
+ empty = false;
+ break;
+ }
+ } while (FindNextFileW(hFind, &findData));
+
+ FindClose(hFind);
+ }
+ }
+
+ return empty;
+}
+
+#else
+
+static int path_found_entry(void *payload, git_buf *path)
+{
+ GIT_UNUSED(payload);
+ return !git_path_is_dot_or_dotdot(path->ptr);
+}
+
+bool git_path_is_empty_dir(const char *path)
+{
+ int error;
+ git_buf dir = GIT_BUF_INIT;
+
+ if (!git_path_isdir(path))
+ return false;
+
+ if ((error = git_buf_sets(&dir, path)) != 0)
+ giterr_clear();
+ else
+ error = git_path_direach(&dir, 0, path_found_entry, NULL);
+
+ git_buf_free(&dir);
+
+ return !error;
+}
+
#endif
- if (*(path + offset) == '/')
- return offset;
+int git_path_set_error(int errno_value, const char *path, const char *action)
+{
+ switch (errno_value) {
+ case ENOENT:
+ case ENOTDIR:
+ giterr_set(GITERR_OS, "Could not find '%s' to %s", path, action);
+ return GIT_ENOTFOUND;
+
+ case EINVAL:
+ case ENAMETOOLONG:
+ giterr_set(GITERR_OS, "Invalid path for filesystem '%s'", path);
+ return GIT_EINVALIDSPEC;
+
+ case EEXIST:
+ giterr_set(GITERR_OS, "Failed %s - '%s' already exists", action, path);
+ return GIT_EEXISTS;
+
+ default:
+ giterr_set(GITERR_OS, "Could not %s '%s'", action, path);
+ return -1;
+ }
+}
+
+int git_path_lstat(const char *path, struct stat *st)
+{
+ if (p_lstat(path, st) == 0)
+ return 0;
+
+ return git_path_set_error(errno, path, "stat");
+}
+
+static bool _check_dir_contents(
+ git_buf *dir,
+ const char *sub,
+ bool (*predicate)(const char *))
+{
+ bool result;
+ size_t dir_size = git_buf_len(dir);
+ size_t sub_size = strlen(sub);
+
+ /* leave base valid even if we could not make space for subdir */
+ if (git_buf_try_grow(dir, dir_size + sub_size + 2, false, false) < 0)
+ return false;
+
+ /* save excursion */
+ git_buf_joinpath(dir, dir->ptr, sub);
+
+ result = predicate(dir->ptr);
+
+ /* restore path */
+ git_buf_truncate(dir, dir_size);
+ return result;
+}
+
+bool git_path_contains(git_buf *dir, const char *item)
+{
+ return _check_dir_contents(dir, item, &git_path_exists);
+}
+
+bool git_path_contains_dir(git_buf *base, const char *subdir)
+{
+ return _check_dir_contents(base, subdir, &git_path_isdir);
+}
+
+bool git_path_contains_file(git_buf *base, const char *file)
+{
+ return _check_dir_contents(base, file, &git_path_isfile);
+}
+
+int git_path_find_dir(git_buf *dir, const char *path, const char *base)
+{
+ int error = git_path_join_unrooted(dir, path, base, NULL);
+
+ if (!error) {
+ char buf[GIT_PATH_MAX];
+ if (p_realpath(dir->ptr, buf) != NULL)
+ error = git_buf_sets(dir, buf);
+ }
+
+ /* call dirname if this is not a directory */
+ if (!error) /* && git_path_isdir(dir->ptr) == false) */
+ error = (git_path_dirname_r(dir, dir->ptr) < 0) ? -1 : 0;
+
+ if (!error)
+ error = git_path_to_dir(dir);
+
+ return error;
+}
+
+int git_path_resolve_relative(git_buf *path, size_t ceiling)
+{
+ char *base, *to, *from, *next;
+ size_t len;
+
+ if (!path || git_buf_oom(path))
+ return -1;
+
+ if (ceiling > path->size)
+ ceiling = path->size;
+
+ /* recognize drive prefixes, etc. that should not be backed over */
+ if (ceiling == 0)
+ ceiling = git_path_root(path->ptr) + 1;
+
+ /* recognize URL prefixes that should not be backed over */
+ if (ceiling == 0) {
+ for (next = path->ptr; *next && git__isalpha(*next); ++next);
+ if (next[0] == ':' && next[1] == '/' && next[2] == '/')
+ ceiling = (next + 3) - path->ptr;
+ }
+
+ base = to = from = path->ptr + ceiling;
+
+ while (*from) {
+ for (next = from; *next && *next != '/'; ++next);
+
+ len = next - from;
+
+ if (len == 1 && from[0] == '.')
+ /* do nothing with singleton dot */;
+
+ else if (len == 2 && from[0] == '.' && from[1] == '.') {
+ /* error out if trying to up one from a hard base */
+ if (to == base && ceiling != 0) {
+ giterr_set(GITERR_INVALID,
+ "Cannot strip root component off url");
+ return -1;
+ }
+
+ /* no more path segments to strip,
+ * use '../' as a new base path */
+ if (to == base) {
+ if (*next == '/')
+ len++;
+
+ if (to != from)
+ memmove(to, from, len);
+
+ to += len;
+ /* this is now the base, can't back up from a
+ * relative prefix */
+ base = to;
+ } else {
+ /* back up a path segment */
+ while (to > base && to[-1] == '/') to--;
+ while (to > base && to[-1] != '/') to--;
+ }
+ } else {
+ if (*next == '/' && *from != '/')
+ len++;
+
+ if (to != from)
+ memmove(to, from, len);
+
+ to += len;
+ }
+
+ from += len;
+
+ while (*from == '/') from++;
+ }
+
+ *to = '\0';
+
+ path->size = to - path->ptr;
+
+ return 0;
+}
+
+int git_path_apply_relative(git_buf *target, const char *relpath)
+{
+ git_buf_joinpath(target, git_buf_cstr(target), relpath);
+ return git_path_resolve_relative(target, 0);
+}
+
+int git_path_cmp(
+ const char *name1, size_t len1, int isdir1,
+ const char *name2, size_t len2, int isdir2,
+ int (*compare)(const char *, const char *, size_t))
+{
+ unsigned char c1, c2;
+ size_t len = len1 < len2 ? len1 : len2;
+ int cmp;
+
+ cmp = compare(name1, name2, len);
+ if (cmp)
+ return cmp;
+
+ c1 = name1[len];
+ c2 = name2[len];
+
+ if (c1 == '\0' && isdir1)
+ c1 = '/';
+
+ if (c2 == '\0' && isdir2)
+ c2 = '/';
+
+ return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
+}
+
+bool git_path_has_non_ascii(const char *path, size_t pathlen)
+{
+ const uint8_t *scan = (const uint8_t *)path, *end;
+
+ for (end = scan + pathlen; scan < end; ++scan)
+ if (*scan & 0x80)
+ return true;
+
+ return false;
+}
+
+#ifdef GIT_USE_ICONV
+
+int git_path_iconv_init_precompose(git_path_iconv_t *ic)
+{
+ git_buf_init(&ic->buf, 0);
+ ic->map = iconv_open(GIT_PATH_REPO_ENCODING, GIT_PATH_NATIVE_ENCODING);
+ return 0;
+}
+
+void git_path_iconv_clear(git_path_iconv_t *ic)
+{
+ if (ic) {
+ if (ic->map != (iconv_t)-1)
+ iconv_close(ic->map);
+ git_buf_free(&ic->buf);
+ }
+}
+
+int git_path_iconv(git_path_iconv_t *ic, char **in, size_t *inlen)
+{
+ char *nfd = *in, *nfc;
+ size_t nfdlen = *inlen, nfclen, wantlen = nfdlen, rv;
+ int retry = 1;
+
+ if (!ic || ic->map == (iconv_t)-1 ||
+ !git_path_has_non_ascii(*in, *inlen))
+ return 0;
+
+ git_buf_clear(&ic->buf);
+
+ while (1) {
+ if (git_buf_grow(&ic->buf, wantlen + 1) < 0)
+ return -1;
+
+ nfc = ic->buf.ptr + ic->buf.size;
+ nfclen = ic->buf.asize - ic->buf.size;
+
+ rv = iconv(ic->map, &nfd, &nfdlen, &nfc, &nfclen);
+
+ ic->buf.size = (nfc - ic->buf.ptr);
+
+ if (rv != (size_t)-1)
+ break;
+
+ /* if we cannot convert the data (probably because iconv thinks
+ * it is not valid UTF-8 source data), then use original data
+ */
+ if (errno != E2BIG)
+ return 0;
+
+ /* make space for 2x the remaining data to be converted
+ * (with per retry overhead to avoid infinite loops)
+ */
+ wantlen = ic->buf.size + max(nfclen, nfdlen) * 2 + (size_t)(retry * 4);
+
+ if (retry++ > 4)
+ goto fail;
+ }
+
+ ic->buf.ptr[ic->buf.size] = '\0';
+
+ *in = ic->buf.ptr;
+ *inlen = ic->buf.size;
+
+ return 0;
+
+fail:
+ giterr_set(GITERR_OS, "Unable to convert unicode path data");
+ return -1;
+}
+
+static const char *nfc_file = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D.XXXXXX";
+static const char *nfd_file = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D.XXXXXX";
+
+/* Check if the platform is decomposing unicode data for us. We will
+ * emulate core Git and prefer to use precomposed unicode data internally
+ * on these platforms, composing the decomposed unicode on the fly.
+ *
+ * This mainly happens on the Mac where HDFS stores filenames as
+ * decomposed unicode. Even on VFAT and SAMBA file systems, the Mac will
+ * return decomposed unicode from readdir() even when the actual
+ * filesystem is storing precomposed unicode.
+ */
+bool git_path_does_fs_decompose_unicode(const char *root)
+{
+ git_buf path = GIT_BUF_INIT;
+ int fd;
+ bool found_decomposed = false;
+ char tmp[6];
+
+ /* Create a file using a precomposed path and then try to find it
+ * using the decomposed name. If the lookup fails, then we will mark
+ * that we should precompose unicode for this repository.
+ */
+ if (git_buf_joinpath(&path, root, nfc_file) < 0 ||
+ (fd = p_mkstemp(path.ptr)) < 0)
+ goto done;
+ p_close(fd);
+
+ /* record trailing digits generated by mkstemp */
+ memcpy(tmp, path.ptr + path.size - sizeof(tmp), sizeof(tmp));
+
+ /* try to look up as NFD path */
+ if (git_buf_joinpath(&path, root, nfd_file) < 0)
+ goto done;
+ memcpy(path.ptr + path.size - sizeof(tmp), tmp, sizeof(tmp));
+
+ found_decomposed = git_path_exists(path.ptr);
+
+ /* remove temporary file (using original precomposed path) */
+ if (git_buf_joinpath(&path, root, nfc_file) < 0)
+ goto done;
+ memcpy(path.ptr + path.size - sizeof(tmp), tmp, sizeof(tmp));
- return -1; /* Not a real error. Rather a signal than the path is not rooted */
+ (void)p_unlink(path.ptr);
+
+done:
+ git_buf_free(&path);
+ return found_decomposed;
+}
+
+#else
+
+bool git_path_does_fs_decompose_unicode(const char *root)
+{
+ GIT_UNUSED(root);
+ return false;
}
-int git_path_prettify(char *path_out, const char *path, const char *base)
+#endif
+
+#if defined(__sun) || defined(__GNU__)
+typedef char path_dirent_data[sizeof(struct dirent) + FILENAME_MAX + 1];
+#else
+typedef struct dirent path_dirent_data;
+#endif
+
+int git_path_direach(
+ git_buf *path,
+ uint32_t flags,
+ int (*fn)(void *, git_buf *),
+ void *arg)
{
- char *result;
+ int error = 0;
+ ssize_t wd_len;
+ DIR *dir;
+ path_dirent_data de_data;
+ struct dirent *de, *de_buf = (struct dirent *)&de_data;
+
+ (void)flags;
+
+#ifdef GIT_USE_ICONV
+ git_path_iconv_t ic = GIT_PATH_ICONV_INIT;
+#endif
+
+ if (git_path_to_dir(path) < 0)
+ return -1;
+
+ wd_len = git_buf_len(path);
+
+ if ((dir = opendir(path->ptr)) == NULL) {
+ giterr_set(GITERR_OS, "Failed to open directory '%s'", path->ptr);
+ if (errno == ENOENT)
+ return GIT_ENOTFOUND;
+
+ return -1;
+ }
+
+#ifdef GIT_USE_ICONV
+ if ((flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0)
+ (void)git_path_iconv_init_precompose(&ic);
+#endif
+
+ while (p_readdir_r(dir, de_buf, &de) == 0 && de != NULL) {
+ char *de_path = de->d_name;
+ size_t de_len = strlen(de_path);
+
+ if (git_path_is_dot_or_dotdot(de_path))
+ continue;
+
+#ifdef GIT_USE_ICONV
+ if ((error = git_path_iconv(&ic, &de_path, &de_len)) < 0)
+ break;
+#endif
+
+ if ((error = git_buf_put(path, de_path, de_len)) < 0)
+ break;
- if (base == NULL || git_path_root(path) >= 0) {
- result = p_realpath(path, path_out);
- } else {
- char aux_path[GIT_PATH_MAX];
- git_path_join(aux_path, base, path);
- result = p_realpath(aux_path, path_out);
+ error = fn(arg, path);
+
+ git_buf_truncate(path, wd_len); /* restore path */
+
+ if (error != 0) {
+ giterr_set_after_callback(error);
+ break;
+ }
}
- return result ? GIT_SUCCESS : GIT_EOSERR;
+ closedir(dir);
+
+#ifdef GIT_USE_ICONV
+ git_path_iconv_clear(&ic);
+#endif
+
+ return error;
}
-int git_path_prettify_dir(char *path_out, const char *path, const char *base)
+int git_path_dirload(
+ const char *path,
+ size_t prefix_len,
+ size_t alloc_extra,
+ unsigned int flags,
+ git_vector *contents)
{
- size_t end;
+ int error, need_slash;
+ DIR *dir;
+ size_t path_len;
+ path_dirent_data de_data;
+ struct dirent *de, *de_buf = (struct dirent *)&de_data;
+
+ (void)flags;
+
+#ifdef GIT_USE_ICONV
+ git_path_iconv_t ic = GIT_PATH_ICONV_INIT;
+#endif
- if (git_path_prettify(path_out, path, base) < GIT_SUCCESS)
- return GIT_EOSERR;
+ assert(path && contents);
+
+ path_len = strlen(path);
+
+ if (!path_len || path_len < prefix_len) {
+ giterr_set(GITERR_INVALID, "Invalid directory path '%s'", path);
+ return -1;
+ }
+ if ((dir = opendir(path)) == NULL) {
+ giterr_set(GITERR_OS, "Failed to open directory '%s'", path);
+ return -1;
+ }
+
+#ifdef GIT_USE_ICONV
+ if ((flags & GIT_PATH_DIR_PRECOMPOSE_UNICODE) != 0)
+ (void)git_path_iconv_init_precompose(&ic);
+#endif
- end = strlen(path_out);
+ path += prefix_len;
+ path_len -= prefix_len;
+ need_slash = (path_len > 0 && path[path_len-1] != '/') ? 1 : 0;
+
+ while ((error = p_readdir_r(dir, de_buf, &de)) == 0 && de != NULL) {
+ char *entry_path, *de_path = de->d_name;
+ size_t alloc_size, de_len = strlen(de_path);
+
+ if (git_path_is_dot_or_dotdot(de_path))
+ continue;
+
+#ifdef GIT_USE_ICONV
+ if ((error = git_path_iconv(&ic, &de_path, &de_len)) < 0)
+ break;
+#endif
+
+ alloc_size = path_len + need_slash + de_len + 1 + alloc_extra;
+ if ((entry_path = git__calloc(alloc_size, 1)) == NULL) {
+ error = -1;
+ break;
+ }
+
+ if (path_len)
+ memcpy(entry_path, path, path_len);
+ if (need_slash)
+ entry_path[path_len] = '/';
+ memcpy(&entry_path[path_len + need_slash], de_path, de_len);
+
+ if ((error = git_vector_insert(contents, entry_path)) < 0)
+ break;
+ }
+
+ closedir(dir);
+
+#ifdef GIT_USE_ICONV
+ git_path_iconv_clear(&ic);
+#endif
+
+ if (error != 0)
+ giterr_set(GITERR_OS, "Failed to process directory entry in '%s'", path);
+
+ return error;
+}
+
+int git_path_with_stat_cmp(const void *a, const void *b)
+{
+ const git_path_with_stat *psa = a, *psb = b;
+ return strcmp(psa->path, psb->path);
+}
+
+int git_path_with_stat_cmp_icase(const void *a, const void *b)
+{
+ const git_path_with_stat *psa = a, *psb = b;
+ return strcasecmp(psa->path, psb->path);
+}
+
+int git_path_dirload_with_stat(
+ const char *path,
+ size_t prefix_len,
+ unsigned int flags,
+ const char *start_stat,
+ const char *end_stat,
+ git_vector *contents)
+{
+ int error;
+ unsigned int i;
+ git_path_with_stat *ps;
+ git_buf full = GIT_BUF_INIT;
+ int (*strncomp)(const char *a, const char *b, size_t sz);
+ size_t start_len = start_stat ? strlen(start_stat) : 0;
+ size_t end_len = end_stat ? strlen(end_stat) : 0, cmp_len;
+
+ if (git_buf_set(&full, path, prefix_len) < 0)
+ return -1;
+
+ error = git_path_dirload(
+ path, prefix_len, sizeof(git_path_with_stat) + 1, flags, contents);
+ if (error < 0) {
+ git_buf_free(&full);
+ return error;
+ }
+
+ strncomp = (flags & GIT_PATH_DIR_IGNORE_CASE) != 0 ?
+ git__strncasecmp : git__strncmp;
+
+ /* stat struct at start of git_path_with_stat, so shift path text */
+ git_vector_foreach(contents, i, ps) {
+ size_t path_len = strlen((char *)ps);
+ memmove(ps->path, ps, path_len + 1);
+ ps->path_len = path_len;
+ }
+
+ git_vector_foreach(contents, i, ps) {
+ /* skip if before start_stat or after end_stat */
+ cmp_len = min(start_len, ps->path_len);
+ if (cmp_len && strncomp(ps->path, start_stat, cmp_len) < 0)
+ continue;
+ cmp_len = min(end_len, ps->path_len);
+ if (cmp_len && strncomp(ps->path, end_stat, cmp_len) > 0)
+ continue;
+
+ git_buf_truncate(&full, prefix_len);
+
+ if ((error = git_buf_joinpath(&full, full.ptr, ps->path)) < 0 ||
+ (error = git_path_lstat(full.ptr, &ps->st)) < 0) {
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
+ git_vector_remove(contents, i--);
+ continue;
+ }
+
+ break;
+ }
+
+ if (S_ISDIR(ps->st.st_mode)) {
+ ps->path[ps->path_len++] = '/';
+ ps->path[ps->path_len] = '\0';
+ }
+ }
+
+ /* sort now that directory suffix is added */
+ git_vector_sort(contents);
+
+ git_buf_free(&full);
+
+ return error;
+}
+
+int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path)
+{
+ int error;
- if (end && path_out[end - 1] != '/') {
- path_out[end] = '/';
- path_out[end + 1] = '\0';
+ /* If url_or_path begins with file:// treat it as a URL */
+ if (!git__prefixcmp(url_or_path, "file://")) {
+ if ((error = git_path_fromurl(local_path_out, url_or_path)) < 0) {
+ return error;
+ }
+ } else { /* We assume url_or_path is already a path */
+ if ((error = git_buf_sets(local_path_out, url_or_path)) < 0) {
+ return error;
+ }
}
- return GIT_SUCCESS;
+ return 0;
}
diff --git a/src/path.h b/src/path.h
index 36e22a768..3e6efe3de 100644
--- a/src/path.h
+++ b/src/path.h
@@ -1,10 +1,22 @@
/*
- * posix.h - Path management methods
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_path_h__
#define INCLUDE_path_h__
#include "common.h"
+#include "buffer.h"
+#include "vector.h"
+
+/**
+ * Path manipulation utils
+ *
+ * These are path utilities that munge paths without actually
+ * looking at the real filesystem.
+ */
/*
* The dirname() function shall take a pointer to a character string
@@ -19,11 +31,13 @@
* The `git_path_dirname` implementation is thread safe. The returned
* string must be manually free'd.
*
- * The `git_path_dirname_r` implementation expects a string allocated
- * by the user with big enough size.
+ * The `git_path_dirname_r` implementation writes the dirname to a `git_buf`
+ * if the buffer pointer is not NULL.
+ * It returns an error code < 0 if there is an allocation error, otherwise
+ * the length of the dirname (which will be > 0).
*/
extern char *git_path_dirname(const char *path);
-extern int git_path_dirname_r(char *buffer, size_t bufflen, const char *path);
+extern int git_path_dirname_r(git_buf *buffer, const char *path);
/*
* This function returns the basename of the file, which is the last
@@ -37,34 +51,61 @@ extern int git_path_dirname_r(char *buffer, size_t bufflen, const char *path);
* The `git_path_basename` implementation is thread safe. The returned
* string must be manually free'd.
*
- * The `git_path_basename_r` implementation expects a string allocated
- * by the user with big enough size.
+ * The `git_path_basename_r` implementation writes the basename to a `git_buf`.
+ * It returns an error code < 0 if there is an allocation error, otherwise
+ * the length of the basename (which will be >= 0).
*/
extern char *git_path_basename(const char *path);
-extern int git_path_basename_r(char *buffer, size_t bufflen, const char *path);
+extern int git_path_basename_r(git_buf *buffer, const char *path);
+
+/* Return the offset of the start of the basename. Unlike the other
+ * basename functions, this returns 0 if the path is empty.
+ */
+extern size_t git_path_basename_offset(git_buf *buffer);
extern const char *git_path_topdir(const char *path);
/**
- * Join two paths together. Takes care of properly fixing the
- * middle slashes and everything
+ * Find offset to root of path if path has one.
*
- * The paths are joined together into buffer_out; this is expected
- * to be an user allocated buffer of `GIT_PATH_MAX` size
+ * This will return a number >= 0 which is the offset to the start of the
+ * path, if the path is rooted (i.e. "/rooted/path" returns 0 and
+ * "c:/windows/rooted/path" returns 2). If the path is not rooted, this
+ * returns < 0.
*/
-extern void git_path_join_n(char *buffer_out, int npath, ...);
+extern int git_path_root(const char *path);
-GIT_INLINE(void) git_path_join(char *buffer_out, const char *path_a, const char *path_b)
-{
- git_path_join_n(buffer_out, 2, path_a, path_b);
-}
+/**
+ * Ensure path has a trailing '/'.
+ */
+extern int git_path_to_dir(git_buf *path);
-int git_path_root(const char *path);
+/**
+ * Ensure string has a trailing '/' if there is space for it.
+ */
+extern void git_path_string_to_dir(char* path, size_t size);
-int git_path_prettify(char *path_out, const char *path, const char *base);
-int git_path_prettify_dir(char *path_out, const char *path, const char *base);
+/**
+ * Taken from git.git; returns nonzero if the given path is "." or "..".
+ */
+GIT_INLINE(int) git_path_is_dot_or_dotdot(const char *name)
+{
+ return (name[0] == '.' &&
+ (name[1] == '\0' ||
+ (name[1] == '.' && name[2] == '\0')));
+}
#ifdef GIT_WIN32
+GIT_INLINE(int) git_path_is_dot_or_dotdotW(const wchar_t *name)
+{
+ return (name[0] == L'.' &&
+ (name[1] == L'\0' ||
+ (name[1] == L'.' && name[2] == L'\0')));
+}
+
+/**
+ * Convert backslashes in path to forward slashes.
+ */
GIT_INLINE(void) git_path_mkposix(char *path)
{
while (*path) {
@@ -78,4 +119,326 @@ GIT_INLINE(void) git_path_mkposix(char *path)
# define git_path_mkposix(p) /* blank */
#endif
+/**
+ * Check if string is a relative path (i.e. starts with "./" or "../")
+ */
+GIT_INLINE(int) git_path_is_relative(const char *p)
+{
+ return (p[0] == '.' && (p[1] == '/' || (p[1] == '.' && p[2] == '/')));
+}
+
+extern int git__percent_decode(git_buf *decoded_out, const char *input);
+
+/**
+ * Extract path from file:// URL.
+ */
+extern int git_path_fromurl(git_buf *local_path_out, const char *file_url);
+
+
+/**
+ * Path filesystem utils
+ *
+ * These are path utilities that actually access the filesystem.
+ */
+
+/**
+ * Check if a file exists and can be accessed.
+ * @return true or false
+ */
+extern bool git_path_exists(const char *path);
+
+/**
+ * Check if the given path points to a directory.
+ * @return true or false
+ */
+extern bool git_path_isdir(const char *path);
+
+/**
+ * Check if the given path points to a regular file.
+ * @return true or false
+ */
+extern bool git_path_isfile(const char *path);
+
+/**
+ * Check if the given path is a directory, and is empty.
+ */
+extern bool git_path_is_empty_dir(const char *path);
+
+/**
+ * Stat a file and/or link and set error if needed.
+ */
+extern int git_path_lstat(const char *path, struct stat *st);
+
+/**
+ * Check if the parent directory contains the item.
+ *
+ * @param dir Directory to check.
+ * @param item Item that might be in the directory.
+ * @return 0 if item exists in directory, <0 otherwise.
+ */
+extern bool git_path_contains(git_buf *dir, const char *item);
+
+/**
+ * Check if the given path contains the given subdirectory.
+ *
+ * @param parent Directory path that might contain subdir
+ * @param subdir Subdirectory name to look for in parent
+ * @return true if subdirectory exists, false otherwise.
+ */
+extern bool git_path_contains_dir(git_buf *parent, const char *subdir);
+
+/**
+ * Check if the given path contains the given file.
+ *
+ * @param dir Directory path that might contain file
+ * @param file File name to look for in parent
+ * @return true if file exists, false otherwise.
+ */
+extern bool git_path_contains_file(git_buf *dir, const char *file);
+
+/**
+ * Prepend base to unrooted path or just copy path over.
+ *
+ * This will optionally return the index into the path where the "root"
+ * is, either the end of the base directory prefix or the path root.
+ */
+extern int git_path_join_unrooted(
+ git_buf *path_out, const char *path, const char *base, ssize_t *root_at);
+
+/**
+ * Clean up path, prepending base if it is not already rooted.
+ */
+extern int git_path_prettify(git_buf *path_out, const char *path, const char *base);
+
+/**
+ * Clean up path, prepending base if it is not already rooted and
+ * appending a slash.
+ */
+extern int git_path_prettify_dir(git_buf *path_out, const char *path, const char *base);
+
+/**
+ * Get a directory from a path.
+ *
+ * If path is a directory, this acts like `git_path_prettify_dir`
+ * (cleaning up path and appending a '/'). If path is a normal file,
+ * this prettifies it, then removed the filename a la dirname and
+ * appends the trailing '/'. If the path does not exist, it is
+ * treated like a regular filename.
+ */
+extern int git_path_find_dir(git_buf *dir, const char *path, const char *base);
+
+/**
+ * Resolve relative references within a path.
+ *
+ * This eliminates "./" and "../" relative references inside a path,
+ * as well as condensing multiple slashes into single ones. It will
+ * not touch the path before the "ceiling" length.
+ *
+ * Additionally, this will recognize an "c:/" drive prefix or a "xyz://" URL
+ * prefix and not touch that part of the path.
+ */
+extern int git_path_resolve_relative(git_buf *path, size_t ceiling);
+
+/**
+ * Apply a relative path to base path.
+ *
+ * Note that the base path could be a filename or a URL and this
+ * should still work. The relative path is walked segment by segment
+ * with three rules: series of slashes will be condensed to a single
+ * slash, "." will be eaten with no change, and ".." will remove a
+ * segment from the base path.
+ */
+extern int git_path_apply_relative(git_buf *target, const char *relpath);
+
+enum {
+ GIT_PATH_DIR_IGNORE_CASE = (1u << 0),
+ GIT_PATH_DIR_PRECOMPOSE_UNICODE = (1u << 1),
+};
+
+/**
+ * Walk each directory entry, except '.' and '..', calling fn(state).
+ *
+ * @param pathbuf Buffer the function reads the initial directory
+ * path from, and updates with each successive entry's name.
+ * @param flags Combination of GIT_PATH_DIR flags.
+ * @param callback Callback for each entry. Passed the `payload` and each
+ * successive path inside the directory as a full path. This may
+ * safely append text to the pathbuf if needed. Return non-zero to
+ * cancel iteration (and return value will be propagated back).
+ * @param payload Passed to callback as first argument.
+ * @return 0 on success or error code from OS error or from callback
+ */
+extern int git_path_direach(
+ git_buf *pathbuf,
+ uint32_t flags,
+ int (*callback)(void *payload, git_buf *path),
+ void *payload);
+
+/**
+ * Sort function to order two paths
+ */
+extern int git_path_cmp(
+ const char *name1, size_t len1, int isdir1,
+ const char *name2, size_t len2, int isdir2,
+ int (*compare)(const char *, const char *, size_t));
+
+/**
+ * Invoke callback up path directory by directory until the ceiling is
+ * reached (inclusive of a final call at the root_path).
+ *
+ * Returning anything other than 0 from the callback function
+ * will stop the iteration and propogate the error to the caller.
+ *
+ * @param pathbuf Buffer the function reads the directory from and
+ * and updates with each successive name.
+ * @param ceiling Prefix of path at which to stop walking up. If NULL,
+ * this will walk all the way up to the root. If not a prefix of
+ * pathbuf, the callback will be invoked a single time on the
+ * original input path.
+ * @param callback Function to invoke on each path. Passed the `payload`
+ * and the buffer containing the current path. The path should not
+ * be modified in any way. Return non-zero to stop iteration.
+ * @param state Passed to fn as the first ath.
+ */
+extern int git_path_walk_up(
+ git_buf *pathbuf,
+ const char *ceiling,
+ int (*callback)(void *payload, git_buf *path),
+ void *payload);
+
+/**
+ * Load all directory entries (except '.' and '..') into a vector.
+ *
+ * For cases where `git_path_direach()` is not appropriate, this
+ * allows you to load the filenames in a directory into a vector
+ * of strings. That vector can then be sorted, iterated, or whatever.
+ * Remember to free alloc of the allocated strings when you are done.
+ *
+ * @param path The directory to read from.
+ * @param prefix_len When inserting entries, the trailing part of path
+ * will be prefixed after this length. I.e. given path "/a/b" and
+ * prefix_len 3, the entries will look like "b/e1", "b/e2", etc.
+ * @param alloc_extra Extra bytes to add to each string allocation in
+ * case you want to append anything funny.
+ * @param flags Combination of GIT_PATH_DIR flags.
+ * @param contents Vector to fill with directory entry names.
+ */
+extern int git_path_dirload(
+ const char *path,
+ size_t prefix_len,
+ size_t alloc_extra,
+ uint32_t flags,
+ git_vector *contents);
+
+
+typedef struct {
+ struct stat st;
+ size_t path_len;
+ char path[GIT_FLEX_ARRAY];
+} git_path_with_stat;
+
+extern int git_path_with_stat_cmp(const void *a, const void *b);
+extern int git_path_with_stat_cmp_icase(const void *a, const void *b);
+
+/**
+ * Load all directory entries along with stat info into a vector.
+ *
+ * This adds four things on top of plain `git_path_dirload`:
+ *
+ * 1. Each entry in the vector is a `git_path_with_stat` struct that
+ * contains both the path and the stat info
+ * 2. The entries will be sorted alphabetically
+ * 3. Entries that are directories will be suffixed with a '/'
+ * 4. Optionally, you can be a start and end prefix and only elements
+ * after the start and before the end (inclusively) will be stat'ed.
+ *
+ * @param path The directory to read from
+ * @param prefix_len The trailing part of path to prefix to entry paths
+ * @param flags GIT_PATH_DIR flags from above
+ * @param start_stat As optimization, only stat values after this prefix
+ * @param end_stat As optimization, only stat values before this prefix
+ * @param contents Vector to fill with git_path_with_stat structures
+ */
+extern int git_path_dirload_with_stat(
+ const char *path,
+ size_t prefix_len,
+ uint32_t flags,
+ const char *start_stat,
+ const char *end_stat,
+ git_vector *contents);
+
+enum { GIT_PATH_NOTEQUAL = 0, GIT_PATH_EQUAL = 1, GIT_PATH_PREFIX = 2 };
+
+/*
+ * Determines if a path is equal to or potentially a child of another.
+ * @param parent The possible parent
+ * @param child The possible child
+ */
+GIT_INLINE(int) git_path_equal_or_prefixed(
+ const char *parent,
+ const char *child)
+{
+ const char *p = parent, *c = child;
+
+ while (*p && *c) {
+ if (*p++ != *c++)
+ return GIT_PATH_NOTEQUAL;
+ }
+
+ if (*p != '\0')
+ return GIT_PATH_NOTEQUAL;
+ if (*c == '\0')
+ return GIT_PATH_EQUAL;
+ if (*c == '/')
+ return GIT_PATH_PREFIX;
+
+ return GIT_PATH_NOTEQUAL;
+}
+
+/* translate errno to libgit2 error code and set error message */
+extern int git_path_set_error(
+ int errno_value, const char *path, const char *action);
+
+/* check if non-ascii characters are present in filename */
+extern bool git_path_has_non_ascii(const char *path, size_t pathlen);
+
+#define GIT_PATH_REPO_ENCODING "UTF-8"
+
+#ifdef __APPLE__
+#define GIT_PATH_NATIVE_ENCODING "UTF-8-MAC"
+#else
+#define GIT_PATH_NATIVE_ENCODING "UTF-8"
+#endif
+
+#ifdef GIT_USE_ICONV
+
+#include <iconv.h>
+
+typedef struct {
+ iconv_t map;
+ git_buf buf;
+} git_path_iconv_t;
+
+#define GIT_PATH_ICONV_INIT { (iconv_t)-1, GIT_BUF_INIT }
+
+/* Init iconv data for converting decomposed UTF-8 to precomposed */
+extern int git_path_iconv_init_precompose(git_path_iconv_t *ic);
+
+/* Clear allocated iconv data */
+extern void git_path_iconv_clear(git_path_iconv_t *ic);
+
+/*
+ * Rewrite `in` buffer using iconv map if necessary, replacing `in`
+ * pointer internal iconv buffer if rewrite happened. The `in` pointer
+ * will be left unchanged if no rewrite was needed.
+ */
+extern int git_path_iconv(git_path_iconv_t *ic, char **in, size_t *inlen);
+
+#endif /* GIT_USE_ICONV */
+
+extern bool git_path_does_fs_decompose_unicode(const char *root);
+
+/* Used for paths to repositories on the filesystem */
+extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path);
+
#endif
diff --git a/src/pathspec.c b/src/pathspec.c
new file mode 100644
index 000000000..a01d74f07
--- /dev/null
+++ b/src/pathspec.c
@@ -0,0 +1,719 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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/pathspec.h"
+#include "git2/diff.h"
+#include "pathspec.h"
+#include "buf_text.h"
+#include "attr_file.h"
+#include "iterator.h"
+#include "repository.h"
+#include "index.h"
+#include "bitvec.h"
+#include "diff.h"
+
+/* what is the common non-wildcard prefix for all items in the pathspec */
+char *git_pathspec_prefix(const git_strarray *pathspec)
+{
+ git_buf prefix = GIT_BUF_INIT;
+ const char *scan;
+
+ if (!pathspec || !pathspec->count ||
+ git_buf_text_common_prefix(&prefix, pathspec) < 0)
+ return NULL;
+
+ /* diff prefix will only be leading non-wildcards */
+ for (scan = prefix.ptr; *scan; ++scan) {
+ if (git__iswildcard(*scan) &&
+ (scan == prefix.ptr || (*(scan - 1) != '\\')))
+ break;
+ }
+ git_buf_truncate(&prefix, scan - prefix.ptr);
+
+ if (prefix.size <= 0) {
+ git_buf_free(&prefix);
+ return NULL;
+ }
+
+ git_buf_text_unescape(&prefix);
+
+ return git_buf_detach(&prefix);
+}
+
+/* is there anything in the spec that needs to be filtered on */
+bool git_pathspec_is_empty(const git_strarray *pathspec)
+{
+ size_t i;
+
+ if (pathspec == NULL)
+ return true;
+
+ for (i = 0; i < pathspec->count; ++i) {
+ const char *str = pathspec->strings[i];
+
+ if (str && str[0])
+ return false;
+ }
+
+ return true;
+}
+
+/* build a vector of fnmatch patterns to evaluate efficiently */
+int git_pathspec__vinit(
+ git_vector *vspec, const git_strarray *strspec, git_pool *strpool)
+{
+ size_t i;
+
+ memset(vspec, 0, sizeof(*vspec));
+
+ if (git_pathspec_is_empty(strspec))
+ return 0;
+
+ if (git_vector_init(vspec, strspec->count, NULL) < 0)
+ return -1;
+
+ for (i = 0; i < strspec->count; ++i) {
+ int ret;
+ const char *pattern = strspec->strings[i];
+ git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch));
+ if (!match)
+ return -1;
+
+ match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE |
+ GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_NOLEADINGDIR;
+
+ ret = git_attr_fnmatch__parse(match, strpool, NULL, &pattern);
+ if (ret == GIT_ENOTFOUND) {
+ git__free(match);
+ continue;
+ } else if (ret < 0) {
+ git__free(match);
+ return ret;
+ }
+
+ if (git_vector_insert(vspec, match) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+/* free data from the pathspec vector */
+void git_pathspec__vfree(git_vector *vspec)
+{
+ git_vector_free_deep(vspec);
+}
+
+struct pathspec_match_context {
+ int fnmatch_flags;
+ int (*strcomp)(const char *, const char *);
+ int (*strncomp)(const char *, const char *, size_t);
+};
+
+static void pathspec_match_context_init(
+ struct pathspec_match_context *ctxt,
+ bool disable_fnmatch,
+ bool casefold)
+{
+ if (disable_fnmatch)
+ ctxt->fnmatch_flags = -1;
+ else if (casefold)
+ ctxt->fnmatch_flags = FNM_CASEFOLD;
+ else
+ ctxt->fnmatch_flags = 0;
+
+ if (casefold) {
+ ctxt->strcomp = git__strcasecmp;
+ ctxt->strncomp = git__strncasecmp;
+ } else {
+ ctxt->strcomp = git__strcmp;
+ ctxt->strncomp = git__strncmp;
+ }
+}
+
+static int pathspec_match_one(
+ const git_attr_fnmatch *match,
+ struct pathspec_match_context *ctxt,
+ const char *path)
+{
+ int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : FNM_NOMATCH;
+
+ if (result == FNM_NOMATCH)
+ result = ctxt->strcomp(match->pattern, path) ? FNM_NOMATCH : 0;
+
+ if (ctxt->fnmatch_flags >= 0 && result == FNM_NOMATCH)
+ result = p_fnmatch(match->pattern, path, ctxt->fnmatch_flags);
+
+ /* if we didn't match, look for exact dirname prefix match */
+ if (result == FNM_NOMATCH &&
+ (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 &&
+ ctxt->strncomp(path, match->pattern, match->length) == 0 &&
+ path[match->length] == '/')
+ result = 0;
+
+ /* if we didn't match and this is a negative match, check for exact
+ * match of filename with leading '!'
+ */
+ if (result == FNM_NOMATCH &&
+ (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0 &&
+ *path == '!' &&
+ ctxt->strncomp(path + 1, match->pattern, match->length) == 0 &&
+ (!path[match->length + 1] || path[match->length + 1] == '/'))
+ return 1;
+
+ if (result == 0)
+ return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? 0 : 1;
+ return -1;
+}
+
+static int git_pathspec__match_at(
+ size_t *matched_at,
+ const git_vector *vspec,
+ struct pathspec_match_context *ctxt,
+ const char *path0,
+ const char *path1)
+{
+ int result = GIT_ENOTFOUND;
+ size_t i = 0;
+ const git_attr_fnmatch *match;
+
+ git_vector_foreach(vspec, i, match) {
+ if (path0 && (result = pathspec_match_one(match, ctxt, path0)) >= 0)
+ break;
+ if (path1 && (result = pathspec_match_one(match, ctxt, path1)) >= 0)
+ break;
+ }
+
+ *matched_at = i;
+ return result;
+}
+
+/* match a path against the vectorized pathspec */
+bool git_pathspec__match(
+ const git_vector *vspec,
+ const char *path,
+ bool disable_fnmatch,
+ bool casefold,
+ const char **matched_pathspec,
+ size_t *matched_at)
+{
+ int result;
+ size_t pos;
+ struct pathspec_match_context ctxt;
+
+ if (matched_pathspec)
+ *matched_pathspec = NULL;
+ if (matched_at)
+ *matched_at = GIT_PATHSPEC_NOMATCH;
+
+ if (!vspec || !vspec->length)
+ return true;
+
+ pathspec_match_context_init(&ctxt, disable_fnmatch, casefold);
+
+ result = git_pathspec__match_at(&pos, vspec, &ctxt, path, NULL);
+ if (result >= 0) {
+ if (matched_pathspec) {
+ const git_attr_fnmatch *match = git_vector_get(vspec, pos);
+ *matched_pathspec = match->pattern;
+ }
+
+ if (matched_at)
+ *matched_at = pos;
+ }
+
+ return (result > 0);
+}
+
+
+int git_pathspec__init(git_pathspec *ps, const git_strarray *paths)
+{
+ int error = 0;
+
+ memset(ps, 0, sizeof(*ps));
+
+ ps->prefix = git_pathspec_prefix(paths);
+
+ if ((error = git_pool_init(&ps->pool, 1, 0)) < 0 ||
+ (error = git_pathspec__vinit(&ps->pathspec, paths, &ps->pool)) < 0)
+ git_pathspec__clear(ps);
+
+ return error;
+}
+
+void git_pathspec__clear(git_pathspec *ps)
+{
+ git__free(ps->prefix);
+ git_pathspec__vfree(&ps->pathspec);
+ git_pool_clear(&ps->pool);
+ memset(ps, 0, sizeof(*ps));
+}
+
+int git_pathspec_new(git_pathspec **out, const git_strarray *pathspec)
+{
+ int error = 0;
+ git_pathspec *ps = git__malloc(sizeof(git_pathspec));
+ GITERR_CHECK_ALLOC(ps);
+
+ if ((error = git_pathspec__init(ps, pathspec)) < 0) {
+ git__free(ps);
+ return error;
+ }
+
+ GIT_REFCOUNT_INC(ps);
+ *out = ps;
+ return 0;
+}
+
+static void pathspec_free(git_pathspec *ps)
+{
+ git_pathspec__clear(ps);
+ git__free(ps);
+}
+
+void git_pathspec_free(git_pathspec *ps)
+{
+ if (!ps)
+ return;
+ GIT_REFCOUNT_DEC(ps, pathspec_free);
+}
+
+int git_pathspec_matches_path(
+ const git_pathspec *ps, uint32_t flags, const char *path)
+{
+ bool no_fnmatch = (flags & GIT_PATHSPEC_NO_GLOB) != 0;
+ bool casefold = (flags & GIT_PATHSPEC_IGNORE_CASE) != 0;
+
+ assert(ps && path);
+
+ return (0 != git_pathspec__match(
+ &ps->pathspec, path, no_fnmatch, casefold, NULL, NULL));
+}
+
+static void pathspec_match_free(git_pathspec_match_list *m)
+{
+ git_pathspec_free(m->pathspec);
+ m->pathspec = NULL;
+
+ git_array_clear(m->matches);
+ git_array_clear(m->failures);
+ git_pool_clear(&m->pool);
+ git__free(m);
+}
+
+static git_pathspec_match_list *pathspec_match_alloc(
+ git_pathspec *ps, int datatype)
+{
+ git_pathspec_match_list *m = git__calloc(1, sizeof(git_pathspec_match_list));
+
+ if (m != NULL && git_pool_init(&m->pool, 1, 0) < 0) {
+ pathspec_match_free(m);
+ m = NULL;
+ }
+
+ /* need to keep reference to pathspec and increment refcount because
+ * failures array stores pointers to the pattern strings of the
+ * pathspec that had no matches
+ */
+ GIT_REFCOUNT_INC(ps);
+ m->pathspec = ps;
+ m->datatype = datatype;
+
+ return m;
+}
+
+GIT_INLINE(size_t) pathspec_mark_pattern(git_bitvec *used, size_t pos)
+{
+ if (!git_bitvec_get(used, pos)) {
+ git_bitvec_set(used, pos, true);
+ return 1;
+ }
+
+ return 0;
+}
+
+static size_t pathspec_mark_remaining(
+ git_bitvec *used,
+ git_vector *patterns,
+ struct pathspec_match_context *ctxt,
+ size_t start,
+ const char *path0,
+ const char *path1)
+{
+ size_t count = 0;
+
+ if (path1 == path0)
+ path1 = NULL;
+
+ for (; start < patterns->length; ++start) {
+ const git_attr_fnmatch *pat = git_vector_get(patterns, start);
+
+ if (git_bitvec_get(used, start))
+ continue;
+
+ if (path0 && pathspec_match_one(pat, ctxt, path0) > 0)
+ count += pathspec_mark_pattern(used, start);
+ else if (path1 && pathspec_match_one(pat, ctxt, path1) > 0)
+ count += pathspec_mark_pattern(used, start);
+ }
+
+ return count;
+}
+
+static int pathspec_build_failure_array(
+ git_pathspec_string_array_t *failures,
+ git_vector *patterns,
+ git_bitvec *used,
+ git_pool *pool)
+{
+ size_t pos;
+ char **failed;
+ const git_attr_fnmatch *pat;
+
+ for (pos = 0; pos < patterns->length; ++pos) {
+ if (git_bitvec_get(used, pos))
+ continue;
+
+ if ((failed = git_array_alloc(*failures)) == NULL)
+ return -1;
+
+ pat = git_vector_get(patterns, pos);
+
+ if ((*failed = git_pool_strdup(pool, pat->pattern)) == NULL)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int pathspec_match_from_iterator(
+ git_pathspec_match_list **out,
+ git_iterator *iter,
+ uint32_t flags,
+ git_pathspec *ps)
+{
+ int error = 0;
+ git_pathspec_match_list *m = NULL;
+ const git_index_entry *entry = NULL;
+ struct pathspec_match_context ctxt;
+ git_vector *patterns = &ps->pathspec;
+ bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0;
+ bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0;
+ size_t pos, used_ct = 0, found_files = 0;
+ git_index *index = NULL;
+ git_bitvec used_patterns;
+ char **file;
+
+ if (git_bitvec_init(&used_patterns, patterns->length) < 0)
+ return -1;
+
+ if (out) {
+ *out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_STRINGS);
+ GITERR_CHECK_ALLOC(m);
+ }
+
+ if ((error = git_iterator_reset(iter, ps->prefix, ps->prefix)) < 0)
+ goto done;
+
+ if (git_iterator_type(iter) == GIT_ITERATOR_TYPE_WORKDIR &&
+ (error = git_repository_index__weakptr(
+ &index, git_iterator_owner(iter))) < 0)
+ goto done;
+
+ pathspec_match_context_init(
+ &ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0,
+ git_iterator_ignore_case(iter));
+
+ while (!(error = git_iterator_advance(&entry, iter))) {
+ /* search for match with entry->path */
+ int result = git_pathspec__match_at(
+ &pos, patterns, &ctxt, entry->path, NULL);
+
+ /* no matches for this path */
+ if (result < 0)
+ continue;
+
+ /* if result was a negative pattern match, then don't list file */
+ if (!result) {
+ used_ct += pathspec_mark_pattern(&used_patterns, pos);
+ continue;
+ }
+
+ /* check if path is ignored and untracked */
+ if (index != NULL &&
+ git_iterator_current_is_ignored(iter) &&
+ git_index__find_pos(NULL, index, entry->path, 0, GIT_INDEX_STAGE_ANY) < 0)
+ continue;
+
+ /* mark the matched pattern as used */
+ used_ct += pathspec_mark_pattern(&used_patterns, pos);
+ ++found_files;
+
+ /* if find_failures is on, check if any later patterns also match */
+ if (find_failures && used_ct < patterns->length)
+ used_ct += pathspec_mark_remaining(
+ &used_patterns, patterns, &ctxt, pos + 1, entry->path, NULL);
+
+ /* if only looking at failures, exit early or just continue */
+ if (failures_only || !out) {
+ if (used_ct == patterns->length)
+ break;
+ continue;
+ }
+
+ /* insert matched path into matches array */
+ if ((file = (char **)git_array_alloc(m->matches)) == NULL ||
+ (*file = git_pool_strdup(&m->pool, entry->path)) == NULL) {
+ error = -1;
+ goto done;
+ }
+ }
+
+ if (error < 0 && error != GIT_ITEROVER)
+ goto done;
+ error = 0;
+
+ /* insert patterns that had no matches into failures array */
+ if (find_failures && used_ct < patterns->length &&
+ (error = pathspec_build_failure_array(
+ &m->failures, patterns, &used_patterns, &m->pool)) < 0)
+ goto done;
+
+ /* if every pattern failed to match, then we have failed */
+ if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_files) {
+ giterr_set(GITERR_INVALID, "No matching files were found");
+ error = GIT_ENOTFOUND;
+ }
+
+done:
+ git_bitvec_free(&used_patterns);
+
+ if (error < 0) {
+ pathspec_match_free(m);
+ if (out) *out = NULL;
+ }
+
+ return error;
+}
+
+static git_iterator_flag_t pathspec_match_iter_flags(uint32_t flags)
+{
+ git_iterator_flag_t f = 0;
+
+ if ((flags & GIT_PATHSPEC_IGNORE_CASE) != 0)
+ f |= GIT_ITERATOR_IGNORE_CASE;
+ else if ((flags & GIT_PATHSPEC_USE_CASE) != 0)
+ f |= GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ return f;
+}
+
+int git_pathspec_match_workdir(
+ git_pathspec_match_list **out,
+ git_repository *repo,
+ uint32_t flags,
+ git_pathspec *ps)
+{
+ int error = 0;
+ git_iterator *iter;
+
+ assert(repo);
+
+ if (!(error = git_iterator_for_workdir(
+ &iter, repo, pathspec_match_iter_flags(flags), NULL, NULL))) {
+
+ error = pathspec_match_from_iterator(out, iter, flags, ps);
+
+ git_iterator_free(iter);
+ }
+
+ return error;
+}
+
+int git_pathspec_match_index(
+ git_pathspec_match_list **out,
+ git_index *index,
+ uint32_t flags,
+ git_pathspec *ps)
+{
+ int error = 0;
+ git_iterator *iter;
+
+ assert(index);
+
+ if (!(error = git_iterator_for_index(
+ &iter, index, pathspec_match_iter_flags(flags), NULL, NULL))) {
+
+ error = pathspec_match_from_iterator(out, iter, flags, ps);
+
+ git_iterator_free(iter);
+ }
+
+ return error;
+}
+
+int git_pathspec_match_tree(
+ git_pathspec_match_list **out,
+ git_tree *tree,
+ uint32_t flags,
+ git_pathspec *ps)
+{
+ int error = 0;
+ git_iterator *iter;
+
+ assert(tree);
+
+ if (!(error = git_iterator_for_tree(
+ &iter, tree, pathspec_match_iter_flags(flags), NULL, NULL))) {
+
+ error = pathspec_match_from_iterator(out, iter, flags, ps);
+
+ git_iterator_free(iter);
+ }
+
+ return error;
+}
+
+int git_pathspec_match_diff(
+ git_pathspec_match_list **out,
+ git_diff *diff,
+ uint32_t flags,
+ git_pathspec *ps)
+{
+ int error = 0;
+ git_pathspec_match_list *m = NULL;
+ struct pathspec_match_context ctxt;
+ git_vector *patterns = &ps->pathspec;
+ bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0;
+ bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0;
+ size_t i, pos, used_ct = 0, found_deltas = 0;
+ const git_diff_delta *delta, **match;
+ git_bitvec used_patterns;
+
+ assert(diff);
+
+ if (git_bitvec_init(&used_patterns, patterns->length) < 0)
+ return -1;
+
+ if (out) {
+ *out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_DIFF);
+ GITERR_CHECK_ALLOC(m);
+ }
+
+ pathspec_match_context_init(
+ &ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0,
+ git_diff_is_sorted_icase(diff));
+
+ git_vector_foreach(&diff->deltas, i, delta) {
+ /* search for match with delta */
+ int result = git_pathspec__match_at(
+ &pos, patterns, &ctxt, delta->old_file.path, delta->new_file.path);
+
+ /* no matches for this path */
+ if (result < 0)
+ continue;
+
+ /* mark the matched pattern as used */
+ used_ct += pathspec_mark_pattern(&used_patterns, pos);
+
+ /* if result was a negative pattern match, then don't list file */
+ if (!result)
+ continue;
+
+ ++found_deltas;
+
+ /* if find_failures is on, check if any later patterns also match */
+ if (find_failures && used_ct < patterns->length)
+ used_ct += pathspec_mark_remaining(
+ &used_patterns, patterns, &ctxt, pos + 1,
+ delta->old_file.path, delta->new_file.path);
+
+ /* if only looking at failures, exit early or just continue */
+ if (failures_only || !out) {
+ if (used_ct == patterns->length)
+ break;
+ continue;
+ }
+
+ /* insert matched delta into matches array */
+ if (!(match = (const git_diff_delta **)git_array_alloc(m->matches))) {
+ error = -1;
+ goto done;
+ } else {
+ *match = delta;
+ }
+ }
+
+ /* insert patterns that had no matches into failures array */
+ if (find_failures && used_ct < patterns->length &&
+ (error = pathspec_build_failure_array(
+ &m->failures, patterns, &used_patterns, &m->pool)) < 0)
+ goto done;
+
+ /* if every pattern failed to match, then we have failed */
+ if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_deltas) {
+ giterr_set(GITERR_INVALID, "No matching deltas were found");
+ error = GIT_ENOTFOUND;
+ }
+
+done:
+ git_bitvec_free(&used_patterns);
+
+ if (error < 0) {
+ pathspec_match_free(m);
+ if (out) *out = NULL;
+ }
+
+ return error;
+}
+
+void git_pathspec_match_list_free(git_pathspec_match_list *m)
+{
+ if (m)
+ pathspec_match_free(m);
+}
+
+size_t git_pathspec_match_list_entrycount(
+ const git_pathspec_match_list *m)
+{
+ return m ? git_array_size(m->matches) : 0;
+}
+
+const char *git_pathspec_match_list_entry(
+ const git_pathspec_match_list *m, size_t pos)
+{
+ if (!m || m->datatype != PATHSPEC_DATATYPE_STRINGS ||
+ !git_array_valid_index(m->matches, pos))
+ return NULL;
+
+ return *((const char **)git_array_get(m->matches, pos));
+}
+
+const git_diff_delta *git_pathspec_match_list_diff_entry(
+ const git_pathspec_match_list *m, size_t pos)
+{
+ if (!m || m->datatype != PATHSPEC_DATATYPE_DIFF ||
+ !git_array_valid_index(m->matches, pos))
+ return NULL;
+
+ return *((const git_diff_delta **)git_array_get(m->matches, pos));
+}
+
+size_t git_pathspec_match_list_failed_entrycount(
+ const git_pathspec_match_list *m)
+{
+ return m ? git_array_size(m->failures) : 0;
+}
+
+const char * git_pathspec_match_list_failed_entry(
+ const git_pathspec_match_list *m, size_t pos)
+{
+ char **entry = m ? git_array_get(m->failures, pos) : NULL;
+
+ return entry ? *entry : NULL;
+}
+
diff --git a/src/pathspec.h b/src/pathspec.h
new file mode 100644
index 000000000..40cd21c3f
--- /dev/null
+++ b/src/pathspec.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 <git2/pathspec.h>
+#include "buffer.h"
+#include "vector.h"
+#include "pool.h"
+#include "array.h"
+
+/* public compiled pathspec */
+struct git_pathspec {
+ git_refcount rc;
+ char *prefix;
+ git_vector pathspec;
+ git_pool pool;
+};
+
+enum {
+ PATHSPEC_DATATYPE_STRINGS = 0,
+ PATHSPEC_DATATYPE_DIFF = 1,
+};
+
+typedef git_array_t(char *) git_pathspec_string_array_t;
+
+/* public interface to pathspec matching */
+struct git_pathspec_match_list {
+ git_pathspec *pathspec;
+ git_array_t(void *) matches;
+ git_pathspec_string_array_t failures;
+ git_pool pool;
+ int datatype;
+};
+
+/* what is the common non-wildcard prefix for all items in the pathspec */
+extern char *git_pathspec_prefix(const git_strarray *pathspec);
+
+/* is there anything in the spec that needs to be filtered on */
+extern bool git_pathspec_is_empty(const git_strarray *pathspec);
+
+/* build a vector of fnmatch patterns to evaluate efficiently */
+extern int git_pathspec__vinit(
+ git_vector *vspec, const git_strarray *strspec, git_pool *strpool);
+
+/* free data from the pathspec vector */
+extern void git_pathspec__vfree(git_vector *vspec);
+
+#define GIT_PATHSPEC_NOMATCH ((size_t)-1)
+
+/*
+ * Match a path against the vectorized pathspec.
+ * The matched pathspec is passed back into the `matched_pathspec` parameter,
+ * unless it is passed as NULL by the caller.
+ */
+extern bool git_pathspec__match(
+ const git_vector *vspec,
+ const char *path,
+ bool disable_fnmatch,
+ bool casefold,
+ const char **matched_pathspec,
+ size_t *matched_at);
+
+/* easy pathspec setup */
+
+extern int git_pathspec__init(git_pathspec *ps, const git_strarray *paths);
+
+extern void git_pathspec__clear(git_pathspec *ps);
+
+#endif
diff --git a/src/pkt.c b/src/pkt.c
deleted file mode 100644
index 215909055..000000000
--- a/src/pkt.c
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#include "common.h"
-
-#include "git2/types.h"
-#include "git2/errors.h"
-
-#include "pkt.h"
-#include "util.h"
-#include "netops.h"
-
-#include <ctype.h>
-
-#define PKT_LEN_SIZE 4
-
-static int flush_pkt(git_pkt **out)
-{
- git_pkt *pkt;
-
- pkt = git__malloc(sizeof(git_pkt));
- if (pkt == NULL)
- return GIT_ENOMEM;
-
- pkt->type = GIT_PKT_FLUSH;
- *out = pkt;
-
- return GIT_SUCCESS;
-}
-
-/*
- * Parse an other-ref line.
- */
-int ref_pkt(git_pkt **out, const char *line, size_t len)
-{
- git_pkt_ref *pkt;
- int error, has_caps = 0;
-
- pkt = git__malloc(sizeof(git_pkt_ref));
- if (pkt == NULL)
- return GIT_ENOMEM;
-
- memset(pkt, 0x0, sizeof(git_pkt_ref));
- pkt->type = GIT_PKT_REF;
- error = git_oid_fromstr(&pkt->head.oid, line);
- if (error < GIT_SUCCESS) {
- error = git__throw(error, "Failed to parse reference ID");
- goto out;
- }
-
- /* Check for a bit of consistency */
- if (line[GIT_OID_HEXSZ] != ' ') {
- error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse ref. No SP");
- goto out;
- }
-
- /* Jump from the name */
- line += GIT_OID_HEXSZ + 1;
- len -= (GIT_OID_HEXSZ + 1);
-
- if (strlen(line) < len)
- has_caps = 1;
-
- if (line[len - 1] == '\n')
- --len;
-
- pkt->head.name = git__malloc(len + 1);
- if (pkt->head.name == NULL) {
- error = GIT_ENOMEM;
- goto out;
- }
- memcpy(pkt->head.name, line, len);
- pkt->head.name[len] = '\0';
-
- if (has_caps) {
- pkt->capabilities = strchr(pkt->head.name, '\0') + 1;
- }
-
-out:
- if (error < GIT_SUCCESS)
- free(pkt);
- else
- *out = (git_pkt *)pkt;
-
- return error;
-}
-
-static ssize_t parse_len(const char *line)
-{
- char num[PKT_LEN_SIZE + 1];
- int i, error;
- long len;
- const char *num_end;
-
- memcpy(num, line, PKT_LEN_SIZE);
- num[PKT_LEN_SIZE] = '\0';
-
- for (i = 0; i < PKT_LEN_SIZE; ++i) {
- if (!isxdigit(num[i]))
- return GIT_ENOTNUM;
- }
-
- error = git__strtol32(&len, num, &num_end, 16);
- if (error < GIT_SUCCESS) {
- return error;
- }
-
- return (unsigned int) len;
-}
-
-/*
- * As per the documentation, the syntax is:
- *
- * pkt-line = data-pkt / flush-pkt
- * data-pkt = pkt-len pkt-payload
- * pkt-len = 4*(HEXDIG)
- * pkt-payload = (pkt-len -4)*(OCTET)
- * flush-pkt = "0000"
- *
- * Which means that the first four bytes are the length of the line,
- * in ASCII hexadecimal (including itself)
- */
-
-int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t bufflen)
-{
- int error = GIT_SUCCESS;
- size_t len;
-
- /* Not even enough for the length */
- if (bufflen > 0 && bufflen < PKT_LEN_SIZE)
- return GIT_ESHORTBUFFER;
-
- error = parse_len(line);
- if (error < GIT_SUCCESS) {
- return git__throw(error, "Failed to parse pkt length");
- }
-
- len = error;
-
- /*
- * If we were given a buffer length, then make sure there is
- * enough in the buffer to satisfy this line
- */
- if (bufflen > 0 && bufflen < len)
- return GIT_ESHORTBUFFER;
-
- line += PKT_LEN_SIZE;
- /*
- * TODO: How do we deal with empty lines? Try again? with the next
- * line?
- */
- if (len == PKT_LEN_SIZE) {
- *out = line;
- return GIT_SUCCESS;
- }
-
- if (len == 0) { /* Flush pkt */
- *out = line;
- return flush_pkt(head);
- }
-
- len -= PKT_LEN_SIZE; /* the encoded length includes its own size */
-
- /*
- * For now, we're just going to assume we're parsing references
- */
-
- error = ref_pkt(head, line, len);
- *out = line + len;
-
- return error;
-}
-
-void git_pkt_free(git_pkt *pkt)
-{
- if(pkt->type == GIT_PKT_REF) {
- git_pkt_ref *p = (git_pkt_ref *) pkt;
- free(p->head.name);
- }
-
- free(pkt);
-}
-
-int git_pkt_send_flush(int s)
-{
- char flush[] = "0000";
-
- return gitno_send(s, flush, STRLEN(flush), 0);
-}
diff --git a/src/pkt.h b/src/pkt.h
deleted file mode 100644
index 6dc5486cd..000000000
--- a/src/pkt.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#ifndef INCLUDE_pkt_h__
-#define INCLUDE_pkt_h__
-
-#include "common.h"
-#include "git2/net.h"
-
-enum git_pkt_type {
- GIT_PKT_CMD,
- GIT_PKT_FLUSH,
- GIT_PKT_REF,
- GIT_PKT_HAVE,
-};
-
-/* 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;
-
-int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len);
-int git_pkt_send_flush(int s);
-void git_pkt_free(git_pkt *pkt);
-
-#endif
diff --git a/src/pool.c b/src/pool.c
new file mode 100644
index 000000000..146f118b4
--- /dev/null
+++ b/src/pool.c
@@ -0,0 +1,330 @@
+#include "pool.h"
+#ifndef GIT_WIN32
+#include <unistd.h>
+#endif
+
+struct git_pool_page {
+ git_pool_page *next;
+ uint32_t size;
+ uint32_t avail;
+ char data[GIT_FLEX_ARRAY];
+};
+
+struct pool_freelist {
+ struct pool_freelist *next;
+};
+
+#define GIT_POOL_MIN_USABLE 4
+#define GIT_POOL_MIN_PAGESZ 2 * sizeof(void*)
+
+static void *pool_alloc_page(git_pool *pool, uint32_t size);
+static void pool_insert_page(git_pool *pool, git_pool_page *page);
+
+int git_pool_init(
+ git_pool *pool, uint32_t item_size, uint32_t items_per_page)
+{
+ assert(pool);
+
+ if (!item_size)
+ item_size = 1;
+ /* round up item_size for decent object alignment */
+ if (item_size > 4)
+ item_size = (item_size + 7) & ~7;
+ else if (item_size == 3)
+ item_size = 4;
+
+ if (!items_per_page)
+ items_per_page = git_pool__suggest_items_per_page(item_size);
+ if (item_size * items_per_page < GIT_POOL_MIN_PAGESZ)
+ items_per_page = (GIT_POOL_MIN_PAGESZ + item_size - 1) / item_size;
+
+ memset(pool, 0, sizeof(git_pool));
+ pool->item_size = item_size;
+ pool->page_size = item_size * items_per_page;
+
+ return 0;
+}
+
+void git_pool_clear(git_pool *pool)
+{
+ git_pool_page *scan, *next;
+
+ for (scan = pool->open; scan != NULL; scan = next) {
+ next = scan->next;
+ git__free(scan);
+ }
+ pool->open = NULL;
+
+ for (scan = pool->full; scan != NULL; scan = next) {
+ next = scan->next;
+ git__free(scan);
+ }
+ pool->full = NULL;
+
+ pool->free_list = NULL;
+
+ pool->items = 0;
+
+ pool->has_string_alloc = 0;
+ pool->has_multi_item_alloc = 0;
+ pool->has_large_page_alloc = 0;
+}
+
+void git_pool_swap(git_pool *a, git_pool *b)
+{
+ git_pool temp;
+
+ if (a == b)
+ return;
+
+ memcpy(&temp, a, sizeof(temp));
+ memcpy(a, b, sizeof(temp));
+ memcpy(b, &temp, sizeof(temp));
+}
+
+static void pool_insert_page(git_pool *pool, git_pool_page *page)
+{
+ git_pool_page *scan;
+
+ /* If there are no open pages or this page has the most open space,
+ * insert it at the beginning of the list. This is the common case.
+ */
+ if (pool->open == NULL || pool->open->avail < page->avail) {
+ page->next = pool->open;
+ pool->open = page;
+ return;
+ }
+
+ /* Otherwise insert into sorted position. */
+ for (scan = pool->open;
+ scan->next && scan->next->avail > page->avail;
+ scan = scan->next);
+ page->next = scan->next;
+ scan->next = page;
+}
+
+static void *pool_alloc_page(git_pool *pool, uint32_t size)
+{
+ git_pool_page *page;
+ uint32_t alloc_size;
+
+ if (size <= pool->page_size)
+ alloc_size = pool->page_size;
+ else {
+ alloc_size = size;
+ pool->has_large_page_alloc = 1;
+ }
+
+ page = git__calloc(1, alloc_size + sizeof(git_pool_page));
+ if (!page)
+ return NULL;
+
+ page->size = alloc_size;
+ page->avail = alloc_size - size;
+
+ if (page->avail > 0)
+ pool_insert_page(pool, page);
+ else {
+ page->next = pool->full;
+ pool->full = page;
+ }
+
+ pool->items++;
+
+ return page->data;
+}
+
+GIT_INLINE(void) pool_remove_page(
+ git_pool *pool, git_pool_page *page, git_pool_page *prev)
+{
+ if (prev == NULL)
+ pool->open = page->next;
+ else
+ prev->next = page->next;
+}
+
+void *git_pool_malloc(git_pool *pool, uint32_t items)
+{
+ git_pool_page *scan = pool->open, *prev;
+ uint32_t size = items * pool->item_size;
+ void *ptr = NULL;
+
+ pool->has_string_alloc = 0;
+ if (items > 1)
+ pool->has_multi_item_alloc = 1;
+ else if (pool->free_list != NULL) {
+ ptr = pool->free_list;
+ pool->free_list = ((struct pool_freelist *)pool->free_list)->next;
+ return ptr;
+ }
+
+ /* just add a block if there is no open one to accomodate this */
+ if (size >= pool->page_size || !scan || scan->avail < size)
+ return pool_alloc_page(pool, size);
+
+ pool->items++;
+
+ /* find smallest block in free list with space */
+ for (scan = pool->open, prev = NULL;
+ scan->next && scan->next->avail >= size;
+ prev = scan, scan = scan->next);
+
+ /* allocate space from the block */
+ ptr = &scan->data[scan->size - scan->avail];
+ scan->avail -= size;
+
+ /* move to full list if there is almost no space left */
+ if (scan->avail < pool->item_size || scan->avail < GIT_POOL_MIN_USABLE) {
+ pool_remove_page(pool, scan, prev);
+ scan->next = pool->full;
+ pool->full = scan;
+ }
+ /* reorder list if block is now smaller than the one after it */
+ else if (scan->next != NULL && scan->next->avail > scan->avail) {
+ pool_remove_page(pool, scan, prev);
+ pool_insert_page(pool, scan);
+ }
+
+ return ptr;
+}
+
+char *git_pool_strndup(git_pool *pool, const char *str, size_t n)
+{
+ char *ptr = NULL;
+
+ assert(pool && str && pool->item_size == sizeof(char));
+
+ if ((uint32_t)(n + 1) < n)
+ return NULL;
+
+ if ((ptr = git_pool_malloc(pool, (uint32_t)(n + 1))) != NULL) {
+ memcpy(ptr, str, n);
+ ptr[n] = '\0';
+ }
+
+ pool->has_string_alloc = 1;
+
+ return ptr;
+}
+
+char *git_pool_strdup(git_pool *pool, const char *str)
+{
+ assert(pool && str && pool->item_size == sizeof(char));
+
+ return git_pool_strndup(pool, str, strlen(str));
+}
+
+char *git_pool_strdup_safe(git_pool *pool, const char *str)
+{
+ return str ? git_pool_strdup(pool, str) : NULL;
+}
+
+char *git_pool_strcat(git_pool *pool, const char *a, const char *b)
+{
+ void *ptr;
+ size_t len_a, len_b;
+
+ assert(pool && a && b && pool->item_size == sizeof(char));
+
+ len_a = a ? strlen(a) : 0;
+ len_b = b ? strlen(b) : 0;
+
+ if ((ptr = git_pool_malloc(pool, (uint32_t)(len_a + len_b + 1))) != NULL) {
+ if (len_a)
+ memcpy(ptr, a, len_a);
+ if (len_b)
+ memcpy(((char *)ptr) + len_a, b, len_b);
+ *(((char *)ptr) + len_a + len_b) = '\0';
+ }
+ pool->has_string_alloc = 1;
+
+ return ptr;
+}
+
+void git_pool_free(git_pool *pool, void *ptr)
+{
+ struct pool_freelist *item = ptr;
+
+ assert(pool && pool->item_size >= sizeof(void*));
+
+ if (item) {
+ item->next = pool->free_list;
+ pool->free_list = item;
+ }
+}
+
+void git_pool_free_array(git_pool *pool, size_t count, void **ptrs)
+{
+ struct pool_freelist **items = (struct pool_freelist **)ptrs;
+ size_t i;
+
+ assert(pool && ptrs && pool->item_size >= sizeof(void*));
+
+ if (!count)
+ return;
+
+ for (i = count - 1; i > 0; --i)
+ items[i]->next = items[i - 1];
+
+ items[i]->next = pool->free_list;
+ pool->free_list = items[count - 1];
+}
+
+uint32_t git_pool__open_pages(git_pool *pool)
+{
+ uint32_t ct = 0;
+ git_pool_page *scan;
+ for (scan = pool->open; scan != NULL; scan = scan->next) ct++;
+ return ct;
+}
+
+uint32_t git_pool__full_pages(git_pool *pool)
+{
+ uint32_t ct = 0;
+ git_pool_page *scan;
+ for (scan = pool->full; scan != NULL; scan = scan->next) ct++;
+ return ct;
+}
+
+bool git_pool__ptr_in_pool(git_pool *pool, void *ptr)
+{
+ git_pool_page *scan;
+ for (scan = pool->open; scan != NULL; scan = scan->next)
+ if ((void *)scan->data <= ptr &&
+ (void *)(((char *)scan->data) + scan->size) > ptr)
+ return true;
+ for (scan = pool->full; scan != NULL; scan = scan->next)
+ if ((void *)scan->data <= ptr &&
+ (void *)(((char *)scan->data) + scan->size) > ptr)
+ return true;
+ return false;
+}
+
+uint32_t git_pool__system_page_size(void)
+{
+ static uint32_t size = 0;
+
+ if (!size) {
+#ifdef GIT_WIN32
+ SYSTEM_INFO info;
+ GetSystemInfo(&info);
+ size = (uint32_t)info.dwPageSize;
+#elif defined(__amigaos4__)
+ size = (uint32_t)4096; /* 4K as there is no global value we can query */
+#else
+ size = (uint32_t)sysconf(_SC_PAGE_SIZE);
+#endif
+
+ size -= 2 * sizeof(void *); /* allow space for malloc overhead */
+ }
+
+ return size;
+}
+
+uint32_t git_pool__suggest_items_per_page(uint32_t item_size)
+{
+ uint32_t page_bytes =
+ git_pool__system_page_size() - sizeof(git_pool_page);
+ return page_bytes / item_size;
+}
+
diff --git a/src/pool.h b/src/pool.h
new file mode 100644
index 000000000..5ac9b764f
--- /dev/null
+++ b/src/pool.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_pool_h__
+#define INCLUDE_pool_h__
+
+#include "common.h"
+
+typedef struct git_pool_page git_pool_page;
+
+/**
+ * Chunked allocator.
+ *
+ * A `git_pool` can be used when you want to cheaply allocate
+ * multiple items of the same type and are willing to free them
+ * all together with a single call. The two most common cases
+ * are a set of fixed size items (such as lots of OIDs) or a
+ * bunch of strings.
+ *
+ * Internally, a `git_pool` allocates pages of memory and then
+ * deals out blocks from the trailing unused portion of each page.
+ * The pages guarantee that the number of actual allocations done
+ * will be much smaller than the number of items needed.
+ *
+ * For examples of how to set up a `git_pool` see `git_pool_init`.
+ */
+typedef struct {
+ git_pool_page *open; /* pages with space left */
+ git_pool_page *full; /* pages with no space left */
+ void *free_list; /* optional: list of freed blocks */
+ uint32_t item_size; /* size of single alloc unit in bytes */
+ uint32_t page_size; /* size of page in bytes */
+ uint32_t items;
+ unsigned has_string_alloc : 1; /* was the strdup function used */
+ unsigned has_multi_item_alloc : 1; /* was items ever > 1 in malloc */
+ unsigned has_large_page_alloc : 1; /* are any pages > page_size */
+} git_pool;
+
+#define GIT_POOL_INIT_STRINGPOOL { 0, 0, 0, 1, 4000, 0, 0, 0, 0 }
+
+/**
+ * Initialize a pool.
+ *
+ * To allocation strings, use like this:
+ *
+ * git_pool_init(&string_pool, 1, 0);
+ * my_string = git_pool_strdup(&string_pool, your_string);
+ *
+ * To allocate items of fixed size, use like this:
+ *
+ * git_pool_init(&pool, sizeof(item), 0);
+ * my_item = git_pool_malloc(&pool, 1);
+ *
+ * Of course, you can use this in other ways, but those are the
+ * two most common patterns.
+ */
+extern int git_pool_init(
+ git_pool *pool, uint32_t item_size, uint32_t items_per_page);
+
+/**
+ * Free all items in pool
+ */
+extern void git_pool_clear(git_pool *pool);
+
+/**
+ * Swap two pools with one another
+ */
+extern void git_pool_swap(git_pool *a, git_pool *b);
+
+/**
+ * Allocate space for one or more items from a pool.
+ */
+extern void *git_pool_malloc(git_pool *pool, uint32_t items);
+
+/**
+ * Allocate space and zero it out.
+ */
+GIT_INLINE(void *) git_pool_mallocz(git_pool *pool, uint32_t items)
+{
+ void *ptr = git_pool_malloc(pool, items);
+ if (ptr)
+ memset(ptr, 0, (size_t)items * (size_t)pool->item_size);
+ return ptr;
+}
+
+/**
+ * Allocate space and duplicate string data into it.
+ *
+ * This is allowed only for pools with item_size == sizeof(char)
+ */
+extern char *git_pool_strndup(git_pool *pool, const char *str, size_t n);
+
+/**
+ * Allocate space and duplicate a string into it.
+ *
+ * This is allowed only for pools with item_size == sizeof(char)
+ */
+extern char *git_pool_strdup(git_pool *pool, const char *str);
+
+/**
+ * Allocate space and duplicate a string into it, NULL is no error.
+ *
+ * This is allowed only for pools with item_size == sizeof(char)
+ */
+extern char *git_pool_strdup_safe(git_pool *pool, const char *str);
+
+/**
+ * Allocate space for the concatenation of two strings.
+ *
+ * This is allowed only for pools with item_size == sizeof(char)
+ */
+extern char *git_pool_strcat(git_pool *pool, const char *a, const char *b);
+
+/**
+ * Push a block back onto the free list for the pool.
+ *
+ * This is allowed only if the item_size is >= sizeof(void*).
+ *
+ * In some cases, it is helpful to "release" an allocated block
+ * for reuse. Pools don't support a general purpose free, but
+ * they will keep a simple free blocks linked list provided the
+ * native block size is large enough to hold a void pointer
+ */
+extern void git_pool_free(git_pool *pool, void *ptr);
+
+/**
+ * Push an array of pool allocated blocks efficiently onto the free list.
+ *
+ * This has the same constraints as `git_pool_free()` above.
+ */
+extern void git_pool_free_array(git_pool *pool, size_t count, void **ptrs);
+
+/*
+ * Misc utilities
+ */
+
+extern uint32_t git_pool__open_pages(git_pool *pool);
+
+extern uint32_t git_pool__full_pages(git_pool *pool);
+
+extern bool git_pool__ptr_in_pool(git_pool *pool, void *ptr);
+
+extern uint32_t git_pool__system_page_size(void);
+
+extern uint32_t git_pool__suggest_items_per_page(uint32_t item_size);
+
+#endif
diff --git a/src/posix.c b/src/posix.c
index 4bb8c3246..7aeb0e6c1 100644
--- a/src/posix.c
+++ b/src/posix.c
@@ -1,28 +1,172 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
#include "common.h"
#include "posix.h"
#include "path.h"
#include <stdio.h>
#include <ctype.h>
-int p_open(const char *path, int flags)
+#ifndef GIT_WIN32
+
+#ifdef NO_ADDRINFO
+
+int p_getaddrinfo(
+ const char *host,
+ const char *port,
+ struct addrinfo *hints,
+ struct addrinfo **info)
+{
+ struct addrinfo *ainfo, *ai;
+ int p = 0;
+
+ GIT_UNUSED(hints);
+
+ if ((ainfo = malloc(sizeof(struct addrinfo))) == NULL)
+ return -1;
+
+ if ((ainfo->ai_hostent = gethostbyname(host)) == NULL) {
+ free(ainfo);
+ return -2;
+ }
+
+ ainfo->ai_servent = getservbyname(port, 0);
+
+ if (ainfo->ai_servent)
+ ainfo->ai_port = ainfo->ai_servent->s_port;
+ else
+ ainfo->ai_port = atol(port);
+
+ memcpy(&ainfo->ai_addr_in.sin_addr,
+ ainfo->ai_hostent->h_addr_list[0],
+ ainfo->ai_hostent->h_length);
+
+ ainfo->ai_protocol = 0;
+ ainfo->ai_socktype = hints->ai_socktype;
+ ainfo->ai_family = ainfo->ai_hostent->h_addrtype;
+ ainfo->ai_addr_in.sin_family = ainfo->ai_family;
+ ainfo->ai_addr_in.sin_port = ainfo->ai_port;
+ ainfo->ai_addr = (struct addrinfo *)&ainfo->ai_addr_in;
+ ainfo->ai_addrlen = sizeof(struct sockaddr_in);
+
+ *info = ainfo;
+
+ if (ainfo->ai_hostent->h_addr_list[1] == NULL) {
+ ainfo->ai_next = NULL;
+ return 0;
+ }
+
+ ai = ainfo;
+
+ for (p = 1; ainfo->ai_hostent->h_addr_list[p] != NULL; p++) {
+ ai->ai_next = malloc(sizeof(struct addrinfo));
+ memcpy(&ai->ai_next, ainfo, sizeof(struct addrinfo));
+ memcpy(&ai->ai_next->ai_addr_in.sin_addr,
+ ainfo->ai_hostent->h_addr_list[p],
+ ainfo->ai_hostent->h_length);
+ ai->ai_next->ai_addr = (struct addrinfo *)&ai->ai_next->ai_addr_in;
+ ai = ai->ai_next;
+ }
+
+ ai->ai_next = NULL;
+ return 0;
+}
+
+void p_freeaddrinfo(struct addrinfo *info)
+{
+ struct addrinfo *p, *next;
+
+ p = info;
+
+ while(p != NULL) {
+ next = p->ai_next;
+ free(p);
+ p = next;
+ }
+}
+
+const char *p_gai_strerror(int ret)
+{
+ switch(ret) {
+ case -1: return "Out of memory"; break;
+ case -2: return "Address lookup failed"; break;
+ default: return "Unknown error"; break;
+ }
+}
+
+#endif /* NO_ADDRINFO */
+
+int p_open(const char *path, volatile int flags, ...)
+{
+ mode_t mode = 0;
+
+ if (flags & O_CREAT) {
+ va_list arg_list;
+
+ va_start(arg_list, flags);
+ mode = (mode_t)va_arg(arg_list, int);
+ va_end(arg_list);
+ }
+
+ return open(path, flags | O_BINARY | O_CLOEXEC, mode);
+}
+
+int p_creat(const char *path, mode_t mode)
{
- return open(path, flags | O_BINARY);
+ return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_CLOEXEC, mode);
+}
+
+int p_getcwd(char *buffer_out, size_t size)
+{
+ char *cwd_buffer;
+
+ assert(buffer_out && size > 0);
+
+ cwd_buffer = getcwd(buffer_out, size);
+
+ if (cwd_buffer == NULL)
+ return -1;
+
+ git_path_mkposix(buffer_out);
+ git_path_string_to_dir(buffer_out, size); /* append trailing slash */
+
+ return 0;
}
-int p_creat(const char *path, int mode)
+int p_rename(const char *from, const char *to)
{
- return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, mode);
+ if (!link(from, to)) {
+ p_unlink(from);
+ return 0;
+ }
+
+ if (!rename(from, to))
+ return 0;
+
+ return -1;
}
+#endif /* GIT_WIN32 */
+
int p_read(git_file fd, void *buf, size_t cnt)
{
char *b = buf;
+
while (cnt) {
- ssize_t r = read(fd, b, cnt);
+ ssize_t r;
+#ifdef GIT_WIN32
+ assert((size_t)((unsigned int)cnt) == cnt);
+ r = read(fd, b, (unsigned int)cnt);
+#else
+ r = read(fd, b, cnt);
+#endif
if (r < 0) {
if (errno == EINTR || errno == EAGAIN)
continue;
- return GIT_EOSERR;
+ return -1;
}
if (!r)
break;
@@ -35,40 +179,71 @@ int p_read(git_file fd, void *buf, size_t cnt)
int p_write(git_file fd, const void *buf, size_t cnt)
{
const char *b = buf;
+
while (cnt) {
- ssize_t r = write(fd, b, cnt);
+ ssize_t r;
+#ifdef GIT_WIN32
+ assert((size_t)((unsigned int)cnt) == cnt);
+ r = write(fd, b, (unsigned int)cnt);
+#else
+ r = write(fd, b, cnt);
+#endif
if (r < 0) {
- if (errno == EINTR || errno == EAGAIN)
+ if (errno == EINTR || GIT_ISBLOCKED(errno))
continue;
- return GIT_EOSERR;
+ return -1;
}
if (!r) {
errno = EPIPE;
- return GIT_EOSERR;
+ return -1;
}
cnt -= r;
b += r;
}
- return GIT_SUCCESS;
+ return 0;
}
-int p_getcwd(char *buffer_out, size_t size)
+#ifdef NO_MMAP
+
+#include "map.h"
+
+long git__page_size(void)
{
- char *cwd_buffer;
+ /* dummy; here we don't need any alignment anyway */
+ return 4096;
+}
- assert(buffer_out && size > 0);
-#ifdef GIT_WIN32
- cwd_buffer = _getcwd(buffer_out, size);
-#else
- cwd_buffer = getcwd(buffer_out, size);
-#endif
+int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset)
+{
+ GIT_MMAP_VALIDATE(out, len, prot, flags);
- if (cwd_buffer == NULL)
- return git__throw(GIT_EOSERR, "Failed to retrieve current working directory");
+ out->data = NULL;
+ out->len = 0;
- git_path_mkposix(buffer_out);
+ if ((prot & GIT_PROT_WRITE) && ((flags & GIT_MAP_TYPE) == GIT_MAP_SHARED)) {
+ giterr_set(GITERR_OS, "Trying to map shared-writeable");
+ return -1;
+ }
+
+ out->data = malloc(len);
+ GITERR_CHECK_ALLOC(out->data);
- git_path_join(buffer_out, buffer_out, ""); //Ensure the path ends with a trailing slash
- return GIT_SUCCESS;
+ if ((p_lseek(fd, offset, SEEK_SET) < 0) || ((size_t)p_read(fd, out->data, len) != len)) {
+ giterr_set(GITERR_OS, "mmap emulation failed");
+ return -1;
+ }
+
+ out->len = len;
+ return 0;
}
+
+int p_munmap(git_map *map)
+{
+ assert(map != NULL);
+ free(map->data);
+
+ return 0;
+}
+
+#endif
diff --git a/src/posix.h b/src/posix.h
index 622bf8351..965cd98d5 100644
--- a/src/posix.h
+++ b/src/posix.h
@@ -1,5 +1,8 @@
/*
- * posix.h - OS agnostic POSIX calls
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_posix_h__
#define INCLUDE_posix_h__
@@ -7,48 +10,132 @@
#include "common.h"
#include <fcntl.h>
#include <time.h>
+#include "fnmatch.h"
-#ifdef GIT_WIN32
-# include "win32/posix.h"
-#else
-# include "unix/posix.h"
-#endif
-
+#ifndef S_IFGITLINK
#define S_IFGITLINK 0160000
#define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK)
+#endif
+
+/* if S_ISGID is not defined, then don't try to set it */
+#ifndef S_ISGID
+#define S_ISGID 0
+#endif
#if !defined(O_BINARY)
#define O_BINARY 0
#endif
+#if !defined(O_CLOEXEC)
+#define O_CLOEXEC 0
+#endif
-typedef int git_file;
+/* Determine whether an errno value indicates that a read or write failed
+ * because the descriptor is blocked.
+ */
+#if defined(EWOULDBLOCK)
+#define GIT_ISBLOCKED(e) ((e) == EAGAIN || (e) == EWOULDBLOCK)
+#else
+#define GIT_ISBLOCKED(e) ((e) == EAGAIN)
+#endif
+typedef int git_file;
/**
* Standard POSIX Methods
*
* All the methods starting with the `p_` prefix are
- * direct ports of the standard POSIX methods.
+ * direct ports of the standard POSIX methods.
*
* Some of the methods are slightly wrapped to provide
* saner defaults. Some of these methods are emulated
- * in Windows platforns.
+ * in Windows platforms.
*
* Use your manpages to check the docs on these.
- * Straightforward
*/
-extern int p_open(const char *path, int flags);
-extern int p_creat(const char *path, int mode);
+
extern int p_read(git_file fd, void *buf, size_t cnt);
extern int p_write(git_file fd, const void *buf, size_t cnt);
-extern int p_getcwd(char *buffer_out, size_t size);
+#define p_fstat(f,b) fstat(f, b)
#define p_lseek(f,n,w) lseek(f, n, w)
+#define p_close(fd) close(fd)
+#define p_umask(m) umask(m)
+
+extern int p_open(const char *path, int flags, ...);
+extern int p_creat(const char *path, mode_t mode);
+extern int p_getcwd(char *buffer_out, size_t size);
+extern int p_rename(const char *from, const char *to);
+
+#ifndef GIT_WIN32
+
#define p_stat(p,b) stat(p, b)
-#define p_fstat(f,b) fstat(f, b)
#define p_chdir(p) chdir(p)
#define p_rmdir(p) rmdir(p)
#define p_chmod(p,m) chmod(p, m)
-#define p_close(fd) close(fd)
+#define p_access(p,m) access(p,m)
+#define p_ftruncate(fd, sz) ftruncate(fd, sz)
+#define p_recv(s,b,l,f) recv(s,b,l,f)
+#define p_send(s,b,l,f) send(s,b,l,f)
+typedef int GIT_SOCKET;
+#define INVALID_SOCKET -1
+
+#define p_localtime_r localtime_r
+#define p_gmtime_r gmtime_r
+
+#else
+
+typedef SOCKET GIT_SOCKET;
+extern struct tm * p_localtime_r (const time_t *timer, struct tm *result);
+extern struct tm * p_gmtime_r (const time_t *timer, struct tm *result);
+
+#endif
+
+/**
+ * Platform-dependent methods
+ */
+#ifdef GIT_WIN32
+# include "win32/posix.h"
+#else
+# include "unix/posix.h"
+#endif
+
+#include "strnlen.h"
+
+#ifdef NO_READDIR_R
+# include <dirent.h>
+GIT_INLINE(int) p_readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result)
+{
+ GIT_UNUSED(entry);
+ *result = readdir(dirp);
+ return 0;
+}
+#else /* NO_READDIR_R */
+# define p_readdir_r(d,e,r) readdir_r(d,e,r)
+#endif
+
+#ifdef NO_ADDRINFO
+# include <netdb.h>
+struct addrinfo {
+ struct hostent *ai_hostent;
+ struct servent *ai_servent;
+ struct sockaddr_in ai_addr_in;
+ struct sockaddr *ai_addr;
+ size_t ai_addrlen;
+ int ai_family;
+ int ai_socktype;
+ int ai_protocol;
+ long ai_port;
+ struct addrinfo *ai_next;
+};
+
+extern int p_getaddrinfo(const char *host, const char *port,
+ struct addrinfo *hints, struct addrinfo **info);
+extern void p_freeaddrinfo(struct addrinfo *info);
+extern const char *p_gai_strerror(int ret);
+#else
+# define p_getaddrinfo(a, b, c, d) getaddrinfo(a, b, c, d)
+# define p_freeaddrinfo(a) freeaddrinfo(a)
+# define p_gai_strerror(c) gai_strerror(c)
+#endif /* NO_ADDRINFO */
#endif
diff --git a/src/ppc/sha1.c b/src/ppc/sha1.c
deleted file mode 100644
index ec6a1926d..000000000
--- a/src/ppc/sha1.c
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * SHA-1 implementation.
- *
- * Copyright (C) 2005 Paul Mackerras <paulus@samba.org>
- *
- * This version assumes we are running on a big-endian machine.
- * It calls an external sha1_core() to process blocks of 64 bytes.
- */
-#include <stdio.h>
-#include <string.h>
-#include "sha1.h"
-
-extern void ppc_sha1_core(uint32_t *hash, const unsigned char *p,
- unsigned int nblocks);
-
-int ppc_SHA1_Init(ppc_SHA_CTX *c)
-{
- c->hash[0] = 0x67452301;
- c->hash[1] = 0xEFCDAB89;
- c->hash[2] = 0x98BADCFE;
- c->hash[3] = 0x10325476;
- c->hash[4] = 0xC3D2E1F0;
- c->len = 0;
- c->cnt = 0;
- return 0;
-}
-
-int ppc_SHA1_Update(ppc_SHA_CTX *c, const void *ptr, unsigned long n)
-{
- unsigned long nb;
- const unsigned char *p = ptr;
-
- c->len += (uint64_t) n << 3;
- while (n != 0) {
- if (c->cnt || n < 64) {
- nb = 64 - c->cnt;
- if (nb > n)
- nb = n;
- memcpy(&c->buf.b[c->cnt], p, nb);
- if ((c->cnt += nb) == 64) {
- ppc_sha1_core(c->hash, c->buf.b, 1);
- c->cnt = 0;
- }
- } else {
- nb = n >> 6;
- ppc_sha1_core(c->hash, p, nb);
- nb <<= 6;
- }
- n -= nb;
- p += nb;
- }
- return 0;
-}
-
-int ppc_SHA1_Final(unsigned char *hash, ppc_SHA_CTX *c)
-{
- unsigned int cnt = c->cnt;
-
- c->buf.b[cnt++] = 0x80;
- if (cnt > 56) {
- if (cnt < 64)
- memset(&c->buf.b[cnt], 0, 64 - cnt);
- ppc_sha1_core(c->hash, c->buf.b, 1);
- cnt = 0;
- }
- if (cnt < 56)
- memset(&c->buf.b[cnt], 0, 56 - cnt);
- c->buf.l[7] = c->len;
- ppc_sha1_core(c->hash, c->buf.b, 1);
- memcpy(hash, c->hash, 20);
- return 0;
-}
diff --git a/src/ppc/sha1.h b/src/ppc/sha1.h
deleted file mode 100644
index 70957110c..000000000
--- a/src/ppc/sha1.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * SHA-1 implementation.
- *
- * Copyright (C) 2005 Paul Mackerras <paulus@samba.org>
- */
-#include <stdint.h>
-
-typedef struct {
- uint32_t hash[5];
- uint32_t cnt;
- uint64_t len;
- union {
- 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 SHA_CTX ppc_SHA_CTX
-#define SHA1_Init ppc_SHA1_Init
-#define SHA1_Update ppc_SHA1_Update
-#define SHA1_Final ppc_SHA1_Final
diff --git a/src/ppc/sha1ppc.S b/src/ppc/sha1ppc.S
deleted file mode 100644
index 1711eef6e..000000000
--- a/src/ppc/sha1ppc.S
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * SHA-1 implementation for PowerPC.
- *
- * Copyright (C) 2005 Paul Mackerras <paulus@samba.org>
- */
-
-/*
- * PowerPC calling convention:
- * %r0 - volatile temp
- * %r1 - stack pointer.
- * %r2 - reserved
- * %r3-%r12 - Incoming arguments & return values; volatile.
- * %r13-%r31 - Callee-save registers
- * %lr - Return address, volatile
- * %ctr - volatile
- *
- * Register usage in this routine:
- * %r0 - temp
- * %r3 - argument (pointer to 5 words of SHA state)
- * %r4 - argument (pointer to data to hash)
- * %r5 - Constant K in SHA round (initially number of blocks to hash)
- * %r6-%r10 - Working copies of SHA variables A..E (actually E..A order)
- * %r11-%r26 - Data being hashed W[].
- * %r27-%r31 - Previous copies of A..E, for final add back.
- * %ctr - loop count
- */
-
-
-/*
- * We roll the registers for A, B, C, D, E around on each
- * iteration; E on iteration t is D on iteration t+1, and so on.
- * We use registers 6 - 10 for this. (Registers 27 - 31 hold
- * the previous values.)
- */
-#define RA(t) (((t)+4)%5+6)
-#define RB(t) (((t)+3)%5+6)
-#define RC(t) (((t)+2)%5+6)
-#define RD(t) (((t)+1)%5+6)
-#define RE(t) (((t)+0)%5+6)
-
-/* We use registers 11 - 26 for the W values */
-#define W(t) ((t)%16+11)
-
-/* Register 5 is used for the constant k */
-
-/*
- * The basic SHA-1 round function is:
- * E += ROTL(A,5) + F(B,C,D) + W[i] + K; B = ROTL(B,30)
- * Then the variables are renamed: (A,B,C,D,E) = (E,A,B,C,D).
- *
- * Every 20 rounds, the function F() and the constant K changes:
- * - 20 rounds of f0(b,c,d) = "bit wise b ? c : d" = (^b & d) + (b & c)
- * - 20 rounds of f1(b,c,d) = b^c^d = (b^d)^c
- * - 20 rounds of f2(b,c,d) = majority(b,c,d) = (b&d) + ((b^d)&c)
- * - 20 more rounds of f1(b,c,d)
- *
- * These are all scheduled for near-optimal performance on a G4.
- * The G4 is a 3-issue out-of-order machine with 3 ALUs, but it can only
- * *consider* starting the oldest 3 instructions per cycle. So to get
- * maximum performance out of it, you have to treat it as an in-order
- * machine. Which means interleaving the computation round t with the
- * computation of W[t+4].
- *
- * The first 16 rounds use W values loaded directly from memory, while the
- * remaining 64 use values computed from those first 16. We preload
- * 4 values before starting, so there are three kinds of rounds:
- * - The first 12 (all f0) also load the W values from memory.
- * - The next 64 compute W(i+4) in parallel. 8*f0, 20*f1, 20*f2, 16*f1.
- * - The last 4 (all f1) do not do anything with W.
- *
- * Therefore, we have 6 different round functions:
- * STEPD0_LOAD(t,s) - Perform round t and load W(s). s < 16
- * STEPD0_UPDATE(t,s) - Perform round t and compute W(s). s >= 16.
- * STEPD1_UPDATE(t,s)
- * STEPD2_UPDATE(t,s)
- * STEPD1(t) - Perform round t with no load or update.
- *
- * The G5 is more fully out-of-order, and can find the parallelism
- * by itself. The big limit is that it has a 2-cycle ALU latency, so
- * even though it's 2-way, the code has to be scheduled as if it's
- * 4-way, which can be a limit. To help it, we try to schedule the
- * read of RA(t) as late as possible so it doesn't stall waiting for
- * the previous round's RE(t-1), and we try to rotate RB(t) as early
- * as possible while reading RC(t) (= RB(t-1)) as late as possible.
- */
-
-/* the initial loads. */
-#define LOADW(s) \
- lwz W(s),(s)*4(%r4)
-
-/*
- * Perform a step with F0, and load W(s). Uses W(s) as a temporary
- * before loading it.
- * This is actually 10 instructions, which is an awkward fit.
- * It can execute grouped as listed, or delayed one instruction.
- * (If delayed two instructions, there is a stall before the start of the
- * second line.) Thus, two iterations take 7 cycles, 3.5 cycles per round.
- */
-#define STEPD0_LOAD(t,s) \
-add RE(t),RE(t),W(t); andc %r0,RD(t),RB(t); and W(s),RC(t),RB(t); \
-add RE(t),RE(t),%r0; rotlwi %r0,RA(t),5; rotlwi RB(t),RB(t),30; \
-add RE(t),RE(t),W(s); add %r0,%r0,%r5; lwz W(s),(s)*4(%r4); \
-add RE(t),RE(t),%r0
-
-/*
- * This is likewise awkward, 13 instructions. However, it can also
- * execute starting with 2 out of 3 possible moduli, so it does 2 rounds
- * in 9 cycles, 4.5 cycles/round.
- */
-#define STEPD0_UPDATE(t,s,loadk...) \
-add RE(t),RE(t),W(t); andc %r0,RD(t),RB(t); xor W(s),W((s)-16),W((s)-3); \
-add RE(t),RE(t),%r0; and %r0,RC(t),RB(t); xor W(s),W(s),W((s)-8); \
-add RE(t),RE(t),%r0; rotlwi %r0,RA(t),5; xor W(s),W(s),W((s)-14); \
-add RE(t),RE(t),%r5; loadk; rotlwi RB(t),RB(t),30; rotlwi W(s),W(s),1; \
-add RE(t),RE(t),%r0
-
-/* Nicely optimal. Conveniently, also the most common. */
-#define STEPD1_UPDATE(t,s,loadk...) \
-add RE(t),RE(t),W(t); xor %r0,RD(t),RB(t); xor W(s),W((s)-16),W((s)-3); \
-add RE(t),RE(t),%r5; loadk; xor %r0,%r0,RC(t); xor W(s),W(s),W((s)-8); \
-add RE(t),RE(t),%r0; rotlwi %r0,RA(t),5; xor W(s),W(s),W((s)-14); \
-add RE(t),RE(t),%r0; rotlwi RB(t),RB(t),30; rotlwi W(s),W(s),1
-
-/*
- * The naked version, no UPDATE, for the last 4 rounds. 3 cycles per.
- * We could use W(s) as a temp register, but we don't need it.
- */
-#define STEPD1(t) \
- add RE(t),RE(t),W(t); xor %r0,RD(t),RB(t); \
-rotlwi RB(t),RB(t),30; add RE(t),RE(t),%r5; xor %r0,%r0,RC(t); \
-add RE(t),RE(t),%r0; rotlwi %r0,RA(t),5; /* spare slot */ \
-add RE(t),RE(t),%r0
-
-/*
- * 14 instructions, 5 cycles per. The majority function is a bit
- * awkward to compute. This can execute with a 1-instruction delay,
- * but it causes a 2-instruction delay, which triggers a stall.
- */
-#define STEPD2_UPDATE(t,s,loadk...) \
-add RE(t),RE(t),W(t); and %r0,RD(t),RB(t); xor W(s),W((s)-16),W((s)-3); \
-add RE(t),RE(t),%r0; xor %r0,RD(t),RB(t); xor W(s),W(s),W((s)-8); \
-add RE(t),RE(t),%r5; loadk; and %r0,%r0,RC(t); xor W(s),W(s),W((s)-14); \
-add RE(t),RE(t),%r0; rotlwi %r0,RA(t),5; rotlwi W(s),W(s),1; \
-add RE(t),RE(t),%r0; rotlwi RB(t),RB(t),30
-
-#define STEP0_LOAD4(t,s) \
- STEPD0_LOAD(t,s); \
- STEPD0_LOAD((t+1),(s)+1); \
- STEPD0_LOAD((t)+2,(s)+2); \
- STEPD0_LOAD((t)+3,(s)+3)
-
-#define STEPUP4(fn, t, s, loadk...) \
- STEP##fn##_UPDATE(t,s,); \
- STEP##fn##_UPDATE((t)+1,(s)+1,); \
- STEP##fn##_UPDATE((t)+2,(s)+2,); \
- STEP##fn##_UPDATE((t)+3,(s)+3,loadk)
-
-#define STEPUP20(fn, t, s, loadk...) \
- STEPUP4(fn, t, s,); \
- STEPUP4(fn, (t)+4, (s)+4,); \
- STEPUP4(fn, (t)+8, (s)+8,); \
- STEPUP4(fn, (t)+12, (s)+12,); \
- STEPUP4(fn, (t)+16, (s)+16, loadk)
-
- .globl ppc_sha1_core
-ppc_sha1_core:
- stwu %r1,-80(%r1)
- stmw %r13,4(%r1)
-
- /* Load up A - E */
- lmw %r27,0(%r3)
-
- mtctr %r5
-
-1:
- LOADW(0)
- lis %r5,0x5a82
- mr RE(0),%r31
- LOADW(1)
- mr RD(0),%r30
- mr RC(0),%r29
- LOADW(2)
- ori %r5,%r5,0x7999 /* K0-19 */
- mr RB(0),%r28
- LOADW(3)
- mr RA(0),%r27
-
- STEP0_LOAD4(0, 4)
- STEP0_LOAD4(4, 8)
- STEP0_LOAD4(8, 12)
- STEPUP4(D0, 12, 16,)
- STEPUP4(D0, 16, 20, lis %r5,0x6ed9)
-
- ori %r5,%r5,0xeba1 /* K20-39 */
- STEPUP20(D1, 20, 24, lis %r5,0x8f1b)
-
- ori %r5,%r5,0xbcdc /* K40-59 */
- STEPUP20(D2, 40, 44, lis %r5,0xca62)
-
- ori %r5,%r5,0xc1d6 /* K60-79 */
- STEPUP4(D1, 60, 64,)
- STEPUP4(D1, 64, 68,)
- STEPUP4(D1, 68, 72,)
- STEPUP4(D1, 72, 76,)
- addi %r4,%r4,64
- STEPD1(76)
- STEPD1(77)
- STEPD1(78)
- STEPD1(79)
-
- /* Add results to original values */
- add %r31,%r31,RE(0)
- add %r30,%r30,RD(0)
- add %r29,%r29,RC(0)
- add %r28,%r28,RB(0)
- add %r27,%r27,RA(0)
-
- bdnz 1b
-
- /* Save final hash, restore registers, and return */
- stmw %r27,0(%r3)
- lmw %r13,4(%r1)
- addi %r1,%r1,80
- blr
diff --git a/src/pqueue.c b/src/pqueue.c
index 9883a35d8..54a60ca04 100644
--- a/src/pqueue.c
+++ b/src/pqueue.c
@@ -1,157 +1,117 @@
/*
- * BORING COPYRIGHT NOTICE:
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * This file is a heavily modified version of the priority queue found
- * in the Apache project and the libpqueue library.
- *
- * https://github.com/vy/libpqueue
- *
- * These are the original authors:
- *
- * Copyright 2010 Volkan Yazıcı <volkan.yazici@gmail.com>
- * Copyright 2006-2010 The Apache Software Foundation
- *
- * This file is licensed under the Apache 2.0 license, which
- * supposedly makes it compatible with the GPLv2 that libgit2 uses.
- *
- * Check the Apache license at:
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * So much licensing trouble for a binary heap. Oh well.
+ * 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 "pqueue.h"
+#include "util.h"
-#define left(i) ((i) << 1)
-#define right(i) (((i) << 1) + 1)
-#define parent(i) ((i) >> 1)
+#define PQUEUE_LCHILD_OF(I) (((I)<<1)+1)
+#define PQUEUE_RCHILD_OF(I) (((I)<<1)+2)
+#define PQUEUE_PARENT_OF(I) (((I)-1)>>1)
-int git_pqueue_init(git_pqueue *q, size_t n, git_pqueue_cmp cmppri)
+int git_pqueue_init(
+ git_pqueue *pq,
+ uint32_t flags,
+ size_t init_size,
+ git_vector_cmp cmp)
{
- assert(q);
+ int error = git_vector_init(pq, init_size, cmp);
- /* Need to allocate n+1 elements since element 0 isn't used. */
- if ((q->d = malloc((n + 1) * sizeof(void *))) == NULL)
- return GIT_ENOMEM;
+ if (!error) {
+ /* mix in our flags */
+ pq->flags |= flags;
- q->size = 1;
- q->avail = q->step = (n + 1); /* see comment above about n+1 */
- q->cmppri = cmppri;
+ /* if fixed size heap, pretend vector is exactly init_size elements */
+ if ((flags & GIT_PQUEUE_FIXED_SIZE) && init_size > 0)
+ pq->_alloc_size = init_size;
+ }
- return GIT_SUCCESS;
+ return error;
}
-
-void git_pqueue_free(git_pqueue *q)
+static void pqueue_up(git_pqueue *pq, size_t el)
{
- free(q->d);
- q->d = NULL;
-}
+ size_t parent_el = PQUEUE_PARENT_OF(el);
+ void *kid = git_vector_get(pq, el);
-void git_pqueue_clear(git_pqueue *q)
-{
- q->size = 1;
-}
-
-size_t git_pqueue_size(git_pqueue *q)
-{
- /* queue element 0 exists but doesn't count since it isn't used. */
- return (q->size - 1);
-}
+ while (el > 0) {
+ void *parent = pq->contents[parent_el];
+ if (pq->_cmp(parent, kid) <= 0)
+ break;
-static void bubble_up(git_pqueue *q, size_t i)
-{
- size_t parent_node;
- void *moving_node = q->d[i];
+ pq->contents[el] = parent;
- for (parent_node = parent(i);
- ((i > 1) && q->cmppri(q->d[parent_node], moving_node));
- i = parent_node, parent_node = parent(i)) {
- q->d[i] = q->d[parent_node];
- }
+ el = parent_el;
+ parent_el = PQUEUE_PARENT_OF(el);
+ }
- q->d[i] = moving_node;
+ pq->contents[el] = kid;
}
-
-static size_t maxchild(git_pqueue *q, size_t i)
+static void pqueue_down(git_pqueue *pq, size_t el)
{
- size_t child_node = left(i);
+ void *parent = git_vector_get(pq, el), *kid, *rkid;
- if (child_node >= q->size)
- return 0;
+ while (1) {
+ size_t kid_el = PQUEUE_LCHILD_OF(el);
- if ((child_node + 1) < q->size &&
- q->cmppri(q->d[child_node], q->d[child_node + 1]))
- child_node++; /* use right child instead of left */
+ if ((kid = git_vector_get(pq, kid_el)) == NULL)
+ break;
- return child_node;
-}
+ if ((rkid = git_vector_get(pq, kid_el + 1)) != NULL &&
+ pq->_cmp(kid, rkid) > 0) {
+ kid = rkid;
+ kid_el += 1;
+ }
+ if (pq->_cmp(parent, kid) <= 0)
+ break;
-static void percolate_down(git_pqueue *q, size_t i)
-{
- size_t child_node;
- void *moving_node = q->d[i];
-
- while ((child_node = maxchild(q, i)) != 0 &&
- q->cmppri(moving_node, q->d[child_node])) {
- q->d[i] = q->d[child_node];
- i = child_node;
- }
-
- q->d[i] = moving_node;
-}
-
-
-int git_pqueue_insert(git_pqueue *q, void *d)
-{
- void *tmp;
- size_t i;
- size_t newsize;
-
- if (!q) return 1;
+ pq->contents[el] = kid;
+ el = kid_el;
+ }
- /* allocate more memory if necessary */
- if (q->size >= q->avail) {
- newsize = q->size + q->step;
- if ((tmp = realloc(q->d, sizeof(void *) * newsize)) == NULL)
- return GIT_ENOMEM;
-
- q->d = tmp;
- q->avail = newsize;
- }
-
- /* insert item */
- i = q->size++;
- q->d[i] = d;
- bubble_up(q, i);
-
- return GIT_SUCCESS;
+ pq->contents[el] = parent;
}
-
-void *git_pqueue_pop(git_pqueue *q)
+int git_pqueue_insert(git_pqueue *pq, void *item)
{
- void *head;
-
- if (!q || q->size == 1)
- return NULL;
-
- head = q->d[1];
- q->d[1] = q->d[--q->size];
- percolate_down(q, 1);
-
- return head;
+ int error = 0;
+
+ /* if heap is full, pop the top element if new one should replace it */
+ if ((pq->flags & GIT_PQUEUE_FIXED_SIZE) != 0 &&
+ pq->length >= pq->_alloc_size)
+ {
+ /* skip this item if below min item in heap */
+ if (pq->_cmp(item, git_vector_get(pq, 0)) <= 0)
+ return 0;
+ /* otherwise remove the min item before inserting new */
+ (void)git_pqueue_pop(pq);
+ }
+
+ if (!(error = git_vector_insert(pq, item)))
+ pqueue_up(pq, pq->length - 1);
+
+ return error;
}
-
-void *git_pqueue_peek(git_pqueue *q)
+void *git_pqueue_pop(git_pqueue *pq)
{
- if (!q || q->size == 1)
- return NULL;
- return q->d[1];
+ void *rval = git_pqueue_get(pq, 0);
+
+ if (git_pqueue_size(pq) > 1) {
+ /* move last item to top of heap, shrink, and push item down */
+ pq->contents[0] = git_vector_last(pq);
+ git_vector_pop(pq);
+ pqueue_down(pq, 0);
+ } else {
+ /* all we need to do is shrink the heap in this case */
+ git_vector_pop(pq);
+ }
+
+ return rval;
}
diff --git a/src/pqueue.h b/src/pqueue.h
index ef8362c33..da7b74edf 100644
--- a/src/pqueue.h
+++ b/src/pqueue.h
@@ -1,97 +1,56 @@
/*
- * BORING COPYRIGHT NOTICE:
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * This file is a heavily modified version of the priority queue found
- * in the Apache project and the libpqueue library.
- *
- * https://github.com/vy/libpqueue
- *
- * These are the original authors:
- *
- * Copyright 2010 Volkan Yazıcı <volkan.yazici@gmail.com>
- * Copyright 2006-2010 The Apache Software Foundation
- *
- * This file is licensed under the Apache 2.0 license, which
- * supposedly makes it compatible with the GPLv2 that libgit2 uses.
- *
- * Check the Apache license at:
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * So much licensing trouble for a binary heap. Oh well.
+ * 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_pqueue_h__
#define INCLUDE_pqueue_h__
-/** callback functions to get/set/compare the priority of an element */
-typedef int (*git_pqueue_cmp)(void *a, void *b);
+#include "vector.h"
-/** the priority queue handle */
-typedef struct {
- size_t size, avail, step;
- git_pqueue_cmp cmppri;
- void **d;
-} git_pqueue;
+typedef git_vector git_pqueue;
+enum {
+ /* flag meaning: don't grow heap, keep highest values only */
+ GIT_PQUEUE_FIXED_SIZE = (GIT_VECTOR_FLAG_MAX << 1),
+};
/**
- * initialize the queue
- *
- * @param n the initial estimate of the number of queue items for which memory
- * should be preallocated
- * @param cmppri the callback function to compare two nodes of the queue
+ * Initialize priority queue
*
- * @Return the handle or NULL for insufficent memory
+ * @param pq The priority queue struct to initialize
+ * @param flags Flags (see above) to control queue behavior
+ * @param init_size The initial queue size
+ * @param cmp The entry priority comparison function
+ * @return 0 on success, <0 on error
*/
-int git_pqueue_init(git_pqueue *q, size_t n, git_pqueue_cmp cmppri);
+extern int git_pqueue_init(
+ git_pqueue *pq,
+ uint32_t flags,
+ size_t init_size,
+ git_vector_cmp cmp);
+#define git_pqueue_free git_vector_free
+#define git_pqueue_clear git_vector_clear
+#define git_pqueue_size git_vector_length
+#define git_pqueue_get git_vector_get
/**
- * free all memory used by the queue
- * @param q the queue
- */
-void git_pqueue_free(git_pqueue *q);
-
-/**
- * clear all the elements in the queue
- * @param q the queue
- */
-void git_pqueue_clear(git_pqueue *q);
-
-/**
- * return the size of the queue.
- * @param q the queue
- */
-size_t git_pqueue_size(git_pqueue *q);
-
-
-/**
- * insert an item into the queue.
- * @param q the queue
- * @param d the item
- * @return 0 on success
- */
-int git_pqueue_insert(git_pqueue *q, void *d);
-
-
-/**
- * pop the highest-ranking item from the queue.
- * @param p the queue
- * @param d where to copy the entry to
- * @return NULL on error, otherwise the entry
+ * Insert a new item into the queue
+ *
+ * @param pq The priority queue
+ * @param item Pointer to the item data
+ * @return 0 on success, <0 on failure
*/
-void *git_pqueue_pop(git_pqueue *q);
-
+extern int git_pqueue_insert(git_pqueue *pq, void *item);
/**
- * access highest-ranking item without removing it.
- * @param q the queue
- * @param d the entry
- * @return NULL on error, otherwise the entry
+ * Remove the top item in the priority queue
+ *
+ * @param pq The priority queue
+ * @return item from heap on success, NULL if queue is empty
*/
-void *git_pqueue_peek(git_pqueue *q);
-
-#endif /* PQUEUE_H */
-/** @} */
+extern void *git_pqueue_pop(git_pqueue *pq);
+#endif
diff --git a/src/push.c b/src/push.c
new file mode 100644
index 000000000..be5ec1c0e
--- /dev/null
+++ b/src/push.c
@@ -0,0 +1,724 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2.h"
+
+#include "common.h"
+#include "pack.h"
+#include "pack-objects.h"
+#include "remote.h"
+#include "vector.h"
+#include "push.h"
+#include "tree.h"
+
+static int push_spec_rref_cmp(const void *a, const void *b)
+{
+ const push_spec *push_spec_a = a, *push_spec_b = b;
+
+ return strcmp(push_spec_a->rref, push_spec_b->rref);
+}
+
+static int push_status_ref_cmp(const void *a, const void *b)
+{
+ const push_status *push_status_a = a, *push_status_b = b;
+
+ return strcmp(push_status_a->ref, push_status_b->ref);
+}
+
+int git_push_new(git_push **out, git_remote *remote)
+{
+ git_push *p;
+
+ *out = NULL;
+
+ p = git__calloc(1, sizeof(*p));
+ GITERR_CHECK_ALLOC(p);
+
+ p->repo = remote->repo;
+ p->remote = remote;
+ p->report_status = 1;
+ p->pb_parallelism = 1;
+
+ if (git_vector_init(&p->specs, 0, push_spec_rref_cmp) < 0) {
+ git__free(p);
+ return -1;
+ }
+
+ if (git_vector_init(&p->status, 0, push_status_ref_cmp) < 0) {
+ git_vector_free(&p->specs);
+ git__free(p);
+ return -1;
+ }
+
+ *out = p;
+ return 0;
+}
+
+int git_push_set_options(git_push *push, const git_push_options *opts)
+{
+ if (!push || !opts)
+ return -1;
+
+ GITERR_CHECK_VERSION(opts, GIT_PUSH_OPTIONS_VERSION, "git_push_options");
+
+ push->pb_parallelism = opts->pb_parallelism;
+
+ return 0;
+}
+
+int git_push_set_callbacks(
+ git_push *push,
+ git_packbuilder_progress pack_progress_cb,
+ void *pack_progress_cb_payload,
+ git_push_transfer_progress transfer_progress_cb,
+ void *transfer_progress_cb_payload)
+{
+ if (!push)
+ return -1;
+
+ push->pack_progress_cb = pack_progress_cb;
+ push->pack_progress_cb_payload = pack_progress_cb_payload;
+
+ push->transfer_progress_cb = transfer_progress_cb;
+ push->transfer_progress_cb_payload = transfer_progress_cb_payload;
+
+ return 0;
+}
+
+static void free_refspec(push_spec *spec)
+{
+ if (spec == NULL)
+ return;
+
+ if (spec->lref)
+ git__free(spec->lref);
+
+ if (spec->rref)
+ git__free(spec->rref);
+
+ git__free(spec);
+}
+
+static int check_rref(char *ref)
+{
+ if (git__prefixcmp(ref, "refs/")) {
+ giterr_set(GITERR_INVALID, "Not a valid reference '%s'", ref);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int check_lref(git_push *push, char *ref)
+{
+ /* lref must be resolvable to an existing object */
+ git_object *obj;
+ int error = git_revparse_single(&obj, push->repo, ref);
+ git_object_free(obj);
+
+ if (!error)
+ return 0;
+
+ if (error == GIT_ENOTFOUND)
+ giterr_set(GITERR_REFERENCE,
+ "src refspec '%s' does not match any existing object", ref);
+ else
+ giterr_set(GITERR_INVALID, "Not a valid reference '%s'", ref);
+ return -1;
+}
+
+static int parse_refspec(git_push *push, push_spec **spec, const char *str)
+{
+ push_spec *s;
+ char *delim;
+
+ *spec = NULL;
+
+ s = git__calloc(1, sizeof(*s));
+ GITERR_CHECK_ALLOC(s);
+
+ if (str[0] == '+') {
+ s->force = true;
+ str++;
+ }
+
+ delim = strchr(str, ':');
+ if (delim == NULL) {
+ s->lref = git__strdup(str);
+ if (!s->lref || check_lref(push, s->lref) < 0)
+ goto on_error;
+ } else {
+ if (delim - str) {
+ s->lref = git__strndup(str, delim - str);
+ if (!s->lref || check_lref(push, s->lref) < 0)
+ goto on_error;
+ }
+
+ if (strlen(delim + 1)) {
+ s->rref = git__strdup(delim + 1);
+ if (!s->rref || check_rref(s->rref) < 0)
+ goto on_error;
+ }
+ }
+
+ if (!s->lref && !s->rref)
+ goto on_error;
+
+ /* If rref is ommitted, use the same ref name as lref */
+ if (!s->rref) {
+ s->rref = git__strdup(s->lref);
+ if (!s->rref || check_rref(s->rref) < 0)
+ goto on_error;
+ }
+
+ *spec = s;
+ return 0;
+
+on_error:
+ free_refspec(s);
+ return -1;
+}
+
+int git_push_add_refspec(git_push *push, const char *refspec)
+{
+ push_spec *spec;
+
+ if (parse_refspec(push, &spec, refspec) < 0 ||
+ git_vector_insert(&push->specs, spec) < 0)
+ return -1;
+
+ return 0;
+}
+
+int git_push_update_tips(
+ git_push *push,
+ const git_signature *signature,
+ const char *reflog_message)
+{
+ git_buf remote_ref_name = GIT_BUF_INIT;
+ size_t i, j;
+ git_refspec *fetch_spec;
+ push_spec *push_spec = NULL;
+ git_reference *remote_ref;
+ push_status *status;
+ int error = 0;
+
+ git_vector_foreach(&push->status, i, status) {
+ int fire_callback = 1;
+
+ /* Find the corresponding remote ref */
+ fetch_spec = git_remote__matching_refspec(push->remote, status->ref);
+ if (!fetch_spec)
+ continue;
+
+ if ((error = git_refspec_transform(&remote_ref_name, fetch_spec, status->ref)) < 0)
+ goto on_error;
+
+ /* Find matching push ref spec */
+ git_vector_foreach(&push->specs, j, push_spec) {
+ if (!strcmp(push_spec->rref, status->ref))
+ break;
+ }
+
+ /* Could not find the corresponding push ref spec for this push update */
+ if (j == push->specs.length)
+ continue;
+
+ /* If this ref update was successful (ok, not ng), it will have an empty message */
+ if (status->msg == NULL) {
+ /* Update the remote ref */
+ if (git_oid_iszero(&push_spec->loid)) {
+ error = git_reference_lookup(&remote_ref, push->remote->repo, git_buf_cstr(&remote_ref_name));
+
+ if (error >= 0) {
+ error = git_reference_delete(remote_ref);
+ git_reference_free(remote_ref);
+ }
+ } else {
+ error = git_reference_create(NULL, push->remote->repo,
+ git_buf_cstr(&remote_ref_name), &push_spec->loid, 1, signature,
+ reflog_message ? reflog_message : "update by push");
+ }
+ }
+
+ if (error < 0) {
+ if (error != GIT_ENOTFOUND)
+ goto on_error;
+
+ giterr_clear();
+ fire_callback = 0;
+ }
+
+ if (fire_callback && push->remote->callbacks.update_tips) {
+ error = push->remote->callbacks.update_tips(git_buf_cstr(&remote_ref_name),
+ &push_spec->roid, &push_spec->loid, push->remote->callbacks.payload);
+
+ if (error < 0)
+ goto on_error;
+ }
+ }
+
+ error = 0;
+
+on_error:
+ git_buf_free(&remote_ref_name);
+ return error;
+}
+
+/**
+ * Insert all tags until we find a non-tag object, which is returned
+ * in `out`.
+ */
+static int enqueue_tag(git_object **out, git_push *push, git_oid *id)
+{
+ git_object *obj = NULL, *target = NULL;
+ int error;
+
+ if ((error = git_object_lookup(&obj, push->repo, id, GIT_OBJ_TAG)) < 0)
+ return error;
+
+ while (git_object_type(obj) == GIT_OBJ_TAG) {
+ if ((error = git_packbuilder_insert(push->pb, git_object_id(obj), NULL)) < 0)
+ break;
+
+ if ((error = git_tag_target(&target, (git_tag *) obj)) < 0)
+ break;
+
+ git_object_free(obj);
+ obj = target;
+ }
+
+ if (error < 0)
+ git_object_free(obj);
+ else
+ *out = obj;
+
+ return error;
+}
+
+static int revwalk(git_vector *commits, git_push *push)
+{
+ git_remote_head *head;
+ push_spec *spec;
+ git_revwalk *rw;
+ git_oid oid;
+ unsigned int i;
+ int error = -1;
+
+ if (git_revwalk_new(&rw, push->repo) < 0)
+ return -1;
+
+ git_revwalk_sorting(rw, GIT_SORT_TIME);
+
+ git_vector_foreach(&push->specs, i, spec) {
+ git_otype type;
+ size_t size;
+
+ if (git_oid_iszero(&spec->loid))
+ /*
+ * Delete reference on remote side;
+ * nothing to do here.
+ */
+ continue;
+
+ if (git_oid_equal(&spec->loid, &spec->roid))
+ continue; /* up-to-date */
+
+ if (git_odb_read_header(&size, &type, push->repo->_odb, &spec->loid) < 0)
+ goto on_error;
+
+ if (type == GIT_OBJ_TAG) {
+ git_object *target;
+
+ if ((error = enqueue_tag(&target, push, &spec->loid)) < 0)
+ goto on_error;
+
+ if (git_object_type(target) == GIT_OBJ_COMMIT) {
+ if (git_revwalk_push(rw, git_object_id(target)) < 0) {
+ git_object_free(target);
+ goto on_error;
+ }
+ } else {
+ if (git_packbuilder_insert(
+ push->pb, git_object_id(target), NULL) < 0) {
+ git_object_free(target);
+ goto on_error;
+ }
+ }
+ git_object_free(target);
+ } else if (git_revwalk_push(rw, &spec->loid) < 0)
+ goto on_error;
+
+ if (!spec->force) {
+ git_oid base;
+
+ if (git_oid_iszero(&spec->roid))
+ continue;
+
+ if (!git_odb_exists(push->repo->_odb, &spec->roid)) {
+ giterr_set(GITERR_REFERENCE, "Cannot push missing reference");
+ error = GIT_ENONFASTFORWARD;
+ goto on_error;
+ }
+
+ error = git_merge_base(&base, push->repo,
+ &spec->loid, &spec->roid);
+
+ if (error == GIT_ENOTFOUND ||
+ (!error && !git_oid_equal(&base, &spec->roid))) {
+ giterr_set(GITERR_REFERENCE,
+ "Cannot push non-fastforwardable reference");
+ error = GIT_ENONFASTFORWARD;
+ goto on_error;
+ }
+
+ if (error < 0)
+ goto on_error;
+ }
+ }
+
+ git_vector_foreach(&push->remote->refs, i, head) {
+ if (git_oid_iszero(&head->oid))
+ continue;
+
+ /* TODO */
+ git_revwalk_hide(rw, &head->oid);
+ }
+
+ while ((error = git_revwalk_next(&oid, rw)) == 0) {
+ git_oid *o = git__malloc(GIT_OID_RAWSZ);
+ if (!o) {
+ error = -1;
+ goto on_error;
+ }
+ git_oid_cpy(o, &oid);
+ if ((error = git_vector_insert(commits, o)) < 0)
+ goto on_error;
+ }
+
+on_error:
+ git_revwalk_free(rw);
+ return error == GIT_ITEROVER ? 0 : error;
+}
+
+static int enqueue_object(
+ const git_tree_entry *entry,
+ git_packbuilder *pb)
+{
+ switch (git_tree_entry_type(entry)) {
+ case GIT_OBJ_COMMIT:
+ return 0;
+ case GIT_OBJ_TREE:
+ return git_packbuilder_insert_tree(pb, &entry->oid);
+ default:
+ return git_packbuilder_insert(pb, &entry->oid, entry->filename);
+ }
+}
+
+static int queue_differences(
+ git_tree *base,
+ git_tree *delta,
+ git_packbuilder *pb)
+{
+ git_tree *b_child = NULL, *d_child = NULL;
+ size_t b_length = git_tree_entrycount(base);
+ size_t d_length = git_tree_entrycount(delta);
+ size_t i = 0, j = 0;
+ int error;
+
+ while (i < b_length && j < d_length) {
+ const git_tree_entry *b_entry = git_tree_entry_byindex(base, i);
+ const git_tree_entry *d_entry = git_tree_entry_byindex(delta, j);
+ int cmp = 0;
+
+ if (!git_oid__cmp(&b_entry->oid, &d_entry->oid))
+ goto loop;
+
+ cmp = strcmp(b_entry->filename, d_entry->filename);
+
+ /* If the entries are both trees and they have the same name but are
+ * different, then we'll recurse after adding the right-hand entry */
+ if (!cmp &&
+ git_tree_entry__is_tree(b_entry) &&
+ git_tree_entry__is_tree(d_entry)) {
+ /* Add the right-hand entry */
+ if ((error = git_packbuilder_insert(pb, &d_entry->oid,
+ d_entry->filename)) < 0)
+ goto on_error;
+
+ /* Acquire the subtrees and recurse */
+ if ((error = git_tree_lookup(&b_child,
+ git_tree_owner(base), &b_entry->oid)) < 0 ||
+ (error = git_tree_lookup(&d_child,
+ git_tree_owner(delta), &d_entry->oid)) < 0 ||
+ (error = queue_differences(b_child, d_child, pb)) < 0)
+ goto on_error;
+
+ git_tree_free(b_child); b_child = NULL;
+ git_tree_free(d_child); d_child = NULL;
+ }
+ /* If the object is new or different in the right-hand tree,
+ * then enumerate it */
+ else if (cmp >= 0 &&
+ (error = enqueue_object(d_entry, pb)) < 0)
+ goto on_error;
+
+ loop:
+ if (cmp <= 0) i++;
+ if (cmp >= 0) j++;
+ }
+
+ /* Drain the right-hand tree of entries */
+ for (; j < d_length; j++)
+ if ((error = enqueue_object(git_tree_entry_byindex(delta, j), pb)) < 0)
+ goto on_error;
+
+ error = 0;
+
+on_error:
+ if (b_child)
+ git_tree_free(b_child);
+
+ if (d_child)
+ git_tree_free(d_child);
+
+ return error;
+}
+
+static int queue_objects(git_push *push)
+{
+ git_vector commits = GIT_VECTOR_INIT;
+ git_oid *oid;
+ size_t i;
+ unsigned j;
+ int error;
+
+ if ((error = revwalk(&commits, push)) < 0)
+ goto on_error;
+
+ git_vector_foreach(&commits, i, oid) {
+ git_commit *parent = NULL, *commit;
+ git_tree *tree = NULL, *ptree = NULL;
+ size_t parentcount;
+
+ if ((error = git_commit_lookup(&commit, push->repo, oid)) < 0)
+ goto on_error;
+
+ /* Insert the commit */
+ if ((error = git_packbuilder_insert(push->pb, oid, NULL)) < 0)
+ goto loop_error;
+
+ parentcount = git_commit_parentcount(commit);
+
+ if (!parentcount) {
+ if ((error = git_packbuilder_insert_tree(push->pb,
+ git_commit_tree_id(commit))) < 0)
+ goto loop_error;
+ } else {
+ if ((error = git_tree_lookup(&tree, push->repo,
+ git_commit_tree_id(commit))) < 0 ||
+ (error = git_packbuilder_insert(push->pb,
+ git_commit_tree_id(commit), NULL)) < 0)
+ goto loop_error;
+
+ /* For each parent, add the items which are different */
+ for (j = 0; j < parentcount; j++) {
+ if ((error = git_commit_parent(&parent, commit, j)) < 0 ||
+ (error = git_commit_tree(&ptree, parent)) < 0 ||
+ (error = queue_differences(ptree, tree, push->pb)) < 0)
+ goto loop_error;
+
+ git_tree_free(ptree); ptree = NULL;
+ git_commit_free(parent); parent = NULL;
+ }
+ }
+
+ error = 0;
+
+ loop_error:
+ if (tree)
+ git_tree_free(tree);
+
+ if (ptree)
+ git_tree_free(ptree);
+
+ if (parent)
+ git_commit_free(parent);
+
+ git_commit_free(commit);
+
+ if (error < 0)
+ goto on_error;
+ }
+
+ error = 0;
+
+on_error:
+ git_vector_free_deep(&commits);
+ return error;
+}
+
+static int calculate_work(git_push *push)
+{
+ git_remote_head *head;
+ push_spec *spec;
+ unsigned int i, j;
+
+ /* Update local and remote oids*/
+
+ git_vector_foreach(&push->specs, i, spec) {
+ if (spec->lref) {
+ /* This is a create or update. Local ref must exist. */
+ if (git_reference_name_to_id(
+ &spec->loid, push->repo, spec->lref) < 0) {
+ giterr_set(GITERR_REFERENCE, "No such reference '%s'", spec->lref);
+ return -1;
+ }
+ }
+
+ if (spec->rref) {
+ /* Remote ref may or may not (e.g. during create) already exist. */
+ git_vector_foreach(&push->remote->refs, j, head) {
+ if (!strcmp(spec->rref, head->name)) {
+ git_oid_cpy(&spec->roid, &head->oid);
+ break;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int do_push(git_push *push)
+{
+ int error = 0;
+ git_transport *transport = push->remote->transport;
+
+ if (!transport->push) {
+ giterr_set(GITERR_NET, "Remote transport doesn't support push");
+ error = -1;
+ goto on_error;
+ }
+
+ /*
+ * A pack-file MUST be sent if either create or update command
+ * is used, even if the server already has all the necessary
+ * objects. In this case the client MUST send an empty pack-file.
+ */
+
+ if ((error = git_packbuilder_new(&push->pb, push->repo)) < 0)
+ goto on_error;
+
+ git_packbuilder_set_threads(push->pb, push->pb_parallelism);
+
+ if (push->pack_progress_cb)
+ if ((error = git_packbuilder_set_callbacks(push->pb, push->pack_progress_cb, push->pack_progress_cb_payload)) < 0)
+ goto on_error;
+
+ if ((error = calculate_work(push)) < 0 ||
+ (error = queue_objects(push)) < 0 ||
+ (error = transport->push(transport, push)) < 0)
+ goto on_error;
+
+on_error:
+ git_packbuilder_free(push->pb);
+ return error;
+}
+
+static int filter_refs(git_remote *remote)
+{
+ const git_remote_head **heads;
+ size_t heads_len, i;
+
+ git_vector_clear(&remote->refs);
+
+ if (git_remote_ls(&heads, &heads_len, remote) < 0)
+ return -1;
+
+ for (i = 0; i < heads_len; i++) {
+ if (git_vector_insert(&remote->refs, (void *)heads[i]) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+int git_push_finish(git_push *push)
+{
+ int error;
+
+ if (!git_remote_connected(push->remote) &&
+ (error = git_remote_connect(push->remote, GIT_DIRECTION_PUSH)) < 0)
+ return error;
+
+ if ((error = filter_refs(push->remote)) < 0 ||
+ (error = do_push(push)) < 0)
+ return error;
+
+ return 0;
+}
+
+int git_push_unpack_ok(const git_push *push)
+{
+ return push->unpack_ok;
+}
+
+int git_push_status_foreach(git_push *push,
+ int (*cb)(const char *ref, const char *msg, void *data),
+ void *data)
+{
+ push_status *status;
+ unsigned int i;
+
+ git_vector_foreach(&push->status, i, status) {
+ int error = cb(status->ref, status->msg, data);
+ if (error)
+ return giterr_set_after_callback(error);
+ }
+
+ return 0;
+}
+
+void git_push_status_free(push_status *status)
+{
+ if (status == NULL)
+ return;
+
+ git__free(status->msg);
+ git__free(status->ref);
+ git__free(status);
+}
+
+void git_push_free(git_push *push)
+{
+ push_spec *spec;
+ push_status *status;
+ unsigned int i;
+
+ if (push == NULL)
+ return;
+
+ git_vector_foreach(&push->specs, i, spec) {
+ free_refspec(spec);
+ }
+ git_vector_free(&push->specs);
+
+ git_vector_foreach(&push->status, i, status) {
+ git_push_status_free(status);
+ }
+ git_vector_free(&push->status);
+
+ git__free(push);
+}
+
+int git_push_init_options(git_push_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_push_options, GIT_PUSH_OPTIONS_INIT);
+ return 0;
+}
diff --git a/src/push.h b/src/push.h
new file mode 100644
index 000000000..6c8bf7229
--- /dev/null
+++ b/src/push.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_push_h__
+#define INCLUDE_push_h__
+
+#include "git2.h"
+
+typedef struct push_spec {
+ char *lref;
+ char *rref;
+
+ git_oid loid;
+ git_oid roid;
+
+ bool force;
+} push_spec;
+
+typedef struct push_status {
+ bool ok;
+
+ char *ref;
+ char *msg;
+} push_status;
+
+struct git_push {
+ git_repository *repo;
+ git_packbuilder *pb;
+ git_remote *remote;
+ git_vector specs;
+ bool report_status;
+
+ /* report-status */
+ bool unpack_ok;
+ git_vector status;
+
+ /* options */
+ unsigned pb_parallelism;
+
+ git_packbuilder_progress pack_progress_cb;
+ void *pack_progress_cb_payload;
+ git_push_transfer_progress transfer_progress_cb;
+ void *transfer_progress_cb_payload;
+};
+
+/**
+ * Free the given push status object
+ *
+ * @param status The push status object
+ */
+void git_push_status_free(push_status *status);
+
+#endif
diff --git a/src/refdb.c b/src/refdb.c
new file mode 100644
index 000000000..69bf74734
--- /dev/null
+++ b/src/refdb.c
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "posix.h"
+
+#include "git2/object.h"
+#include "git2/refs.h"
+#include "git2/refdb.h"
+#include "git2/sys/refdb_backend.h"
+
+#include "hash.h"
+#include "refdb.h"
+#include "refs.h"
+#include "reflog.h"
+
+int git_refdb_new(git_refdb **out, git_repository *repo)
+{
+ git_refdb *db;
+
+ assert(out && repo);
+
+ db = git__calloc(1, sizeof(*db));
+ GITERR_CHECK_ALLOC(db);
+
+ db->repo = repo;
+
+ *out = db;
+ GIT_REFCOUNT_INC(db);
+ return 0;
+}
+
+int git_refdb_open(git_refdb **out, git_repository *repo)
+{
+ git_refdb *db;
+ git_refdb_backend *dir;
+
+ assert(out && repo);
+
+ *out = NULL;
+
+ if (git_refdb_new(&db, repo) < 0)
+ return -1;
+
+ /* Add the default (filesystem) backend */
+ if (git_refdb_backend_fs(&dir, repo) < 0) {
+ git_refdb_free(db);
+ return -1;
+ }
+
+ db->repo = repo;
+ db->backend = dir;
+
+ *out = db;
+ return 0;
+}
+
+static void refdb_free_backend(git_refdb *db)
+{
+ if (db->backend) {
+ if (db->backend->free)
+ db->backend->free(db->backend);
+ else
+ git__free(db->backend);
+ }
+}
+
+int git_refdb_set_backend(git_refdb *db, git_refdb_backend *backend)
+{
+ refdb_free_backend(db);
+ db->backend = backend;
+
+ return 0;
+}
+
+int git_refdb_compress(git_refdb *db)
+{
+ assert(db);
+
+ if (db->backend->compress)
+ return db->backend->compress(db->backend);
+
+ return 0;
+}
+
+void git_refdb__free(git_refdb *db)
+{
+ refdb_free_backend(db);
+ git__memzero(db, sizeof(*db));
+ git__free(db);
+}
+
+void git_refdb_free(git_refdb *db)
+{
+ if (db == NULL)
+ return;
+
+ GIT_REFCOUNT_DEC(db, git_refdb__free);
+}
+
+int git_refdb_exists(int *exists, git_refdb *refdb, const char *ref_name)
+{
+ assert(exists && refdb && refdb->backend);
+
+ return refdb->backend->exists(exists, refdb->backend, ref_name);
+}
+
+int git_refdb_lookup(git_reference **out, git_refdb *db, const char *ref_name)
+{
+ git_reference *ref;
+ int error;
+
+ assert(db && db->backend && out && ref_name);
+
+ error = db->backend->lookup(&ref, db->backend, ref_name);
+ if (error < 0)
+ return error;
+
+ GIT_REFCOUNT_INC(db);
+ ref->db = db;
+
+ *out = ref;
+ return 0;
+}
+
+int git_refdb_iterator(git_reference_iterator **out, git_refdb *db, const char *glob)
+{
+ if (!db->backend || !db->backend->iterator) {
+ giterr_set(GITERR_REFERENCE, "This backend doesn't support iterators");
+ return -1;
+ }
+
+ if (db->backend->iterator(out, db->backend, glob) < 0)
+ return -1;
+
+ GIT_REFCOUNT_INC(db);
+ (*out)->db = db;
+
+ return 0;
+}
+
+int git_refdb_iterator_next(git_reference **out, git_reference_iterator *iter)
+{
+ int error;
+
+ if ((error = iter->next(out, iter)) < 0)
+ return error;
+
+ GIT_REFCOUNT_INC(iter->db);
+ (*out)->db = iter->db;
+
+ return 0;
+}
+
+int git_refdb_iterator_next_name(const char **out, git_reference_iterator *iter)
+{
+ return iter->next_name(out, iter);
+}
+
+void git_refdb_iterator_free(git_reference_iterator *iter)
+{
+ GIT_REFCOUNT_DEC(iter->db, git_refdb__free);
+ iter->free(iter);
+}
+
+int git_refdb_write(git_refdb *db, git_reference *ref, int force, const git_signature *who, const char *message, const git_oid *old_id, const char *old_target)
+{
+ assert(db && db->backend);
+
+ GIT_REFCOUNT_INC(db);
+ ref->db = db;
+
+ return db->backend->write(db->backend, ref, force, who, message, old_id, old_target);
+}
+
+int git_refdb_rename(
+ git_reference **out,
+ git_refdb *db,
+ const char *old_name,
+ const char *new_name,
+ int force,
+ const git_signature *who,
+ const char *message)
+{
+ int error;
+
+ assert(db && db->backend);
+ error = db->backend->rename(out, db->backend, old_name, new_name, force, who, message);
+ if (error < 0)
+ return error;
+
+ if (out) {
+ GIT_REFCOUNT_INC(db);
+ (*out)->db = db;
+ }
+
+ return 0;
+}
+
+int git_refdb_delete(struct git_refdb *db, const char *ref_name, const git_oid *old_id, const char *old_target)
+{
+ assert(db && db->backend);
+ return db->backend->del(db->backend, ref_name, old_id, old_target);
+}
+
+int git_refdb_reflog_read(git_reflog **out, git_refdb *db, const char *name)
+{
+ int error;
+
+ assert(db && db->backend);
+
+ if ((error = db->backend->reflog_read(out, db->backend, name)) < 0)
+ return error;
+
+ GIT_REFCOUNT_INC(db);
+ (*out)->db = db;
+
+ return 0;
+}
+
+int git_refdb_has_log(git_refdb *db, const char *refname)
+{
+ assert(db && refname);
+
+ return db->backend->has_log(db->backend, refname);
+}
+
+int git_refdb_ensure_log(git_refdb *db, const char *refname)
+{
+ assert(db && refname);
+
+ return db->backend->ensure_log(db->backend, refname);
+}
+
+int git_refdb_init_backend(git_refdb_backend *backend, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ backend, version, git_refdb_backend, GIT_REFDB_BACKEND_INIT);
+ return 0;
+}
diff --git a/src/refdb.h b/src/refdb.h
new file mode 100644
index 000000000..cbad86faf
--- /dev/null
+++ b/src/refdb.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_refdb_h__
+#define INCLUDE_refdb_h__
+
+#include "git2/refdb.h"
+#include "repository.h"
+
+struct git_refdb {
+ git_refcount rc;
+ git_repository *repo;
+ git_refdb_backend *backend;
+};
+
+void git_refdb__free(git_refdb *db);
+
+int git_refdb_exists(
+ int *exists,
+ git_refdb *refdb,
+ const char *ref_name);
+
+int git_refdb_lookup(
+ git_reference **out,
+ git_refdb *refdb,
+ const char *ref_name);
+
+int git_refdb_rename(
+ git_reference **out,
+ git_refdb *db,
+ const char *old_name,
+ const char *new_name,
+ int force,
+ const git_signature *who,
+ const char *message);
+
+int git_refdb_iterator(git_reference_iterator **out, git_refdb *db, const char *glob);
+int git_refdb_iterator_next(git_reference **out, git_reference_iterator *iter);
+int git_refdb_iterator_next_name(const char **out, git_reference_iterator *iter);
+void git_refdb_iterator_free(git_reference_iterator *iter);
+
+int git_refdb_write(git_refdb *refdb, git_reference *ref, int force, const git_signature *who, const char *message, const git_oid *old_id, const char *old_target);
+int git_refdb_delete(git_refdb *refdb, const char *ref_name, const git_oid *old_id, const char *old_target);
+
+int git_refdb_reflog_read(git_reflog **out, git_refdb *db, const char *name);
+int git_refdb_reflog_write(git_reflog *reflog);
+
+int git_refdb_has_log(git_refdb *db, const char *refname);
+int git_refdb_ensure_log(git_refdb *refdb, const char *refname);
+
+
+
+#endif
diff --git a/src/refdb_fs.c b/src/refdb_fs.c
new file mode 100644
index 000000000..0e36ca8ac
--- /dev/null
+++ b/src/refdb_fs.c
@@ -0,0 +1,1856 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "refs.h"
+#include "hash.h"
+#include "repository.h"
+#include "fileops.h"
+#include "filebuf.h"
+#include "pack.h"
+#include "reflog.h"
+#include "refdb.h"
+#include "refdb_fs.h"
+#include "iterator.h"
+#include "sortedcache.h"
+#include "signature.h"
+
+#include <git2/tag.h>
+#include <git2/object.h>
+#include <git2/refdb.h>
+#include <git2/branch.h>
+#include <git2/sys/refdb_backend.h>
+#include <git2/sys/refs.h>
+#include <git2/sys/reflog.h>
+
+GIT__USE_STRMAP;
+
+#define DEFAULT_NESTING_LEVEL 5
+#define MAX_NESTING_LEVEL 10
+
+enum {
+ PACKREF_HAS_PEEL = 1,
+ PACKREF_WAS_LOOSE = 2,
+ PACKREF_CANNOT_PEEL = 4,
+ PACKREF_SHADOWED = 8,
+};
+
+enum {
+ PEELING_NONE = 0,
+ PEELING_STANDARD,
+ PEELING_FULL
+};
+
+struct packref {
+ git_oid oid;
+ git_oid peel;
+ char flags;
+ char name[GIT_FLEX_ARRAY];
+};
+
+typedef struct refdb_fs_backend {
+ git_refdb_backend parent;
+
+ git_repository *repo;
+ char *path;
+
+ git_sortedcache *refcache;
+ int peeling_mode;
+ git_iterator_flag_t iterator_flags;
+ uint32_t direach_flags;
+} refdb_fs_backend;
+
+static int packref_cmp(const void *a_, const void *b_)
+{
+ const struct packref *a = a_, *b = b_;
+ return strcmp(a->name, b->name);
+}
+
+static int packed_reload(refdb_fs_backend *backend)
+{
+ int error;
+ git_buf packedrefs = GIT_BUF_INIT;
+ char *scan, *eof, *eol;
+
+ if (!backend->path)
+ return 0;
+
+ error = git_sortedcache_lockandload(backend->refcache, &packedrefs);
+
+ /*
+ * If we can't find the packed-refs, clear table and return.
+ * Any other error just gets passed through.
+ * If no error, and file wasn't changed, just return.
+ * Anything else means we need to refresh the packed refs.
+ */
+ if (error <= 0) {
+ if (error == GIT_ENOTFOUND) {
+ git_sortedcache_clear(backend->refcache, true);
+ giterr_clear();
+ error = 0;
+ }
+ return error;
+ }
+
+ /* At this point, refresh the packed refs from the loaded buffer. */
+
+ git_sortedcache_clear(backend->refcache, false);
+
+ scan = (char *)packedrefs.ptr;
+ eof = scan + packedrefs.size;
+
+ backend->peeling_mode = PEELING_NONE;
+
+ if (*scan == '#') {
+ static const char *traits_header = "# pack-refs with: ";
+
+ if (git__prefixcmp(scan, traits_header) == 0) {
+ scan += strlen(traits_header);
+ eol = strchr(scan, '\n');
+
+ if (!eol)
+ goto parse_failed;
+ *eol = '\0';
+
+ if (strstr(scan, " fully-peeled ") != NULL) {
+ backend->peeling_mode = PEELING_FULL;
+ } else if (strstr(scan, " peeled ") != NULL) {
+ backend->peeling_mode = PEELING_STANDARD;
+ }
+
+ scan = eol + 1;
+ }
+ }
+
+ while (scan < eof && *scan == '#') {
+ if (!(eol = strchr(scan, '\n')))
+ goto parse_failed;
+ scan = eol + 1;
+ }
+
+ while (scan < eof) {
+ struct packref *ref;
+ git_oid oid;
+
+ /* parse "<OID> <refname>\n" */
+
+ if (git_oid_fromstr(&oid, scan) < 0)
+ goto parse_failed;
+ scan += GIT_OID_HEXSZ;
+
+ if (*scan++ != ' ')
+ goto parse_failed;
+ if (!(eol = strchr(scan, '\n')))
+ goto parse_failed;
+ *eol = '\0';
+ if (eol[-1] == '\r')
+ eol[-1] = '\0';
+
+ if (git_sortedcache_upsert((void **)&ref, backend->refcache, scan) < 0)
+ goto parse_failed;
+ scan = eol + 1;
+
+ git_oid_cpy(&ref->oid, &oid);
+
+ /* look for optional "^<OID>\n" */
+
+ if (*scan == '^') {
+ if (git_oid_fromstr(&oid, scan + 1) < 0)
+ goto parse_failed;
+ scan += GIT_OID_HEXSZ + 1;
+
+ if (scan < eof) {
+ if (!(eol = strchr(scan, '\n')))
+ goto parse_failed;
+ scan = eol + 1;
+ }
+
+ git_oid_cpy(&ref->peel, &oid);
+ ref->flags |= PACKREF_HAS_PEEL;
+ }
+ else if (backend->peeling_mode == PEELING_FULL ||
+ (backend->peeling_mode == PEELING_STANDARD &&
+ git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) == 0))
+ ref->flags |= PACKREF_CANNOT_PEEL;
+ }
+
+ git_sortedcache_wunlock(backend->refcache);
+ git_buf_free(&packedrefs);
+
+ return 0;
+
+parse_failed:
+ giterr_set(GITERR_REFERENCE, "Corrupted packed references file");
+
+ git_sortedcache_clear(backend->refcache, false);
+ git_sortedcache_wunlock(backend->refcache);
+ git_buf_free(&packedrefs);
+
+ return -1;
+}
+
+static int loose_parse_oid(
+ git_oid *oid, const char *filename, git_buf *file_content)
+{
+ const char *str = git_buf_cstr(file_content);
+
+ if (git_buf_len(file_content) < GIT_OID_HEXSZ)
+ goto corrupted;
+
+ /* we need to get 40 OID characters from the file */
+ if (git_oid_fromstr(oid, str) < 0)
+ goto corrupted;
+
+ /* If the file is longer than 40 chars, the 41st must be a space */
+ str += GIT_OID_HEXSZ;
+ if (*str == '\0' || git__isspace(*str))
+ return 0;
+
+corrupted:
+ giterr_set(GITERR_REFERENCE, "Corrupted loose reference file: %s", filename);
+ return -1;
+}
+
+static int loose_readbuffer(git_buf *buf, const char *base, const char *path)
+{
+ int error;
+
+ /* build full path to file */
+ if ((error = git_buf_joinpath(buf, base, path)) < 0 ||
+ (error = git_futils_readbuffer(buf, buf->ptr)) < 0)
+ git_buf_free(buf);
+
+ return error;
+}
+
+static int loose_lookup_to_packfile(refdb_fs_backend *backend, const char *name)
+{
+ int error = 0;
+ git_buf ref_file = GIT_BUF_INIT;
+ struct packref *ref = NULL;
+ git_oid oid;
+
+ /* if we fail to load the loose reference, assume someone changed
+ * the filesystem under us and skip it...
+ */
+ if (loose_readbuffer(&ref_file, backend->path, name) < 0) {
+ giterr_clear();
+ goto done;
+ }
+
+ /* skip symbolic refs */
+ if (!git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF))
+ goto done;
+
+ /* parse OID from file */
+ if ((error = loose_parse_oid(&oid, name, &ref_file)) < 0)
+ goto done;
+
+ git_sortedcache_wlock(backend->refcache);
+
+ if (!(error = git_sortedcache_upsert(
+ (void **)&ref, backend->refcache, name))) {
+
+ git_oid_cpy(&ref->oid, &oid);
+ ref->flags = PACKREF_WAS_LOOSE;
+ }
+
+ git_sortedcache_wunlock(backend->refcache);
+
+done:
+ git_buf_free(&ref_file);
+ return error;
+}
+
+static int _dirent_loose_load(void *payload, git_buf *full_path)
+{
+ refdb_fs_backend *backend = payload;
+ const char *file_path;
+
+ if (git__suffixcmp(full_path->ptr, ".lock") == 0)
+ return 0;
+
+ if (git_path_isdir(full_path->ptr)) {
+ int error = git_path_direach(
+ full_path, backend->direach_flags, _dirent_loose_load, backend);
+ /* Race with the filesystem, ignore it */
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ return 0;
+ }
+
+ return error;
+ }
+
+ file_path = full_path->ptr + strlen(backend->path);
+
+ return loose_lookup_to_packfile(backend, file_path);
+}
+
+/*
+ * Load all the loose references from the repository
+ * into the in-memory Packfile, and build a vector with
+ * all the references so it can be written back to
+ * disk.
+ */
+static int packed_loadloose(refdb_fs_backend *backend)
+{
+ int error;
+ git_buf refs_path = GIT_BUF_INIT;
+
+ if (git_buf_joinpath(&refs_path, backend->path, GIT_REFS_DIR) < 0)
+ return -1;
+
+ /*
+ * Load all the loose files from disk into the Packfile table.
+ * This will overwrite any old packed entries with their
+ * updated loose versions
+ */
+ error = git_path_direach(
+ &refs_path, backend->direach_flags, _dirent_loose_load, backend);
+
+ git_buf_free(&refs_path);
+
+ return error;
+}
+
+static int refdb_fs_backend__exists(
+ int *exists,
+ git_refdb_backend *_backend,
+ const char *ref_name)
+{
+ refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+ git_buf ref_path = GIT_BUF_INIT;
+
+ assert(backend);
+
+ if (packed_reload(backend) < 0 ||
+ git_buf_joinpath(&ref_path, backend->path, ref_name) < 0)
+ return -1;
+
+ *exists = git_path_isfile(ref_path.ptr) ||
+ (git_sortedcache_lookup(backend->refcache, ref_name) != NULL);
+
+ git_buf_free(&ref_path);
+ return 0;
+}
+
+static const char *loose_parse_symbolic(git_buf *file_content)
+{
+ const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF);
+ const char *refname_start;
+
+ refname_start = (const char *)file_content->ptr;
+
+ if (git_buf_len(file_content) < header_len + 1) {
+ giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");
+ return NULL;
+ }
+
+ /*
+ * Assume we have already checked for the header
+ * before calling this function
+ */
+ refname_start += header_len;
+
+ return refname_start;
+}
+
+static int loose_lookup(
+ git_reference **out,
+ refdb_fs_backend *backend,
+ const char *ref_name)
+{
+ git_buf ref_file = GIT_BUF_INIT;
+ int error = 0;
+
+ if (out)
+ *out = NULL;
+
+ if ((error = loose_readbuffer(&ref_file, backend->path, ref_name)) < 0)
+ /* cannot read loose ref file - gah */;
+ else if (git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF) == 0) {
+ const char *target;
+
+ git_buf_rtrim(&ref_file);
+
+ if (!(target = loose_parse_symbolic(&ref_file)))
+ error = -1;
+ else if (out != NULL)
+ *out = git_reference__alloc_symbolic(ref_name, target);
+ } else {
+ git_oid oid;
+
+ if (!(error = loose_parse_oid(&oid, ref_name, &ref_file)) &&
+ out != NULL)
+ *out = git_reference__alloc(ref_name, &oid, NULL);
+ }
+
+ git_buf_free(&ref_file);
+ return error;
+}
+
+static int ref_error_notfound(const char *name)
+{
+ giterr_set(GITERR_REFERENCE, "Reference '%s' not found", name);
+ return GIT_ENOTFOUND;
+}
+
+static int packed_lookup(
+ git_reference **out,
+ refdb_fs_backend *backend,
+ const char *ref_name)
+{
+ int error = 0;
+ struct packref *entry;
+
+ if (packed_reload(backend) < 0)
+ return -1;
+
+ if (git_sortedcache_rlock(backend->refcache) < 0)
+ return -1;
+
+ entry = git_sortedcache_lookup(backend->refcache, ref_name);
+ if (!entry) {
+ error = ref_error_notfound(ref_name);
+ } else {
+ *out = git_reference__alloc(ref_name, &entry->oid, &entry->peel);
+ if (!*out)
+ error = -1;
+ }
+
+ git_sortedcache_runlock(backend->refcache);
+
+ return error;
+}
+
+static int refdb_fs_backend__lookup(
+ git_reference **out,
+ git_refdb_backend *_backend,
+ const char *ref_name)
+{
+ refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+ int error;
+
+ assert(backend);
+
+ if (!(error = loose_lookup(out, backend, ref_name)))
+ return 0;
+
+ /* only try to lookup this reference on the packfile if it
+ * wasn't found on the loose refs; not if there was a critical error */
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = packed_lookup(out, backend, ref_name);
+ }
+
+ return error;
+}
+
+typedef struct {
+ git_reference_iterator parent;
+
+ char *glob;
+
+ git_pool pool;
+ git_vector loose;
+
+ git_sortedcache *cache;
+ size_t loose_pos;
+ size_t packed_pos;
+} refdb_fs_iter;
+
+static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter)
+{
+ refdb_fs_iter *iter = (refdb_fs_iter *) _iter;
+
+ git_vector_free(&iter->loose);
+ git_pool_clear(&iter->pool);
+ git_sortedcache_free(iter->cache);
+ git__free(iter);
+}
+
+static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter)
+{
+ int error = 0;
+ git_buf path = GIT_BUF_INIT;
+ git_iterator *fsit = NULL;
+ const git_index_entry *entry = NULL;
+
+ if (!backend->path) /* do nothing if no path for loose refs */
+ return 0;
+
+ if ((error = git_buf_printf(&path, "%s/refs", backend->path)) < 0 ||
+ (error = git_iterator_for_filesystem(
+ &fsit, path.ptr, backend->iterator_flags, NULL, NULL)) < 0) {
+ git_buf_free(&path);
+ return error;
+ }
+
+ error = git_buf_sets(&path, GIT_REFS_DIR);
+
+ while (!error && !git_iterator_advance(&entry, fsit)) {
+ const char *ref_name;
+ struct packref *ref;
+ char *ref_dup;
+
+ git_buf_truncate(&path, strlen(GIT_REFS_DIR));
+ git_buf_puts(&path, entry->path);
+ ref_name = git_buf_cstr(&path);
+
+ if (git__suffixcmp(ref_name, ".lock") == 0 ||
+ (iter->glob && p_fnmatch(iter->glob, ref_name, 0) != 0))
+ continue;
+
+ git_sortedcache_rlock(backend->refcache);
+ ref = git_sortedcache_lookup(backend->refcache, ref_name);
+ if (ref)
+ ref->flags |= PACKREF_SHADOWED;
+ git_sortedcache_runlock(backend->refcache);
+
+ ref_dup = git_pool_strdup(&iter->pool, ref_name);
+ if (!ref_dup)
+ error = -1;
+ else
+ error = git_vector_insert(&iter->loose, ref_dup);
+ }
+
+ git_iterator_free(fsit);
+ git_buf_free(&path);
+
+ return error;
+}
+
+static int refdb_fs_backend__iterator_next(
+ git_reference **out, git_reference_iterator *_iter)
+{
+ int error = GIT_ITEROVER;
+ refdb_fs_iter *iter = (refdb_fs_iter *)_iter;
+ refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend;
+ struct packref *ref;
+
+ while (iter->loose_pos < iter->loose.length) {
+ const char *path = git_vector_get(&iter->loose, iter->loose_pos++);
+
+ if (loose_lookup(out, backend, path) == 0)
+ return 0;
+
+ giterr_clear();
+ }
+
+ if (!iter->cache) {
+ if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0)
+ return error;
+ }
+
+ error = GIT_ITEROVER;
+ while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) {
+ ref = git_sortedcache_entry(iter->cache, iter->packed_pos++);
+ if (!ref) /* stop now if another thread deleted refs and we past end */
+ break;
+
+ if (ref->flags & PACKREF_SHADOWED)
+ continue;
+ if (iter->glob && p_fnmatch(iter->glob, ref->name, 0) != 0)
+ continue;
+
+ *out = git_reference__alloc(ref->name, &ref->oid, &ref->peel);
+ error = (*out != NULL) ? 0 : -1;
+ break;
+ }
+
+ return error;
+}
+
+static int refdb_fs_backend__iterator_next_name(
+ const char **out, git_reference_iterator *_iter)
+{
+ int error = GIT_ITEROVER;
+ refdb_fs_iter *iter = (refdb_fs_iter *)_iter;
+ refdb_fs_backend *backend = (refdb_fs_backend *)iter->parent.db->backend;
+ struct packref *ref;
+
+ while (iter->loose_pos < iter->loose.length) {
+ const char *path = git_vector_get(&iter->loose, iter->loose_pos++);
+
+ if (loose_lookup(NULL, backend, path) == 0) {
+ *out = path;
+ return 0;
+ }
+
+ giterr_clear();
+ }
+
+ if (!iter->cache) {
+ if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0)
+ return error;
+ }
+
+ error = GIT_ITEROVER;
+ while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) {
+ ref = git_sortedcache_entry(iter->cache, iter->packed_pos++);
+ if (!ref) /* stop now if another thread deleted refs and we past end */
+ break;
+
+ if (ref->flags & PACKREF_SHADOWED)
+ continue;
+ if (iter->glob && p_fnmatch(iter->glob, ref->name, 0) != 0)
+ continue;
+
+ *out = ref->name;
+ error = 0;
+ break;
+ }
+
+ return error;
+}
+
+static int refdb_fs_backend__iterator(
+ git_reference_iterator **out, git_refdb_backend *_backend, const char *glob)
+{
+ refdb_fs_iter *iter;
+ refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+
+ assert(backend);
+
+ if (packed_reload(backend) < 0)
+ return -1;
+
+ iter = git__calloc(1, sizeof(refdb_fs_iter));
+ GITERR_CHECK_ALLOC(iter);
+
+ if (git_pool_init(&iter->pool, 1, 0) < 0 ||
+ git_vector_init(&iter->loose, 8, NULL) < 0)
+ goto fail;
+
+ if (glob != NULL &&
+ (iter->glob = git_pool_strdup(&iter->pool, glob)) == NULL)
+ goto fail;
+
+ iter->parent.next = refdb_fs_backend__iterator_next;
+ iter->parent.next_name = refdb_fs_backend__iterator_next_name;
+ iter->parent.free = refdb_fs_backend__iterator_free;
+
+ if (iter_load_loose_paths(backend, iter) < 0)
+ goto fail;
+
+ *out = (git_reference_iterator *)iter;
+ return 0;
+
+fail:
+ refdb_fs_backend__iterator_free((git_reference_iterator *)iter);
+ return -1;
+}
+
+static bool ref_is_available(
+ const char *old_ref, const char *new_ref, const char *this_ref)
+{
+ if (old_ref == NULL || strcmp(old_ref, this_ref)) {
+ size_t reflen = strlen(this_ref);
+ size_t newlen = strlen(new_ref);
+ size_t cmplen = reflen < newlen ? reflen : newlen;
+ const char *lead = reflen < newlen ? new_ref : this_ref;
+
+ if (!strncmp(new_ref, this_ref, cmplen) && lead[cmplen] == '/') {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static int reference_path_available(
+ refdb_fs_backend *backend,
+ const char *new_ref,
+ const char* old_ref,
+ int force)
+{
+ size_t i;
+
+ if (packed_reload(backend) < 0)
+ return -1;
+
+ if (!force) {
+ int exists;
+
+ if (refdb_fs_backend__exists(
+ &exists, (git_refdb_backend *)backend, new_ref) < 0)
+ return -1;
+
+ if (exists) {
+ giterr_set(GITERR_REFERENCE,
+ "Failed to write reference '%s': a reference with "
+ "that name already exists.", new_ref);
+ return GIT_EEXISTS;
+ }
+ }
+
+ git_sortedcache_rlock(backend->refcache);
+
+ for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) {
+ struct packref *ref = git_sortedcache_entry(backend->refcache, i);
+
+ if (ref && !ref_is_available(old_ref, new_ref, ref->name)) {
+ git_sortedcache_runlock(backend->refcache);
+ giterr_set(GITERR_REFERENCE,
+ "Path to reference '%s' collides with existing one", new_ref);
+ return -1;
+ }
+ }
+
+ git_sortedcache_runlock(backend->refcache);
+ return 0;
+}
+
+static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char *name)
+{
+ int error;
+ git_buf ref_path = GIT_BUF_INIT;
+
+ assert(file && backend && name);
+
+ /* Remove a possibly existing empty directory hierarchy
+ * which name would collide with the reference name
+ */
+ if (git_futils_rmdir_r(name, backend->path, GIT_RMDIR_SKIP_NONEMPTY) < 0)
+ return -1;
+
+ if (git_buf_joinpath(&ref_path, backend->path, name) < 0)
+ return -1;
+
+ error = git_filebuf_open(file, ref_path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE);
+
+ git_buf_free(&ref_path);
+ return error;
+}
+
+static int loose_commit(git_filebuf *file, const git_reference *ref)
+{
+ assert(file && ref);
+
+ if (ref->type == GIT_REF_OID) {
+ char oid[GIT_OID_HEXSZ + 1];
+ git_oid_nfmt(oid, sizeof(oid), &ref->target.oid);
+
+ git_filebuf_printf(file, "%s\n", oid);
+ } else if (ref->type == GIT_REF_SYMBOLIC) {
+ git_filebuf_printf(file, GIT_SYMREF "%s\n", ref->target.symbolic);
+ } else {
+ assert(0); /* don't let this happen */
+ }
+
+ return git_filebuf_commit(file);
+}
+
+/*
+ * Find out what object this reference resolves to.
+ *
+ * For references that point to a 'big' tag (e.g. an
+ * actual tag object on the repository), we need to
+ * cache on the packfile the OID of the object to
+ * which that 'big tag' is pointing to.
+ */
+static int packed_find_peel(refdb_fs_backend *backend, struct packref *ref)
+{
+ git_object *object;
+
+ if (ref->flags & PACKREF_HAS_PEEL || ref->flags & PACKREF_CANNOT_PEEL)
+ return 0;
+
+ /*
+ * Find the tagged object in the repository
+ */
+ if (git_object_lookup(&object, backend->repo, &ref->oid, GIT_OBJ_ANY) < 0)
+ return -1;
+
+ /*
+ * If the tagged object is a Tag object, we need to resolve it;
+ * if the ref is actually a 'weak' ref, we don't need to resolve
+ * anything.
+ */
+ if (git_object_type(object) == GIT_OBJ_TAG) {
+ git_tag *tag = (git_tag *)object;
+
+ /*
+ * Find the object pointed at by this tag
+ */
+ git_oid_cpy(&ref->peel, git_tag_target_id(tag));
+ ref->flags |= PACKREF_HAS_PEEL;
+
+ /*
+ * The reference has now cached the resolved OID, and is
+ * marked at such. When written to the packfile, it'll be
+ * accompanied by this resolved oid
+ */
+ }
+
+ git_object_free(object);
+ return 0;
+}
+
+/*
+ * Write a single reference into a packfile
+ */
+static int packed_write_ref(struct packref *ref, git_filebuf *file)
+{
+ char oid[GIT_OID_HEXSZ + 1];
+ git_oid_nfmt(oid, sizeof(oid), &ref->oid);
+
+ /*
+ * For references that peel to an object in the repo, we must
+ * write the resulting peel on a separate line, e.g.
+ *
+ * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4
+ * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100
+ *
+ * This obviously only applies to tags.
+ * The required peels have already been loaded into `ref->peel_target`.
+ */
+ if (ref->flags & PACKREF_HAS_PEEL) {
+ char peel[GIT_OID_HEXSZ + 1];
+ git_oid_nfmt(peel, sizeof(peel), &ref->peel);
+
+ if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0)
+ return -1;
+ } else {
+ if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Remove all loose references
+ *
+ * Once we have successfully written a packfile,
+ * all the loose references that were packed must be
+ * removed from disk.
+ *
+ * This is a dangerous method; make sure the packfile
+ * is well-written, because we are destructing references
+ * here otherwise.
+ */
+static int packed_remove_loose(refdb_fs_backend *backend)
+{
+ size_t i;
+ git_buf full_path = GIT_BUF_INIT;
+ int failed = 0;
+
+ /* backend->refcache is already locked when this is called */
+
+ for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) {
+ struct packref *ref = git_sortedcache_entry(backend->refcache, i);
+
+ if (!ref || !(ref->flags & PACKREF_WAS_LOOSE))
+ continue;
+
+ if (git_buf_joinpath(&full_path, backend->path, ref->name) < 0)
+ return -1; /* critical; do not try to recover on oom */
+
+ if (git_path_exists(full_path.ptr) && p_unlink(full_path.ptr) < 0) {
+ if (failed)
+ continue;
+
+ giterr_set(GITERR_REFERENCE,
+ "Failed to remove loose reference '%s' after packing: %s",
+ full_path.ptr, strerror(errno));
+ failed = 1;
+ }
+
+ /*
+ * if we fail to remove a single file, this is *not* good,
+ * but we should keep going and remove as many as possible.
+ * After we've removed as many files as possible, we return
+ * the error code anyway.
+ */
+ }
+
+ git_buf_free(&full_path);
+ return failed ? -1 : 0;
+}
+
+/*
+ * Write all the contents in the in-memory packfile to disk.
+ */
+static int packed_write(refdb_fs_backend *backend)
+{
+ git_sortedcache *refcache = backend->refcache;
+ git_filebuf pack_file = GIT_FILEBUF_INIT;
+ size_t i;
+
+ /* lock the cache to updates while we do this */
+ if (git_sortedcache_wlock(refcache) < 0)
+ return -1;
+
+ /* Open the file! */
+ if (git_filebuf_open(&pack_file, git_sortedcache_path(refcache), 0, GIT_PACKEDREFS_FILE_MODE) < 0)
+ goto fail;
+
+ /* Packfiles have a header... apparently
+ * This is in fact not required, but we might as well print it
+ * just for kicks */
+ if (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0)
+ goto fail;
+
+ for (i = 0; i < git_sortedcache_entrycount(refcache); ++i) {
+ struct packref *ref = git_sortedcache_entry(refcache, i);
+
+ if (packed_find_peel(backend, ref) < 0)
+ goto fail;
+
+ if (packed_write_ref(ref, &pack_file) < 0)
+ goto fail;
+ }
+
+ /* if we've written all the references properly, we can commit
+ * the packfile to make the changes effective */
+ if (git_filebuf_commit(&pack_file) < 0)
+ goto fail;
+
+ /* when and only when the packfile has been properly written,
+ * we can go ahead and remove the loose refs */
+ if (packed_remove_loose(backend) < 0)
+ goto fail;
+
+ git_sortedcache_updated(refcache);
+ git_sortedcache_wunlock(refcache);
+
+ /* we're good now */
+ return 0;
+
+fail:
+ git_filebuf_cleanup(&pack_file);
+ git_sortedcache_wunlock(refcache);
+
+ return -1;
+}
+
+static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, const git_oid *old, const git_oid *new, const git_signature *author, const char *message);
+static int has_reflog(git_repository *repo, const char *name);
+
+/* We only write if it's under heads/, remotes/ or notes/ or if it already has a log */
+static int should_write_reflog(int *write, git_repository *repo, const char *name)
+{
+ int error, logall;
+
+ error = git_repository__cvar(&logall, repo, GIT_CVAR_LOGALLREFUPDATES);
+ if (error < 0)
+ return error;
+
+ /* Defaults to the opposite of the repo being bare */
+ if (logall == GIT_LOGALLREFUPDATES_UNSET)
+ logall = !git_repository_is_bare(repo);
+
+ if (!logall) {
+ *write = 0;
+ } else if (has_reflog(repo, name)) {
+ *write = 1;
+ } else if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR) ||
+ !git__strcmp(name, GIT_HEAD_FILE) ||
+ !git__prefixcmp(name, GIT_REFS_REMOTES_DIR) ||
+ !git__prefixcmp(name, GIT_REFS_NOTES_DIR)) {
+ *write = 1;
+ } else {
+ *write = 0;
+ }
+
+ return 0;
+}
+
+static int cmp_old_ref(int *cmp, git_refdb_backend *backend, const char *name,
+ const git_oid *old_id, const char *old_target)
+{
+ int error = 0;
+ git_reference *old_ref = NULL;
+
+ *cmp = 0;
+ /* It "matches" if there is no old value to compare against */
+ if (!old_id && !old_target)
+ return 0;
+
+ if ((error = refdb_fs_backend__lookup(&old_ref, backend, name)) < 0)
+ goto out;
+
+ /* If the types don't match, there's no way the values do */
+ if (old_id && old_ref->type != GIT_REF_OID) {
+ *cmp = -1;
+ goto out;
+ }
+ if (old_target && old_ref->type != GIT_REF_SYMBOLIC) {
+ *cmp = 1;
+ goto out;
+ }
+
+ if (old_id && old_ref->type == GIT_REF_OID)
+ *cmp = git_oid_cmp(old_id, &old_ref->target.oid);
+
+ if (old_target && old_ref->type == GIT_REF_SYMBOLIC)
+ *cmp = git__strcmp(old_target, old_ref->target.symbolic);
+
+out:
+ git_reference_free(old_ref);
+
+ return error;
+}
+
+/*
+ * The git.git comment regarding this, for your viewing pleasure:
+ *
+ * Special hack: If a branch is updated directly and HEAD
+ * points to it (may happen on the remote side of a push
+ * for example) then logically the HEAD reflog should be
+ * updated too.
+ * A generic solution implies reverse symref information,
+ * but finding all symrefs pointing to the given branch
+ * would be rather costly for this rare event (the direct
+ * update of a branch) to be worth it. So let's cheat and
+ * check with HEAD only which should cover 99% of all usage
+ * scenarios (even 100% of the default ones).
+ */
+static int maybe_append_head(refdb_fs_backend *backend, const git_reference *ref, const git_signature *who, const char *message)
+{
+ int error;
+ git_oid old_id = {{0}};
+ git_reference *tmp = NULL, *head = NULL, *peeled = NULL;
+ const char *name;
+
+ if (ref->type == GIT_REF_SYMBOLIC)
+ return 0;
+
+ /* if we can't resolve, we use {0}*40 as old id */
+ git_reference_name_to_id(&old_id, backend->repo, ref->name);
+
+ if ((error = git_reference_lookup(&head, backend->repo, GIT_HEAD_FILE)) < 0)
+ return error;
+
+ if (git_reference_type(head) == GIT_REF_OID)
+ goto cleanup;
+
+ if ((error = git_reference_lookup(&tmp, backend->repo, GIT_HEAD_FILE)) < 0)
+ goto cleanup;
+
+ /* Go down the symref chain until we find the branch */
+ while (git_reference_type(tmp) == GIT_REF_SYMBOLIC) {
+ error = git_reference_lookup(&peeled, backend->repo, git_reference_symbolic_target(tmp));
+ if (error < 0)
+ break;
+
+ git_reference_free(tmp);
+ tmp = peeled;
+ }
+
+ if (error == GIT_ENOTFOUND) {
+ error = 0;
+ name = git_reference_symbolic_target(tmp);
+ } else if (error < 0) {
+ goto cleanup;
+ } else {
+ name = git_reference_name(tmp);
+ }
+
+ if (strcmp(name, ref->name))
+ goto cleanup;
+
+ error = reflog_append(backend, head, &old_id, git_reference_target(ref), who, message);
+
+cleanup:
+ git_reference_free(tmp);
+ git_reference_free(head);
+ return error;
+}
+
+
+static int refdb_fs_backend__write(
+ git_refdb_backend *_backend,
+ const git_reference *ref,
+ int force,
+ const git_signature *who,
+ const char *message,
+ const git_oid *old_id,
+ const char *old_target)
+{
+ refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+ git_filebuf file = GIT_FILEBUF_INIT;
+ int error = 0, cmp = 0, should_write;
+ const char *new_target = NULL;
+ const git_oid *new_id = NULL;
+
+ assert(backend);
+
+ error = reference_path_available(backend, ref->name, NULL, force);
+ if (error < 0)
+ return error;
+
+ /* We need to perform the reflog append and old value check under the ref's lock */
+ if ((error = loose_lock(&file, backend, ref->name)) < 0)
+ return error;
+
+ if ((error = cmp_old_ref(&cmp, _backend, ref->name, old_id, old_target)) < 0)
+ goto on_error;
+
+ if (cmp) {
+ giterr_set(GITERR_REFERENCE, "old reference value does not match");
+ error = GIT_EMODIFIED;
+ goto on_error;
+ }
+
+ if (ref->type == GIT_REF_SYMBOLIC)
+ new_target = ref->target.symbolic;
+ else
+ new_id = &ref->target.oid;
+
+ error = cmp_old_ref(&cmp, _backend, ref->name, new_id, new_target);
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto on_error;
+
+ /* Don't update if we have the same value */
+ if (!error && !cmp) {
+ error = 0;
+ goto on_error; /* not really error */
+ }
+
+ if ((error = should_write_reflog(&should_write, backend->repo, ref->name)) < 0)
+ goto on_error;
+
+ if (should_write) {
+ if ((error = reflog_append(backend, ref, NULL, NULL, who, message)) < 0)
+ goto on_error;
+ if ((error = maybe_append_head(backend, ref, who, message)) < 0)
+ goto on_error;
+ }
+
+ return loose_commit(&file, ref);
+
+on_error:
+ git_filebuf_cleanup(&file);
+ return error;
+}
+
+static int refdb_fs_backend__delete(
+ git_refdb_backend *_backend,
+ const char *ref_name,
+ const git_oid *old_id, const char *old_target)
+{
+ refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+ git_buf loose_path = GIT_BUF_INIT;
+ size_t pack_pos;
+ git_filebuf file = GIT_FILEBUF_INIT;
+ int error = 0, cmp = 0;
+ bool loose_deleted = 0;
+
+ assert(backend && ref_name);
+
+ if ((error = loose_lock(&file, backend, ref_name)) < 0)
+ return error;
+
+ error = cmp_old_ref(&cmp, _backend, ref_name, old_id, old_target);
+ if (error < 0)
+ goto cleanup;
+
+ if (cmp) {
+ giterr_set(GITERR_REFERENCE, "old reference value does not match");
+ error = GIT_EMODIFIED;
+ goto cleanup;
+ }
+
+ /* If a loose reference exists, remove it from the filesystem */
+ if (git_buf_joinpath(&loose_path, backend->path, ref_name) < 0)
+ return -1;
+
+ if (git_path_isfile(loose_path.ptr)) {
+ error = p_unlink(loose_path.ptr);
+ loose_deleted = 1;
+ }
+
+ git_buf_free(&loose_path);
+
+ if (error != 0)
+ goto cleanup;
+
+ if ((error = packed_reload(backend)) < 0)
+ goto cleanup;
+
+ /* If a packed reference exists, remove it from the packfile and repack */
+ if ((error = git_sortedcache_wlock(backend->refcache)) < 0)
+ goto cleanup;
+
+ if (!(error = git_sortedcache_lookup_index(
+ &pack_pos, backend->refcache, ref_name)))
+ error = git_sortedcache_remove(backend->refcache, pack_pos);
+
+ git_sortedcache_wunlock(backend->refcache);
+
+ if (error == GIT_ENOTFOUND) {
+ error = loose_deleted ? 0 : ref_error_notfound(ref_name);
+ goto cleanup;
+ }
+
+ error = packed_write(backend);
+
+cleanup:
+ git_filebuf_cleanup(&file);
+
+ return error;
+}
+
+static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name);
+
+static int refdb_fs_backend__rename(
+ git_reference **out,
+ git_refdb_backend *_backend,
+ const char *old_name,
+ const char *new_name,
+ int force,
+ const git_signature *who,
+ const char *message)
+{
+ refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+ git_reference *old, *new;
+ git_filebuf file = GIT_FILEBUF_INIT;
+ int error;
+
+ assert(backend);
+
+ if ((error = reference_path_available(
+ backend, new_name, old_name, force)) < 0 ||
+ (error = refdb_fs_backend__lookup(&old, _backend, old_name)) < 0)
+ return error;
+
+ if ((error = refdb_fs_backend__delete(_backend, old_name, NULL, NULL)) < 0) {
+ git_reference_free(old);
+ return error;
+ }
+
+ new = git_reference__set_name(old, new_name);
+ if (!new) {
+ git_reference_free(old);
+ return -1;
+ }
+
+ if ((error = loose_lock(&file, backend, new->name)) < 0) {
+ git_reference_free(new);
+ return error;
+ }
+
+ /* Try to rename the refog; it's ok if the old doesn't exist */
+ error = refdb_reflog_fs__rename(_backend, old_name, new_name);
+ if (((error == 0) || (error == GIT_ENOTFOUND)) &&
+ ((error = reflog_append(backend, new, NULL, NULL, who, message)) < 0)) {
+ git_reference_free(new);
+ git_filebuf_cleanup(&file);
+ return error;
+ }
+
+ if (error < 0) {
+ git_reference_free(new);
+ git_filebuf_cleanup(&file);
+ return error;
+ }
+
+
+ if ((error = loose_commit(&file, new)) < 0 || out == NULL) {
+ git_reference_free(new);
+ return error;
+ }
+
+ *out = new;
+ return 0;
+}
+
+static int refdb_fs_backend__compress(git_refdb_backend *_backend)
+{
+ refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+
+ assert(backend);
+
+ if (packed_reload(backend) < 0 || /* load the existing packfile */
+ packed_loadloose(backend) < 0 || /* add all the loose refs */
+ packed_write(backend) < 0) /* write back to disk */
+ return -1;
+
+ return 0;
+}
+
+static void refdb_fs_backend__free(git_refdb_backend *_backend)
+{
+ refdb_fs_backend *backend = (refdb_fs_backend *)_backend;
+
+ assert(backend);
+
+ git_sortedcache_free(backend->refcache);
+ git__free(backend->path);
+ git__free(backend);
+}
+
+static int setup_namespace(git_buf *path, git_repository *repo)
+{
+ char *parts, *start, *end;
+
+ /* Not all repositories have a path */
+ if (repo->path_repository == NULL)
+ return 0;
+
+ /* Load the path to the repo first */
+ git_buf_puts(path, repo->path_repository);
+
+ /* if the repo is not namespaced, nothing else to do */
+ if (repo->namespace == NULL)
+ return 0;
+
+ parts = end = git__strdup(repo->namespace);
+ if (parts == NULL)
+ return -1;
+
+ /*
+ * From `man gitnamespaces`:
+ * namespaces which include a / will expand to a hierarchy
+ * of namespaces; for example, GIT_NAMESPACE=foo/bar will store
+ * refs under refs/namespaces/foo/refs/namespaces/bar/
+ */
+ while ((start = git__strsep(&end, "/")) != NULL) {
+ git_buf_printf(path, "refs/namespaces/%s/", start);
+ }
+
+ git_buf_printf(path, "refs/namespaces/%s/refs", end);
+ git__free(parts);
+
+ /* Make sure that the folder with the namespace exists */
+ if (git_futils_mkdir_r(git_buf_cstr(path), repo->path_repository, 0777) < 0)
+ return -1;
+
+ /* Return root of the namespaced path, i.e. without the trailing '/refs' */
+ git_buf_rtruncate_at_char(path, '/');
+ return 0;
+}
+
+static int reflog_alloc(git_reflog **reflog, const char *name)
+{
+ git_reflog *log;
+
+ *reflog = NULL;
+
+ log = git__calloc(1, sizeof(git_reflog));
+ GITERR_CHECK_ALLOC(log);
+
+ log->ref_name = git__strdup(name);
+ GITERR_CHECK_ALLOC(log->ref_name);
+
+ if (git_vector_init(&log->entries, 0, NULL) < 0) {
+ git__free(log->ref_name);
+ git__free(log);
+ return -1;
+ }
+
+ *reflog = log;
+
+ return 0;
+}
+
+static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size)
+{
+ const char *ptr;
+ git_reflog_entry *entry;
+
+#define seek_forward(_increase) do { \
+ if (_increase >= buf_size) { \
+ giterr_set(GITERR_INVALID, "Ran out of data while parsing reflog"); \
+ goto fail; \
+ } \
+ buf += _increase; \
+ buf_size -= _increase; \
+ } while (0)
+
+ while (buf_size > GIT_REFLOG_SIZE_MIN) {
+ entry = git__calloc(1, sizeof(git_reflog_entry));
+ GITERR_CHECK_ALLOC(entry);
+
+ entry->committer = git__malloc(sizeof(git_signature));
+ GITERR_CHECK_ALLOC(entry->committer);
+
+ if (git_oid_fromstrn(&entry->oid_old, buf, GIT_OID_HEXSZ) < 0)
+ goto fail;
+ seek_forward(GIT_OID_HEXSZ + 1);
+
+ if (git_oid_fromstrn(&entry->oid_cur, buf, GIT_OID_HEXSZ) < 0)
+ goto fail;
+ seek_forward(GIT_OID_HEXSZ + 1);
+
+ ptr = buf;
+
+ /* Seek forward to the end of the signature. */
+ while (*buf && *buf != '\t' && *buf != '\n')
+ seek_forward(1);
+
+ if (git_signature__parse(entry->committer, &ptr, buf + 1, NULL, *buf) < 0)
+ goto fail;
+
+ if (*buf == '\t') {
+ /* We got a message. Read everything till we reach LF. */
+ seek_forward(1);
+ ptr = buf;
+
+ while (*buf && *buf != '\n')
+ seek_forward(1);
+
+ entry->msg = git__strndup(ptr, buf - ptr);
+ GITERR_CHECK_ALLOC(entry->msg);
+ } else
+ entry->msg = NULL;
+
+ while (*buf && *buf == '\n' && buf_size > 1)
+ seek_forward(1);
+
+ if (git_vector_insert(&log->entries, entry) < 0)
+ goto fail;
+ }
+
+ return 0;
+
+#undef seek_forward
+
+fail:
+ if (entry)
+ git_reflog_entry__free(entry);
+
+ return -1;
+}
+
+static int create_new_reflog_file(const char *filepath)
+{
+ int fd, error;
+
+ if ((error = git_futils_mkpath2file(filepath, GIT_REFLOG_DIR_MODE)) < 0)
+ return error;
+
+ if ((fd = p_open(filepath,
+ O_WRONLY | O_CREAT,
+ GIT_REFLOG_FILE_MODE)) < 0)
+ return -1;
+
+ return p_close(fd);
+}
+
+GIT_INLINE(int) retrieve_reflog_path(git_buf *path, git_repository *repo, const char *name)
+{
+ return git_buf_join3(path, '/', repo->path_repository, GIT_REFLOG_DIR, name);
+}
+
+static int refdb_reflog_fs__ensure_log(git_refdb_backend *_backend, const char *name)
+{
+ refdb_fs_backend *backend;
+ git_repository *repo;
+ git_buf path = GIT_BUF_INIT;
+ int error;
+
+ assert(_backend && name);
+
+ backend = (refdb_fs_backend *) _backend;
+ repo = backend->repo;
+
+ if ((error = retrieve_reflog_path(&path, repo, name)) < 0)
+ return error;
+
+ error = create_new_reflog_file(git_buf_cstr(&path));
+ git_buf_free(&path);
+
+ return error;
+}
+
+static int has_reflog(git_repository *repo, const char *name)
+{
+ int ret = 0;
+ git_buf path = GIT_BUF_INIT;
+
+ if (retrieve_reflog_path(&path, repo, name) < 0)
+ goto cleanup;
+
+ ret = git_path_isfile(git_buf_cstr(&path));
+
+cleanup:
+ git_buf_free(&path);
+ return ret;
+}
+
+static int refdb_reflog_fs__has_log(git_refdb_backend *_backend, const char *name)
+{
+ refdb_fs_backend *backend;
+
+ assert(_backend && name);
+
+ backend = (refdb_fs_backend *) _backend;
+
+ return has_reflog(backend->repo, name);
+}
+
+static int refdb_reflog_fs__read(git_reflog **out, git_refdb_backend *_backend, const char *name)
+{
+ int error = -1;
+ git_buf log_path = GIT_BUF_INIT;
+ git_buf log_file = GIT_BUF_INIT;
+ git_reflog *log = NULL;
+ git_repository *repo;
+ refdb_fs_backend *backend;
+
+ assert(out && _backend && name);
+
+ backend = (refdb_fs_backend *) _backend;
+ repo = backend->repo;
+
+ if (reflog_alloc(&log, name) < 0)
+ return -1;
+
+ if (retrieve_reflog_path(&log_path, repo, name) < 0)
+ goto cleanup;
+
+ error = git_futils_readbuffer(&log_file, git_buf_cstr(&log_path));
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto cleanup;
+
+ if ((error == GIT_ENOTFOUND) &&
+ ((error = create_new_reflog_file(git_buf_cstr(&log_path))) < 0))
+ goto cleanup;
+
+ if ((error = reflog_parse(log,
+ git_buf_cstr(&log_file), git_buf_len(&log_file))) < 0)
+ goto cleanup;
+
+ *out = log;
+ goto success;
+
+cleanup:
+ git_reflog_free(log);
+
+success:
+ git_buf_free(&log_file);
+ git_buf_free(&log_path);
+
+ return error;
+}
+
+static int serialize_reflog_entry(
+ git_buf *buf,
+ const git_oid *oid_old,
+ const git_oid *oid_new,
+ const git_signature *committer,
+ const char *msg)
+{
+ char raw_old[GIT_OID_HEXSZ+1];
+ char raw_new[GIT_OID_HEXSZ+1];
+
+ git_oid_tostr(raw_old, GIT_OID_HEXSZ+1, oid_old);
+ git_oid_tostr(raw_new, GIT_OID_HEXSZ+1, oid_new);
+
+ git_buf_clear(buf);
+
+ git_buf_puts(buf, raw_old);
+ git_buf_putc(buf, ' ');
+ git_buf_puts(buf, raw_new);
+
+ git_signature__writebuf(buf, " ", committer);
+
+ /* drop trailing LF */
+ git_buf_rtrim(buf);
+
+ if (msg) {
+ git_buf_putc(buf, '\t');
+ git_buf_puts(buf, msg);
+ }
+
+ git_buf_putc(buf, '\n');
+
+ return git_buf_oom(buf);
+}
+
+static int lock_reflog(git_filebuf *file, refdb_fs_backend *backend, const char *refname)
+{
+ git_repository *repo;
+ git_buf log_path = GIT_BUF_INIT;
+ int error;
+
+ repo = backend->repo;
+
+ if (retrieve_reflog_path(&log_path, repo, refname) < 0)
+ return -1;
+
+ if (!git_path_isfile(git_buf_cstr(&log_path))) {
+ giterr_set(GITERR_INVALID,
+ "Log file for reference '%s' doesn't exist.", refname);
+ error = -1;
+ goto cleanup;
+ }
+
+ error = git_filebuf_open(file, git_buf_cstr(&log_path), 0, GIT_REFLOG_FILE_MODE);
+
+cleanup:
+ git_buf_free(&log_path);
+
+ return error;
+}
+
+static int refdb_reflog_fs__write(git_refdb_backend *_backend, git_reflog *reflog)
+{
+ int error = -1;
+ unsigned int i;
+ git_reflog_entry *entry;
+ refdb_fs_backend *backend;
+ git_buf log = GIT_BUF_INIT;
+ git_filebuf fbuf = GIT_FILEBUF_INIT;
+
+ assert(_backend && reflog);
+
+ backend = (refdb_fs_backend *) _backend;
+
+ if ((error = lock_reflog(&fbuf, backend, reflog->ref_name)) < 0)
+ return -1;
+
+ git_vector_foreach(&reflog->entries, i, entry) {
+ if (serialize_reflog_entry(&log, &(entry->oid_old), &(entry->oid_cur), entry->committer, entry->msg) < 0)
+ goto cleanup;
+
+ if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0)
+ goto cleanup;
+ }
+
+ error = git_filebuf_commit(&fbuf);
+ goto success;
+
+cleanup:
+ git_filebuf_cleanup(&fbuf);
+
+success:
+ git_buf_free(&log);
+
+ return error;
+}
+
+/* Append to the reflog, must be called under reference lock */
+static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, const git_oid *old, const git_oid *new, const git_signature *who, const char *message)
+{
+ int error, is_symbolic;
+ git_oid old_id = {{0}}, new_id = {{0}};
+ git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT;
+ git_repository *repo = backend->repo;
+
+ is_symbolic = ref->type == GIT_REF_SYMBOLIC;
+
+ /* "normal" symbolic updates do not write */
+ if (is_symbolic &&
+ strcmp(ref->name, GIT_HEAD_FILE) &&
+ !(old && new))
+ return 0;
+
+ /* From here on is_symoblic also means that it's HEAD */
+
+ if (old) {
+ git_oid_cpy(&old_id, old);
+ } else {
+ error = git_reference_name_to_id(&old_id, repo, ref->name);
+ if (error < 0 && error != GIT_ENOTFOUND)
+ return error;
+ }
+
+ if (new) {
+ git_oid_cpy(&new_id, new);
+ } else {
+ if (!is_symbolic) {
+ git_oid_cpy(&new_id, git_reference_target(ref));
+ } else {
+ error = git_reference_name_to_id(&new_id, repo, git_reference_symbolic_target(ref));
+ if (error < 0 && error != GIT_ENOTFOUND)
+ return error;
+ /* detaching HEAD does not create an entry */
+ if (error == GIT_ENOTFOUND)
+ return 0;
+
+ giterr_clear();
+ }
+ }
+
+ if ((error = serialize_reflog_entry(&buf, &old_id, &new_id, who, message)) < 0)
+ goto cleanup;
+
+ if ((error = retrieve_reflog_path(&path, repo, ref->name)) < 0)
+ goto cleanup;
+
+ if (((error = git_futils_mkpath2file(git_buf_cstr(&path), 0777)) < 0) &&
+ (error != GIT_EEXISTS)) {
+ goto cleanup;
+ }
+
+ error = git_futils_writebuffer(&buf, git_buf_cstr(&path), O_WRONLY|O_CREAT|O_APPEND, GIT_REFLOG_FILE_MODE);
+
+cleanup:
+ git_buf_free(&buf);
+ git_buf_free(&path);
+
+ return error;
+}
+
+static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_name, const char *new_name)
+{
+ int error = 0, fd;
+ git_buf old_path = GIT_BUF_INIT;
+ git_buf new_path = GIT_BUF_INIT;
+ git_buf temp_path = GIT_BUF_INIT;
+ git_buf normalized = GIT_BUF_INIT;
+ git_repository *repo;
+ refdb_fs_backend *backend;
+
+ assert(_backend && old_name && new_name);
+
+ backend = (refdb_fs_backend *) _backend;
+ repo = backend->repo;
+
+ if ((error = git_reference__normalize_name(
+ &normalized, new_name, GIT_REF_FORMAT_ALLOW_ONELEVEL)) < 0)
+ return error;
+
+ if (git_buf_joinpath(&temp_path, repo->path_repository, GIT_REFLOG_DIR) < 0)
+ return -1;
+
+ if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), old_name) < 0)
+ return -1;
+
+ if (git_buf_joinpath(&new_path, git_buf_cstr(&temp_path), git_buf_cstr(&normalized)) < 0)
+ return -1;
+
+ if (!git_path_exists(git_buf_cstr(&old_path))) {
+ error = GIT_ENOTFOUND;
+ goto cleanup;
+ }
+
+ /*
+ * Move the reflog to a temporary place. This two-phase renaming is required
+ * in order to cope with funny renaming use cases when one tries to move a reference
+ * to a partially colliding namespace:
+ * - a/b -> a/b/c
+ * - a/b/c/d -> a/b/c
+ */
+ if (git_buf_joinpath(&temp_path, git_buf_cstr(&temp_path), "temp_reflog") < 0)
+ return -1;
+
+ if ((fd = git_futils_mktmp(&temp_path, git_buf_cstr(&temp_path), GIT_REFLOG_FILE_MODE)) < 0) {
+ error = -1;
+ goto cleanup;
+ }
+
+ p_close(fd);
+
+ if (p_rename(git_buf_cstr(&old_path), git_buf_cstr(&temp_path)) < 0) {
+ giterr_set(GITERR_OS, "Failed to rename reflog for %s", new_name);
+ error = -1;
+ goto cleanup;
+ }
+
+ if (git_path_isdir(git_buf_cstr(&new_path)) &&
+ (git_futils_rmdir_r(git_buf_cstr(&new_path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0)) {
+ error = -1;
+ goto cleanup;
+ }
+
+ if (git_futils_mkpath2file(git_buf_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0) {
+ error = -1;
+ goto cleanup;
+ }
+
+ if (p_rename(git_buf_cstr(&temp_path), git_buf_cstr(&new_path)) < 0) {
+ giterr_set(GITERR_OS, "Failed to rename reflog for %s", new_name);
+ error = -1;
+ }
+
+cleanup:
+ git_buf_free(&temp_path);
+ git_buf_free(&old_path);
+ git_buf_free(&new_path);
+ git_buf_free(&normalized);
+
+ return error;
+}
+
+static int refdb_reflog_fs__delete(git_refdb_backend *_backend, const char *name)
+{
+ int error;
+ git_buf path = GIT_BUF_INIT;
+
+ git_repository *repo;
+ refdb_fs_backend *backend;
+
+ assert(_backend && name);
+
+ backend = (refdb_fs_backend *) _backend;
+ repo = backend->repo;
+
+ error = retrieve_reflog_path(&path, repo, name);
+
+ if (!error && git_path_exists(path.ptr))
+ error = p_unlink(path.ptr);
+
+ git_buf_free(&path);
+
+ return error;
+
+}
+
+int git_refdb_backend_fs(
+ git_refdb_backend **backend_out,
+ git_repository *repository)
+{
+ int t = 0;
+ git_buf path = GIT_BUF_INIT;
+ refdb_fs_backend *backend;
+
+ backend = git__calloc(1, sizeof(refdb_fs_backend));
+ GITERR_CHECK_ALLOC(backend);
+
+ backend->repo = repository;
+
+ if (setup_namespace(&path, repository) < 0)
+ goto fail;
+
+ backend->path = git_buf_detach(&path);
+
+ if (git_buf_joinpath(&path, backend->path, GIT_PACKEDREFS_FILE) < 0 ||
+ git_sortedcache_new(
+ &backend->refcache, offsetof(struct packref, name),
+ NULL, NULL, packref_cmp, git_buf_cstr(&path)) < 0)
+ goto fail;
+
+ git_buf_free(&path);
+
+ if (!git_repository__cvar(&t, backend->repo, GIT_CVAR_IGNORECASE) && t) {
+ backend->iterator_flags |= GIT_ITERATOR_IGNORE_CASE;
+ backend->direach_flags |= GIT_PATH_DIR_IGNORE_CASE;
+ }
+ if (!git_repository__cvar(&t, backend->repo, GIT_CVAR_PRECOMPOSE) && t) {
+ backend->iterator_flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE;
+ backend->direach_flags |= GIT_PATH_DIR_PRECOMPOSE_UNICODE;
+ }
+
+ backend->parent.exists = &refdb_fs_backend__exists;
+ backend->parent.lookup = &refdb_fs_backend__lookup;
+ backend->parent.iterator = &refdb_fs_backend__iterator;
+ backend->parent.write = &refdb_fs_backend__write;
+ backend->parent.del = &refdb_fs_backend__delete;
+ backend->parent.rename = &refdb_fs_backend__rename;
+ backend->parent.compress = &refdb_fs_backend__compress;
+ backend->parent.has_log = &refdb_reflog_fs__has_log;
+ backend->parent.ensure_log = &refdb_reflog_fs__ensure_log;
+ backend->parent.free = &refdb_fs_backend__free;
+ backend->parent.reflog_read = &refdb_reflog_fs__read;
+ backend->parent.reflog_write = &refdb_reflog_fs__write;
+ backend->parent.reflog_rename = &refdb_reflog_fs__rename;
+ backend->parent.reflog_delete = &refdb_reflog_fs__delete;
+
+ *backend_out = (git_refdb_backend *)backend;
+ return 0;
+
+fail:
+ git_buf_free(&path);
+ git__free(backend->path);
+ git__free(backend);
+ return -1;
+}
diff --git a/src/refdb_fs.h b/src/refdb_fs.h
new file mode 100644
index 000000000..79e296833
--- /dev/null
+++ b/src/refdb_fs.h
@@ -0,0 +1,15 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_refdb_fs_h__
+#define INCLUDE_refdb_fs_h__
+
+typedef struct {
+ git_strmap *packfile;
+ time_t packfile_time;
+} git_refcache;
+
+#endif
diff --git a/src/reflog.c b/src/reflog.c
index 7609d9a4d..8e41621ea 100644
--- a/src/reflog.c
+++ b/src/reflog.c
@@ -1,280 +1,232 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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 "reflog.h"
#include "repository.h"
#include "filebuf.h"
#include "signature.h"
+#include "refdb.h"
+
+#include <git2/sys/refdb_backend.h>
-static int reflog_init(git_reflog **reflog, git_reference *ref)
+git_reflog_entry *git_reflog_entry__alloc(void)
{
- git_reflog *log;
+ return git__calloc(1, sizeof(git_reflog_entry));
+}
- *reflog = NULL;
+void git_reflog_entry__free(git_reflog_entry *entry)
+{
+ git_signature_free(entry->committer);
- log = git__malloc(sizeof(git_reflog));
- if (log == NULL)
- return GIT_ENOMEM;
+ git__free(entry->msg);
+ git__free(entry);
+}
- memset(log, 0x0, sizeof(git_reflog));
+void git_reflog_free(git_reflog *reflog)
+{
+ size_t i;
+ git_reflog_entry *entry;
- log->ref_name = git__strdup(ref->name);
+ if (reflog == NULL)
+ return;
- if (git_vector_init(&log->entries, 0, NULL) < 0) {
- free(log->ref_name);
- free(log);
- return GIT_ENOMEM;
- }
+ if (reflog->db)
+ GIT_REFCOUNT_DEC(reflog->db, git_refdb__free);
- *reflog = log;
+ for (i=0; i < reflog->entries.length; i++) {
+ entry = git_vector_get(&reflog->entries, i);
- return GIT_SUCCESS;
+ git_reflog_entry__free(entry);
+ }
+
+ git_vector_free(&reflog->entries);
+ git__free(reflog->ref_name);
+ git__free(reflog);
}
-static int reflog_write(const char *log_path, const char *oid_old,
- const char *oid_new, const git_signature *committer,
- const char *msg)
+int git_reflog_read(git_reflog **reflog, git_repository *repo, const char *name)
{
+ git_refdb *refdb;
int error;
- git_buf log = GIT_BUF_INIT;
- git_filebuf fbuf;
- assert(log_path && oid_old && oid_new && committer);
+ assert(reflog && repo && name);
- git_buf_puts(&log, oid_old);
- git_buf_putc(&log, ' ');
+ if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
+ return error;
- git_buf_puts(&log, oid_new);
-
- git_signature__writebuf(&log, " ", committer);
- log.size--; /* drop LF */
-
- if (msg) {
- if (strchr(msg, '\n')) {
- git_buf_free(&log);
- return git__throw(GIT_ERROR, "Reflog message cannot contain newline");
- }
-
- git_buf_putc(&log, '\t');
- git_buf_puts(&log, msg);
- }
-
- git_buf_putc(&log, '\n');
+ return git_refdb_reflog_read(reflog, refdb, name);
+}
- if ((error = git_filebuf_open(&fbuf, log_path, GIT_FILEBUF_APPEND)) < GIT_SUCCESS) {
- git_buf_free(&log);
- return git__throw(GIT_ERROR, "Failed to write reflog. Cannot open reflog `%s`", log_path);
- }
+int git_reflog_write(git_reflog *reflog)
+{
+ git_refdb *db;
- git_filebuf_write(&fbuf, log.ptr, log.size);
- error = git_filebuf_commit(&fbuf);
+ assert(reflog && reflog->db);
- git_buf_free(&log);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write reflog");
+ db = reflog->db;
+ return db->backend->reflog_write(db->backend, reflog);
}
-static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size)
+int git_reflog_append(git_reflog *reflog, const git_oid *new_oid, const git_signature *committer, const char *msg)
{
- int error = GIT_SUCCESS;
- const char *ptr;
git_reflog_entry *entry;
+ const git_reflog_entry *previous;
+ const char *newline;
-#define seek_forward(_increase) { \
- if (_increase >= buf_size) \
- return git__throw(GIT_ERROR, "Failed to seek forward. Buffer size exceeded"); \
- buf += _increase; \
- buf_size -= _increase; \
-}
-
- while (buf_size > GIT_REFLOG_SIZE_MIN) {
- entry = git__malloc(sizeof(git_reflog_entry));
- if (entry == NULL)
- return GIT_ENOMEM;
-
- entry->oid_old = git__strndup(buf, GIT_OID_HEXSZ);
- seek_forward(GIT_OID_HEXSZ + 1);
-
- entry->oid_cur = git__strndup(buf, GIT_OID_HEXSZ);
- seek_forward(GIT_OID_HEXSZ + 1);
+ assert(reflog && new_oid && committer);
- ptr = buf;
+ entry = git__calloc(1, sizeof(git_reflog_entry));
+ GITERR_CHECK_ALLOC(entry);
- /* Seek forward to the end of the signature. */
- while (*buf && *buf != '\t' && *buf != '\n')
- seek_forward(1);
+ if ((git_signature_dup(&entry->committer, committer)) < 0)
+ goto cleanup;
- entry->committer = git__malloc(sizeof(git_signature));
- if (entry->committer == NULL)
- return GIT_ENOMEM;
-
- if ((error = git_signature__parse(entry->committer, &ptr, buf + 1, NULL, *buf)) < GIT_SUCCESS)
+ if (msg != NULL) {
+ if ((entry->msg = git__strdup(msg)) == NULL)
goto cleanup;
- if (*buf == '\t') {
- /* We got a message. Read everything till we reach LF. */
- seek_forward(1);
- ptr = buf;
-
- while (*buf && *buf != '\n')
- seek_forward(1);
+ newline = strchr(msg, '\n');
- entry->msg = git__strndup(ptr, buf - ptr);
- } else
- entry->msg = NULL;
+ if (newline) {
+ if (newline[1] != '\0') {
+ giterr_set(GITERR_INVALID, "Reflog message cannot contain newline");
+ goto cleanup;
+ }
- while (*buf && *buf == '\n' && buf_size > 1)
- seek_forward(1);
-
- if ((error = git_vector_insert(&log->entries, entry)) < GIT_SUCCESS)
- goto cleanup;
+ entry->msg[newline - msg] = '\0';
+ }
}
-#undef seek_forward
-
-cleanup:
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse reflog");
-}
-
-void git_reflog_free(git_reflog *reflog)
-{
- unsigned int i;
- git_reflog_entry *entry;
+ previous = git_reflog_entry_byindex(reflog, 0);
- for (i=0; i < reflog->entries.length; i++) {
- entry = git_vector_get(&reflog->entries, i);
+ if (previous == NULL)
+ git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO);
+ else
+ git_oid_cpy(&entry->oid_old, &previous->oid_cur);
- free(entry->oid_old);
- free(entry->oid_cur);
+ git_oid_cpy(&entry->oid_cur, new_oid);
- git_signature_free(entry->committer);
+ if (git_vector_insert(&reflog->entries, entry) < 0)
+ goto cleanup;
- free(entry->msg);
- free(entry);
- }
+ return 0;
- git_vector_free(&reflog->entries);
- free(reflog->ref_name);
- free(reflog);
+cleanup:
+ git_reflog_entry__free(entry);
+ return -1;
}
-int git_reflog_read(git_reflog **reflog, git_reference *ref)
+int git_reflog_rename(git_repository *repo, const char *old_name, const char *new_name)
{
+ git_refdb *refdb;
int error;
- char log_path[GIT_PATH_MAX];
- git_fbuffer log_file = GIT_FBUFFER_INIT;
- git_reflog *log = NULL;
-
- *reflog = NULL;
-
- if ((error = reflog_init(&log, ref)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to read reflog. Cannot init reflog");
-
- git_path_join_n(log_path, 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name);
- if ((error = git_futils_readbuffer(&log_file, log_path)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to read reflog. Cannot read file `%s`", log_path);
+ if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
+ return -1;
- error = reflog_parse(log, log_file.data, log_file.len);
-
- git_futils_freebuffer(&log_file);
-
- if (error == GIT_SUCCESS)
- *reflog = log;
-
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to read reflog");
+ return refdb->backend->reflog_rename(refdb->backend, old_name, new_name);
}
-int git_reflog_write(git_reference *ref, const git_oid *oid_old,
- const git_signature *committer, const char *msg)
+int git_reflog_delete(git_repository *repo, const char *name)
{
+ git_refdb *refdb;
int error;
- char old[GIT_OID_HEXSZ+1];
- char new[GIT_OID_HEXSZ+1];
- char log_path[GIT_PATH_MAX];
- git_reference *r;
- const git_oid *oid;
-
- if ((error = git_reference_resolve(&r, ref)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to write reflog. Cannot resolve reference `%s`", ref->name);
-
- oid = git_reference_oid(r);
- if (oid == NULL)
- return git__throw(GIT_ERROR, "Failed to write reflog. Cannot resolve reference `%s`", r->name);
- git_oid_to_string(new, GIT_OID_HEXSZ+1, oid);
+ if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
+ return -1;
- git_path_join_n(log_path, 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name);
-
- if (git_futils_exists(log_path)) {
- if ((error = git_futils_mkpath2file(log_path)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to write reflog. Cannot create reflog directory");
- } else if (git_futils_isfile(log_path)) {
- return git__throw(GIT_ERROR, "Failed to write reflog. `%s` is directory", log_path);
- } else if (oid_old == NULL)
- return git__throw(GIT_ERROR, "Failed to write reflog. Old OID cannot be NULL for existing reference");
-
- if (oid_old)
- git_oid_to_string(old, GIT_OID_HEXSZ+1, oid_old);
- else
- snprintf(old, GIT_OID_HEXSZ+1, "%0*d", GIT_OID_HEXSZ, 0);
-
- return reflog_write(log_path, old, new, committer, msg);
+ return refdb->backend->reflog_delete(refdb->backend, name);
}
-unsigned int git_reflog_entrycount(git_reflog *reflog)
+size_t git_reflog_entrycount(git_reflog *reflog)
{
assert(reflog);
return reflog->entries.length;
}
-const git_reflog_entry * git_reflog_entry_byindex(git_reflog *reflog, unsigned int idx)
+const git_reflog_entry * git_reflog_entry_byindex(git_reflog *reflog, size_t idx)
{
assert(reflog);
- return git_vector_get(&reflog->entries, idx);
+
+ if (idx >= reflog->entries.length)
+ return NULL;
+
+ return git_vector_get(
+ &reflog->entries, reflog_inverse_index(idx, reflog->entries.length));
}
-char * git_reflog_entry_oidold(const git_reflog_entry *entry)
+const git_oid * git_reflog_entry_id_old(const git_reflog_entry *entry)
{
assert(entry);
- return entry->oid_old;
+ return &entry->oid_old;
}
-char * git_reflog_entry_oidnew(const git_reflog_entry *entry)
+const git_oid * git_reflog_entry_id_new(const git_reflog_entry *entry)
{
assert(entry);
- return entry->oid_cur;
+ return &entry->oid_cur;
}
-git_signature * git_reflog_entry_committer(const git_reflog_entry *entry)
+const git_signature * git_reflog_entry_committer(const git_reflog_entry *entry)
{
assert(entry);
return entry->committer;
}
-char * git_reflog_entry_msg(const git_reflog_entry *entry)
+const char * git_reflog_entry_message(const git_reflog_entry *entry)
{
assert(entry);
return entry->msg;
}
+
+int git_reflog_drop(git_reflog *reflog, size_t idx, int rewrite_previous_entry)
+{
+ size_t entrycount;
+ git_reflog_entry *entry, *previous;
+
+ entrycount = git_reflog_entrycount(reflog);
+
+ entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx);
+
+ if (entry == NULL) {
+ giterr_set(GITERR_REFERENCE, "No reflog entry at index "PRIuZ, idx);
+ return GIT_ENOTFOUND;
+ }
+
+ git_reflog_entry__free(entry);
+
+ if (git_vector_remove(
+ &reflog->entries, reflog_inverse_index(idx, entrycount)) < 0)
+ return -1;
+
+ if (!rewrite_previous_entry)
+ return 0;
+
+ /* No need to rewrite anything when removing the most recent entry */
+ if (idx == 0)
+ return 0;
+
+ /* Have the latest entry just been dropped? */
+ if (entrycount == 1)
+ return 0;
+
+ entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx - 1);
+
+ /* If the oldest entry has just been removed... */
+ if (idx == entrycount - 1) {
+ /* ...clear the oid_old member of the "new" oldest entry */
+ if (git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO) < 0)
+ return -1;
+
+ return 0;
+ }
+
+ previous = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx);
+ git_oid_cpy(&entry->oid_old, &previous->oid_cur);
+
+ return 0;
+}
diff --git a/src/reflog.h b/src/reflog.h
index da352ca8f..2d31ae47d 100644
--- a/src/reflog.h
+++ b/src/reflog.h
@@ -1,3 +1,9 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_reflog_h__
#define INCLUDE_reflog_h__
@@ -6,12 +12,14 @@
#include "vector.h"
#define GIT_REFLOG_DIR "logs/"
+#define GIT_REFLOG_DIR_MODE 0777
+#define GIT_REFLOG_FILE_MODE 0666
#define GIT_REFLOG_SIZE_MIN (2*GIT_OID_HEXSZ+2+17)
struct git_reflog_entry {
- char *oid_old;
- char *oid_cur;
+ git_oid oid_old;
+ git_oid oid_cur;
git_signature *committer;
@@ -19,8 +27,14 @@ struct git_reflog_entry {
};
struct git_reflog {
+ git_refdb *db;
char *ref_name;
git_vector entries;
};
+GIT_INLINE(size_t) reflog_inverse_index(size_t idx, size_t total)
+{
+ return (total - 1) - idx;
+}
+
#endif /* INCLUDE_reflog_h__ */
diff --git a/src/refs.c b/src/refs.c
index beb98a780..1603876da 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -1,1757 +1,1246 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
*/
#include "refs.h"
#include "hash.h"
#include "repository.h"
#include "fileops.h"
+#include "filebuf.h"
+#include "pack.h"
+#include "reflog.h"
+#include "refdb.h"
#include <git2/tag.h>
#include <git2/object.h>
+#include <git2/oid.h>
+#include <git2/branch.h>
+#include <git2/refs.h>
+#include <git2/refdb.h>
+#include <git2/sys/refs.h>
+#include <git2/signature.h>
-#define MAX_NESTING_LEVEL 5
-
-typedef struct {
- git_reference ref;
- git_oid oid;
- git_oid peel_target;
-} reference_oid;
+GIT__USE_STRMAP;
-typedef struct {
- git_reference ref;
- char *target;
-} reference_symbolic;
+#define DEFAULT_NESTING_LEVEL 5
+#define MAX_NESTING_LEVEL 10
-static const int default_table_size = 32;
+enum {
+ GIT_PACKREF_HAS_PEEL = 1,
+ GIT_PACKREF_WAS_LOOSE = 2
+};
-static uint32_t reftable_hash(const void *key, int hash_id)
+static git_reference *alloc_ref(const char *name)
{
- static uint32_t hash_seeds[GIT_HASHTABLE_HASHES] = {
- 2147483647,
- 0x5d20bb23,
- 0x7daaab3c
- };
+ git_reference *ref;
+ size_t namelen = strlen(name);
- return git__hash(key, strlen((const char *)key), hash_seeds[hash_id]);
-}
-
-static void reference_free(git_reference *reference);
-static int reference_create(git_reference **ref_out, git_repository *repo, const char *name, git_rtype type);
-static int reference_read(git_fbuffer *file_content, time_t *mtime, const char *repo_path, const char *ref_name, int *updated);
+ if ((ref = git__calloc(1, sizeof(git_reference) + namelen + 1)) == NULL)
+ return NULL;
-/* loose refs */
-static int loose_parse_symbolic(git_reference *ref, git_fbuffer *file_content);
-static int loose_parse_oid(git_reference *ref, git_fbuffer *file_content);
-static int loose_lookup(git_reference **ref_out, git_repository *repo, const char *name, int skip_symbolic);
-static int loose_write(git_reference *ref);
-static int loose_update(git_reference *ref);
+ memcpy(ref->name, name, namelen + 1);
-/* packed refs */
-static int packed_parse_peel(reference_oid *tag_ref, const char **buffer_out, const char *buffer_end);
-static int packed_parse_oid(reference_oid **ref_out, git_repository *repo, const char **buffer_out, const char *buffer_end);
-static int packed_load(git_repository *repo);
-static int packed_loadloose(git_repository *repository);
-static int packed_write_ref(reference_oid *ref, git_filebuf *file);
-static int packed_find_peel(reference_oid *ref);
-static int packed_remove_loose(git_repository *repo, git_vector *packing_list);
-static int packed_sort(const void *a, const void *b);
-static int packed_write(git_repository *repo);
+ return ref;
+}
-/* internal helpers */
-static int reference_available(git_repository *repo, const char *ref, const char *old_ref);
+git_reference *git_reference__alloc_symbolic(
+ const char *name, const char *target)
+{
+ git_reference *ref;
-/* name normalization */
-static int check_valid_ref_char(char ch);
-static int normalize_name(char *buffer_out, size_t out_size, const char *name, int is_oid_ref);
+ assert(name && target);
-/*****************************************
- * Internal methods - Constructor/destructor
- *****************************************/
-static void reference_free(git_reference *reference)
-{
- if (reference == NULL)
- return;
+ ref = alloc_ref(name);
+ if (!ref)
+ return NULL;
- if (reference->name)
- free(reference->name);
+ ref->type = GIT_REF_SYMBOLIC;
- if (reference->type == GIT_REF_SYMBOLIC)
- free(((reference_symbolic *)reference)->target);
+ if ((ref->target.symbolic = git__strdup(target)) == NULL) {
+ git__free(ref);
+ return NULL;
+ }
- free(reference);
+ return ref;
}
-static int reference_create(
- git_reference **ref_out,
- git_repository *repo,
+git_reference *git_reference__alloc(
const char *name,
- git_rtype type)
+ const git_oid *oid,
+ const git_oid *peel)
{
- char normalized[GIT_REFNAME_MAX];
- int error = GIT_SUCCESS, size;
- git_reference *reference = NULL;
-
- assert(ref_out && repo && name);
-
- if (type == GIT_REF_SYMBOLIC)
- size = sizeof(reference_symbolic);
- else if (type == GIT_REF_OID)
- size = sizeof(reference_oid);
- else
- return git__throw(GIT_EINVALIDARGS,
- "Invalid reference type. Use either GIT_REF_OID or GIT_REF_SYMBOLIC as type specifier");
-
- reference = git__malloc(size);
- if (reference == NULL)
- return GIT_ENOMEM;
+ git_reference *ref;
- memset(reference, 0x0, size);
- reference->owner = repo;
- reference->type = type;
+ assert(name && oid);
- error = normalize_name(normalized, sizeof(normalized), name, (type & GIT_REF_OID));
- if (error < GIT_SUCCESS)
- goto cleanup;
-
- reference->name = git__strdup(normalized);
- if (reference->name == NULL) {
- error = GIT_ENOMEM;
- goto cleanup;
- }
+ ref = alloc_ref(name);
+ if (!ref)
+ return NULL;
- *ref_out = reference;
+ ref->type = GIT_REF_OID;
+ git_oid_cpy(&ref->target.oid, oid);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference");
+ if (peel != NULL)
+ git_oid_cpy(&ref->peel, peel);
-cleanup:
- reference_free(reference);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference");
+ return ref;
}
-static int reference_read(git_fbuffer *file_content, time_t *mtime, const char *repo_path, const char *ref_name, int *updated)
+git_reference *git_reference__set_name(
+ git_reference *ref, const char *name)
{
- char path[GIT_PATH_MAX];
-
- assert(file_content && repo_path && ref_name);
-
- /* Determine the full path of the file */
- git_path_join(path, repo_path, ref_name);
-
- return git_futils_readbuffer_updated(file_content, path, mtime, updated);
+ size_t namelen = strlen(name);
+ git_reference *rewrite =
+ git__realloc(ref, sizeof(git_reference) + namelen + 1);
+ if (rewrite != NULL)
+ memcpy(rewrite->name, name, namelen + 1);
+ return rewrite;
}
-
-
-
-/*****************************************
- * Internal methods - Loose references
- *****************************************/
-static int loose_update(git_reference *ref)
+void git_reference_free(git_reference *reference)
{
- int error, updated;
- git_fbuffer ref_file = GIT_FBUFFER_INIT;
+ if (reference == NULL)
+ return;
- if (ref->type & GIT_REF_PACKED)
- return packed_load(ref->owner);
+ if (reference->type == GIT_REF_SYMBOLIC)
+ git__free(reference->target.symbolic);
-/* error = reference_read(NULL, &ref_time, ref->owner->path_repository, ref->name);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ if (reference->db)
+ GIT_REFCOUNT_DEC(reference->db, git_refdb__free);
- if (ref_time == ref->mtime)
- return GIT_SUCCESS;
-*/
- error = reference_read(&ref_file, &ref->mtime, ref->owner->path_repository, ref->name, &updated);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ git__free(reference);
+}
- if (!updated)
- goto cleanup;
+int git_reference_delete(git_reference *ref)
+{
+ const git_oid *old_id = NULL;
+ const char *old_target = NULL;
- if (ref->type == GIT_REF_SYMBOLIC)
- error = loose_parse_symbolic(ref, &ref_file);
- else if (ref->type == GIT_REF_OID)
- error = loose_parse_oid(ref, &ref_file);
+ if (ref->type == GIT_REF_OID)
+ old_id = &ref->target.oid;
else
- error = git__throw(GIT_EOBJCORRUPTED,
- "Invalid reference type (%d) for loose reference", ref->type);
-
+ old_target = ref->target.symbolic;
-cleanup:
- git_futils_freebuffer(&ref_file);
- if (error != GIT_SUCCESS) {
- reference_free(ref);
- git_hashtable_remove(ref->owner->references.loose_cache, ref->name);
- }
-
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to update loose reference");
+ return git_refdb_delete(ref->db, ref->name, old_id, old_target);
}
-static int loose_parse_symbolic(git_reference *ref, git_fbuffer *file_content)
+int git_reference_remove(git_repository *repo, const char *name)
{
- const unsigned int header_len = strlen(GIT_SYMREF);
- const char *refname_start;
- char *eol;
- reference_symbolic *ref_sym;
-
- refname_start = (const char *)file_content->data;
- ref_sym = (reference_symbolic *)ref;
-
- if (file_content->len < (header_len + 1))
- return git__throw(GIT_EOBJCORRUPTED,
- "Failed to parse loose reference. Object too short");
-
- /*
- * Assume we have already checked for the header
- * before calling this function
- */
-
- refname_start += header_len;
-
- free(ref_sym->target);
- ref_sym->target = git__strdup(refname_start);
- if (ref_sym->target == NULL)
- return GIT_ENOMEM;
+ git_refdb *db;
+ int error;
- /* remove newline at the end of file */
- eol = strchr(ref_sym->target, '\n');
- if (eol == NULL)
- return git__throw(GIT_EOBJCORRUPTED,
- "Failed to parse loose reference. Missing EOL");
+ if ((error = git_repository_refdb__weakptr(&db, repo)) < 0)
+ return error;
- *eol = '\0';
- if (eol[-1] == '\r')
- eol[-1] = '\0';
+ return git_refdb_delete(db, name, NULL, NULL);
+}
- return GIT_SUCCESS;
+int git_reference_lookup(git_reference **ref_out,
+ git_repository *repo, const char *name)
+{
+ return git_reference_lookup_resolved(ref_out, repo, name, 0);
}
-static int loose_parse_oid(git_reference *ref, git_fbuffer *file_content)
+int git_reference_name_to_id(
+ git_oid *out, git_repository *repo, const char *name)
{
int error;
- reference_oid *ref_oid;
- char *buffer;
-
- buffer = (char *)file_content->data;
- ref_oid = (reference_oid *)ref;
-
- /* File format: 40 chars (OID) + newline */
- if (file_content->len < GIT_OID_HEXSZ + 1)
- return git__throw(GIT_EOBJCORRUPTED,
- "Failed to parse loose reference. Reference too short");
+ git_reference *ref;
- if ((error = git_oid_fromstr(&ref_oid->oid, buffer)) < GIT_SUCCESS)
- return git__rethrow(GIT_EOBJCORRUPTED, "Failed to parse loose reference.");
-
- buffer = buffer + GIT_OID_HEXSZ;
- if (*buffer == '\r')
- buffer++;
-
- if (*buffer != '\n')
- return git__throw(GIT_EOBJCORRUPTED,
- "Failed to parse loose reference. Missing EOL");
+ if ((error = git_reference_lookup_resolved(&ref, repo, name, -1)) < 0)
+ return error;
- return GIT_SUCCESS;
+ git_oid_cpy(out, git_reference_target(ref));
+ git_reference_free(ref);
+ return 0;
}
-
-static git_rtype loose_guess_rtype(const char *full_path)
+static int reference_normalize_for_repo(
+ git_refname_t out,
+ git_repository *repo,
+ const char *name)
{
- git_fbuffer ref_file = GIT_FBUFFER_INIT;
- git_rtype type;
+ int precompose;
+ unsigned int flags = GIT_REF_FORMAT_ALLOW_ONELEVEL;
- type = GIT_REF_INVALID;
+ if (!git_repository__cvar(&precompose, repo, GIT_CVAR_PRECOMPOSE) &&
+ precompose)
+ flags |= GIT_REF_FORMAT__PRECOMPOSE_UNICODE;
- if (git_futils_readbuffer(&ref_file, full_path) == GIT_SUCCESS) {
- if (git__prefixcmp((const char *)(ref_file.data), GIT_SYMREF) == 0)
- type = GIT_REF_SYMBOLIC;
- else
- type = GIT_REF_OID;
- }
-
- git_futils_freebuffer(&ref_file);
- return type;
+ return git_reference_normalize_name(out, GIT_REFNAME_MAX, name, flags);
}
-static int loose_lookup(
- git_reference **ref_out,
- git_repository *repo,
- const char *name,
- int skip_symbolic)
+int git_reference_lookup_resolved(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *name,
+ int max_nesting)
{
- int error = GIT_SUCCESS;
- git_fbuffer ref_file = GIT_FBUFFER_INIT;
+ git_refname_t scan_name;
+ git_ref_t scan_type;
+ int error = 0, nesting;
git_reference *ref = NULL;
- time_t ref_time = 0;
+ git_refdb *refdb;
+
+ assert(ref_out && repo && name);
*ref_out = NULL;
- error = reference_read(&ref_file, &ref_time, repo->path_repository, name, NULL);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ if (max_nesting > MAX_NESTING_LEVEL)
+ max_nesting = MAX_NESTING_LEVEL;
+ else if (max_nesting < 0)
+ max_nesting = DEFAULT_NESTING_LEVEL;
- if (git__prefixcmp((const char *)(ref_file.data), GIT_SYMREF) == 0) {
- if (skip_symbolic)
- return GIT_SUCCESS;
+ scan_type = GIT_REF_SYMBOLIC;
- error = reference_create(&ref, repo, name, GIT_REF_SYMBOLIC);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ if ((error = reference_normalize_for_repo(scan_name, repo, name)) < 0)
+ return error;
- error = loose_parse_symbolic(ref, &ref_file);
- } else {
- error = reference_create(&ref, repo, name, GIT_REF_OID);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
+ return error;
- error = loose_parse_oid(ref, &ref_file);
+ for (nesting = max_nesting;
+ nesting >= 0 && scan_type == GIT_REF_SYMBOLIC;
+ nesting--)
+ {
+ if (nesting != max_nesting) {
+ strncpy(scan_name, ref->target.symbolic, sizeof(scan_name));
+ git_reference_free(ref);
+ }
+
+ if ((error = git_refdb_lookup(&ref, refdb, scan_name)) < 0)
+ return error;
+
+ scan_type = ref->type;
}
- if (error < GIT_SUCCESS)
- goto cleanup;
+ if (scan_type != GIT_REF_OID && max_nesting != 0) {
+ giterr_set(GITERR_REFERENCE,
+ "Cannot resolve reference (>%u levels deep)", max_nesting);
+ git_reference_free(ref);
+ return -1;
+ }
- ref->mtime = ref_time;
*ref_out = ref;
- git_futils_freebuffer(&ref_file);
- return GIT_SUCCESS;
-
-cleanup:
- git_futils_freebuffer(&ref_file);
- reference_free(ref);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to lookup loose reference");
+ return 0;
}
-static int loose_write(git_reference *ref)
+int git_reference_dwim(git_reference **out, git_repository *repo, const char *refname)
{
- git_filebuf file;
- char ref_path[GIT_PATH_MAX];
- int error;
- struct stat st;
-
- git_path_join(ref_path, ref->owner->path_repository, ref->name);
-
- if ((error = git_filebuf_open(&file, ref_path, GIT_FILEBUF_FORCE)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to write loose reference");
-
- if (ref->type & GIT_REF_OID) {
- reference_oid *ref_oid = (reference_oid *)ref;
- char oid[GIT_OID_HEXSZ + 1];
-
- memset(oid, 0x0, sizeof(oid));
-
- git_oid_fmt(oid, &ref_oid->oid);
- error = git_filebuf_printf(&file, "%s\n", oid);
- if (error < GIT_SUCCESS)
- goto unlock;
-
- } else if (ref->type & GIT_REF_SYMBOLIC) { /* GIT_REF_SYMBOLIC */
- reference_symbolic *ref_sym = (reference_symbolic *)ref;
+ int error = 0, i;
+ bool fallbackmode = true, foundvalid = false;
+ git_reference *ref;
+ git_buf refnamebuf = GIT_BUF_INIT, name = GIT_BUF_INIT;
+
+ static const char* formatters[] = {
+ "%s",
+ GIT_REFS_DIR "%s",
+ GIT_REFS_TAGS_DIR "%s",
+ GIT_REFS_HEADS_DIR "%s",
+ GIT_REFS_REMOTES_DIR "%s",
+ GIT_REFS_REMOTES_DIR "%s/" GIT_HEAD_FILE,
+ NULL
+ };
- error = git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref_sym->target);
- } else {
- error = git__throw(GIT_EOBJCORRUPTED, "Failed to write reference. Invalid reference type");
- goto unlock;
+ if (*refname)
+ git_buf_puts(&name, refname);
+ else {
+ git_buf_puts(&name, GIT_HEAD_FILE);
+ fallbackmode = false;
}
- error = git_filebuf_commit(&file);
+ for (i = 0; formatters[i] && (fallbackmode || i == 0); i++) {
- if (p_stat(ref_path, &st) == GIT_SUCCESS)
- ref->mtime = st.st_mtime;
+ git_buf_clear(&refnamebuf);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write loose reference");
-
-unlock:
- git_filebuf_cleanup(&file);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write loose reference");
-}
+ if ((error = git_buf_printf(&refnamebuf, formatters[i], git_buf_cstr(&name))) < 0)
+ goto cleanup;
+ if (!git_reference_is_valid_name(git_buf_cstr(&refnamebuf))) {
+ error = GIT_EINVALIDSPEC;
+ continue;
+ }
+ foundvalid = true;
+ error = git_reference_lookup_resolved(&ref, repo, git_buf_cstr(&refnamebuf), -1);
+ if (!error) {
+ *out = ref;
+ error = 0;
+ goto cleanup;
+ }
+ if (error != GIT_ENOTFOUND)
+ goto cleanup;
+ }
+cleanup:
+ if (error && !foundvalid) {
+ /* never found a valid reference name */
+ giterr_set(GITERR_REFERENCE,
+ "Could not use '%s' as valid reference name", git_buf_cstr(&name));
+ }
-/*****************************************
- * Internal methods - Packed references
- *****************************************/
+ git_buf_free(&name);
+ git_buf_free(&refnamebuf);
+ return error;
+}
-static int packed_parse_peel(
- reference_oid *tag_ref,
- const char **buffer_out,
- const char *buffer_end)
+/**
+ * Getters
+ */
+git_ref_t git_reference_type(const git_reference *ref)
{
- const char *buffer = *buffer_out + 1;
-
- assert(buffer[-1] == '^');
-
- /* Ensure it's not the first entry of the file */
- if (tag_ref == NULL)
- return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Reference is the first entry of the file");
-
- /* Ensure reference is a tag */
- if (git__prefixcmp(tag_ref->ref.name, GIT_REFS_TAGS_DIR) != 0)
- return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Reference is not a tag");
-
- if (buffer + GIT_OID_HEXSZ >= buffer_end)
- return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Buffer too small");
-
- /* Is this a valid object id? */
- if (git_oid_fromstr(&tag_ref->peel_target, buffer) < GIT_SUCCESS)
- return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Not a valid object ID");
-
- buffer = buffer + GIT_OID_HEXSZ;
- if (*buffer == '\r')
- buffer++;
-
- if (*buffer != '\n')
- return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Buffer not terminated correctly");
-
- *buffer_out = buffer + 1;
- tag_ref->ref.type |= GIT_REF_HAS_PEEL;
-
- return GIT_SUCCESS;
+ assert(ref);
+ return ref->type;
}
-static int packed_parse_oid(
- reference_oid **ref_out,
- git_repository *repo,
- const char **buffer_out,
- const char *buffer_end)
+const char *git_reference_name(const git_reference *ref)
{
- reference_oid *ref = NULL;
-
- const char *buffer = *buffer_out;
- const char *refname_begin, *refname_end;
-
- int error = GIT_SUCCESS;
- int refname_len;
- char refname[GIT_REFNAME_MAX];
- git_oid id;
-
- refname_begin = (buffer + GIT_OID_HEXSZ + 1);
- if (refname_begin >= buffer_end ||
- refname_begin[-1] != ' ') {
- error = GIT_EPACKEDREFSCORRUPTED;
- goto cleanup;
- }
+ assert(ref);
+ return ref->name;
+}
- /* Is this a valid object id? */
- if ((error = git_oid_fromstr(&id, buffer)) < GIT_SUCCESS)
- goto cleanup;
+git_repository *git_reference_owner(const git_reference *ref)
+{
+ assert(ref);
+ return ref->db->repo;
+}
- refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin);
- if (refname_end == NULL) {
- error = GIT_EPACKEDREFSCORRUPTED;
- goto cleanup;
- }
+const git_oid *git_reference_target(const git_reference *ref)
+{
+ assert(ref);
- refname_len = refname_end - refname_begin;
+ if (ref->type != GIT_REF_OID)
+ return NULL;
- memcpy(refname, refname_begin, refname_len);
- refname[refname_len] = 0;
+ return &ref->target.oid;
+}
- if (refname[refname_len - 1] == '\r')
- refname[refname_len - 1] = 0;
+const git_oid *git_reference_target_peel(const git_reference *ref)
+{
+ assert(ref);
- error = reference_create((git_reference **)&ref, repo, refname, GIT_REF_OID);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ if (ref->type != GIT_REF_OID || git_oid_iszero(&ref->peel))
+ return NULL;
- git_oid_cpy(&ref->oid, &id);
- ref->ref.type |= GIT_REF_PACKED;
+ return &ref->peel;
+}
- *ref_out = ref;
- *buffer_out = refname_end + 1;
+const char *git_reference_symbolic_target(const git_reference *ref)
+{
+ assert(ref);
- return GIT_SUCCESS;
+ if (ref->type != GIT_REF_SYMBOLIC)
+ return NULL;
-cleanup:
- reference_free((git_reference *)ref);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse OID of packed reference");
+ return ref->target.symbolic;
}
-static int packed_load(git_repository *repo)
+static int reference__create(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *name,
+ const git_oid *oid,
+ const char *symbolic,
+ int force,
+ const git_signature *signature,
+ const char *log_message,
+ const git_oid *old_id,
+ const char *old_target)
{
- int error = GIT_SUCCESS, updated;
- git_fbuffer packfile = GIT_FBUFFER_INIT;
- const char *buffer_start, *buffer_end;
- git_refcache *ref_cache = &repo->references;
+ git_refname_t normalized;
+ git_refdb *refdb;
+ git_reference *ref = NULL;
+ int error = 0;
- /* First we make sure we have allocated the hash table */
- if (ref_cache->packfile == NULL) {
- ref_cache->packfile = git_hashtable_alloc(
- default_table_size,
- reftable_hash,
- (git_hash_keyeq_ptr)strcmp);
+ assert(repo && name);
+ assert(symbolic || signature);
- if (ref_cache->packfile == NULL) {
- error = GIT_ENOMEM;
- goto cleanup;
- }
- }
+ if (ref_out)
+ *ref_out = NULL;
- error = reference_read(&packfile, &ref_cache->packfile_time,
- repo->path_repository, GIT_PACKEDREFS_FILE, &updated);
+ error = reference_normalize_for_repo(normalized, repo, name);
+ if (error < 0)
+ return error;
- /*
- * If we couldn't find the file, we need to clear the table and
- * return. On any other error, we return that error. If everything
- * went fine and the file wasn't updated, then there's nothing new
- * for us here, so just return. Anything else means we need to
- * refresh the packed refs.
- */
- if (error == GIT_ENOTFOUND) {
- git_hashtable_clear(ref_cache->packfile);
- return GIT_SUCCESS;
- } else if (error < GIT_SUCCESS) {
- return git__rethrow(error, "Failed to read packed refs");
- } else if (!updated) {
- return GIT_SUCCESS;
- }
+ error = git_repository_refdb__weakptr(&refdb, repo);
+ if (error < 0)
+ return error;
- /*
- * At this point, we want to refresh the packed refs. We already
- * have the contents in our buffer.
- */
+ if (oid != NULL) {
+ git_odb *odb;
- git_hashtable_clear(ref_cache->packfile);
+ assert(symbolic == NULL);
- buffer_start = (const char *)packfile.data;
- buffer_end = (const char *)(buffer_start) + packfile.len;
+ /* Sanity check the reference being created - target must exist. */
+ if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
+ return error;
- while (buffer_start < buffer_end && buffer_start[0] == '#') {
- buffer_start = strchr(buffer_start, '\n');
- if (buffer_start == NULL) {
- error = GIT_EPACKEDREFSCORRUPTED;
- goto cleanup;
+ if (!git_odb_exists(odb, oid)) {
+ giterr_set(GITERR_REFERENCE,
+ "Target OID for the reference doesn't exist on the repository");
+ return -1;
}
- buffer_start++;
- }
-
- while (buffer_start < buffer_end) {
- reference_oid *ref = NULL;
- error = packed_parse_oid(&ref, repo, &buffer_start, buffer_end);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ ref = git_reference__alloc(normalized, oid, NULL);
+ } else {
+ git_refname_t normalized_target;
- if (buffer_start[0] == '^') {
- error = packed_parse_peel(ref, &buffer_start, buffer_end);
- if (error < GIT_SUCCESS)
- goto cleanup;
- }
+ if ((error = reference_normalize_for_repo(normalized_target, repo, symbolic)) < 0)
+ return error;
- error = git_hashtable_insert(ref_cache->packfile, ref->ref.name, ref);
- if (error < GIT_SUCCESS) {
- reference_free((git_reference *)ref);
- goto cleanup;
- }
+ ref = git_reference__alloc_symbolic(normalized, normalized_target);
}
- git_futils_freebuffer(&packfile);
- return GIT_SUCCESS;
-
-cleanup:
- git_hashtable_free(ref_cache->packfile);
- ref_cache->packfile = NULL;
- git_futils_freebuffer(&packfile);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to load packed references");
-}
-
-
+ GITERR_CHECK_ALLOC(ref);
+ if ((error = git_refdb_write(refdb, ref, force, signature, log_message, old_id, old_target)) < 0) {
+ git_reference_free(ref);
+ return error;
+ }
-struct dirent_list_data {
- git_repository *repo;
- size_t repo_path_len;
- unsigned int list_flags;
+ if (ref_out == NULL)
+ git_reference_free(ref);
+ else
+ *ref_out = ref;
- int (*callback)(const char *, void *);
- void *callback_payload;
-};
+ return 0;
+}
-static int _dirent_loose_listall(void *_data, char *full_path)
+static int log_signature(git_signature **out, git_repository *repo)
{
- struct dirent_list_data *data = (struct dirent_list_data *)_data;
- char *file_path = full_path + data->repo_path_len;
-
- if (git_futils_isdir(full_path) == GIT_SUCCESS)
- return git_futils_direach(full_path, GIT_PATH_MAX, _dirent_loose_listall, _data);
-
- /* do not add twice a reference that exists already in the packfile */
- if ((data->list_flags & GIT_REF_PACKED) != 0 &&
- git_hashtable_lookup(data->repo->references.packfile, file_path) != NULL)
- return GIT_SUCCESS;
+ int error;
+ git_signature *who;
- if (data->list_flags != GIT_REF_LISTALL) {
- if ((data->list_flags & loose_guess_rtype(full_path)) == 0)
- return GIT_SUCCESS; /* we are filtering out this reference */
- }
+ if(((error = git_signature_default(&who, repo)) < 0) &&
+ ((error = git_signature_now(&who, "unknown", "unknown")) < 0))
+ return error;
- return data->callback(file_path, data->callback_payload);
+ *out = who;
+ return 0;
}
-static int _dirent_loose_load(void *data, char *full_path)
+int git_reference_create_matching(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *name,
+ const git_oid *id,
+ int force,
+ const git_oid *old_id,
+ const git_signature *signature,
+ const char *log_message)
+
{
- git_repository *repository = (git_repository *)data;
- git_reference *reference, *old_ref;
- char *file_path;
int error;
+ git_signature *who = NULL;
+
+ assert(id);
- if (git_futils_isdir(full_path) == GIT_SUCCESS)
- return git_futils_direach(full_path, GIT_PATH_MAX, _dirent_loose_load, repository);
-
- file_path = full_path + strlen(repository->path_repository);
- error = loose_lookup(&reference, repository, file_path, 1);
- if (error == GIT_SUCCESS && reference != NULL) {
- reference->type |= GIT_REF_PACKED;
-
- if (git_hashtable_insert2(repository->references.packfile, reference->name, reference, (void **)&old_ref) < GIT_SUCCESS) {
- reference_free(reference);
- return GIT_ENOMEM;
- }
-
- if (old_ref != NULL)
- reference_free(old_ref);
+ if (!signature) {
+ if ((error = log_signature(&who, repo)) < 0)
+ return error;
+ else
+ signature = who;
}
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to load loose dirent");
+ error = reference__create(
+ ref_out, repo, name, id, NULL, force, signature, log_message, old_id, NULL);
+
+ git_signature_free(who);
+ return error;
}
-/*
- * Load all the loose references from the repository
- * into the in-memory Packfile, and build a vector with
- * all the references so it can be written back to
- * disk.
- */
-static int packed_loadloose(git_repository *repository)
+int git_reference_create(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *name,
+ const git_oid *id,
+ int force,
+ const git_signature *signature,
+ const char *log_message)
{
- char refs_path[GIT_PATH_MAX];
-
- /* the packfile must have been previously loaded! */
- assert(repository->references.packfile);
+ return git_reference_create_matching(ref_out, repo, name, id, force, NULL, signature, log_message);
+}
- git_path_join(refs_path, repository->path_repository, GIT_REFS_DIR);
+int git_reference_symbolic_create_matching(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *name,
+ const char *target,
+ int force,
+ const char *old_target,
+ const git_signature *signature,
+ const char *log_message)
+{
+ int error;
+ git_signature *who = NULL;
- /* Remove any loose references from the cache */
- {
- const void *GIT_UNUSED(_unused);
- git_reference *reference;
+ assert(target);
- GIT_HASHTABLE_FOREACH(repository->references.loose_cache, _unused, reference,
- reference_free(reference);
- );
+ if (!signature) {
+ if ((error = log_signature(&who, repo)) < 0)
+ return error;
+ else
+ signature = who;
}
- git_hashtable_clear(repository->references.loose_cache);
+ error = reference__create(
+ ref_out, repo, name, NULL, target, force, signature, log_message, NULL, old_target);
- /*
- * Load all the loose files from disk into the Packfile table.
- * This will overwrite any old packed entries with their
- * updated loose versions
- */
- return git_futils_direach(refs_path, GIT_PATH_MAX, _dirent_loose_load, repository);
+ git_signature_free(who);
+ return error;
}
-/*
- * Write a single reference into a packfile
- */
-static int packed_write_ref(reference_oid *ref, git_filebuf *file)
+int git_reference_symbolic_create(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *name,
+ const char *target,
+ int force,
+ const git_signature *signature,
+ const char *log_message)
{
- int error;
- char oid[GIT_OID_HEXSZ + 1];
-
- git_oid_fmt(oid, &ref->oid);
- oid[GIT_OID_HEXSZ] = 0;
-
- /*
- * For references that peel to an object in the repo, we must
- * write the resulting peel on a separate line, e.g.
- *
- * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4
- * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100
- *
- * This obviously only applies to tags.
- * The required peels have already been loaded into `ref->peel_target`.
- */
- if (ref->ref.type & GIT_REF_HAS_PEEL) {
- char peel[GIT_OID_HEXSZ + 1];
- git_oid_fmt(peel, &ref->peel_target);
- peel[GIT_OID_HEXSZ] = 0;
-
- error = git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->ref.name, peel);
- } else {
- error = git_filebuf_printf(file, "%s %s\n", oid, ref->ref.name);
- }
-
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write packed reference");
+ return git_reference_symbolic_create_matching(ref_out, repo, name, target, force, NULL, signature, log_message);
}
-/*
- * Find out what object this reference resolves to.
- *
- * For references that point to a 'big' tag (e.g. an
- * actual tag object on the repository), we need to
- * cache on the packfile the OID of the object to
- * which that 'big tag' is pointing to.
- */
-static int packed_find_peel(reference_oid *ref)
+static int ensure_is_an_updatable_direct_reference(git_reference *ref)
{
- git_object *object;
- int error;
-
- if (ref->ref.type & GIT_REF_HAS_PEEL)
- return GIT_SUCCESS;
-
- /*
- * Only applies to tags, i.e. references
- * in the /refs/tags folder
- */
- if (git__prefixcmp(ref->ref.name, GIT_REFS_TAGS_DIR) != 0)
- return GIT_SUCCESS;
-
- /*
- * Find the tagged object in the repository
- */
- error = git_object_lookup(&object, ref->ref.owner, &ref->oid, GIT_OBJ_ANY);
- if (error < GIT_SUCCESS)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to find packed reference");
-
- /*
- * If the tagged object is a Tag object, we need to resolve it;
- * if the ref is actually a 'weak' ref, we don't need to resolve
- * anything.
- */
- if (git_object_type(object) == GIT_OBJ_TAG) {
- git_tag *tag = (git_tag *)object;
-
- /*
- * Find the object pointed at by this tag
- */
- git_oid_cpy(&ref->peel_target, git_tag_target_oid(tag));
- ref->ref.type |= GIT_REF_HAS_PEEL;
-
- /*
- * The reference has now cached the resolved OID, and is
- * marked at such. When written to the packfile, it'll be
- * accompanied by this resolved oid
- */
- }
+ if (ref->type == GIT_REF_OID)
+ return 0;
- git_object_close(object);
-
- return GIT_SUCCESS;
+ giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference");
+ return -1;
}
-/*
- * Remove all loose references
- *
- * Once we have successfully written a packfile,
- * all the loose references that were packed must be
- * removed from disk.
- *
- * This is a dangerous method; make sure the packfile
- * is well-written, because we are destructing references
- * here otherwise.
- */
-static int packed_remove_loose(git_repository *repo, git_vector *packing_list)
+int git_reference_set_target(
+ git_reference **out,
+ git_reference *ref,
+ const git_oid *id,
+ const git_signature *signature,
+ const char *log_message)
{
- unsigned int i;
- char full_path[GIT_PATH_MAX];
- int error = GIT_SUCCESS;
- git_reference *reference;
+ int error;
+ git_repository *repo;
- for (i = 0; i < packing_list->length; ++i) {
- git_reference *ref = git_vector_get(packing_list, i);
+ assert(out && ref && id);
- /* Ensure the packed reference doesn't exist
- * in a (more up-to-date?) state as a loose reference
- */
- reference = git_hashtable_lookup(ref->owner->references.loose_cache, ref->name);
- if (reference != NULL)
- continue;
+ repo = ref->db->repo;
- git_path_join(full_path, repo->path_repository, ref->name);
-
- if (git_futils_exists(full_path) == GIT_SUCCESS &&
- p_unlink(full_path) < GIT_SUCCESS)
- error = GIT_EOSERR;
-
- /*
- * if we fail to remove a single file, this is *not* good,
- * but we should keep going and remove as many as possible.
- * After we've removed as many files as possible, we return
- * the error code anyway.
- *
- * TODO: mark this with a very special error code?
- * GIT_EFAILTORMLOOSE
- */
- }
+ if ((error = ensure_is_an_updatable_direct_reference(ref)) < 0)
+ return error;
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to remove loose packed reference");
+ return git_reference_create_matching(out, repo, ref->name, id, 1, &ref->target.oid, signature, log_message);
}
-static int packed_sort(const void *a, const void *b)
+static int ensure_is_an_updatable_symbolic_reference(git_reference *ref)
{
- const git_reference *ref_a = (const git_reference *)a;
- const git_reference *ref_b = (const git_reference *)b;
+ if (ref->type == GIT_REF_SYMBOLIC)
+ return 0;
- return strcmp(ref_a->name, ref_b->name);
+ giterr_set(GITERR_REFERENCE, "Cannot set symbolic target on a direct reference");
+ return -1;
}
-/*
- * Write all the contents in the in-memory packfile to disk.
- */
-static int packed_write(git_repository *repo)
+int git_reference_symbolic_set_target(
+ git_reference **out,
+ git_reference *ref,
+ const char *target,
+ const git_signature *signature,
+ const char *log_message)
{
- git_filebuf pack_file;
int error;
- unsigned int i;
- char pack_file_path[GIT_PATH_MAX];
-
- git_vector packing_list;
- size_t total_refs;
- assert(repo && repo->references.packfile);
+ assert(out && ref && target);
- total_refs = repo->references.packfile->key_count;
- if ((error = git_vector_init(&packing_list, total_refs, packed_sort)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to write packed reference");
-
- /* Load all the packfile into a vector */
- {
- git_reference *reference;
- const void *GIT_UNUSED(_unused);
+ if ((error = ensure_is_an_updatable_symbolic_reference(ref)) < 0)
+ return error;
- GIT_HASHTABLE_FOREACH(repo->references.packfile, _unused, reference,
- git_vector_insert(&packing_list, reference); /* cannot fail: vector already has the right size */
- );
- }
+ return git_reference_symbolic_create_matching(
+ out, ref->db->repo, ref->name, target, 1, ref->target.symbolic, signature, log_message);
+}
- /* sort the vector so the entries appear sorted on the packfile */
- git_vector_sort(&packing_list);
+static int reference__rename(git_reference **out, git_reference *ref, const char *new_name, int force,
+ const git_signature *signature, const char *message)
+{
+ git_refname_t normalized;
+ bool should_head_be_updated = false;
+ int error = 0;
- /* Now we can open the file! */
- git_path_join(pack_file_path, repo->path_repository, GIT_PACKEDREFS_FILE);
- if ((error = git_filebuf_open(&pack_file, pack_file_path, 0)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to write packed reference");
+ assert(ref && new_name && signature);
- /* Packfiles have a header... apparently
- * This is in fact not required, but we might as well print it
- * just for kicks */
- if ((error = git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to write packed reference");
+ if ((error = reference_normalize_for_repo(
+ normalized, git_reference_owner(ref), new_name)) < 0)
+ return error;
- for (i = 0; i < packing_list.length; ++i) {
- reference_oid *ref = (reference_oid *)git_vector_get(&packing_list, i);
- /* only direct references go to the packfile; otherwise
- * this is a disaster */
- assert(ref->ref.type & GIT_REF_OID);
+ /* Check if we have to update HEAD. */
+ if ((error = git_branch_is_head(ref)) < 0)
+ return error;
- if ((error = packed_find_peel(ref)) < GIT_SUCCESS) {
- error = git__throw(GIT_EOBJCORRUPTED, "A reference cannot be peeled");
- goto cleanup;
- }
+ should_head_be_updated = (error > 0);
+ if ((error = git_refdb_rename(out, ref->db, ref->name, normalized, force, signature, message)) < 0)
+ return error;
- if ((error = packed_write_ref(ref, &pack_file)) < GIT_SUCCESS)
- goto cleanup;
+ /* Update HEAD it was pointing to the reference being renamed */
+ if (should_head_be_updated &&
+ (error = git_repository_set_head(ref->db->repo, normalized, signature, message)) < 0) {
+ giterr_set(GITERR_REFERENCE, "Failed to update HEAD after renaming reference");
+ return error;
}
-cleanup:
- /* if we've written all the references properly, we can commit
- * the packfile to make the changes effective */
- if (error == GIT_SUCCESS) {
- error = git_filebuf_commit(&pack_file);
+ return 0;
+}
- /* when and only when the packfile has been properly written,
- * we can go ahead and remove the loose refs */
- if (error == GIT_SUCCESS) {
- struct stat st;
- error = packed_remove_loose(repo, &packing_list);
+int git_reference_rename(
+ git_reference **out,
+ git_reference *ref,
+ const char *new_name,
+ int force,
+ const git_signature *signature,
+ const char *log_message)
+{
+ git_signature *who = (git_signature*)signature;
+ int error;
- if (p_stat(pack_file_path, &st) == GIT_SUCCESS)
- repo->references.packfile_time = st.st_mtime;
- }
+ /* Should we return an error if there is no default? */
+ if (!who &&
+ ((error = git_signature_default(&who, ref->db->repo)) < 0) &&
+ ((error = git_signature_now(&who, "unknown", "unknown")) < 0)) {
+ return error;
}
- else git_filebuf_cleanup(&pack_file);
- git_vector_free(&packing_list);
+ error = reference__rename(out, ref, new_name, force, who, log_message);
+
+ if (!signature)
+ git_signature_free(who);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write packed reference");
+ return error;
}
-static int _reference_available_cb(const char *ref, void *data)
+int git_reference_resolve(git_reference **ref_out, const git_reference *ref)
{
- const char *new, *old;
- git_vector *refs;
+ switch (git_reference_type(ref)) {
+ case GIT_REF_OID:
+ return git_reference_lookup(ref_out, ref->db->repo, ref->name);
- assert(ref && data);
+ case GIT_REF_SYMBOLIC:
+ return git_reference_lookup_resolved(ref_out, ref->db->repo, ref->target.symbolic, -1);
- refs = (git_vector *)data;
+ default:
+ giterr_set(GITERR_REFERENCE, "Invalid reference");
+ return -1;
+ }
+}
- new = (const char *)git_vector_get(refs, 0);
- old = (const char *)git_vector_get(refs, 1);
+int git_reference_foreach(
+ git_repository *repo,
+ git_reference_foreach_cb callback,
+ void *payload)
+{
+ git_reference_iterator *iter;
+ git_reference *ref;
+ int error;
- if (!old || strcmp(old, ref)) {
- int reflen = strlen(ref);
- int newlen = strlen(new);
- int cmplen = reflen < newlen ? reflen : newlen;
- const char *lead = reflen < newlen ? new : ref;
+ if ((error = git_reference_iterator_new(&iter, repo)) < 0)
+ return error;
- if (!strncmp(new, ref, cmplen) &&
- lead[cmplen] == '/')
- return GIT_EEXISTS;
+ while (!(error = git_reference_next(&ref, iter))) {
+ if ((error = callback(ref, payload)) != 0) {
+ giterr_set_after_callback(error);
+ break;
+ }
}
- return GIT_SUCCESS;
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ git_reference_iterator_free(iter);
+ return error;
}
-static int reference_available(git_repository *repo, const char *ref, const char* old_ref)
+int git_reference_foreach_name(
+ git_repository *repo,
+ git_reference_foreach_name_cb callback,
+ void *payload)
{
+ git_reference_iterator *iter;
+ const char *refname;
int error;
- git_vector refs;
-
- if (git_vector_init(&refs, 2, NULL) < GIT_SUCCESS)
- return GIT_ENOMEM;
- git_vector_insert(&refs, (void *)ref);
- git_vector_insert(&refs, (void *)old_ref);
+ if ((error = git_reference_iterator_new(&iter, repo)) < 0)
+ return error;
- error = git_reference_foreach(repo, GIT_REF_LISTALL, _reference_available_cb, (void *)&refs);
+ while (!(error = git_reference_next_name(&refname, iter))) {
+ if ((error = callback(refname, payload)) != 0) {
+ giterr_set_after_callback(error);
+ break;
+ }
+ }
- git_vector_free(&refs);
+ if (error == GIT_ITEROVER)
+ error = 0;
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__throw(GIT_EEXISTS, "Reference name `%s` conflicts with existing reference", ref);
+ git_reference_iterator_free(iter);
+ return error;
}
-/*****************************************
- * External Library API
- *****************************************/
-
-/**
- * Constructors
- */
-int git_reference_lookup(git_reference **ref_out, git_repository *repo, const char *name)
+int git_reference_foreach_glob(
+ git_repository *repo,
+ const char *glob,
+ git_reference_foreach_name_cb callback,
+ void *payload)
{
+ git_reference_iterator *iter;
+ const char *refname;
int error;
- char normalized_name[GIT_REFNAME_MAX];
-
- assert(ref_out && repo && name);
-
- *ref_out = NULL;
-
- error = normalize_name(normalized_name, sizeof(normalized_name), name, 0);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to lookup reference");
- /* First, check has been previously loaded and cached */
- *ref_out = git_hashtable_lookup(repo->references.loose_cache, normalized_name);
- if (*ref_out != NULL)
- return loose_update(*ref_out);
+ if ((error = git_reference_iterator_glob_new(&iter, repo, glob)) < 0)
+ return error;
- /* Then check if there is a loose file for that reference.*/
- error = loose_lookup(ref_out, repo, normalized_name, 0);
+ while (!(error = git_reference_next_name(&refname, iter))) {
+ if ((error = callback(refname, payload)) != 0) {
+ giterr_set_after_callback(error);
+ break;
+ }
+ }
- /* If the file exists, we store it on the cache */
- if (error == GIT_SUCCESS)
- return git_hashtable_insert(repo->references.loose_cache, (*ref_out)->name, (*ref_out));
+ if (error == GIT_ITEROVER)
+ error = 0;
- /* The loose lookup has failed, but not because the reference wasn't found;
- * probably the loose reference is corrupted. this is bad. */
- if (error != GIT_ENOTFOUND)
- return git__rethrow(error, "Failed to lookup reference");
+ git_reference_iterator_free(iter);
+ return error;
+}
- /*
- * If we cannot find a loose reference, we look into the packfile
- * Load the packfile first if it hasn't been loaded
- */
- /* load all the packed references */
- error = packed_load(repo);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to lookup reference");
+int git_reference_iterator_new(git_reference_iterator **out, git_repository *repo)
+{
+ git_refdb *refdb;
- /* Look up on the packfile */
- *ref_out = git_hashtable_lookup(repo->references.packfile, normalized_name);
- if (*ref_out != NULL)
- return GIT_SUCCESS;
+ if (git_repository_refdb__weakptr(&refdb, repo) < 0)
+ return -1;
- /* The reference doesn't exist anywhere */
- return git__throw(GIT_ENOTFOUND, "Failed to lookup reference. Reference doesn't exist");
+ return git_refdb_iterator(out, refdb, NULL);
}
-/**
- * Getters
- */
-git_rtype git_reference_type(git_reference *ref)
+int git_reference_iterator_glob_new(
+ git_reference_iterator **out, git_repository *repo, const char *glob)
{
- assert(ref);
-
- if (ref->type & GIT_REF_OID)
- return GIT_REF_OID;
+ git_refdb *refdb;
- if (ref->type & GIT_REF_SYMBOLIC)
- return GIT_REF_SYMBOLIC;
+ if (git_repository_refdb__weakptr(&refdb, repo) < 0)
+ return -1;
- return GIT_REF_INVALID;
+ return git_refdb_iterator(out, refdb, glob);
}
-const char *git_reference_name(git_reference *ref)
+int git_reference_next(git_reference **out, git_reference_iterator *iter)
{
- assert(ref);
- return ref->name;
+ return git_refdb_iterator_next(out, iter);
}
-git_repository *git_reference_owner(git_reference *ref)
+int git_reference_next_name(const char **out, git_reference_iterator *iter)
{
- assert(ref);
- return ref->owner;
+ return git_refdb_iterator_next_name(out, iter);
}
-const git_oid *git_reference_oid(git_reference *ref)
+void git_reference_iterator_free(git_reference_iterator *iter)
{
- assert(ref);
-
- if ((ref->type & GIT_REF_OID) == 0)
- return NULL;
-
- if (loose_update(ref) < GIT_SUCCESS)
- return NULL;
+ if (iter == NULL)
+ return;
- return &((reference_oid *)ref)->oid;
+ git_refdb_iterator_free(iter);
}
-const char *git_reference_target(git_reference *ref)
+static int cb__reflist_add(const char *ref, void *data)
{
- assert(ref);
-
- if ((ref->type & GIT_REF_SYMBOLIC) == 0)
- return NULL;
-
- if (loose_update(ref) < GIT_SUCCESS)
- return NULL;
-
- return ((reference_symbolic *)ref)->target;
+ char *name = git__strdup(ref);
+ GITERR_CHECK_ALLOC(name);
+ return git_vector_insert((git_vector *)data, name);
}
-int git_reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target, int force)
+int git_reference_list(
+ git_strarray *array,
+ git_repository *repo)
{
- char normalized[GIT_REFNAME_MAX];
- int error = GIT_SUCCESS, updated = 0;
- git_reference *ref = NULL, *old_ref = NULL;
-
- if (git_reference_lookup(&ref, repo, name) == GIT_SUCCESS && !force)
- return git__throw(GIT_EEXISTS, "Failed to create symbolic reference. Reference already exists");
-
- /*
- * If they old ref was of the same type, then we can just update
- * it (once we've checked that the target is valid). Otherwise we
- * need a new reference because we can't make a symbolic ref out
- * of an oid one.
- * If if didn't exist, then we need to create a new one anyway.
- */
- if (ref && ref->type & GIT_REF_SYMBOLIC){
- updated = 1;
- } else {
- ref = NULL;
- error = reference_create(&ref, repo, name, GIT_REF_SYMBOLIC);
- if (error < GIT_SUCCESS)
- goto cleanup;
- }
+ git_vector ref_list;
- /* The target can aither be the name of an object id reference or the name of another symbolic reference */
- error = normalize_name(normalized, sizeof(normalized), target, 0);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ assert(array && repo);
- /* set the target; this will write the reference on disk */
- error = git_reference_set_target(ref, normalized);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ array->strings = NULL;
+ array->count = 0;
- /*
- * If we didn't update the ref, then we need to insert or replace
- * it in the loose cache. If we replaced a ref, free it.
- */
- if (!updated){
- error = git_hashtable_insert2(repo->references.loose_cache, ref->name, ref, (void **) &old_ref);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ if (git_vector_init(&ref_list, 8, NULL) < 0)
+ return -1;
- if(old_ref)
- reference_free(old_ref);
+ if (git_reference_foreach_name(
+ repo, &cb__reflist_add, (void *)&ref_list) < 0) {
+ git_vector_free(&ref_list);
+ return -1;
}
- *ref_out = ref;
-
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create symbolic reference");
+ array->strings = (char **)git_vector_detach(&array->count, NULL, &ref_list);
-cleanup:
- reference_free(ref);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create symbolic reference");
+ return 0;
}
-int git_reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id, int force)
+static int is_valid_ref_char(char ch)
{
- int error = GIT_SUCCESS, updated = 0;
- git_reference *ref = NULL, *old_ref = NULL;
+ if ((unsigned) ch <= ' ')
+ return 0;
- if(git_reference_lookup(&ref, repo, name) == GIT_SUCCESS && !force)
- return git__throw(GIT_EEXISTS, "Failed to create reference OID. Reference already exists");
-
- if ((error = reference_available(repo, name, NULL)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to create reference");
-
- /*
- * If they old ref was of the same type, then we can just update
- * it (once we've checked that the target is valid). Otherwise we
- * need a new reference because we can't make a symbolic ref out
- * of an oid one.
- * If if didn't exist, then we need to create a new one anyway.
- */
- if (ref && ref-> type & GIT_REF_OID){
- updated = 1;
- } else {
- ref = NULL;
- error = reference_create(&ref, repo, name, GIT_REF_OID);
- if (error < GIT_SUCCESS)
- goto cleanup;
- }
-
- /* set the oid; this will write the reference on disk */
- error = git_reference_set_oid(ref, id);
- if (error < GIT_SUCCESS)
- goto cleanup;
-
- if(!updated){
- error = git_hashtable_insert2(repo->references.loose_cache, ref->name, ref, (void **) &old_ref);
- if (error < GIT_SUCCESS)
- goto cleanup;
-
- if(old_ref)
- reference_free(old_ref);
+ switch (ch) {
+ case '~':
+ case '^':
+ case ':':
+ case '\\':
+ case '?':
+ case '[':
+ case '*':
+ return 0;
+ default:
+ return 1;
}
-
- *ref_out = ref;
-
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference OID");
-
-cleanup:
- reference_free(ref);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference OID");
}
-/**
- * Setters
- */
-
-/*
- * Change the OID target of a reference.
- *
- * For loose references, just change the oid in memory
- * and overwrite the file in disk.
- *
- * For packed files, this is not pretty:
- * For performance reasons, we write the new reference
- * loose on disk (it replaces the old on the packfile),
- * but we cannot invalidate the pointer to the reference,
- * and most importantly, the `packfile` object must stay
- * consistent with the representation of the packfile
- * on disk. This is what we need to:
- *
- * 1. Copy the reference
- * 2. Change the oid on the original
- * 3. Write the original to disk
- * 4. Write the original to the loose cache
- * 5. Replace the original with the copy (old reference) in the packfile cache
- */
-int git_reference_set_oid(git_reference *ref, const git_oid *id)
+static int ensure_segment_validity(const char *name)
{
- reference_oid *ref_oid;
- reference_oid *ref_old = NULL;
- int error = GIT_SUCCESS;
+ const char *current = name;
+ char prev = '\0';
+ const int lock_len = (int)strlen(GIT_FILELOCK_EXTENSION);
+ int segment_len;
- if ((ref->type & GIT_REF_OID) == 0)
- return git__throw(GIT_EINVALIDREFSTATE, "Failed to set OID target of reference. Not an OID reference");
+ if (*current == '.')
+ return -1; /* Refname starts with "." */
- ref_oid = (reference_oid *)ref;
+ for (current = name; ; current++) {
+ if (*current == '\0' || *current == '/')
+ break;
- assert(ref->owner);
+ if (!is_valid_ref_char(*current))
+ return -1; /* Illegal character in refname */
- /* Don't let the user create references to OIDs that
- * don't exist in the ODB */
- if (!git_odb_exists(git_repository_database(ref->owner), id))
- return git__throw(GIT_ENOTFOUND, "Failed to set OID target of reference. OID doesn't exist in ODB");
+ if (prev == '.' && *current == '.')
+ return -1; /* Refname contains ".." */
- /* duplicate the reference;
- * this copy will stay on the packfile cache */
- if (ref->type & GIT_REF_PACKED) {
- ref_old = git__malloc(sizeof(reference_oid));
- if (ref_old == NULL)
- return GIT_ENOMEM;
+ if (prev == '@' && *current == '{')
+ return -1; /* Refname contains "@{" */
- ref_old->ref.name = git__strdup(ref->name);
- if (ref_old->ref.name == NULL) {
- free(ref_old);
- return GIT_ENOMEM;
- }
+ prev = *current;
}
- git_oid_cpy(&ref_oid->oid, id);
- ref->type &= ~GIT_REF_HAS_PEEL;
+ segment_len = (int)(current - name);
- error = loose_write(ref);
- if (error < GIT_SUCCESS)
- goto cleanup;
-
- if (ref->type & GIT_REF_PACKED) {
- /* insert the original on the loose cache */
- error = git_hashtable_insert(ref->owner->references.loose_cache, ref->name, ref);
- if (error < GIT_SUCCESS)
- goto cleanup;
-
- ref->type &= ~GIT_REF_PACKED;
-
- /* replace the original in the packfile with the copy */
- error = git_hashtable_insert(ref->owner->references.packfile, ref_old->ref.name, ref_old);
- if (error < GIT_SUCCESS)
- goto cleanup;
- }
-
- return GIT_SUCCESS;
+ /* A refname component can not end with ".lock" */
+ if (segment_len >= lock_len &&
+ !memcmp(current - lock_len, GIT_FILELOCK_EXTENSION, lock_len))
+ return -1;
-cleanup:
- reference_free((git_reference *)ref_old);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to set OID target of reference");
+ return segment_len;
}
-/*
- * Change the target of a symbolic reference.
- *
- * This is easy because symrefs cannot be inside
- * a pack. We just change the target in memory
- * and overwrite the file on disk.
- */
-int git_reference_set_target(git_reference *ref, const char *target)
+static bool is_all_caps_and_underscore(const char *name, size_t len)
{
- reference_symbolic *ref_sym;
+ size_t i;
+ char c;
- if ((ref->type & GIT_REF_SYMBOLIC) == 0)
- return git__throw(GIT_EINVALIDREFSTATE, "Failed to set reference target. Not a symbolic reference");
+ assert(name && len > 0);
- ref_sym = (reference_symbolic *)ref;
+ for (i = 0; i < len; i++)
+ {
+ c = name[i];
+ if ((c < 'A' || c > 'Z') && c != '_')
+ return false;
+ }
- free(ref_sym->target);
- ref_sym->target = git__strdup(target);
- if (ref_sym->target == NULL)
- return GIT_ENOMEM;
+ if (*name == '_' || name[len - 1] == '_')
+ return false;
- return loose_write(ref);
+ return true;
}
-/**
- * Other
- */
-
-int git_reference_rename(git_reference *ref, const char *new_name, int force)
+/* Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100 */
+int git_reference__normalize_name(
+ git_buf *buf,
+ const char *name,
+ unsigned int flags)
{
- int error;
- char *old_name = NULL;
-
- char aux_path[GIT_PATH_MAX];
- char normalized[GIT_REFNAME_MAX];
-
- const char *target_ref = NULL;
- const char *head_target = NULL;
- const git_oid *target_oid = NULL;
- git_reference *new_ref = NULL, *old_ref = NULL, *head = NULL;
-
- assert(ref);
-
- error = normalize_name(normalized, sizeof(normalized), new_name, ref->type & GIT_REF_OID);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to rename reference. Invalid name");
+ char *current;
+ int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC;
+ unsigned int process_flags;
+ bool normalize = (buf != NULL);
- new_name = normalized;
+#ifdef GIT_USE_ICONV
+ git_path_iconv_t ic = GIT_PATH_ICONV_INIT;
+#endif
- error = git_reference_lookup(&new_ref, ref->owner, new_name);
- if (error == GIT_SUCCESS) {
- if (!force)
- return git__throw(GIT_EEXISTS, "Failed to rename reference. Reference already exists");
+ assert(name);
- error = git_reference_delete(new_ref);
- }
+ process_flags = flags;
+ current = (char *)name;
- if (error < GIT_SUCCESS && error != GIT_ENOTFOUND)
+ if (*current == '/')
goto cleanup;
- if ((error = reference_available(ref->owner, new_name, ref->name)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to rename reference. Reference already exists");
-
- /*
- * First, we backup the reference targets. Just keeping the old
- * reference won't work, since we may have to remove it to create
- * the new reference, e.g. when renaming foo/bar -> foo.
- */
+ if (normalize)
+ git_buf_clear(buf);
- old_name = git__strdup(ref->name);
-
- if (ref->type & GIT_REF_SYMBOLIC) {
- if ((target_ref = git_reference_target(ref)) == NULL)
- goto cleanup;
- } else {
- if ((target_oid = git_reference_oid(ref)) == NULL)
+#ifdef GIT_USE_ICONV
+ if ((flags & GIT_REF_FORMAT__PRECOMPOSE_UNICODE) != 0) {
+ size_t namelen = strlen(current);
+ if ((error = git_path_iconv_init_precompose(&ic)) < 0 ||
+ (error = git_path_iconv(&ic, &current, &namelen)) < 0)
goto cleanup;
+ error = GIT_EINVALIDSPEC;
}
+#endif
+
+ while (true) {
+ segment_len = ensure_segment_validity(current);
+ if (segment_len < 0) {
+ if ((process_flags & GIT_REF_FORMAT_REFSPEC_PATTERN) &&
+ current[0] == '*' &&
+ (current[1] == '\0' || current[1] == '/')) {
+ /* Accept one wildcard as a full refname component. */
+ process_flags &= ~GIT_REF_FORMAT_REFSPEC_PATTERN;
+ segment_len = 1;
+ } else
+ goto cleanup;
+ }
- /*
- * Now delete the old ref and remove an possibly existing directory
- * named `new_name`.
- */
-
- if (ref->type & GIT_REF_PACKED) {
- ref->type &= ~GIT_REF_PACKED;
+ if (segment_len > 0) {
+ if (normalize) {
+ size_t cur_len = git_buf_len(buf);
- git_hashtable_remove(ref->owner->references.packfile, old_name);
- if ((error = packed_write(ref->owner)) < GIT_SUCCESS)
- goto rollback;
- } else {
- git_path_join(aux_path, ref->owner->path_repository, old_name);
- if ((error = p_unlink(aux_path)) < GIT_SUCCESS)
- goto cleanup;
+ git_buf_joinpath(buf, git_buf_cstr(buf), current);
+ git_buf_truncate(buf,
+ cur_len + segment_len + (segments_count ? 1 : 0));
- git_hashtable_remove(ref->owner->references.loose_cache, old_name);
- }
+ if (git_buf_oom(buf)) {
+ error = -1;
+ goto cleanup;
+ }
+ }
- /* build new path */
- git_path_join(aux_path, ref->owner->path_repository, new_name);
+ segments_count++;
+ }
- if (git_futils_exists(aux_path) == GIT_SUCCESS) {
- if (git_futils_isdir(aux_path) == GIT_SUCCESS) {
- if ((error = git_futils_rmdir_r(aux_path, 0)) < GIT_SUCCESS)
- goto rollback;
- } else goto rollback;
- }
+ /* No empty segment is allowed when not normalizing */
+ if (segment_len == 0 && !normalize)
+ goto cleanup;
- /*
- * Crude hack: delete any logs till we support proper reflogs.
- * Otherwise git.git will possibly fail and leave a mess. git.git
- * writes reflogs by default in any repo with a working directory:
- *
- * "We only enable reflogs in repositories that have a working directory
- * associated with them, as shared/bare repositories do not have
- * an easy means to prune away old log entries, or may fail logging
- * entirely if the user's gecos information is not valid during a push.
- * This heuristic was suggested on the mailing list by Junio."
- *
- * Shawn O. Pearce - 0bee59186976b1d9e6b2dd77332480c9480131d5
- *
- * TODO
- *
- */
-
- git_path_join_n(aux_path, 3, ref->owner->path_repository, "logs", old_name);
- if (git_futils_isfile(aux_path) == GIT_SUCCESS) {
- if ((error = p_unlink(aux_path)) < GIT_SUCCESS)
- goto rollback;
- }
+ if (current[segment_len] == '\0')
+ break;
- /*
- * Finally we can create the new reference.
- */
- if (ref->type & GIT_REF_SYMBOLIC) {
- if ((error = git_reference_create_symbolic(&new_ref, ref->owner, new_name, target_ref, 0)) < GIT_SUCCESS)
- goto rollback;
- } else {
- if ((error = git_reference_create_oid(&new_ref, ref->owner, new_name, target_oid, 0)) < GIT_SUCCESS)
- goto rollback;
+ current += segment_len + 1;
}
- free(ref->name);
- ref->name = new_ref->name;
-
- /*
- * No need in new_ref anymore. We created it to fix the change on disk.
- * TODO: Refactoring required.
- */
- new_ref->name = NULL;
- reference_free(new_ref);
+ /* A refname can not be empty */
+ if (segment_len == 0 && segments_count == 0)
+ goto cleanup;
- if ((error = git_hashtable_insert2(ref->owner->references.loose_cache, ref->name, ref, (void **)&old_ref)) < GIT_SUCCESS)
- goto rollback;
+ /* A refname can not end with "." */
+ if (current[segment_len - 1] == '.')
+ goto cleanup;
- /*
- * Check if we have to update HEAD.
- */
+ /* A refname can not end with "/" */
+ if (current[segment_len - 1] == '/')
+ goto cleanup;
- if ((error = git_reference_lookup(&head, ref->owner, GIT_HEAD_FILE)) < GIT_SUCCESS)
+ if ((segments_count == 1 ) && !(flags & GIT_REF_FORMAT_ALLOW_ONELEVEL))
goto cleanup;
- head_target = git_reference_target(head);
+ if ((segments_count == 1 ) &&
+ !(flags & GIT_REF_FORMAT_REFSPEC_SHORTHAND) &&
+ !(is_all_caps_and_underscore(name, (size_t)segment_len) ||
+ ((flags & GIT_REF_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name))))
+ goto cleanup;
+
+ if ((segments_count > 1)
+ && (is_all_caps_and_underscore(name, strchr(name, '/') - name)))
+ goto cleanup;
- if (head_target && !strcmp(head_target, old_name))
- if ((error = git_reference_create_symbolic(&head, ref->owner, "HEAD", ref->name, 1)) < GIT_SUCCESS)
- goto rollback;
+ error = 0;
cleanup:
- free(old_name);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to rename reference");
-
-rollback:
- /*
- * Try to create the old reference again.
- */
- if (ref->type & GIT_REF_SYMBOLIC)
- error = git_reference_create_symbolic(&new_ref, ref->owner, old_name, target_ref, 0);
- else
- error = git_reference_create_oid(&new_ref, ref->owner, old_name, target_oid, 0);
+ if (error == GIT_EINVALIDSPEC)
+ giterr_set(
+ GITERR_REFERENCE,
+ "The given reference name '%s' is not valid", name);
+
+ if (error && normalize)
+ git_buf_free(buf);
- ref->name = old_name;
+#ifdef GIT_USE_ICONV
+ git_path_iconv_clear(&ic);
+#endif
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to rename reference. Failed to rollback");
+ return error;
}
-/*
- * Delete a reference.
- *
- * If the reference is packed, this is an expensive
- * operation. We need to remove the reference from
- * the memory cache and then rewrite the whole pack
- *
- * If the reference is loose, we remove it on
- * the filesystem and update the in-memory cache
- * accordingly. We also make sure that an older version
- * of it doesn't exist as a packed reference. If this
- * is the case, this packed reference is removed as well.
- *
- * This obviously invalidates the `ref` pointer.
- */
-int git_reference_delete(git_reference *ref)
+int git_reference_normalize_name(
+ char *buffer_out,
+ size_t buffer_size,
+ const char *name,
+ unsigned int flags)
{
+ git_buf buf = GIT_BUF_INIT;
int error;
- git_reference *reference;
-
- assert(ref);
- if (ref->type & GIT_REF_PACKED) {
- /* load the existing packfile */
- if ((error = packed_load(ref->owner)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to delete reference");
+ if ((error = git_reference__normalize_name(&buf, name, flags)) < 0)
+ goto cleanup;
- if (git_hashtable_remove(ref->owner->references.packfile, ref->name) < GIT_SUCCESS)
- return git__throw(GIT_ENOTFOUND, "Reference not found");
+ if (git_buf_len(&buf) > buffer_size - 1) {
+ giterr_set(
+ GITERR_REFERENCE,
+ "The provided buffer is too short to hold the normalization of '%s'", name);
+ error = GIT_EBUFS;
+ goto cleanup;
+ }
- error = packed_write(ref->owner);
- } else {
- char full_path[GIT_PATH_MAX];
- git_path_join(full_path, ref->owner->path_repository, ref->name);
- git_hashtable_remove(ref->owner->references.loose_cache, ref->name);
- error = p_unlink(full_path);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ git_buf_copy_cstr(buffer_out, buffer_size, &buf);
- /* When deleting a loose reference, we have to ensure that an older
- * packed version of it doesn't exist
- */
- if (!git_reference_lookup(&reference, ref->owner, ref->name)) {
- assert((reference->type & GIT_REF_PACKED) != 0);
- error = git_reference_delete(reference);
- }
- }
+ error = 0;
cleanup:
- reference_free(ref);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to delete reference");
+ git_buf_free(&buf);
+ return error;
}
-int git_reference_resolve(git_reference **resolved_ref, git_reference *ref)
-{
- git_repository *repo;
- int error, i;
-
- assert(resolved_ref && ref);
- *resolved_ref = NULL;
-
- if ((error = loose_update(ref)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to resolve reference");
-
- repo = ref->owner;
-
- for (i = 0; i < MAX_NESTING_LEVEL; ++i) {
- reference_symbolic *ref_sym;
-
- *resolved_ref = ref;
+#define GIT_REF_TYPEMASK (GIT_REF_OID | GIT_REF_SYMBOLIC)
- if (ref->type & GIT_REF_OID)
- return GIT_SUCCESS;
-
- ref_sym = (reference_symbolic *)ref;
- if ((error = git_reference_lookup(&ref, repo, ref_sym->target)) < GIT_SUCCESS)
- return error;
- }
-
- return git__throw(GIT_ENOMEM, "Failed to resolve reference. Reference is too nested");
-}
-
-int git_reference_packall(git_repository *repo)
+int git_reference_cmp(
+ const git_reference *ref1,
+ const git_reference *ref2)
{
- int error;
+ git_ref_t type1, type2;
+ assert(ref1 && ref2);
+
+ type1 = git_reference_type(ref1);
+ type2 = git_reference_type(ref2);
- /* load the existing packfile */
- if ((error = packed_load(repo)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to pack references");
+ /* let's put symbolic refs before OIDs */
+ if (type1 != type2)
+ return (type1 == GIT_REF_SYMBOLIC) ? -1 : 1;
- /* update it in-memory with all the loose references */
- if ((error = packed_loadloose(repo)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to pack references");
+ if (type1 == GIT_REF_SYMBOLIC)
+ return strcmp(ref1->target.symbolic, ref2->target.symbolic);
- /* write it back to disk */
- return packed_write(repo);
+ return git_oid__cmp(&ref1->target.oid, &ref2->target.oid);
}
-int git_reference_foreach(git_repository *repo, unsigned int list_flags, int (*callback)(const char *, void *), void *payload)
+static int reference__update_terminal(
+ git_repository *repo,
+ const char *ref_name,
+ const git_oid *oid,
+ int nesting,
+ const git_signature *signature,
+ const char *log_message)
{
- int error;
- struct dirent_list_data data;
- char refs_path[GIT_PATH_MAX];
+ git_reference *ref;
+ int error = 0;
- /* list all the packed references first */
- if (list_flags & GIT_REF_PACKED) {
- const char *ref_name;
- void *GIT_UNUSED(_unused);
+ if (nesting > MAX_NESTING_LEVEL) {
+ giterr_set(GITERR_REFERENCE, "Reference chain too deep (%d)", nesting);
+ return GIT_ENOTFOUND;
+ }
- if ((error = packed_load(repo)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to list references");
+ error = git_reference_lookup(&ref, repo, ref_name);
- GIT_HASHTABLE_FOREACH(repo->references.packfile, ref_name, _unused,
- if ((error = callback(ref_name, payload)) < GIT_SUCCESS)
- return git__throw(error, "Failed to list references. User callback failed");
- );
+ /* If we haven't found the reference at all, create a new reference. */
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ return git_reference_create(NULL, repo, ref_name, oid, 0, signature, log_message);
}
- /* now list the loose references, trying not to
- * duplicate the ref names already in the packed-refs file */
-
- data.repo_path_len = strlen(repo->path_repository);
- data.list_flags = list_flags;
- data.repo = repo;
- data.callback = callback;
- data.callback_payload = payload;
+ if (error < 0)
+ return error;
+ /* If the ref is a symbolic reference, follow its target. */
+ if (git_reference_type(ref) == GIT_REF_SYMBOLIC) {
+ error = reference__update_terminal(repo, git_reference_symbolic_target(ref), oid,
+ nesting+1, signature, log_message);
+ git_reference_free(ref);
+ } else {
+ /* If we're not moving the target, don't recreate the ref */
+ if (0 != git_oid_cmp(git_reference_target(ref), oid))
+ error = git_reference_create(NULL, repo, ref_name, oid, 1, signature, log_message);
+ git_reference_free(ref);
+ }
- git_path_join(refs_path, repo->path_repository, GIT_REFS_DIR);
- return git_futils_direach(refs_path, GIT_PATH_MAX, _dirent_loose_listall, &data);
+ return error;
}
-int cb__reflist_add(const char *ref, void *data)
+/*
+ * Starting with the reference given by `ref_name`, follows symbolic
+ * references until a direct reference is found and updated the OID
+ * on that direct reference to `oid`.
+ */
+int git_reference__update_terminal(
+ git_repository *repo,
+ const char *ref_name,
+ const git_oid *oid,
+ const git_signature *signature,
+ const char *log_message)
{
- return git_vector_insert((git_vector *)data, git__strdup(ref));
+ return reference__update_terminal(repo, ref_name, oid, 0, signature, log_message);
}
-int git_reference_listall(git_strarray *array, git_repository *repo, unsigned int list_flags)
+int git_reference_has_log(git_repository *repo, const char *refname)
{
int error;
- git_vector ref_list;
+ git_refdb *refdb;
- assert(array && repo);
-
- array->strings = NULL;
- array->count = 0;
+ assert(repo && refname);
- if (git_vector_init(&ref_list, 8, NULL) < GIT_SUCCESS)
- return GIT_ENOMEM;
-
- error = git_reference_foreach(repo, list_flags, &cb__reflist_add, (void *)&ref_list);
-
- if (error < GIT_SUCCESS) {
- git_vector_free(&ref_list);
+ if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
return error;
- }
- array->strings = (char **)ref_list.contents;
- array->count = ref_list.length;
- return GIT_SUCCESS;
+ return git_refdb_has_log(refdb, refname);
}
-
-
-
-/*****************************************
- * Init/free (repository API)
- *****************************************/
-int git_repository__refcache_init(git_refcache *refs)
+int git_reference_ensure_log(git_repository *repo, const char *refname)
{
- assert(refs);
+ int error;
+ git_refdb *refdb;
- refs->loose_cache = git_hashtable_alloc(
- default_table_size,
- reftable_hash,
- (git_hash_keyeq_ptr)strcmp);
+ assert(repo && refname);
- /* packfile loaded lazily */
- refs->packfile = NULL;
- refs->packfile_time = 0;
+ if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
+ return error;
- return (refs->loose_cache) ? GIT_SUCCESS : GIT_ENOMEM;
+ return git_refdb_ensure_log(refdb, refname);
}
-void git_repository__refcache_free(git_refcache *refs)
+int git_reference__is_branch(const char *ref_name)
{
- git_reference *reference;
- const void *GIT_UNUSED(_unused);
-
- assert(refs);
-
- GIT_HASHTABLE_FOREACH(refs->loose_cache, _unused, reference,
- reference_free(reference);
- );
-
- git_hashtable_free(refs->loose_cache);
-
- if (refs->packfile) {
- GIT_HASHTABLE_FOREACH(refs->packfile, _unused, reference,
- reference_free(reference);
- );
-
- git_hashtable_free(refs->packfile);
- }
+ return git__prefixcmp(ref_name, GIT_REFS_HEADS_DIR) == 0;
}
-
-
-/*****************************************
- * Name normalization
- *****************************************/
-static int check_valid_ref_char(char ch)
+int git_reference_is_branch(const git_reference *ref)
{
- if (ch <= ' ')
- return GIT_ERROR;
-
- switch (ch) {
- case '~':
- case '^':
- case ':':
- case '\\':
- case '?':
- case '[':
- case '*':
- return GIT_ERROR;
- break;
-
- default:
- return GIT_SUCCESS;
- }
+ assert(ref);
+ return git_reference__is_branch(ref->name);
}
-static int normalize_name(char *buffer_out, size_t out_size, const char *name, int is_oid_ref)
+int git_reference__is_remote(const char *ref_name)
{
- const char *name_end, *buffer_out_start;
- const char *current;
- int contains_a_slash = 0;
-
- assert(name && buffer_out);
+ return git__prefixcmp(ref_name, GIT_REFS_REMOTES_DIR) == 0;
+}
- buffer_out_start = buffer_out;
- current = name;
- name_end = name + strlen(name);
+int git_reference_is_remote(const git_reference *ref)
+{
+ assert(ref);
+ return git_reference__is_remote(ref->name);
+}
- /* Terminating null byte */
- out_size--;
+int git_reference__is_tag(const char *ref_name)
+{
+ return git__prefixcmp(ref_name, GIT_REFS_TAGS_DIR) == 0;
+}
- /* A refname can not be empty */
- if (name_end == name)
- return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name is empty");
+int git_reference_is_tag(const git_reference *ref)
+{
+ assert(ref);
+ return git_reference__is_tag(ref->name);
+}
- /* A refname can not end with a dot or a slash */
- if (*(name_end - 1) == '.' || *(name_end - 1) == '/')
- return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name ends with dot or slash");
+int git_reference__is_note(const char *ref_name)
+{
+ return git__prefixcmp(ref_name, GIT_REFS_NOTES_DIR) == 0;
+}
- while (current < name_end && out_size) {
- if (check_valid_ref_char(*current))
- return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name contains invalid characters");
+int git_reference_is_note(const git_reference *ref)
+{
+ assert(ref);
+ return git_reference__is_note(ref->name);
+}
- if (buffer_out > buffer_out_start) {
- char prev = *(buffer_out - 1);
+static int peel_error(int error, git_reference *ref, const char* msg)
+{
+ giterr_set(
+ GITERR_INVALID,
+ "The reference '%s' cannot be peeled - %s", git_reference_name(ref), msg);
+ return error;
+}
- /* A refname can not start with a dot nor contain a double dot */
- if (*current == '.' && ((prev == '.') || (prev == '/')))
- return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name starts with a dot or contains a double dot");
+int git_reference_peel(
+ git_object **peeled,
+ git_reference *ref,
+ git_otype target_type)
+{
+ git_reference *resolved = NULL;
+ git_object *target = NULL;
+ int error;
- /* '@{' is forbidden within a refname */
- if (*current == '{' && prev == '@')
- return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name contains '@{'");
+ assert(ref);
- /* Prevent multiple slashes from being added to the output */
- if (*current == '/' && prev == '/') {
- current++;
- continue;
- }
- }
+ if (ref->type == GIT_REF_OID) {
+ resolved = ref;
+ } else {
+ if ((error = git_reference_resolve(&resolved, ref)) < 0)
+ return peel_error(error, ref, "Cannot resolve reference");
+ }
- if (*current == '/')
- contains_a_slash = 1;
+ if (!git_oid_iszero(&resolved->peel)) {
+ error = git_object_lookup(&target,
+ git_reference_owner(ref), &resolved->peel, GIT_OBJ_ANY);
+ } else {
+ error = git_object_lookup(&target,
+ git_reference_owner(ref), &resolved->target.oid, GIT_OBJ_ANY);
+ }
- *buffer_out++ = *current++;
- out_size--;
+ if (error < 0) {
+ peel_error(error, ref, "Cannot retrieve reference target");
+ goto cleanup;
}
- if (!out_size)
- return git__throw(GIT_EINVALIDREFNAME, "Reference name is too long");
+ if (target_type == GIT_OBJ_ANY && git_object_type(target) != GIT_OBJ_TAG)
+ error = git_object_dup(peeled, target);
+ else
+ error = git_object_peel(peeled, target, target_type);
- /* Object id refname have to contain at least one slash, except
- * for HEAD in a detached state or MERGE_HEAD if we're in the
- * middle of a merge */
- if (is_oid_ref && !contains_a_slash && (strcmp(name, GIT_HEAD_FILE) && strcmp(name, GIT_MERGE_HEAD_FILE)))
- return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name contains no slashes");
+cleanup:
+ git_object_free(target);
- /* A refname can not end with ".lock" */
- if (!git__suffixcmp(name, GIT_FILELOCK_EXTENSION))
- return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name ends with '.lock'");
+ if (resolved != ref)
+ git_reference_free(resolved);
- *buffer_out = '\0';
+ return error;
+}
- /*
- * For object id references, name has to start with refs/. Again,
- * we need to allow HEAD to be in a detached state.
- */
- if (is_oid_ref && !(git__prefixcmp(buffer_out_start, GIT_REFS_DIR) ||
- strcmp(buffer_out_start, GIT_HEAD_FILE)))
- return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name does not start with 'refs/'");
+int git_reference__is_valid_name(const char *refname, unsigned int flags)
+{
+ if (git_reference__normalize_name(NULL, refname, flags) < 0) {
+ giterr_clear();
+ return false;
+ }
- return GIT_SUCCESS;
+ return true;
}
-int git_reference__normalize_name(char *buffer_out, size_t out_size, const char *name)
+int git_reference_is_valid_name(const char *refname)
{
- return normalize_name(buffer_out, out_size, name, 0);
+ return git_reference__is_valid_name(refname, GIT_REF_FORMAT_ALLOW_ONELEVEL);
}
-int git_reference__normalize_name_oid(char *buffer_out, size_t out_size, const char *name)
+const char *git_reference_shorthand(const git_reference *ref)
{
- return normalize_name(buffer_out, out_size, name, 1);
+ const char *name = ref->name;
+
+ if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR))
+ return name + strlen(GIT_REFS_HEADS_DIR);
+ else if (!git__prefixcmp(name, GIT_REFS_TAGS_DIR))
+ return name + strlen(GIT_REFS_TAGS_DIR);
+ else if (!git__prefixcmp(name, GIT_REFS_REMOTES_DIR))
+ return name + strlen(GIT_REFS_REMOTES_DIR);
+ else if (!git__prefixcmp(name, GIT_REFS_DIR))
+ return name + strlen(GIT_REFS_DIR);
+
+ /* No shorthands are avaiable, so just return the name */
+ return name;
}
-
-
diff --git a/src/refs.h b/src/refs.h
index dfac455e0..f75a4bf7e 100644
--- a/src/refs.h
+++ b/src/refs.h
@@ -1,46 +1,101 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_refs_h__
#define INCLUDE_refs_h__
#include "common.h"
#include "git2/oid.h"
#include "git2/refs.h"
-#include "hashtable.h"
+#include "git2/refdb.h"
+#include "strmap.h"
+#include "buffer.h"
+#include "oid.h"
#define GIT_REFS_DIR "refs/"
#define GIT_REFS_HEADS_DIR GIT_REFS_DIR "heads/"
#define GIT_REFS_TAGS_DIR GIT_REFS_DIR "tags/"
#define GIT_REFS_REMOTES_DIR GIT_REFS_DIR "remotes/"
+#define GIT_REFS_NOTES_DIR GIT_REFS_DIR "notes/"
+#define GIT_REFS_DIR_MODE 0777
+#define GIT_REFS_FILE_MODE 0666
#define GIT_RENAMED_REF_FILE GIT_REFS_DIR "RENAMED-REF"
#define GIT_SYMREF "ref: "
#define GIT_PACKEDREFS_FILE "packed-refs"
-#define GIT_PACKEDREFS_HEADER "# pack-refs with: peeled "
+#define GIT_PACKEDREFS_HEADER "# pack-refs with: peeled fully-peeled "
+#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_REF_FORMAT__PRECOMPOSE_UNICODE (1u << 16)
+
#define GIT_REFNAME_MAX 1024
+typedef char git_refname_t[GIT_REFNAME_MAX];
+
struct git_reference {
- git_repository *owner;
- char *name;
- unsigned int type;
- time_t mtime;
+ git_refdb *db;
+ git_ref_t type;
+
+ union {
+ git_oid oid;
+ char *symbolic;
+ } target;
+
+ git_oid peel;
+ char name[0];
};
-typedef struct {
- git_hashtable *packfile;
- git_hashtable *loose_cache;
- time_t packfile_time;
-} git_refcache;
+git_reference *git_reference__set_name(git_reference *ref, const char *name);
+int git_reference__normalize_name(git_buf *buf, const char *name, unsigned int flags);
+int git_reference__update_terminal(git_repository *repo, const char *ref_name, const git_oid *oid, const git_signature *signature, const char *log_message);
+int git_reference__is_valid_name(const char *refname, unsigned int flags);
+int git_reference__is_branch(const char *ref_name);
+int git_reference__is_remote(const char *ref_name);
+int git_reference__is_tag(const char *ref_name);
-void git_repository__refcache_free(git_refcache *refs);
-int git_repository__refcache_init(git_refcache *refs);
+/**
+ * Lookup a reference by name and try to resolve to an OID.
+ *
+ * You can control how many dereferences this will attempt to resolve the
+ * reference with the `max_deref` parameter, or pass -1 to use a sane
+ * default. If you pass 0 for `max_deref`, this will not attempt to resolve
+ * the reference. For any value of `max_deref` other than 0, not
+ * successfully resolving the reference will be reported as an error.
-int git_reference__normalize_name(char *buffer_out, size_t out_size, const char *name);
-int git_reference__normalize_name_oid(char *buffer_out, size_t out_size, const char *name);
+ * The generated reference must be freed by the user.
+ *
+ * @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 max_deref Maximum number of dereferences to make of symbolic refs, 0 means simple lookup, < 0 means use default reasonable value
+ * @return 0 on success or < 0 on error; not being able to resolve the reference is an error unless 0 was passed for max_deref
+ */
+int git_reference_lookup_resolved(
+ git_reference **reference_out,
+ git_repository *repo,
+ const char *name,
+ int max_deref);
#endif
diff --git a/src/refspec.c b/src/refspec.c
index 8500e07ea..fa60aa7aa 100644
--- a/src/refspec.c
+++ b/src/refspec.c
@@ -1,26 +1,8 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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/errors.h"
@@ -28,81 +10,302 @@
#include "common.h"
#include "refspec.h"
#include "util.h"
+#include "posix.h"
+#include "refs.h"
+#include "vector.h"
-int git_refspec_parse(git_refspec *refspec, const char *str)
+int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch)
{
- char *delim;
+ // Ported from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/remote.c#L518-636
+
+ size_t llen;
+ int is_glob = 0;
+ const char *lhs, *rhs;
+ int flags;
+
+ assert(refspec && input);
memset(refspec, 0x0, sizeof(git_refspec));
+ refspec->push = !is_fetch;
- if (*str == '+') {
+ lhs = input;
+ if (*lhs == '+') {
refspec->force = 1;
- str++;
+ lhs++;
}
- delim = strchr(str, ':');
- if (delim == NULL)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse refspec. No ':'");
+ rhs = strrchr(lhs, ':');
- refspec->src = git__strndup(str, delim - str);
- if (refspec->src == NULL)
- return GIT_ENOMEM;
+ /*
+ * Before going on, special case ":" (or "+:") as a refspec
+ * for matching refs.
+ */
+ if (!is_fetch && rhs == lhs && rhs[1] == '\0') {
+ refspec->matching = 1;
+ return 0;
+ }
- refspec->dst = git__strdup(delim + 1);
- if (refspec->dst == NULL) {
- free(refspec->src);
- refspec->src = NULL;
- return GIT_ENOMEM;
+ if (rhs) {
+ size_t rlen = strlen(++rhs);
+ is_glob = (1 <= rlen && strchr(rhs, '*'));
+ refspec->dst = git__strndup(rhs, rlen);
}
- return GIT_SUCCESS;
+ llen = (rhs ? (size_t)(rhs - lhs - 1) : strlen(lhs));
+ if (1 <= llen && memchr(lhs, '*', llen)) {
+ if ((rhs && !is_glob) || (!rhs && is_fetch))
+ goto invalid;
+ is_glob = 1;
+ } else if (rhs && is_glob)
+ goto invalid;
+
+ refspec->pattern = is_glob;
+ refspec->src = git__strndup(lhs, llen);
+ flags = GIT_REF_FORMAT_ALLOW_ONELEVEL | GIT_REF_FORMAT_REFSPEC_SHORTHAND
+ | (is_glob ? GIT_REF_FORMAT_REFSPEC_PATTERN : 0);
+
+ if (is_fetch) {
+ /*
+ * LHS
+ * - empty is allowed; it means HEAD.
+ * - otherwise it must be a valid looking ref.
+ */
+ if (!*refspec->src)
+ ; /* empty is ok */
+ else if (!git_reference__is_valid_name(refspec->src, flags))
+ goto invalid;
+ /*
+ * RHS
+ * - missing is ok, and is same as empty.
+ * - empty is ok; it means not to store.
+ * - otherwise it must be a valid looking ref.
+ */
+ if (!refspec->dst)
+ ; /* ok */
+ else if (!*refspec->dst)
+ ; /* ok */
+ else if (!git_reference__is_valid_name(refspec->dst, flags))
+ goto invalid;
+ } else {
+ /*
+ * LHS
+ * - empty is allowed; it means delete.
+ * - when wildcarded, it must be a valid looking ref.
+ * - otherwise, it must be an extended SHA-1, but
+ * there is no existing way to validate this.
+ */
+ if (!*refspec->src)
+ ; /* empty is ok */
+ else if (is_glob) {
+ if (!git_reference__is_valid_name(refspec->src, flags))
+ goto invalid;
+ }
+ else {
+ ; /* anything goes, for now */
+ }
+ /*
+ * RHS
+ * - missing is allowed, but LHS then must be a
+ * valid looking ref.
+ * - empty is not allowed.
+ * - otherwise it must be a valid looking ref.
+ */
+ if (!refspec->dst) {
+ if (!git_reference__is_valid_name(refspec->src, flags))
+ goto invalid;
+ } else if (!*refspec->dst) {
+ goto invalid;
+ } else {
+ if (!git_reference__is_valid_name(refspec->dst, flags))
+ goto invalid;
+ }
+ }
+
+ refspec->string = git__strdup(input);
+ GITERR_CHECK_ALLOC(refspec->string);
+
+ return 0;
+
+ invalid:
+ return -1;
+}
+
+void git_refspec__free(git_refspec *refspec)
+{
+ if (refspec == NULL)
+ return;
+
+ git__free(refspec->src);
+ git__free(refspec->dst);
+ git__free(refspec->string);
}
const char *git_refspec_src(const git_refspec *refspec)
{
- return refspec->src;
+ return refspec == NULL ? NULL : refspec->src;
}
const char *git_refspec_dst(const git_refspec *refspec)
{
- return refspec->dst;
+ return refspec == NULL ? NULL : refspec->dst;
}
-int git_refspec_src_match(const git_refspec *refspec, const char *refname)
+const char *git_refspec_string(const git_refspec *refspec)
{
- return git__fnmatch(refspec->src, refname, 0);
+ return refspec == NULL ? NULL : refspec->string;
}
-int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, const char *name)
+int git_refspec_force(const git_refspec *refspec)
{
- size_t baselen, namelen;
+ assert(refspec);
- baselen = strlen(spec->dst);
- if (outlen <= baselen)
- return git__throw(GIT_EINVALIDREFNAME, "Reference name too long");
+ return refspec->force;
+}
- /*
- * No '*' at the end means that it's mapped to one specific local
- * branch, so no actual transformation is needed.
- */
- if (spec->dst[baselen - 1] != '*') {
- memcpy(out, spec->dst, baselen + 1); /* include '\0' */
- return GIT_SUCCESS;
+int git_refspec_src_matches(const git_refspec *refspec, const char *refname)
+{
+ if (refspec == NULL || refspec->src == NULL)
+ return false;
+
+ return (p_fnmatch(refspec->src, refname, 0) == 0);
+}
+
+int git_refspec_dst_matches(const git_refspec *refspec, const char *refname)
+{
+ if (refspec == NULL || refspec->dst == NULL)
+ return false;
+
+ return (p_fnmatch(refspec->dst, refname, 0) == 0);
+}
+
+static int refspec_transform(
+ git_buf *out, const char *from, const char *to, const char *name)
+{
+ size_t to_len = to ? strlen(to) : 0;
+ size_t from_len = from ? strlen(from) : 0;
+ size_t name_len = name ? strlen(name) : 0;
+
+ git_buf_sanitize(out);
+
+ if (git_buf_set(out, to, to_len) < 0)
+ return -1;
+
+ if (to_len > 0) {
+ /* No '*' at the end of 'to' means that refspec is mapped to one
+ * specific branch, so no actual transformation is needed.
+ */
+ if (out->ptr[to_len - 1] != '*')
+ return 0;
+ git_buf_shorten(out, 1); /* remove trailing '*' copied from 'to' */
+ }
+
+ if (from_len > 0) /* ignore trailing '*' from 'from' */
+ from_len--;
+ if (from_len > name_len)
+ from_len = name_len;
+
+ return git_buf_put(out, name + from_len, name_len - from_len);
+}
+
+int git_refspec_transform(git_buf *out, const git_refspec *spec, const char *name)
+{
+ return refspec_transform(out, spec->src, spec->dst, name);
+}
+
+int git_refspec_rtransform(git_buf *out, const git_refspec *spec, const char *name)
+{
+ return refspec_transform(out, spec->dst, spec->src, name);
+}
+
+int git_refspec__serialize(git_buf *out, const git_refspec *refspec)
+{
+ if (refspec->force)
+ git_buf_putc(out, '+');
+
+ git_buf_printf(out, "%s:%s",
+ refspec->src != NULL ? refspec->src : "",
+ refspec->dst != NULL ? refspec->dst : "");
+
+ return git_buf_oom(out) == false;
+}
+
+int git_refspec_is_wildcard(const git_refspec *spec)
+{
+ assert(spec && spec->src);
+
+ return (spec->src[strlen(spec->src) - 1] == '*');
+}
+
+git_direction git_refspec_direction(const git_refspec *spec)
+{
+ assert(spec);
+
+ return spec->push;
+}
+
+int git_refspec__dwim_one(git_vector *out, git_refspec *spec, git_vector *refs)
+{
+ git_buf buf = GIT_BUF_INIT;
+ size_t j, pos;
+ git_remote_head key;
+
+ const char* formatters[] = {
+ GIT_REFS_DIR "%s",
+ GIT_REFS_TAGS_DIR "%s",
+ GIT_REFS_HEADS_DIR "%s",
+ NULL
+ };
+
+ git_refspec *cur = git__calloc(1, sizeof(git_refspec));
+ GITERR_CHECK_ALLOC(cur);
+
+ cur->force = spec->force;
+ cur->push = spec->push;
+ cur->pattern = spec->pattern;
+ cur->matching = spec->matching;
+ cur->string = git__strdup(spec->string);
+
+ /* shorthand on the lhs */
+ if (git__prefixcmp(spec->src, GIT_REFS_DIR)) {
+ for (j = 0; formatters[j]; j++) {
+ git_buf_clear(&buf);
+ if (git_buf_printf(&buf, formatters[j], spec->src) < 0)
+ return -1;
+
+ key.name = (char *) git_buf_cstr(&buf);
+ if (!git_vector_search(&pos, refs, &key)) {
+ /* we found something to match the shorthand, set src to that */
+ cur->src = git_buf_detach(&buf);
+ }
+ }
}
- /* There's a '*' at the end, so remove its length */
- baselen--;
+ /* No shorthands found, copy over the name */
+ if (cur->src == NULL && spec->src != NULL) {
+ cur->src = git__strdup(spec->src);
+ GITERR_CHECK_ALLOC(cur->src);
+ }
+
+ if (spec->dst && git__prefixcmp(spec->dst, GIT_REFS_DIR)) {
+ /* if it starts with "remotes" then we just prepend "refs/" */
+ if (!git__prefixcmp(spec->dst, "remotes/")) {
+ git_buf_puts(&buf, GIT_REFS_DIR);
+ } else {
+ git_buf_puts(&buf, GIT_REFS_HEADS_DIR);
+ }
- /* skip the prefix, -1 is for the '*' */
- name += strlen(spec->src) - 1;
+ if (git_buf_puts(&buf, spec->dst) < 0)
+ return -1;
- namelen = strlen(name);
+ cur->dst = git_buf_detach(&buf);
+ }
- if (outlen <= baselen + namelen)
- return git__throw(GIT_EINVALIDREFNAME, "Reference name too long");
+ git_buf_free(&buf);
- memcpy(out, spec->dst, baselen);
- memcpy(out + baselen, name, namelen + 1);
+ if (cur->dst == NULL && spec->dst != NULL) {
+ cur->dst = git__strdup(spec->dst);
+ GITERR_CHECK_ALLOC(cur->dst);
+ }
- return GIT_SUCCESS;
+ return git_vector_insert(out, cur);
}
diff --git a/src/refspec.h b/src/refspec.h
index 230135a4a..9a87c97a5 100644
--- a/src/refspec.h
+++ b/src/refspec.h
@@ -1,14 +1,49 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
#ifndef INCLUDE_refspec_h__
#define INCLUDE_refspec_h__
#include "git2/refspec.h"
+#include "buffer.h"
+#include "vector.h"
struct git_refspec {
- int force;
+ char *string;
char *src;
char *dst;
+ unsigned int force :1,
+ push : 1,
+ pattern :1,
+ matching :1;
};
-int git_refspec_parse(struct git_refspec *refspec, const char *str);
+#define GIT_REFSPEC_TAGS "refs/tags/*:refs/tags/*"
+
+int git_refspec__parse(
+ struct git_refspec *refspec,
+ const char *str,
+ bool is_fetch);
+
+void git_refspec__free(git_refspec *refspec);
+
+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);
+
+/**
+ * DWIM `spec` with `refs` existing on the remote, append the dwim'ed
+ * result in `out`.
+ */
+int git_refspec__dwim_one(git_vector *out, git_refspec *spec, git_vector *refs);
#endif
diff --git a/src/remote.c b/src/remote.c
index 2812f5de6..47b61b1b1 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -1,218 +1,1972 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
*/
-#include "git2/remote.h"
#include "git2/config.h"
#include "git2/types.h"
+#include "git2/oid.h"
+#include "git2/net.h"
+#include "common.h"
#include "config.h"
#include "repository.h"
#include "remote.h"
+#include "fetch.h"
+#include "refs.h"
+#include "refspec.h"
+#include "fetchhead.h"
+
+static int dwim_refspecs(git_vector *out, git_vector *refspecs, git_vector *refs);
+
+static int add_refspec(git_remote *remote, const char *string, bool is_fetch)
+{
+ git_refspec *spec;
+
+ spec = git__calloc(1, sizeof(git_refspec));
+ GITERR_CHECK_ALLOC(spec);
+
+ if (git_refspec__parse(spec, string, is_fetch) < 0) {
+ git__free(spec);
+ return -1;
+ }
+
+ spec->push = !is_fetch;
+ if (git_vector_insert(&remote->refspecs, spec) < 0) {
+ git_refspec__free(spec);
+ git__free(spec);
+ return -1;
+ }
+
+ return 0;
+}
-static int refspec_parse(git_refspec *refspec, const char *str)
+static int download_tags_value(git_remote *remote, git_config *cfg)
{
- char *delim;
+ const git_config_entry *ce;
+ git_buf buf = GIT_BUF_INIT;
+ int error;
+
+ /* The 0 value is the default (auto), let's see if we need to change it */
+ if (git_buf_printf(&buf, "remote.%s.tagopt", remote->name) < 0)
+ return -1;
- memset(refspec, 0x0, sizeof(git_refspec));
+ error = git_config__lookup_entry(&ce, cfg, git_buf_cstr(&buf), false);
+ git_buf_free(&buf);
- if (*str == '+') {
- refspec->force = 1;
- str++;
+ if (!error && ce && ce->value) {
+ if (!strcmp(ce->value, "--no-tags"))
+ remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE;
+ else if (!strcmp(ce->value, "--tags"))
+ remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL;
}
- delim = strchr(str, ':');
- if (delim == NULL)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse refspec. No ':'");
+ return error;
+}
- refspec->src = git__strndup(str, delim - str);
- if (refspec->src == NULL)
- return GIT_ENOMEM;
+static int ensure_remote_name_is_valid(const char *name)
+{
+ int error = 0;
- refspec->dst = git__strdup(delim + 1);
- if (refspec->dst == NULL) {
- free(refspec->src);
- refspec->src = NULL;
- return GIT_ENOMEM;
+ if (!git_remote_is_valid_name(name)) {
+ giterr_set(
+ GITERR_CONFIG,
+ "'%s' is not a valid remote name.", name ? name : "(null)");
+ error = GIT_EINVALIDSPEC;
}
- return GIT_SUCCESS;
+ return error;
}
-static int parse_remote_refspec(git_config *cfg, git_refspec *refspec, const char *var)
+static int get_check_cert(int *out, git_repository *repo)
{
+ git_config *cfg;
const char *val;
+ int error = 0;
+
+ assert(out && repo);
+
+ /* By default, we *DO* want to verify the certificate. */
+ *out = 1;
+
+ /* Go through the possible sources for SSL verification settings, from
+ * most specific to least specific. */
+
+ /* GIT_SSL_NO_VERIFY environment variable */
+ if ((val = getenv("GIT_SSL_NO_VERIFY")) != NULL)
+ return git_config_parse_bool(out, val);
+
+ /* http.sslVerify config setting */
+ if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
+ return error;
+
+ *out = git_config__get_bool_force(cfg, "http.sslverify", 1);
+ return 0;
+}
+
+static int create_internal(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch)
+{
+ git_remote *remote;
+ git_buf fetchbuf = GIT_BUF_INIT;
+ int error = -1;
+
+ /* name is optional */
+ assert(out && repo && url);
+
+ remote = git__calloc(1, sizeof(git_remote));
+ GITERR_CHECK_ALLOC(remote);
+
+ remote->repo = repo;
+ remote->update_fetchhead = 1;
+
+ if (get_check_cert(&remote->check_cert, repo) < 0)
+ goto on_error;
+
+ if (git_vector_init(&remote->refs, 32, NULL) < 0)
+ goto on_error;
+
+ remote->url = git__strdup(url);
+ GITERR_CHECK_ALLOC(remote->url);
+
+ if (name != NULL) {
+ remote->name = git__strdup(name);
+ GITERR_CHECK_ALLOC(remote->name);
+ }
+
+ if (fetch != NULL) {
+ if (add_refspec(remote, fetch, true) < 0)
+ goto on_error;
+ }
+
+ if (!name)
+ /* A remote without a name doesn't download tags */
+ remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE;
+
+ *out = remote;
+ git_buf_free(&fetchbuf);
+ return 0;
+
+on_error:
+ git_remote_free(remote);
+ git_buf_free(&fetchbuf);
+ return error;
+}
+
+static int ensure_remote_doesnot_exist(git_repository *repo, const char *name)
+{
int error;
+ git_remote *remote;
- error = git_config_get_string(cfg, var, &val);
- if (error < GIT_SUCCESS)
+ error = git_remote_load(&remote, repo, name);
+
+ if (error == GIT_ENOTFOUND)
+ return 0;
+
+ if (error < 0)
+ return error;
+
+ git_remote_free(remote);
+
+ giterr_set(
+ GITERR_CONFIG,
+ "Remote '%s' already exists.", name);
+
+ return GIT_EEXISTS;
+}
+
+
+int git_remote_create(git_remote **out, git_repository *repo, const char *name, const char *url)
+{
+ git_buf buf = GIT_BUF_INIT;
+ git_remote *remote = NULL;
+ int error;
+
+ if ((error = ensure_remote_name_is_valid(name)) < 0)
+ return error;
+
+ if ((error = ensure_remote_doesnot_exist(repo, name)) < 0)
return error;
- return refspec_parse(refspec, val);
+ if (git_buf_printf(&buf, "+refs/heads/*:refs/remotes/%s/*", name) < 0)
+ return -1;
+
+ if (create_internal(&remote, repo, name, url, git_buf_cstr(&buf)) < 0)
+ goto on_error;
+
+ git_buf_free(&buf);
+
+ if (git_remote_save(remote) < 0)
+ goto on_error;
+
+ *out = remote;
+
+ return 0;
+
+on_error:
+ git_buf_free(&buf);
+ git_remote_free(remote);
+ return -1;
}
-int git_remote_get(git_remote **out, git_config *cfg, const char *name)
+int git_remote_create_with_fetchspec(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch)
{
+ git_remote *remote = NULL;
+ int error;
+
+ if ((error = ensure_remote_name_is_valid(name)) < 0)
+ return error;
+
+ if ((error = ensure_remote_doesnot_exist(repo, name)) < 0)
+ return error;
+
+ if (create_internal(&remote, repo, name, url, fetch) < 0)
+ goto on_error;
+
+ if (git_remote_save(remote) < 0)
+ goto on_error;
+
+ *out = remote;
+
+ return 0;
+
+on_error:
+ git_remote_free(remote);
+ return -1;
+}
+
+int git_remote_create_anonymous(git_remote **out, git_repository *repo, const char *url, const char *fetch)
+{
+ int error;
git_remote *remote;
- char *buf = NULL;
+
+ if ((error = create_internal(&remote, repo, NULL, url, fetch)) < 0)
+ return error;
+
+ *out = remote;
+ return 0;
+}
+
+int git_remote_dup(git_remote **dest, git_remote *source)
+{
+ int error = 0;
+ git_strarray refspecs = { 0 };
+ git_remote *remote = git__calloc(1, sizeof(git_remote));
+ GITERR_CHECK_ALLOC(remote);
+
+ if (source->name != NULL) {
+ remote->name = git__strdup(source->name);
+ GITERR_CHECK_ALLOC(remote->name);
+ }
+
+ if (source->url != NULL) {
+ remote->url = git__strdup(source->url);
+ GITERR_CHECK_ALLOC(remote->url);
+ }
+
+ if (source->pushurl != NULL) {
+ remote->pushurl = git__strdup(source->pushurl);
+ GITERR_CHECK_ALLOC(remote->pushurl);
+ }
+
+ remote->repo = source->repo;
+ remote->download_tags = source->download_tags;
+ remote->check_cert = source->check_cert;
+ remote->update_fetchhead = source->update_fetchhead;
+
+ if (git_vector_init(&remote->refs, 32, NULL) < 0 ||
+ git_vector_init(&remote->refspecs, 2, NULL) < 0 ||
+ git_vector_init(&remote->active_refspecs, 2, NULL) < 0) {
+ error = -1;
+ goto cleanup;
+ }
+
+ if ((error = git_remote_get_fetch_refspecs(&refspecs, source)) < 0 ||
+ (error = git_remote_set_fetch_refspecs(remote, &refspecs)) < 0)
+ goto cleanup;
+
+ git_strarray_free(&refspecs);
+
+ if ((error = git_remote_get_push_refspecs(&refspecs, source)) < 0 ||
+ (error = git_remote_set_push_refspecs(remote, &refspecs)) < 0)
+ goto cleanup;
+
+ *dest = remote;
+
+cleanup:
+
+ git_strarray_free(&refspecs);
+
+ if (error < 0)
+ git__free(remote);
+
+ return error;
+}
+
+struct refspec_cb_data {
+ git_remote *remote;
+ int fetch;
+};
+
+static int refspec_cb(const git_config_entry *entry, void *payload)
+{
+ struct refspec_cb_data *data = (struct refspec_cb_data *)payload;
+ return add_refspec(data->remote, entry->value, data->fetch);
+}
+
+static int get_optional_config(
+ bool *found, git_config *config, git_buf *buf,
+ git_config_foreach_cb cb, void *payload)
+{
+ int error = 0;
+ const char *key = git_buf_cstr(buf);
+
+ if (git_buf_oom(buf))
+ return -1;
+
+ if (cb != NULL)
+ error = git_config_get_multivar_foreach(config, key, NULL, cb, payload);
+ else
+ error = git_config_get_string(payload, config, key);
+
+ if (found)
+ *found = !error;
+
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
+ }
+
+ return error;
+}
+
+int git_remote_load(git_remote **out, git_repository *repo, const char *name)
+{
+ git_remote *remote;
+ git_buf buf = GIT_BUF_INIT;
const char *val;
- int ret, error, buf_len;
+ int error = 0;
+ git_config *config;
+ struct refspec_cb_data data = { NULL };
+ bool optional_setting_found = false, found;
+
+ assert(out && repo && name);
+
+ if ((error = ensure_remote_name_is_valid(name)) < 0)
+ return error;
+
+ if ((error = git_repository_config_snapshot(&config, repo)) < 0)
+ return error;
remote = git__malloc(sizeof(git_remote));
- if (remote == NULL)
- return GIT_ENOMEM;
+ GITERR_CHECK_ALLOC(remote);
memset(remote, 0x0, sizeof(git_remote));
+ remote->update_fetchhead = 1;
remote->name = git__strdup(name);
- if (remote->name == NULL) {
- error = GIT_ENOMEM;
+ GITERR_CHECK_ALLOC(remote->name);
+
+ if ((error = get_check_cert(&remote->check_cert, repo)) < 0)
goto cleanup;
- }
- /* "fetch" is the longest var name we're interested in */
- buf_len = STRLEN("remote.") + STRLEN(".fetch") + strlen(name) + 1;
- buf = git__malloc(buf_len);
- if (buf == NULL) {
- error = GIT_ENOMEM;
+ if (git_vector_init(&remote->refs, 32, NULL) < 0 ||
+ git_vector_init(&remote->refspecs, 2, NULL) < 0 ||
+ git_vector_init(&remote->active_refspecs, 2, NULL) < 0) {
+ error = -1;
goto cleanup;
}
- ret = snprintf(buf, buf_len, "%s.%s.%s", "remote", name, "url");
- if (ret < 0) {
- error = git__throw(GIT_EOSERR, "Failed to build config var name");
+ if ((error = git_buf_printf(&buf, "remote.%s.url", name)) < 0)
goto cleanup;
- }
- error = git_config_get_string(cfg, buf, &val);
- if (error < GIT_SUCCESS) {
- error = git__rethrow(error, "Remote's url doesn't exist");
+ if ((error = get_optional_config(&found, config, &buf, NULL, (void *)&val)) < 0)
goto cleanup;
+
+ optional_setting_found |= found;
+
+ remote->repo = repo;
+
+ if (found && strlen(val) > 0) {
+ remote->url = git__strdup(val);
+ GITERR_CHECK_ALLOC(remote->url);
}
- remote->url = git__strdup(val);
- if (remote->url == NULL) {
- error = GIT_ENOMEM;
+ val = NULL;
+ git_buf_clear(&buf);
+ git_buf_printf(&buf, "remote.%s.pushurl", name);
+
+ if ((error = get_optional_config(&found, config, &buf, NULL, (void *)&val)) < 0)
goto cleanup;
- }
- ret = snprintf(buf, buf_len, "%s.%s.%s", "remote", name, "fetch");
- if (ret < 0) {
- error = git__throw(GIT_EOSERR, "Failed to build config var name");
+ optional_setting_found |= found;
+
+ if (!optional_setting_found) {
+ error = GIT_ENOTFOUND;
+ giterr_set(GITERR_CONFIG, "Remote '%s' does not exist.", name);
goto cleanup;
}
- error = parse_remote_refspec(cfg, &remote->fetch, buf);
- if (error < GIT_SUCCESS) {
- error = git__rethrow(error, "Failed to get fetch refspec");
- goto cleanup;
+ if (found && strlen(val) > 0) {
+ remote->pushurl = git__strdup(val);
+ GITERR_CHECK_ALLOC(remote->pushurl);
}
- ret = snprintf(buf, buf_len, "%s.%s.%s", "remote", name, "push");
- if (ret < 0) {
- error = git__throw(GIT_EOSERR, "Failed to build config var name");
+ data.remote = remote;
+ data.fetch = true;
+
+ git_buf_clear(&buf);
+ git_buf_printf(&buf, "remote.%s.fetch", name);
+
+ if ((error = get_optional_config(NULL, config, &buf, refspec_cb, &data)) < 0)
goto cleanup;
- }
- error = parse_remote_refspec(cfg, &remote->push, buf);
- /* Not finding push is fine */
- if (error == GIT_ENOTFOUND)
- error = GIT_SUCCESS;
+ data.fetch = false;
+ git_buf_clear(&buf);
+ git_buf_printf(&buf, "remote.%s.push", name);
+
+ if ((error = get_optional_config(NULL, config, &buf, refspec_cb, &data)) < 0)
+ goto cleanup;
+
+ if (download_tags_value(remote, config) < 0)
+ goto cleanup;
- if (error < GIT_SUCCESS)
+ /* Move the data over to where the matching functions can find them */
+ if (dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &remote->refs) < 0)
goto cleanup;
*out = remote;
cleanup:
- free(buf);
- if (error < GIT_SUCCESS)
+ git_config_free(config);
+ git_buf_free(&buf);
+
+ if (error < 0)
git_remote_free(remote);
return error;
}
-const char *git_remote_name(struct git_remote *remote)
+static int update_config_refspec(const git_remote *remote, git_config *config, int direction)
+{
+ git_buf name = GIT_BUF_INIT;
+ unsigned int push;
+ const char *dir;
+ size_t i;
+ int error = 0;
+ const char *cname;
+
+ push = direction == GIT_DIRECTION_PUSH;
+ dir = push ? "push" : "fetch";
+
+ if (git_buf_printf(&name, "remote.%s.%s", remote->name, dir) < 0)
+ return -1;
+ cname = git_buf_cstr(&name);
+
+ /* Clear out the existing config */
+ while (!error)
+ error = git_config_delete_multivar(config, cname, ".*");
+
+ if (error != GIT_ENOTFOUND)
+ return error;
+
+ for (i = 0; i < remote->refspecs.length; i++) {
+ git_refspec *spec = git_vector_get(&remote->refspecs, i);
+
+ if (spec->push != push)
+ continue;
+
+ // "$^" is a unmatcheable regexp: it will not match anything at all, so
+ // all values will be considered new and we will not replace any
+ // present value.
+ if ((error = git_config_set_multivar(
+ config, cname, "$^", spec->string)) < 0) {
+ goto cleanup;
+ }
+ }
+
+ giterr_clear();
+ error = 0;
+
+cleanup:
+ git_buf_free(&name);
+
+ return error;
+}
+
+int git_remote_save(const git_remote *remote)
+{
+ int error;
+ git_config *cfg;
+ const char *tagopt = NULL;
+ git_buf buf = GIT_BUF_INIT;
+ const git_config_entry *existing;
+
+ assert(remote);
+
+ if (!remote->name) {
+ giterr_set(GITERR_INVALID, "Can't save an anonymous remote.");
+ return GIT_EINVALIDSPEC;
+ }
+
+ if ((error = ensure_remote_name_is_valid(remote->name)) < 0)
+ return error;
+
+ if ((error = git_repository_config__weakptr(&cfg, remote->repo)) < 0)
+ return error;
+
+ if ((error = git_buf_printf(&buf, "remote.%s.url", remote->name)) < 0)
+ return error;
+
+ /* after this point, buffer is allocated so end with cleanup */
+
+ if ((error = git_config_set_string(
+ cfg, git_buf_cstr(&buf), remote->url)) < 0)
+ goto cleanup;
+
+ git_buf_clear(&buf);
+ if ((error = git_buf_printf(&buf, "remote.%s.pushurl", remote->name)) < 0)
+ goto cleanup;
+
+ if ((error = git_config__update_entry(
+ cfg, git_buf_cstr(&buf), remote->pushurl, true, false)) < 0)
+ goto cleanup;
+
+ if ((error = update_config_refspec(remote, cfg, GIT_DIRECTION_FETCH)) < 0)
+ goto cleanup;
+
+ if ((error = update_config_refspec(remote, cfg, GIT_DIRECTION_PUSH)) < 0)
+ goto cleanup;
+
+ /*
+ * What action to take depends on the old and new values. This
+ * is describes by the table below. tagopt means whether the
+ * is already a value set in the config
+ *
+ * AUTO ALL or NONE
+ * +-----------------------+
+ * tagopt | remove | set |
+ * +---------+-------------|
+ * !tagopt | nothing | set |
+ * +---------+-------------+
+ */
+
+ git_buf_clear(&buf);
+ if ((error = git_buf_printf(&buf, "remote.%s.tagopt", remote->name)) < 0)
+ goto cleanup;
+
+ if ((error = git_config__lookup_entry(
+ &existing, cfg, git_buf_cstr(&buf), false)) < 0)
+ goto cleanup;
+
+ if (remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_ALL)
+ tagopt = "--tags";
+ else if (remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_NONE)
+ tagopt = "--no-tags";
+ else if (existing != NULL)
+ tagopt = NULL;
+
+ error = git_config__update_entry(
+ cfg, git_buf_cstr(&buf), tagopt, true, false);
+
+cleanup:
+ git_buf_free(&buf);
+ return error;
+}
+
+const char *git_remote_name(const git_remote *remote)
{
+ assert(remote);
return remote->name;
}
-const char *git_remote_url(struct git_remote *remote)
+git_repository *git_remote_owner(const git_remote *remote)
+{
+ assert(remote);
+ return remote->repo;
+}
+
+const char *git_remote_url(const git_remote *remote)
{
+ assert(remote);
return remote->url;
}
-const git_refspec *git_remote_fetchspec(struct git_remote *remote)
+int git_remote_set_url(git_remote *remote, const char* url)
+{
+ assert(remote);
+ assert(url);
+
+ git__free(remote->url);
+ remote->url = git__strdup(url);
+ GITERR_CHECK_ALLOC(remote->url);
+
+ return 0;
+}
+
+const char *git_remote_pushurl(const git_remote *remote)
{
- return &remote->fetch;
+ assert(remote);
+ return remote->pushurl;
}
-const git_refspec *git_remote_pushspec(struct git_remote *remote)
+int git_remote_set_pushurl(git_remote *remote, const char* url)
{
- return &remote->push;
+ assert(remote);
+
+ git__free(remote->pushurl);
+ if (url) {
+ remote->pushurl = git__strdup(url);
+ GITERR_CHECK_ALLOC(remote->pushurl);
+ } else {
+ remote->pushurl = NULL;
+ }
+ return 0;
}
-int git_remote_connect(git_remote *remote, int direction)
+const char* git_remote__urlfordirection(git_remote *remote, int direction)
+{
+ assert(remote);
+
+ assert(direction == GIT_DIRECTION_FETCH || direction == GIT_DIRECTION_PUSH);
+
+ if (direction == GIT_DIRECTION_FETCH) {
+ return remote->url;
+ }
+
+ if (direction == GIT_DIRECTION_PUSH) {
+ return remote->pushurl ? remote->pushurl : remote->url;
+ }
+
+ return NULL;
+}
+
+int git_remote_connect(git_remote *remote, git_direction direction)
{
- int error;
git_transport *t;
+ const char *url;
+ int flags = GIT_TRANSPORTFLAGS_NONE;
+ int error;
- error = git_transport_new(&t, remote->url);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to create transport");
+ assert(remote);
- error = git_transport_connect(t, direction);
- if (error < GIT_SUCCESS) {
- error = git__rethrow(error, "Failed to connect the transport");
- goto cleanup;
+ t = remote->transport;
+
+ url = git_remote__urlfordirection(remote, direction);
+ if (url == NULL) {
+ giterr_set(GITERR_INVALID,
+ "Malformed remote '%s' - missing URL", remote->name);
+ return -1;
}
+ /* A transport could have been supplied in advance with
+ * git_remote_set_transport */
+ if (!t && (error = git_transport_new(&t, remote, url)) < 0)
+ return error;
+
+ if (t->set_callbacks &&
+ (error = t->set_callbacks(t, remote->callbacks.sideband_progress, NULL, remote->callbacks.payload)) < 0)
+ goto on_error;
+
+ if (!remote->check_cert)
+ flags |= GIT_TRANSPORTFLAGS_NO_CHECK_CERT;
+
+ if ((error = t->connect(t, url, remote->callbacks.credentials, remote->callbacks.payload, direction, flags)) != 0)
+ goto on_error;
+
remote->transport = t;
+ return 0;
+
+on_error:
+ t->free(t);
+
+ if (t == remote->transport)
+ remote->transport = NULL;
+
+ return error;
+}
+
+int git_remote_ls(const git_remote_head ***out, size_t *size, git_remote *remote)
+{
+ assert(remote);
+
+ return remote->transport->ls(out, size, remote->transport);
+}
+
+int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_url)
+{
+ git_config *cfg;
+ const git_config_entry *ce;
+ const char *val = NULL;
+ int error;
+
+ assert(remote);
+
+ if (!proxy_url || !remote->repo)
+ return -1;
+
+ *proxy_url = NULL;
+
+ if ((error = git_repository_config__weakptr(&cfg, remote->repo)) < 0)
+ return error;
+
+ /* Go through the possible sources for proxy configuration, from most specific
+ * to least specific. */
+
+ /* remote.<name>.proxy config setting */
+ if (remote->name && remote->name[0]) {
+ git_buf buf = GIT_BUF_INIT;
+
+ if ((error = git_buf_printf(&buf, "remote.%s.proxy", remote->name)) < 0)
+ return error;
+
+ error = git_config__lookup_entry(&ce, cfg, git_buf_cstr(&buf), false);
+ git_buf_free(&buf);
+
+ if (error < 0)
+ return error;
+
+ if (ce && ce->value) {
+ val = ce->value;
+ goto found;
+ }
+ }
+
+ /* http.proxy config setting */
+ if ((error = git_config__lookup_entry(&ce, cfg, "http.proxy", false)) < 0)
+ return error;
+ if (ce && ce->value) {
+ val = ce->value;
+ goto found;
+ }
+
+ /* HTTP_PROXY / HTTPS_PROXY environment variables */
+ val = use_ssl ? getenv("HTTPS_PROXY") : getenv("HTTP_PROXY");
+
+found:
+ if (val && val[0]) {
+ *proxy_url = git__strdup(val);
+ GITERR_CHECK_ALLOC(*proxy_url);
+ }
+
+ return 0;
+}
+
+/* DWIM `refspecs` based on `refs` and append the output to `out` */
+static int dwim_refspecs(git_vector *out, git_vector *refspecs, git_vector *refs)
+{
+ size_t i;
+ git_refspec *spec;
+
+ git_vector_foreach(refspecs, i, spec) {
+ if (git_refspec__dwim_one(out, spec, refs) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+static void free_refspecs(git_vector *vec)
+{
+ size_t i;
+ git_refspec *spec;
+
+ git_vector_foreach(vec, i, spec) {
+ git_refspec__free(spec);
+ git__free(spec);
+ }
+
+ git_vector_clear(vec);
+}
+
+static int remote_head_cmp(const void *_a, const void *_b)
+{
+ const git_remote_head *a = (git_remote_head *) _a;
+ const git_remote_head *b = (git_remote_head *) _b;
+
+ return git__strcmp_cb(a->name, b->name);
+}
+
+static int ls_to_vector(git_vector *out, git_remote *remote)
+{
+ git_remote_head **heads;
+ size_t heads_len, i;
+
+ if (git_remote_ls((const git_remote_head ***)&heads, &heads_len, remote) < 0)
+ return -1;
+
+ if (git_vector_init(out, heads_len, remote_head_cmp) < 0)
+ return -1;
+
+ for (i = 0; i < heads_len; i++) {
+ if (git_vector_insert(out, heads[i]) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+int git_remote_download(git_remote *remote)
+{
+ int error;
+ git_vector refs;
+
+ assert(remote);
+
+ if (ls_to_vector(&refs, remote) < 0)
+ return -1;
+
+ free_refspecs(&remote->active_refspecs);
+
+ error = dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &refs);
+ git_vector_free(&refs);
+
+ if (error < 0)
+ return error;
+
+ if ((error = git_fetch_negotiate(remote)) < 0)
+ return error;
+
+ return git_fetch_download_pack(remote);
+}
+
+int git_remote_fetch(
+ git_remote *remote,
+ const git_signature *signature,
+ const char *reflog_message)
+{
+ int error;
+ git_buf reflog_msg_buf = GIT_BUF_INIT;
+
+ /* Connect and download everything */
+ if ((error = git_remote_connect(remote, GIT_DIRECTION_FETCH)) != 0)
+ return error;
+
+ error = git_remote_download(remote);
+
+ /* We don't need to be connected anymore */
+ git_remote_disconnect(remote);
+
+ /* If the download failed, return the error */
+ if (error != 0)
+ return error;
+
+ /* Default reflog message */
+ if (reflog_message)
+ git_buf_sets(&reflog_msg_buf, reflog_message);
+ else {
+ git_buf_printf(&reflog_msg_buf, "fetch %s",
+ remote->name ? remote->name : remote->url);
+ }
+
+ /* Create "remote/foo" branches for all remote branches */
+ error = git_remote_update_tips(remote, signature, git_buf_cstr(&reflog_msg_buf));
+ git_buf_free(&reflog_msg_buf);
+ return error;
+}
+
+static int remote_head_for_fetchspec_src(git_remote_head **out, git_vector *update_heads, const char *fetchspec_src)
+{
+ unsigned int i;
+ git_remote_head *remote_ref;
+
+ assert(update_heads && fetchspec_src);
+
+ *out = NULL;
+
+ git_vector_foreach(update_heads, i, remote_ref) {
+ if (strcmp(remote_ref->name, fetchspec_src) == 0) {
+ *out = remote_ref;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int remote_head_for_ref(git_remote_head **out, git_refspec *spec, git_vector *update_heads, git_reference *ref)
+{
+ git_reference *resolved_ref = NULL;
+ git_buf remote_name = GIT_BUF_INIT;
+ git_buf upstream_name = GIT_BUF_INIT;
+ git_repository *repo;
+ const char *ref_name;
+ int error = 0;
+
+ assert(out && spec && ref);
+
+ *out = NULL;
+
+ repo = git_reference_owner(ref);
+
+ error = git_reference_resolve(&resolved_ref, ref);
+
+ /* If we're in an unborn branch, let's pretend nothing happened */
+ if (error == GIT_ENOTFOUND && git_reference_type(ref) == GIT_REF_SYMBOLIC) {
+ ref_name = git_reference_symbolic_target(ref);
+ error = 0;
+ } else {
+ ref_name = git_reference_name(resolved_ref);
+ }
+
+ if ((!git_reference__is_branch(ref_name)) ||
+ (error = git_branch_upstream_name(&upstream_name, repo, ref_name)) < 0 ||
+ (error = git_refspec_rtransform(&remote_name, spec, upstream_name.ptr)) < 0) {
+ /* Not an error if there is no upstream */
+ if (error == GIT_ENOTFOUND)
+ error = 0;
+
+ goto cleanup;
+ }
+
+ error = remote_head_for_fetchspec_src(out, update_heads, git_buf_cstr(&remote_name));
+
cleanup:
- if (error < GIT_SUCCESS)
- git_transport_free(t);
+ git_reference_free(resolved_ref);
+ git_buf_free(&remote_name);
+ git_buf_free(&upstream_name);
+ return error;
+}
+
+static int git_remote_write_fetchhead(git_remote *remote, git_refspec *spec, git_vector *update_heads)
+{
+ git_reference *head_ref = NULL;
+ git_fetchhead_ref *fetchhead_ref;
+ git_remote_head *remote_ref, *merge_remote_ref;
+ git_vector fetchhead_refs;
+ bool include_all_fetchheads;
+ unsigned int i = 0;
+ int error = 0;
+
+ assert(remote);
+
+ /* no heads, nothing to do */
+ if (update_heads->length == 0)
+ return 0;
+
+ 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, spec, update_heads, head_ref)) < 0)
+ goto cleanup;
+ } else {
+ /* If we're fetching a single refspec, that's the only thing that should be in FETCH_HEAD. */
+ if ((error = remote_head_for_fetchspec_src(&merge_remote_ref, update_heads, git_refspec_src(spec))) < 0)
+ goto cleanup;
+ }
+
+ /* Create the FETCH_HEAD file */
+ git_vector_foreach(update_heads, i, remote_ref) {
+ int merge_this_fetchhead = (merge_remote_ref == remote_ref);
+
+ if (!include_all_fetchheads &&
+ !git_refspec_src_matches(spec, remote_ref->name) &&
+ !merge_this_fetchhead)
+ continue;
+
+ if (git_fetchhead_ref_create(&fetchhead_ref,
+ &remote_ref->oid,
+ merge_this_fetchhead,
+ remote_ref->name,
+ git_remote_url(remote)) < 0)
+ goto cleanup;
+
+ if (git_vector_insert(&fetchhead_refs, fetchhead_ref) < 0)
+ goto cleanup;
+ }
+
+ git_fetchhead_write(remote->repo, &fetchhead_refs);
+
+cleanup:
+ for (i = 0; i < fetchhead_refs.length; ++i)
+ git_fetchhead_ref_free(fetchhead_refs.contents[i]);
+
+ git_vector_free(&fetchhead_refs);
+ git_reference_free(head_ref);
return error;
}
-int git_remote_ls(git_remote *remote, git_headarray *refs)
+static int update_tips_for_spec(
+ git_remote *remote,
+ git_refspec *spec,
+ git_vector *refs,
+ const git_signature *signature,
+ const char *log_message)
{
- return git_transport_ls(remote->transport, refs);
+ int error = 0, autotag;
+ unsigned int i = 0;
+ git_buf refname = GIT_BUF_INIT;
+ git_oid old;
+ git_odb *odb;
+ git_remote_head *head;
+ git_reference *ref;
+ git_refspec tagspec;
+ git_vector update_heads;
+
+ assert(remote);
+
+ if (git_repository_odb__weakptr(&odb, remote->repo) < 0)
+ return -1;
+
+ if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0)
+ return -1;
+
+ /* Make a copy of the transport's refs */
+ if (git_vector_init(&update_heads, 16, NULL) < 0)
+ return -1;
+
+ for (; i < refs->length; ++i) {
+ head = git_vector_get(refs, i);
+ autotag = 0;
+
+ /* Ignore malformed ref names (which also saves us from tag^{} */
+ if (!git_reference_is_valid_name(head->name))
+ continue;
+
+ if (git_refspec_src_matches(spec, head->name) && spec->dst) {
+ if (git_refspec_transform(&refname, spec, head->name) < 0)
+ goto on_error;
+ } else if (remote->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_NONE) {
+
+ if (remote->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_ALL)
+ autotag = 1;
+
+ if (!git_refspec_src_matches(&tagspec, head->name))
+ continue;
+
+ git_buf_clear(&refname);
+ if (git_buf_puts(&refname, head->name) < 0)
+ goto on_error;
+ } else {
+ continue;
+ }
+
+ if (autotag && !git_odb_exists(odb, &head->oid))
+ continue;
+
+ if (git_vector_insert(&update_heads, head) < 0)
+ goto on_error;
+
+ error = git_reference_name_to_id(&old, remote->repo, refname.ptr);
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto on_error;
+
+ if (error == GIT_ENOTFOUND)
+ memset(&old, 0, GIT_OID_RAWSZ);
+
+ if (!git_oid__cmp(&old, &head->oid))
+ continue;
+
+ /* In autotag mode, don't overwrite any locally-existing tags */
+ error = git_reference_create(&ref, remote->repo, refname.ptr, &head->oid, !autotag,
+ signature, log_message);
+ if (error < 0 && error != GIT_EEXISTS)
+ goto on_error;
+
+ git_reference_free(ref);
+
+ if (remote->callbacks.update_tips != NULL) {
+ if (remote->callbacks.update_tips(refname.ptr, &old, &head->oid, remote->callbacks.payload) < 0)
+ goto on_error;
+ }
+ }
+
+ if (git_remote_update_fetchhead(remote) &&
+ (error = git_remote_write_fetchhead(remote, spec, &update_heads)) < 0)
+ goto on_error;
+
+ git_vector_free(&update_heads);
+ git_refspec__free(&tagspec);
+ git_buf_free(&refname);
+ return 0;
+
+on_error:
+ git_vector_free(&update_heads);
+ git_refspec__free(&tagspec);
+ git_buf_free(&refname);
+ return -1;
+
+}
+
+int git_remote_update_tips(
+ git_remote *remote,
+ const git_signature *signature,
+ const char *reflog_message)
+{
+ git_refspec *spec, tagspec;
+ git_vector refs;
+ int error;
+ size_t i;
+
+ if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0)
+ return -1;
+
+
+ if ((error = ls_to_vector(&refs, remote)) < 0)
+ goto out;
+
+ if (remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_ALL) {
+ error = update_tips_for_spec(remote, &tagspec, &refs, signature, reflog_message);
+ goto out;
+ }
+
+ git_vector_foreach(&remote->active_refspecs, i, spec) {
+ if (spec->push)
+ continue;
+
+ if ((error = update_tips_for_spec(remote, spec, &refs, signature, reflog_message)) < 0)
+ goto out;
+ }
+
+out:
+ git_vector_free(&refs);
+ git_refspec__free(&tagspec);
+ return error;
+}
+
+int git_remote_connected(const git_remote *remote)
+{
+ assert(remote);
+
+ if (!remote->transport || !remote->transport->is_connected)
+ return 0;
+
+ /* Ask the transport if it's connected. */
+ return remote->transport->is_connected(remote->transport);
+}
+
+void git_remote_stop(git_remote *remote)
+{
+ assert(remote);
+
+ if (remote->transport && remote->transport->cancel)
+ remote->transport->cancel(remote->transport);
+}
+
+void git_remote_disconnect(git_remote *remote)
+{
+ assert(remote);
+
+ if (git_remote_connected(remote))
+ remote->transport->close(remote->transport);
}
void git_remote_free(git_remote *remote)
{
- free(remote->fetch.src);
- free(remote->fetch.dst);
- free(remote->push.src);
- free(remote->push.dst);
- free(remote->url);
- free(remote->name);
+ if (remote == NULL)
+ return;
+
if (remote->transport != NULL) {
- if (remote->transport->connected)
- git_transport_close(remote->transport);
- git_transport_free(remote->transport);
+ git_remote_disconnect(remote);
+
+ remote->transport->free(remote->transport);
+ remote->transport = NULL;
}
- free(remote);
+
+ git_vector_free(&remote->refs);
+
+ free_refspecs(&remote->refspecs);
+ git_vector_free(&remote->refspecs);
+
+ free_refspecs(&remote->active_refspecs);
+ git_vector_free(&remote->active_refspecs);
+
+ git__free(remote->url);
+ git__free(remote->pushurl);
+ git__free(remote->name);
+ git__free(remote);
+}
+
+static int remote_list_cb(const git_config_entry *entry, void *payload)
+{
+ git_vector *list = payload;
+ const char *name = entry->name + strlen("remote.");
+ size_t namelen = strlen(name);
+ char *remote_name;
+
+ /* we know name matches "remote.<stuff>.(push)?url" */
+
+ if (!strcmp(&name[namelen - 4], ".url"))
+ remote_name = git__strndup(name, namelen - 4); /* strip ".url" */
+ else
+ remote_name = git__strndup(name, namelen - 8); /* strip ".pushurl" */
+ GITERR_CHECK_ALLOC(remote_name);
+
+ return git_vector_insert(list, remote_name);
+}
+
+int git_remote_list(git_strarray *remotes_list, git_repository *repo)
+{
+ int error;
+ git_config *cfg;
+ git_vector list = GIT_VECTOR_INIT;
+
+ if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
+ return error;
+
+ if ((error = git_vector_init(&list, 4, git__strcmp_cb)) < 0)
+ return error;
+
+ error = git_config_foreach_match(
+ cfg, "^remote\\..*\\.(push)?url$", remote_list_cb, &list);
+
+ if (error < 0) {
+ git_vector_free_deep(&list);
+ return error;
+ }
+
+ git_vector_uniq(&list, git__free);
+
+ remotes_list->strings =
+ (char **)git_vector_detach(&remotes_list->count, NULL, &list);
+
+ return 0;
+}
+
+void git_remote_check_cert(git_remote *remote, int check)
+{
+ assert(remote);
+
+ remote->check_cert = check;
+}
+
+int git_remote_set_callbacks(git_remote *remote, const git_remote_callbacks *callbacks)
+{
+ assert(remote && callbacks);
+
+ GITERR_CHECK_VERSION(callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks");
+
+ memcpy(&remote->callbacks, callbacks, sizeof(git_remote_callbacks));
+
+ if (remote->transport && remote->transport->set_callbacks)
+ return remote->transport->set_callbacks(remote->transport,
+ remote->callbacks.sideband_progress,
+ NULL,
+ remote->callbacks.payload);
+
+ return 0;
+}
+
+const git_remote_callbacks *git_remote_get_callbacks(git_remote *remote)
+{
+ assert(remote);
+
+ return &remote->callbacks;
+}
+
+int git_remote_set_transport(git_remote *remote, git_transport *transport)
+{
+ assert(remote && transport);
+
+ GITERR_CHECK_VERSION(transport, GIT_TRANSPORT_VERSION, "git_transport");
+
+ if (remote->transport) {
+ giterr_set(GITERR_NET, "A transport is already bound to this remote");
+ return -1;
+ }
+
+ remote->transport = transport;
+ return 0;
+}
+
+const git_transfer_progress* git_remote_stats(git_remote *remote)
+{
+ assert(remote);
+ return &remote->stats;
+}
+
+git_remote_autotag_option_t git_remote_autotag(const git_remote *remote)
+{
+ return remote->download_tags;
+}
+
+void git_remote_set_autotag(git_remote *remote, git_remote_autotag_option_t value)
+{
+ remote->download_tags = value;
+}
+
+static int rename_remote_config_section(
+ git_repository *repo,
+ const char *old_name,
+ const char *new_name)
+{
+ git_buf old_section_name = GIT_BUF_INIT,
+ new_section_name = GIT_BUF_INIT;
+ int error = -1;
+
+ if (git_buf_printf(&old_section_name, "remote.%s", old_name) < 0)
+ goto cleanup;
+
+ if (new_name &&
+ (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),
+ new_name ? git_buf_cstr(&new_section_name) : NULL);
+
+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)
+{
+ int error;
+ struct update_data data = { NULL };
+
+ if ((error = git_repository_config__weakptr(&data.config, repo)) < 0)
+ return error;
+
+ data.old_remote_name = old_name;
+ data.new_remote_name = new_name;
+
+ return git_config_foreach_match(
+ data.config, "branch\\..+\\.remote", update_config_entries_cb, &data);
+}
+
+static int rename_one_remote_reference(
+ git_reference *reference_in,
+ const char *old_remote_name,
+ const char *new_remote_name)
+{
+ int error;
+ git_reference *ref = NULL, *dummy = NULL;
+ git_buf namespace = GIT_BUF_INIT, old_namespace = GIT_BUF_INIT;
+ git_buf new_name = GIT_BUF_INIT;
+ git_buf log_message = GIT_BUF_INIT;
+ size_t pfx_len;
+ const char *target;
+
+ if ((error = git_buf_printf(&namespace, GIT_REFS_REMOTES_DIR "%s/", new_remote_name)) < 0)
+ return error;
+
+ pfx_len = strlen(GIT_REFS_REMOTES_DIR) + strlen(old_remote_name) + 1;
+ git_buf_puts(&new_name, namespace.ptr);
+ if ((error = git_buf_puts(&new_name, git_reference_name(reference_in) + pfx_len)) < 0)
+ goto cleanup;
+
+ if ((error = git_buf_printf(&log_message,
+ "renamed remote %s to %s",
+ old_remote_name, new_remote_name)) < 0)
+ goto cleanup;
+
+ if ((error = git_reference_rename(&ref, reference_in, git_buf_cstr(&new_name), 1,
+ NULL, git_buf_cstr(&log_message))) < 0)
+ goto cleanup;
+
+ if (git_reference_type(ref) != GIT_REF_SYMBOLIC)
+ goto cleanup;
+
+ /* Handle refs like origin/HEAD -> origin/master */
+ target = git_reference_symbolic_target(ref);
+ if ((error = git_buf_printf(&old_namespace, GIT_REFS_REMOTES_DIR "%s/", old_remote_name)) < 0)
+ goto cleanup;
+
+ if (git__prefixcmp(target, old_namespace.ptr))
+ goto cleanup;
+
+ git_buf_clear(&new_name);
+ git_buf_puts(&new_name, namespace.ptr);
+ if ((error = git_buf_puts(&new_name, target + pfx_len)) < 0)
+ goto cleanup;
+
+ error = git_reference_symbolic_set_target(&dummy, ref, git_buf_cstr(&new_name),
+ NULL, git_buf_cstr(&log_message));
+
+ git_reference_free(dummy);
+
+cleanup:
+ git_reference_free(reference_in);
+ git_reference_free(ref);
+ git_buf_free(&namespace);
+ git_buf_free(&old_namespace);
+ git_buf_free(&new_name);
+ git_buf_free(&log_message);
+ return error;
+}
+
+static int rename_remote_references(
+ git_repository *repo,
+ const char *old_name,
+ const char *new_name)
+{
+ int error;
+ git_buf buf = GIT_BUF_INIT;
+ git_reference *ref;
+ git_reference_iterator *iter;
+
+ if ((error = git_buf_printf(&buf, GIT_REFS_REMOTES_DIR "%s/*", old_name)) < 0)
+ return error;
+
+ error = git_reference_iterator_glob_new(&iter, repo, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+
+ if (error < 0)
+ return error;
+
+ while ((error = git_reference_next(&ref, iter)) == 0) {
+ if ((error = rename_one_remote_reference(ref, old_name, new_name)) < 0)
+ break;
+ }
+
+ git_reference_iterator_free(iter);
+
+ return (error == GIT_ITEROVER) ? 0 : error;
+}
+
+static int rename_fetch_refspecs(git_vector *problems, git_remote *remote, const char *new_name)
+{
+ git_config *config;
+ git_buf base = GIT_BUF_INIT, var = GIT_BUF_INIT, val = GIT_BUF_INIT;
+ const git_refspec *spec;
+ size_t i;
+ int error = 0;
+
+ if ((error = git_repository_config__weakptr(&config, remote->repo)) < 0)
+ return error;
+
+ if ((error = git_vector_init(problems, 1, NULL)) < 0)
+ return error;
+
+ if ((error = git_buf_printf(
+ &base, "+refs/heads/*:refs/remotes/%s/*", remote->name)) < 0)
+ return error;
+
+ git_vector_foreach(&remote->refspecs, i, spec) {
+ if (spec->push)
+ continue;
+
+ /* Does the dst part of the refspec follow the expected format? */
+ if (strcmp(git_buf_cstr(&base), spec->string)) {
+ char *dup;
+
+ dup = git__strdup(spec->string);
+ GITERR_CHECK_ALLOC(dup);
+
+ if ((error = git_vector_insert(problems, dup)) < 0)
+ break;
+
+ continue;
+ }
+
+ /* If we do want to move it to the new section */
+
+ git_buf_clear(&val);
+ git_buf_clear(&var);
+
+ if (git_buf_printf(
+ &val, "+refs/heads/*:refs/remotes/%s/*", new_name) < 0 ||
+ git_buf_printf(&var, "remote.%s.fetch", new_name) < 0)
+ {
+ error = -1;
+ break;
+ }
+
+ if ((error = git_config_set_string(
+ config, git_buf_cstr(&var), git_buf_cstr(&val))) < 0)
+ break;
+ }
+
+ git_buf_free(&base);
+ git_buf_free(&var);
+ git_buf_free(&val);
+
+ if (error < 0) {
+ char *str;
+ git_vector_foreach(problems, i, str)
+ git__free(str);
+
+ git_vector_free(problems);
+ }
+
+ return error;
+}
+
+int git_remote_rename(git_strarray *out, git_remote *remote, const char *new_name)
+{
+ int error;
+ git_vector problem_refspecs;
+ char *tmp, *dup;
+
+ assert(out && remote && new_name);
+
+ if (!remote->name) {
+ giterr_set(GITERR_INVALID, "Can't rename an anonymous remote.");
+ return GIT_EINVALIDSPEC;
+ }
+
+ if ((error = ensure_remote_name_is_valid(new_name)) < 0)
+ return error;
+
+ if ((error = ensure_remote_doesnot_exist(remote->repo, new_name)) < 0)
+ return error;
+
+ 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(&problem_refspecs, remote, new_name)) < 0)
+ return error;
+
+ out->count = problem_refspecs.length;
+ out->strings = (char **) problem_refspecs.contents;
+
+ dup = git__strdup(new_name);
+ GITERR_CHECK_ALLOC(dup);
+
+ tmp = remote->name;
+ remote->name = dup;
+ git__free(tmp);
+
+ return 0;
+}
+
+int git_remote_update_fetchhead(git_remote *remote)
+{
+ return (remote->update_fetchhead != 0);
+}
+
+void git_remote_set_update_fetchhead(git_remote *remote, int value)
+{
+ remote->update_fetchhead = (value != 0);
+}
+
+int git_remote_is_valid_name(
+ const char *remote_name)
+{
+ git_buf buf = GIT_BUF_INIT;
+ git_refspec refspec;
+ int error = -1;
+
+ if (!remote_name || *remote_name == '\0')
+ return 0;
+
+ git_buf_printf(&buf, "refs/heads/test:refs/remotes/%s/test", remote_name);
+ error = git_refspec__parse(&refspec, git_buf_cstr(&buf), true);
+
+ git_buf_free(&buf);
+ git_refspec__free(&refspec);
+
+ giterr_clear();
+ return error == 0;
+}
+
+git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname)
+{
+ git_refspec *spec;
+ size_t i;
+
+ git_vector_foreach(&remote->active_refspecs, i, spec) {
+ if (spec->push)
+ continue;
+
+ if (git_refspec_src_matches(spec, refname))
+ return spec;
+ }
+
+ return NULL;
+}
+
+git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *refname)
+{
+ git_refspec *spec;
+ size_t i;
+
+ git_vector_foreach(&remote->active_refspecs, i, spec) {
+ if (spec->push)
+ continue;
+
+ if (git_refspec_dst_matches(spec, refname))
+ return spec;
+ }
+
+ return NULL;
+}
+
+void git_remote_clear_refspecs(git_remote *remote)
+{
+ git_refspec *spec;
+ size_t i;
+
+ git_vector_foreach(&remote->refspecs, i, spec) {
+ git_refspec__free(spec);
+ git__free(spec);
+ }
+ git_vector_clear(&remote->refspecs);
+}
+
+static int add_and_dwim(git_remote *remote, const char *str, int push)
+{
+ git_refspec *spec;
+ git_vector *vec;
+
+ if (add_refspec(remote, str, !push) < 0)
+ return -1;
+
+ vec = &remote->refspecs;
+ spec = git_vector_get(vec, vec->length - 1);
+ return git_refspec__dwim_one(&remote->active_refspecs, spec, &remote->refs);
+}
+
+int git_remote_add_fetch(git_remote *remote, const char *refspec)
+{
+ return add_and_dwim(remote, refspec, false);
+}
+
+int git_remote_add_push(git_remote *remote, const char *refspec)
+{
+ return add_and_dwim(remote, refspec, true);
+}
+
+static int set_refspecs(git_remote *remote, git_strarray *array, int push)
+{
+ git_vector *vec = &remote->refspecs;
+ git_refspec *spec;
+ size_t i;
+
+ /* Start by removing any refspecs of the same type */
+ for (i = 0; i < vec->length; i++) {
+ spec = git_vector_get(vec, i);
+ if (spec->push != push)
+ continue;
+
+ git_refspec__free(spec);
+ git__free(spec);
+ git_vector_remove(vec, i);
+ i--;
+ }
+
+ /* And now we add the new ones */
+
+ for (i = 0; i < array->count; i++) {
+ if (add_refspec(remote, array->strings[i], !push) < 0)
+ return -1;
+ }
+
+ free_refspecs(&remote->active_refspecs);
+ git_vector_clear(&remote->active_refspecs);
+
+ return dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &remote->refs);
+}
+
+int git_remote_set_fetch_refspecs(git_remote *remote, git_strarray *array)
+{
+ return set_refspecs(remote, array, false);
+}
+
+int git_remote_set_push_refspecs(git_remote *remote, git_strarray *array)
+{
+ return set_refspecs(remote, array, true);
+}
+
+static int copy_refspecs(git_strarray *array, const git_remote *remote, unsigned int push)
+{
+ size_t i;
+ git_vector refspecs;
+ git_refspec *spec;
+ char *dup;
+
+ if (git_vector_init(&refspecs, remote->refspecs.length, NULL) < 0)
+ return -1;
+
+ git_vector_foreach(&remote->refspecs, i, spec) {
+ if (spec->push != push)
+ continue;
+
+ if ((dup = git__strdup(spec->string)) == NULL)
+ goto on_error;
+
+ if (git_vector_insert(&refspecs, dup) < 0) {
+ git__free(dup);
+ goto on_error;
+ }
+ }
+
+ array->strings = (char **)refspecs.contents;
+ array->count = refspecs.length;
+
+ return 0;
+
+on_error:
+ git_vector_free_deep(&refspecs);
+
+ return -1;
+}
+
+int git_remote_get_fetch_refspecs(git_strarray *array, const git_remote *remote)
+{
+ return copy_refspecs(array, remote, false);
+}
+
+int git_remote_get_push_refspecs(git_strarray *array, const git_remote *remote)
+{
+ return copy_refspecs(array, remote, true);
+}
+
+size_t git_remote_refspec_count(const git_remote *remote)
+{
+ return remote->refspecs.length;
+}
+
+const git_refspec *git_remote_get_refspec(const git_remote *remote, size_t n)
+{
+ return git_vector_get(&remote->refspecs, n);
+}
+
+int git_remote_init_callbacks(git_remote_callbacks *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_remote_callbacks, GIT_REMOTE_CALLBACKS_INIT);
+ return 0;
+}
+
+/* asserts a branch.<foo>.remote format */
+static const char *name_offset(size_t *len_out, const char *name)
+{
+ size_t prefix_len;
+ const char *dot;
+
+ prefix_len = strlen("remote.");
+ dot = strchr(name + prefix_len, '.');
+
+ assert(dot);
+
+ *len_out = dot - name - prefix_len;
+ return name + prefix_len;
+}
+
+static int remove_branch_config_related_entries(
+ git_repository *repo,
+ const char *remote_name)
+{
+ int error;
+ git_config *config;
+ git_config_entry *entry;
+ git_config_iterator *iter;
+ git_buf buf = GIT_BUF_INIT;
+
+ if ((error = git_repository_config__weakptr(&config, repo)) < 0)
+ return error;
+
+ if ((error = git_config_iterator_glob_new(&iter, config, "branch\\..+\\.remote")) < 0)
+ return error;
+
+ /* find any branches with us as upstream and remove that config */
+ while ((error = git_config_next(&entry, iter)) == 0) {
+ const char *branch;
+ size_t branch_len;
+
+ if (strcmp(remote_name, entry->value))
+ continue;
+
+ branch = name_offset(&branch_len, entry->name);
+
+ git_buf_clear(&buf);
+ if (git_buf_printf(&buf, "branch.%.*s.merge", (int)branch_len, branch) < 0)
+ break;
+
+ if ((error = git_config_delete_entry(config, git_buf_cstr(&buf))) < 0)
+ break;
+
+ git_buf_clear(&buf);
+ if (git_buf_printf(&buf, "branch.%.*s.remote", (int)branch_len, branch) < 0)
+ break;
+
+ if ((error = git_config_delete_entry(config, git_buf_cstr(&buf))) < 0)
+ break;
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ git_buf_free(&buf);
+ git_config_iterator_free(iter);
+ return error;
+}
+
+static int remove_refs(git_repository *repo, const git_refspec *spec)
+{
+ git_reference_iterator *iter = NULL;
+ git_vector refs;
+ const char *name;
+ char *dup;
+ int error;
+ size_t i;
+
+ if ((error = git_vector_init(&refs, 8, NULL)) < 0)
+ return error;
+
+ if ((error = git_reference_iterator_new(&iter, repo)) < 0)
+ goto cleanup;
+
+ while ((error = git_reference_next_name(&name, iter)) == 0) {
+ if (!git_refspec_dst_matches(spec, name))
+ continue;
+
+ dup = git__strdup(name);
+ if (!dup) {
+ error = -1;
+ goto cleanup;
+ }
+
+ if ((error = git_vector_insert(&refs, dup)) < 0)
+ goto cleanup;
+ }
+ if (error == GIT_ITEROVER)
+ error = 0;
+ if (error < 0)
+ goto cleanup;
+
+ git_vector_foreach(&refs, i, name) {
+ if ((error = git_reference_remove(repo, name)) < 0)
+ break;
+ }
+
+cleanup:
+ git_reference_iterator_free(iter);
+ git_vector_foreach(&refs, i, dup) {
+ git__free(dup);
+ }
+ git_vector_free(&refs);
+ return error;
+}
+
+static int remove_remote_tracking(git_repository *repo, const char *remote_name)
+{
+ git_remote *remote;
+ int error;
+ size_t i, count;
+
+ /* we want to use what's on the config, regardless of changes to the instance in memory */
+ if ((error = git_remote_load(&remote, repo, remote_name)) < 0)
+ return error;
+
+ count = git_remote_refspec_count(remote);
+ for (i = 0; i < count; i++) {
+ const git_refspec *refspec = git_remote_get_refspec(remote, i);
+
+ /* shouldn't ever actually happen */
+ if (refspec == NULL)
+ continue;
+
+ if ((error = remove_refs(repo, refspec)) < 0)
+ break;
+ }
+
+ git_remote_free(remote);
+ return error;
+}
+
+int git_remote_delete(git_remote *remote)
+{
+ int error;
+ git_repository *repo;
+
+ assert(remote);
+
+ if (!remote->name) {
+ giterr_set(GITERR_INVALID, "Can't delete an anonymous remote.");
+ return -1;
+ }
+
+ repo = git_remote_owner(remote);
+
+ if ((error = remove_branch_config_related_entries(repo,
+ git_remote_name(remote))) < 0)
+ return error;
+
+ if ((error = remove_remote_tracking(repo, git_remote_name(remote))) < 0)
+ return error;
+
+ if ((error = rename_remote_config_section(
+ repo, git_remote_name(remote), NULL)) < 0)
+ return error;
+
+ return 0;
+}
+
+int git_remote_default_branch(git_buf *out, git_remote *remote)
+{
+ const git_remote_head **heads;
+ const git_remote_head *guess = NULL;
+ const git_oid *head_id;
+ size_t heads_len, i;
+ int error;
+
+ if ((error = git_remote_ls(&heads, &heads_len, remote)) < 0)
+ return error;
+
+ if (heads_len == 0)
+ return GIT_ENOTFOUND;
+
+ git_buf_sanitize(out);
+ /* the first one must be HEAD so if that has the symref info, we're done */
+ if (heads[0]->symref_target)
+ return git_buf_puts(out, heads[0]->symref_target);
+
+ /*
+ * If there's no symref information, we have to look over them
+ * and guess. We return the first match unless the master
+ * branch is a candidate. Then we return the master branch.
+ */
+ head_id = &heads[0]->oid;
+
+ for (i = 1; i < heads_len; i++) {
+ if (git_oid_cmp(head_id, &heads[i]->oid))
+ continue;
+
+ if (!guess) {
+ guess = heads[i];
+ continue;
+ }
+
+ if (!git__strcmp(GIT_REFS_HEADS_MASTER_FILE, heads[i]->name)) {
+ guess = heads[i];
+ break;
+ }
+ }
+
+ if (!guess)
+ return GIT_ENOTFOUND;
+
+ return git_buf_puts(out, guess->name);
}
diff --git a/src/remote.h b/src/remote.h
index fdd6cd569..4164a14b3 100644
--- a/src/remote.h
+++ b/src/remote.h
@@ -1,16 +1,41 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
#ifndef INCLUDE_remote_h__
#define INCLUDE_remote_h__
-#include "remote.h"
+#include "git2/remote.h"
+#include "git2/transport.h"
+
#include "refspec.h"
-#include "transport.h"
+#include "vector.h"
+
+#define GIT_REMOTE_ORIGIN "origin"
struct git_remote {
char *name;
char *url;
- struct git_refspec fetch;
- struct git_refspec push;
+ char *pushurl;
+ git_vector refs;
+ git_vector refspecs;
+ git_vector active_refspecs;
git_transport *transport;
+ git_repository *repo;
+ git_remote_callbacks callbacks;
+ git_transfer_progress stats;
+ unsigned int need_pack;
+ git_remote_autotag_option_t download_tags;
+ int check_cert;
+ int update_fetchhead;
};
+const char* git_remote__urlfordirection(struct git_remote *remote, int direction);
+int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_url);
+
+git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname);
+git_refspec *git_remote__matching_dst_refspec(git_remote *remote, const char *refname);
+
#endif
diff --git a/src/repo_template.h b/src/repo_template.h
new file mode 100644
index 000000000..099279aa7
--- /dev/null
+++ b/src/repo_template.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_repo_template_h__
+#define INCLUDE_repo_template_h__
+
+#define GIT_OBJECTS_INFO_DIR GIT_OBJECTS_DIR "info/"
+#define GIT_OBJECTS_PACK_DIR GIT_OBJECTS_DIR "pack/"
+
+#define GIT_HOOKS_DIR "hooks/"
+#define GIT_HOOKS_DIR_MODE 0777
+
+#define GIT_HOOKS_README_FILE GIT_HOOKS_DIR "README.sample"
+#define GIT_HOOKS_README_MODE 0777
+#define GIT_HOOKS_README_CONTENT \
+"#!/bin/sh\n"\
+"#\n"\
+"# Place appropriately named executable hook scripts into this directory\n"\
+"# to intercept various actions that git takes. See `git help hooks` for\n"\
+"# more information.\n"
+
+#define GIT_INFO_DIR "info/"
+#define GIT_INFO_DIR_MODE 0777
+
+#define GIT_INFO_EXCLUDE_FILE GIT_INFO_DIR "exclude"
+#define GIT_INFO_EXCLUDE_MODE 0666
+#define GIT_INFO_EXCLUDE_CONTENT \
+"# File patterns to ignore; see `git help ignore` for more information.\n"\
+"# Lines that start with '#' are comments.\n"
+
+#define GIT_DESC_FILE "description"
+#define GIT_DESC_MODE 0666
+#define GIT_DESC_CONTENT \
+"Unnamed repository; edit this file 'description' to name the repository.\n"
+
+typedef struct {
+ const char *path;
+ mode_t mode;
+ const char *content;
+} repo_template_item;
+
+static repo_template_item repo_template[] = {
+ { GIT_OBJECTS_INFO_DIR, GIT_OBJECT_DIR_MODE, NULL }, /* '/objects/info/' */
+ { GIT_OBJECTS_PACK_DIR, GIT_OBJECT_DIR_MODE, NULL }, /* '/objects/pack/' */
+ { GIT_REFS_HEADS_DIR, GIT_REFS_DIR_MODE, NULL }, /* '/refs/heads/' */
+ { GIT_REFS_TAGS_DIR, GIT_REFS_DIR_MODE, NULL }, /* '/refs/tags/' */
+ { GIT_HOOKS_DIR, GIT_HOOKS_DIR_MODE, NULL }, /* '/hooks/' */
+ { GIT_INFO_DIR, GIT_INFO_DIR_MODE, NULL }, /* '/info/' */
+ { GIT_DESC_FILE, GIT_DESC_MODE, GIT_DESC_CONTENT },
+ { GIT_HOOKS_README_FILE, GIT_HOOKS_README_MODE, GIT_HOOKS_README_CONTENT },
+ { GIT_INFO_EXCLUDE_FILE, GIT_INFO_EXCLUDE_MODE, GIT_INFO_EXCLUDE_CONTENT },
+ { NULL, 0, NULL }
+};
+
+#endif
diff --git a/src/repository.c b/src/repository.c
index cb62d9eb6..e8d50aed3 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -1,30 +1,14 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
*/
-#include <stdarg.h>
+#include <ctype.h>
#include "git2/object.h"
+#include "git2/refdb.h"
+#include "git2/sys/repository.h"
#include "common.h"
#include "repository.h"
@@ -32,757 +16,1647 @@
#include "tag.h"
#include "blob.h"
#include "fileops.h"
+#include "sysdir.h"
+#include "filebuf.h"
+#include "index.h"
#include "config.h"
#include "refs.h"
+#include "filter.h"
+#include "odb.h"
+#include "remote.h"
+#include "merge.h"
+#include "diff_driver.h"
-#define GIT_OBJECTS_INFO_DIR GIT_OBJECTS_DIR "info/"
-#define GIT_OBJECTS_PACK_DIR GIT_OBJECTS_DIR "pack/"
+#ifdef GIT_WIN32
+# include "win32/w32_util.h"
+#endif
-#define GIT_FILE_CONTENT_PREFIX "gitdir: "
+#define GIT_FILE_CONTENT_PREFIX "gitdir:"
#define GIT_BRANCH_MASTER "master"
+#define GIT_REPO_VERSION 0
+
+static void set_odb(git_repository *repo, git_odb *odb)
+{
+ if (odb) {
+ GIT_REFCOUNT_OWN(odb, repo);
+ GIT_REFCOUNT_INC(odb);
+ }
+
+ if ((odb = git__swap(repo->_odb, odb)) != NULL) {
+ GIT_REFCOUNT_OWN(odb, NULL);
+ git_odb_free(odb);
+ }
+}
+
+static void set_refdb(git_repository *repo, git_refdb *refdb)
+{
+ if (refdb) {
+ GIT_REFCOUNT_OWN(refdb, repo);
+ GIT_REFCOUNT_INC(refdb);
+ }
+
+ if ((refdb = git__swap(repo->_refdb, refdb)) != NULL) {
+ GIT_REFCOUNT_OWN(refdb, NULL);
+ git_refdb_free(refdb);
+ }
+}
+
+static void set_config(git_repository *repo, git_config *config)
+{
+ if (config) {
+ GIT_REFCOUNT_OWN(config, repo);
+ GIT_REFCOUNT_INC(config);
+ }
+
+ if ((config = git__swap(repo->_config, config)) != NULL) {
+ GIT_REFCOUNT_OWN(config, NULL);
+ git_config_free(config);
+ }
+
+ git_repository__cvar_cache_clear(repo);
+}
+
+static void set_index(git_repository *repo, git_index *index)
+{
+ if (index) {
+ GIT_REFCOUNT_OWN(index, repo);
+ GIT_REFCOUNT_INC(index);
+ }
+
+ if ((index = git__swap(repo->_index, index)) != NULL) {
+ GIT_REFCOUNT_OWN(index, NULL);
+ git_index_free(index);
+ }
+}
+
+void git_repository__cleanup(git_repository *repo)
+{
+ assert(repo);
+
+ git_cache_clear(&repo->objects);
+ git_attr_cache_flush(repo);
+ git_submodule_cache_free(repo);
+
+ set_config(repo, NULL);
+ set_index(repo, NULL);
+ set_odb(repo, NULL);
+ set_refdb(repo, NULL);
+}
+
+void git_repository_free(git_repository *repo)
+{
+ if (repo == NULL)
+ return;
+
+ git_repository__cleanup(repo);
+
+ git_cache_free(&repo->objects);
+
+ git_diff_driver_registry_free(repo->diff_drivers);
+ repo->diff_drivers = NULL;
+
+ git__free(repo->path_repository);
+ git__free(repo->workdir);
+ git__free(repo->namespace);
+
+ git__memzero(repo, sizeof(*repo));
+ git__free(repo);
+}
+
/*
* Git repository open methods
*
* Open a repository object from its path
*/
-static int assign_repository_dirs(
- git_repository *repo,
- const char *git_dir,
- const char *git_object_directory,
- const char *git_index_file,
- const char *git_work_tree)
+static bool valid_repository_path(git_buf *repository_path)
{
- char path_aux[GIT_PATH_MAX];
- int error = GIT_SUCCESS;
+ /* Check OBJECTS_DIR first, since it will generate the longest path name */
+ if (git_path_contains_dir(repository_path, GIT_OBJECTS_DIR) == false)
+ return false;
- assert(repo);
+ /* Ensure HEAD file exists */
+ if (git_path_contains_file(repository_path, GIT_HEAD_FILE) == false)
+ return false;
- if (git_dir == NULL)
- return git__throw(GIT_ENOTFOUND, "Failed to open repository. Git dir not found");
+ if (git_path_contains_dir(repository_path, GIT_REFS_DIR) == false)
+ return false;
- error = git_path_prettify_dir(path_aux, git_dir, NULL);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to open repository");
+ return true;
+}
- /* store GIT_DIR */
- repo->path_repository = git__strdup(path_aux);
- if (repo->path_repository == NULL)
- return GIT_ENOMEM;
+static git_repository *repository_alloc(void)
+{
+ git_repository *repo = git__calloc(1, sizeof(git_repository));
+ if (!repo)
+ return NULL;
- /* path to GIT_OBJECT_DIRECTORY */
- if (git_object_directory == NULL)
- git_path_join(path_aux, repo->path_repository, GIT_OBJECTS_DIR);
- else {
- error = git_path_prettify_dir(path_aux, git_object_directory, NULL);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to open repository");
+ if (git_cache_init(&repo->objects) < 0) {
+ git__free(repo);
+ return NULL;
}
- /* Store GIT_OBJECT_DIRECTORY */
- repo->path_odb = git__strdup(path_aux);
- if (repo->path_odb == NULL)
- return GIT_ENOMEM;
+ /* set all the entries in the cvar cache to `unset` */
+ git_repository__cvar_cache_clear(repo);
- /* path to GIT_WORK_TREE */
- if (git_work_tree == NULL)
- repo->is_bare = 1;
- else {
- error = git_path_prettify_dir(path_aux, git_work_tree, NULL);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to open repository");
-
- /* Store GIT_WORK_TREE */
- repo->path_workdir = git__strdup(path_aux);
- if (repo->path_workdir == NULL)
- return GIT_ENOMEM;
-
- /* Path to GIT_INDEX_FILE */
- if (git_index_file == NULL)
- git_path_join(path_aux, repo->path_repository, GIT_INDEX_FILE);
- else {
- error = git_path_prettify(path_aux, git_index_file, NULL);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to open repository");
- }
+ return repo;
+}
- /* store GIT_INDEX_FILE */
- repo->path_index = git__strdup(path_aux);
- if (repo->path_index == NULL)
- return GIT_ENOMEM;
- }
+int git_repository_new(git_repository **out)
+{
+ *out = repository_alloc();
+ return 0;
+}
- return GIT_SUCCESS;
+static int load_config_data(git_repository *repo, const git_config *config)
+{
+ int is_bare;
+
+ /* Try to figure out if it's bare, default to non-bare if it's not set */
+ if (git_config_get_bool(&is_bare, config, "core.bare") < 0)
+ repo->is_bare = 0;
+ else
+ repo->is_bare = is_bare;
+
+ return 0;
}
-static int check_repository_dirs(git_repository *repo)
+static int load_workdir(git_repository *repo, git_config *config, git_buf *parent_path)
{
- char path_aux[GIT_PATH_MAX];
+ int error;
+ const git_config_entry *ce;
+ git_buf worktree = GIT_BUF_INIT;
- if (git_futils_isdir(repo->path_repository) < GIT_SUCCESS)
- return git__throw(GIT_ENOTAREPO, "`%s` is not a folder", repo->path_repository);
+ if (repo->is_bare)
+ return 0;
- /* Ensure GIT_OBJECT_DIRECTORY exists */
- if (git_futils_isdir(repo->path_odb) < GIT_SUCCESS)
- return git__throw(GIT_ENOTAREPO, "`%s` does not exist", repo->path_odb);
+ if ((error = git_config__lookup_entry(
+ &ce, config, "core.worktree", false)) < 0)
+ return error;
- /* Ensure HEAD file exists */
- git_path_join(path_aux, repo->path_repository, GIT_HEAD_FILE);
- if (git_futils_isfile(path_aux) < 0)
- return git__throw(GIT_ENOTAREPO, "HEAD file is missing");
+ if (ce && ce->value) {
+ if ((error = git_path_prettify_dir(
+ &worktree, ce->value, repo->path_repository)) < 0)
+ return error;
+
+ repo->workdir = git_buf_detach(&worktree);
+ }
+ else if (parent_path && git_path_isdir(parent_path->ptr))
+ repo->workdir = git_buf_detach(parent_path);
+ else {
+ if (git_path_dirname_r(&worktree, repo->path_repository) < 0 ||
+ git_path_to_dir(&worktree) < 0)
+ return -1;
- return GIT_SUCCESS;
+ repo->workdir = git_buf_detach(&worktree);
+ }
+
+ GITERR_CHECK_ALLOC(repo->workdir);
+ return 0;
}
-static int guess_repository_dirs(git_repository *repo, const char *repository_path)
+/*
+ * This function returns furthest offset into path where a ceiling dir
+ * is found, so we can stop processing the path at that point.
+ *
+ * Note: converting this to use git_bufs instead of GIT_PATH_MAX buffers on
+ * the stack could remove directories name limits, but at the cost of doing
+ * repeated malloc/frees inside the loop below, so let's not do it now.
+ */
+static int find_ceiling_dir_offset(
+ const char *path,
+ const char *ceiling_directories)
{
- char buffer[GIT_PATH_MAX];
- const char *path_work_tree = NULL;
+ char buf[GIT_PATH_MAX + 1];
+ char buf2[GIT_PATH_MAX + 1];
+ const char *ceil, *sep;
+ size_t len, max_len = 0, min_len;
+
+ assert(path);
+
+ min_len = (size_t)(git_path_root(path) + 1);
+
+ if (ceiling_directories == NULL || min_len == 0)
+ return (int)min_len;
+
+ for (sep = ceil = ceiling_directories; *sep; ceil = sep + 1) {
+ for (sep = ceil; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++);
+ len = sep - ceil;
+
+ if (len == 0 || len >= sizeof(buf) || git_path_root(ceil) == -1)
+ continue;
- /* Git directory name */
- if (git_path_basename_r(buffer, sizeof(buffer), repository_path) < 0)
- return git__throw(GIT_EINVALIDPATH, "Unable to parse folder name from `%s`", repository_path);
+ strncpy(buf, ceil, len);
+ buf[len] = '\0';
+
+ if (p_realpath(buf, buf2) == NULL)
+ continue;
- if (strcmp(buffer, DOT_GIT) == 0) {
- /* Path to working dir */
- if (git_path_dirname_r(buffer, sizeof(buffer), repository_path) < 0)
- return git__throw(GIT_EINVALIDPATH, "Unable to parse parent folder name from `%s`", repository_path);
- path_work_tree = buffer;
+ len = strlen(buf2);
+ if (len > 0 && buf2[len-1] == '/')
+ buf[--len] = '\0';
+
+ if (!strncmp(path, buf2, len) &&
+ (path[len] == '/' || !path[len]) &&
+ len > max_len)
+ {
+ max_len = len;
+ }
}
- return assign_repository_dirs(repo, repository_path, NULL, NULL, path_work_tree);
+ return (int)(max_len <= min_len ? min_len : max_len);
}
-static int quickcheck_repository_dir(const char *repository_path)
+/*
+ * Read the contents of `file_path` and set `path_out` to the repo dir that
+ * it points to. Before calling, set `path_out` to the base directory that
+ * should be used if the contents of `file_path` are a relative path.
+ */
+static int read_gitfile(git_buf *path_out, const char *file_path)
{
- char path_aux[GIT_PATH_MAX];
+ int error = 0;
+ git_buf file = GIT_BUF_INIT;
+ size_t prefix_len = strlen(GIT_FILE_CONTENT_PREFIX);
- /* Ensure HEAD file exists */
- git_path_join(path_aux, repository_path, GIT_HEAD_FILE);
- if (git_futils_isfile(path_aux) < 0)
- return GIT_ERROR;
+ assert(path_out && file_path);
- git_path_join(path_aux, repository_path, GIT_OBJECTS_DIR);
- if (git_futils_isdir(path_aux) < 0)
- return GIT_ERROR;
+ if (git_futils_readbuffer(&file, file_path) < 0)
+ return -1;
- git_path_join(path_aux, repository_path, GIT_REFS_DIR);
- if (git_futils_isdir(path_aux) < 0)
- return GIT_ERROR;
+ git_buf_rtrim(&file);
+ /* apparently on Windows, some people use backslashes in paths */
+ git_path_mkposix(file.ptr);
- return GIT_SUCCESS;
+ if (git_buf_len(&file) <= prefix_len ||
+ memcmp(git_buf_cstr(&file), GIT_FILE_CONTENT_PREFIX, prefix_len) != 0)
+ {
+ giterr_set(GITERR_REPOSITORY,
+ "The `.git` file at '%s' is malformed", file_path);
+ error = -1;
+ }
+ else if ((error = git_path_dirname_r(path_out, file_path)) >= 0) {
+ const char *gitlink = git_buf_cstr(&file) + prefix_len;
+ while (*gitlink && git__isspace(*gitlink)) gitlink++;
+
+ error = git_path_prettify_dir(
+ path_out, gitlink, git_buf_cstr(path_out));
+ }
+
+ git_buf_free(&file);
+ return error;
}
-static git_repository *repository_alloc()
+static int find_repo(
+ git_buf *repo_path,
+ git_buf *parent_path,
+ const char *start_path,
+ uint32_t flags,
+ const char *ceiling_dirs)
{
int error;
+ git_buf path = GIT_BUF_INIT;
+ struct stat st;
+ dev_t initial_device = 0;
+ bool try_with_dot_git = ((flags & GIT_REPOSITORY_OPEN_BARE) != 0);
+ int ceiling_offset;
- git_repository *repo = git__malloc(sizeof(git_repository));
- if (!repo)
- return NULL;
+ git_buf_free(repo_path);
- memset(repo, 0x0, sizeof(git_repository));
+ if ((error = git_path_prettify(&path, start_path, NULL)) < 0)
+ return error;
- error = git_cache_init(&repo->objects, GIT_DEFAULT_CACHE_SIZE, &git_object__free);
- if (error < GIT_SUCCESS) {
- free(repo);
- return NULL;
+ ceiling_offset = find_ceiling_dir_offset(path.ptr, ceiling_dirs);
+
+ if (!try_with_dot_git &&
+ (error = git_buf_joinpath(&path, path.ptr, DOT_GIT)) < 0)
+ return error;
+
+ while (!error && !git_buf_len(repo_path)) {
+ if (p_stat(path.ptr, &st) == 0) {
+ /* check that we have not crossed device boundaries */
+ if (initial_device == 0)
+ initial_device = st.st_dev;
+ else if (st.st_dev != initial_device &&
+ (flags & GIT_REPOSITORY_OPEN_CROSS_FS) == 0)
+ break;
+
+ if (S_ISDIR(st.st_mode)) {
+ if (valid_repository_path(&path)) {
+ git_path_to_dir(&path);
+ git_buf_set(repo_path, path.ptr, path.size);
+ break;
+ }
+ }
+ else if (S_ISREG(st.st_mode)) {
+ git_buf repo_link = GIT_BUF_INIT;
+
+ if (!(error = read_gitfile(&repo_link, path.ptr))) {
+ if (valid_repository_path(&repo_link))
+ git_buf_swap(repo_path, &repo_link);
+
+ git_buf_free(&repo_link);
+ break;
+ }
+ git_buf_free(&repo_link);
+ }
+ }
+
+ /* move up one directory level */
+ if (git_path_dirname_r(&path, path.ptr) < 0) {
+ error = -1;
+ break;
+ }
+
+ if (try_with_dot_git) {
+ /* if we tried original dir with and without .git AND either hit
+ * directory ceiling or NO_SEARCH was requested, then be done.
+ */
+ if (path.ptr[ceiling_offset] == '\0' ||
+ (flags & GIT_REPOSITORY_OPEN_NO_SEARCH) != 0)
+ break;
+ /* otherwise look first for .git item */
+ error = git_buf_joinpath(&path, path.ptr, DOT_GIT);
+ }
+ try_with_dot_git = !try_with_dot_git;
}
- if (git_repository__refcache_init(&repo->references) < GIT_SUCCESS) {
- free(repo);
- return NULL;
+ if (!error && parent_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) {
+ if (!git_buf_len(repo_path))
+ git_buf_clear(parent_path);
+ else {
+ git_path_dirname_r(parent_path, path.ptr);
+ git_path_to_dir(parent_path);
+ }
+ if (git_buf_oom(parent_path))
+ return -1;
}
- return repo;
+ git_buf_free(&path);
+
+ if (!git_buf_len(repo_path) && !error) {
+ giterr_set(GITERR_REPOSITORY,
+ "Could not find repository from '%s'", start_path);
+ error = GIT_ENOTFOUND;
+ }
+
+ return error;
}
-static int init_odb(git_repository *repo)
+int git_repository_open_bare(
+ git_repository **repo_ptr,
+ const char *bare_path)
{
- return git_odb_open(&repo->db, repo->path_odb);
+ int error;
+ git_buf path = GIT_BUF_INIT;
+ git_repository *repo = NULL;
+
+ if ((error = git_path_prettify_dir(&path, bare_path, NULL)) < 0)
+ return error;
+
+ if (!valid_repository_path(&path)) {
+ git_buf_free(&path);
+ giterr_set(GITERR_REPOSITORY, "Path is not a repository: %s", bare_path);
+ return GIT_ENOTFOUND;
+ }
+
+ repo = repository_alloc();
+ GITERR_CHECK_ALLOC(repo);
+
+ repo->path_repository = git_buf_detach(&path);
+ GITERR_CHECK_ALLOC(repo->path_repository);
+
+ /* of course we're bare! */
+ repo->is_bare = 1;
+ repo->workdir = NULL;
+
+ *repo_ptr = repo;
+ return 0;
}
-int git_repository_open3(git_repository **repo_out,
- const char *git_dir,
- git_odb *object_database,
- const char *git_index_file,
- const char *git_work_tree)
+int git_repository_open_ext(
+ git_repository **repo_ptr,
+ const char *start_path,
+ unsigned int flags,
+ const char *ceiling_dirs)
{
+ int error;
+ git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT;
git_repository *repo;
- int error = GIT_SUCCESS;
- assert(repo_out);
+ if (repo_ptr)
+ *repo_ptr = NULL;
- if (object_database == NULL)
- return git__throw(GIT_EINVALIDARGS, "Failed to open repository. `object_database` can't be null");
+ error = find_repo(&path, &parent, start_path, flags, ceiling_dirs);
+ if (error < 0 || !repo_ptr)
+ return error;
repo = repository_alloc();
- if (repo == NULL)
- return GIT_ENOMEM;
+ GITERR_CHECK_ALLOC(repo);
- error = assign_repository_dirs(repo,
- git_dir,
- NULL,
- git_index_file,
- git_work_tree);
+ repo->path_repository = git_buf_detach(&path);
+ GITERR_CHECK_ALLOC(repo->path_repository);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0)
+ repo->is_bare = 1;
+ else {
+ git_config *config = NULL;
- error = check_repository_dirs(repo);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ if ((error = git_repository_config_snapshot(&config, repo)) < 0 ||
+ (error = load_config_data(repo, config)) < 0 ||
+ (error = load_workdir(repo, config, &parent)) < 0)
+ git_repository_free(repo);
- repo->db = object_database;
+ git_config_free(config);
+ }
- *repo_out = repo;
- return GIT_SUCCESS;
+ if (!error)
+ *repo_ptr = repo;
+ git_buf_free(&parent);
-cleanup:
- git_repository_free(repo);
- return git__rethrow(error, "Failed to open repository");
+ return error;
}
+int git_repository_open(git_repository **repo_out, const char *path)
+{
+ return git_repository_open_ext(
+ repo_out, path, GIT_REPOSITORY_OPEN_NO_SEARCH, NULL);
+}
-int git_repository_open2(git_repository **repo_out,
- const char *git_dir,
- const char *git_object_directory,
- const char *git_index_file,
- const char *git_work_tree)
+int git_repository_wrap_odb(git_repository **repo_out, git_odb *odb)
{
git_repository *repo;
- int error = GIT_SUCCESS;
-
- assert(repo_out);
repo = repository_alloc();
- if (repo == NULL)
- return GIT_ENOMEM;
+ GITERR_CHECK_ALLOC(repo);
- error = assign_repository_dirs(repo,
- git_dir,
- git_object_directory,
- git_index_file,
- git_work_tree);
+ git_repository_set_odb(repo, odb);
+ *repo_out = repo;
- if (error < GIT_SUCCESS)
- goto cleanup;
+ return 0;
+}
- error = check_repository_dirs(repo);
- if (error < GIT_SUCCESS)
- goto cleanup;
+int git_repository_discover(
+ git_buf *out,
+ const char *start_path,
+ int across_fs,
+ const char *ceiling_dirs)
+{
+ uint32_t flags = across_fs ? GIT_REPOSITORY_OPEN_CROSS_FS : 0;
- error = init_odb(repo);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ assert(start_path);
- *repo_out = repo;
- return GIT_SUCCESS;
+ git_buf_sanitize(out);
-cleanup:
- git_repository_free(repo);
- return git__rethrow(error, "Failed to open repository");
+ return find_repo(out, NULL, start_path, flags, ceiling_dirs);
}
-int git_repository_config(
- git_config **out,
- git_repository *repo,
- const char *user_config_path,
- const char *system_config_path)
+static int load_config(
+ git_config **out,
+ git_repository *repo,
+ const char *global_config_path,
+ const char *xdg_config_path,
+ const char *system_config_path)
{
- char config_path[GIT_PATH_MAX];
int error;
+ git_buf config_path = GIT_BUF_INIT;
+ git_config *cfg = NULL;
- assert(out && repo);
+ assert(repo && out);
- error = git_config_new(out);
- if (error < GIT_SUCCESS)
+ if ((error = git_config_new(&cfg)) < 0)
return error;
- git_path_join(config_path, repo->path_repository, GIT_CONFIG_FILENAME_INREPO);
- error = git_config_add_file_ondisk(*out, config_path, 3);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ error = git_buf_joinpath(
+ &config_path, repo->path_repository, GIT_CONFIG_FILENAME_INREPO);
+ if (error < 0)
+ goto on_error;
+
+ if ((error = git_config_add_file_ondisk(
+ cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, 0)) < 0 &&
+ error != GIT_ENOTFOUND)
+ goto on_error;
+
+ git_buf_free(&config_path);
+
+ if (global_config_path != NULL &&
+ (error = git_config_add_file_ondisk(
+ cfg, global_config_path, GIT_CONFIG_LEVEL_GLOBAL, 0)) < 0 &&
+ error != GIT_ENOTFOUND)
+ goto on_error;
+
+ if (xdg_config_path != NULL &&
+ (error = git_config_add_file_ondisk(
+ cfg, xdg_config_path, GIT_CONFIG_LEVEL_XDG, 0)) < 0 &&
+ error != GIT_ENOTFOUND)
+ goto on_error;
+
+ if (system_config_path != NULL &&
+ (error = git_config_add_file_ondisk(
+ cfg, system_config_path, GIT_CONFIG_LEVEL_SYSTEM, 0)) < 0 &&
+ error != GIT_ENOTFOUND)
+ goto on_error;
+
+ giterr_clear(); /* clear any lingering ENOTFOUND errors */
+
+ *out = cfg;
+ return 0;
+
+on_error:
+ git_buf_free(&config_path);
+ git_config_free(cfg);
+ *out = NULL;
+ return error;
+}
- if (user_config_path != NULL) {
- error = git_config_add_file_ondisk(*out, user_config_path, 2);
- if (error < GIT_SUCCESS)
- goto cleanup;
- }
+static const char *path_unless_empty(git_buf *buf)
+{
+ return git_buf_len(buf) > 0 ? git_buf_cstr(buf) : NULL;
+}
- if (system_config_path != NULL) {
- error = git_config_add_file_ondisk(*out, system_config_path, 1);
- if (error < GIT_SUCCESS)
- goto cleanup;
- }
+int git_repository_config__weakptr(git_config **out, git_repository *repo)
+{
+ int error = 0;
+
+ if (repo->_config == NULL) {
+ git_buf global_buf = GIT_BUF_INIT;
+ git_buf xdg_buf = GIT_BUF_INIT;
+ git_buf system_buf = GIT_BUF_INIT;
+ git_config *config;
+
+ git_config_find_global(&global_buf);
+ git_config_find_xdg(&xdg_buf);
+ git_config_find_system(&system_buf);
+
+ /* If there is no global file, open a backend for it anyway */
+ if (git_buf_len(&global_buf) == 0)
+ git_config__global_location(&global_buf);
+
+ error = load_config(
+ &config, repo,
+ path_unless_empty(&global_buf),
+ path_unless_empty(&xdg_buf),
+ path_unless_empty(&system_buf));
+ if (!error) {
+ GIT_REFCOUNT_OWN(config, repo);
+
+ config = git__compare_and_swap(&repo->_config, NULL, config);
+ if (config != NULL) {
+ GIT_REFCOUNT_OWN(config, NULL);
+ git_config_free(config);
+ }
+ }
- return GIT_SUCCESS;
+ git_buf_free(&global_buf);
+ git_buf_free(&xdg_buf);
+ git_buf_free(&system_buf);
+ }
-cleanup:
- git_config_free(*out);
+ *out = repo->_config;
return error;
}
-static int discover_repository_dirs(git_repository *repo, const char *path)
+int git_repository_config(git_config **out, git_repository *repo)
+{
+ if (git_repository_config__weakptr(out, repo) < 0)
+ return -1;
+
+ GIT_REFCOUNT_INC(*out);
+ return 0;
+}
+
+int git_repository_config_snapshot(git_config **out, git_repository *repo)
{
int error;
+ git_config *weak;
- error = guess_repository_dirs(repo, path);
- if (error < GIT_SUCCESS)
+ if ((error = git_repository_config__weakptr(&weak, repo)) < 0)
return error;
- error = check_repository_dirs(repo);
- if (error < GIT_SUCCESS)
- return error;
+ return git_config_snapshot(out, weak);
+}
- return GIT_SUCCESS;
+void git_repository_set_config(git_repository *repo, git_config *config)
+{
+ assert(repo && config);
+ set_config(repo, config);
}
-int git_repository_open(git_repository **repo_out, const char *path)
+int git_repository_odb__weakptr(git_odb **out, git_repository *repo)
{
- git_repository *repo;
- int error = GIT_SUCCESS;
+ int error = 0;
- assert(repo_out && path);
+ assert(repo && out);
- repo = repository_alloc();
- if (repo == NULL)
- return GIT_ENOMEM;
+ if (repo->_odb == NULL) {
+ git_buf odb_path = GIT_BUF_INIT;
+ git_odb *odb;
- error = discover_repository_dirs(repo, path);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ git_buf_joinpath(&odb_path, repo->path_repository, GIT_OBJECTS_DIR);
- error = init_odb(repo);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ error = git_odb_open(&odb, odb_path.ptr);
+ if (!error) {
+ GIT_REFCOUNT_OWN(odb, repo);
- *repo_out = repo;
- return GIT_SUCCESS;
+ odb = git__compare_and_swap(&repo->_odb, NULL, odb);
+ if (odb != NULL) {
+ GIT_REFCOUNT_OWN(odb, NULL);
+ git_odb_free(odb);
+ }
+ }
-cleanup:
- git_repository_free(repo);
- return git__rethrow(error, "Failed to open repository");
+ git_buf_free(&odb_path);
+ }
+
+ *out = repo->_odb;
+ return error;
}
-static int retrieve_device(dev_t *device_out, const char *path)
+int git_repository_odb(git_odb **out, git_repository *repo)
{
- struct stat path_info;
+ if (git_repository_odb__weakptr(out, repo) < 0)
+ return -1;
- assert(device_out);
+ GIT_REFCOUNT_INC(*out);
+ return 0;
+}
- if (p_lstat(path, &path_info))
- return git__throw(GIT_EOSERR, "Failed to get file informations: %s", path);
+void git_repository_set_odb(git_repository *repo, git_odb *odb)
+{
+ assert(repo && odb);
+ set_odb(repo, odb);
+}
- *device_out = path_info.st_dev;
+int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo)
+{
+ int error = 0;
+
+ assert(out && repo);
+
+ if (repo->_refdb == NULL) {
+ git_refdb *refdb;
+
+ error = git_refdb_open(&refdb, repo);
+ if (!error) {
+ GIT_REFCOUNT_OWN(refdb, repo);
- return GIT_SUCCESS;
+ refdb = git__compare_and_swap(&repo->_refdb, NULL, refdb);
+ if (refdb != NULL) {
+ GIT_REFCOUNT_OWN(refdb, NULL);
+ git_refdb_free(refdb);
+ }
+ }
+ }
+
+ *out = repo->_refdb;
+ return error;
}
-static int retrieve_ceiling_directories_offset(const char *path, const char *ceiling_directories)
+int git_repository_refdb(git_refdb **out, git_repository *repo)
{
- char buf[GIT_PATH_MAX + 1];
- char buf2[GIT_PATH_MAX + 1];
- const char *ceil, *sep;
- int len, max_len = -1;
- int min_len;
+ if (git_repository_refdb__weakptr(out, repo) < 0)
+ return -1;
- assert(path);
+ GIT_REFCOUNT_INC(*out);
+ return 0;
+}
- min_len = git_path_root(path) + 1;
+void git_repository_set_refdb(git_repository *repo, git_refdb *refdb)
+{
+ assert(repo && refdb);
+ set_refdb(repo, refdb);
+}
- if (ceiling_directories == NULL || min_len == 0)
- return min_len;
+int git_repository_index__weakptr(git_index **out, git_repository *repo)
+{
+ int error = 0;
- for (sep = ceil = ceiling_directories; *sep; ceil = sep + 1) {
- for (sep = ceil; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++);
- len = sep - ceil;
+ assert(out && repo);
- if (len == 0 || len > GIT_PATH_MAX || git_path_root(ceil) == -1)
- continue;
+ if (repo->_index == NULL) {
+ git_buf index_path = GIT_BUF_INIT;
+ git_index *index;
- strncpy(buf, ceil, len);
- buf[len] = '\0';
+ git_buf_joinpath(&index_path, repo->path_repository, GIT_INDEX_FILE);
- if (p_realpath(buf, buf2) == NULL)
- continue;
+ error = git_index_open(&index, index_path.ptr);
+ if (!error) {
+ GIT_REFCOUNT_OWN(index, repo);
- len = strlen(buf2);
- if (len > 0 && buf2[len-1] == '/')
- buf[--len] = '\0';
+ index = git__compare_and_swap(&repo->_index, NULL, index);
+ if (index != NULL) {
+ GIT_REFCOUNT_OWN(index, NULL);
+ git_index_free(index);
+ }
- if (!strncmp(path, buf2, len) &&
- path[len] == '/' &&
- len > max_len)
- {
- max_len = len;
+ error = git_index_set_caps(repo->_index, GIT_INDEXCAP_FROM_OWNER);
}
+
+ git_buf_free(&index_path);
}
- return max_len <= min_len ? min_len : max_len;
+ *out = repo->_index;
+ return error;
}
-static int read_gitfile(char *path_out, const char *file_path, const char *base_path)
+int git_repository_index(git_index **out, git_repository *repo)
{
- git_fbuffer file;
- int error, end_offset;
- char *data;
-
- assert(path_out && file_path && base_path);
+ if (git_repository_index__weakptr(out, repo) < 0)
+ return -1;
- error = git_futils_readbuffer(&file, file_path);
+ GIT_REFCOUNT_INC(*out);
+ return 0;
+}
- if (error < GIT_SUCCESS)
- return error;
+void git_repository_set_index(git_repository *repo, git_index *index)
+{
+ assert(repo && index);
+ set_index(repo, index);
+}
- data = (char*)(file.data);
+int git_repository_set_namespace(git_repository *repo, const char *namespace)
+{
+ git__free(repo->namespace);
- if (git__prefixcmp(data, GIT_FILE_CONTENT_PREFIX)) {
- git_futils_freebuffer(&file);
- return git__throw(GIT_ENOTFOUND, "Invalid gitfile format `%s`", file_path);
+ if (namespace == NULL) {
+ repo->namespace = NULL;
+ return 0;
}
- end_offset = strlen(data) - 1;
+ return (repo->namespace = git__strdup(namespace)) ? 0 : -1;
+}
- for (;data[end_offset] == '\r' || data[end_offset] == '\n'; --end_offset);
- data[end_offset + 1] = '\0';
+const char *git_repository_get_namespace(git_repository *repo)
+{
+ return repo->namespace;
+}
- if (STRLEN(GIT_FILE_CONTENT_PREFIX) == end_offset + 1) {
- git_futils_freebuffer(&file);
- return git__throw(GIT_ENOTFOUND, "No path in git file `%s`", file_path);
+static int check_repositoryformatversion(git_config *config)
+{
+ int version;
+
+ if (git_config_get_int32(&version, config, "core.repositoryformatversion") < 0)
+ return -1;
+
+ if (GIT_REPO_VERSION < version) {
+ giterr_set(GITERR_REPOSITORY,
+ "Unsupported repository version %d. Only versions up to %d are supported.",
+ version, GIT_REPO_VERSION);
+ return -1;
}
- data = data + STRLEN(GIT_FILE_CONTENT_PREFIX);
- error = git_path_prettify_dir(path_out, data, base_path);
- git_futils_freebuffer(&file);
+ return 0;
+}
+
+static int repo_init_create_head(const char *git_dir, const char *ref_name)
+{
+ git_buf ref_path = GIT_BUF_INIT;
+ git_filebuf ref = GIT_FILEBUF_INIT;
+ const char *fmt;
+
+ if (git_buf_joinpath(&ref_path, git_dir, GIT_HEAD_FILE) < 0 ||
+ git_filebuf_open(&ref, ref_path.ptr, 0, GIT_REFS_FILE_MODE) < 0)
+ goto fail;
+
+ if (!ref_name)
+ ref_name = GIT_BRANCH_MASTER;
+
+ if (git__prefixcmp(ref_name, GIT_REFS_DIR) == 0)
+ fmt = "ref: %s\n";
+ else
+ fmt = "ref: " GIT_REFS_HEADS_DIR "%s\n";
+
+ if (git_filebuf_printf(&ref, fmt, ref_name) < 0 ||
+ git_filebuf_commit(&ref) < 0)
+ goto fail;
- if (error == 0 && git_futils_exists(path_out) == 0)
- return GIT_SUCCESS;
+ git_buf_free(&ref_path);
+ return 0;
- return git__throw(GIT_EOBJCORRUPTED, "The `.git` file points to an inexisting path");
+fail:
+ git_buf_free(&ref_path);
+ git_filebuf_cleanup(&ref);
+ return -1;
}
-static void git_repository__free_dirs(git_repository *repo)
+static bool is_chmod_supported(const char *file_path)
{
- free(repo->path_workdir);
- repo->path_workdir = NULL;
- free(repo->path_index);
- repo->path_index = NULL;
- free(repo->path_repository);
- repo->path_repository = NULL;
- free(repo->path_odb);
- repo->path_odb = NULL;
+ struct stat st1, st2;
+
+ if (p_stat(file_path, &st1) < 0)
+ return false;
+
+ if (p_chmod(file_path, st1.st_mode ^ S_IXUSR) < 0)
+ return false;
+
+ if (p_stat(file_path, &st2) < 0)
+ return false;
+
+ return (st1.st_mode != st2.st_mode);
}
-void git_repository_free(git_repository *repo)
+static bool is_filesystem_case_insensitive(const char *gitdir_path)
{
- if (repo == NULL)
- return;
+ git_buf path = GIT_BUF_INIT;
+ int is_insensitive = -1;
- git_cache_free(&repo->objects);
- git_repository__refcache_free(&repo->references);
- git_repository__free_dirs(repo);
+ if (!git_buf_joinpath(&path, gitdir_path, "CoNfIg"))
+ is_insensitive = git_path_exists(git_buf_cstr(&path));
+
+ git_buf_free(&path);
+ return is_insensitive;
+}
- if (repo->db != NULL)
- git_odb_close(repo->db);
+static bool are_symlinks_supported(const char *wd_path)
+{
+ git_buf path = GIT_BUF_INIT;
+ int fd;
+ struct stat st;
+ int symlinks_supported = -1;
+
+ if ((fd = git_futils_mktmp(&path, wd_path, 0666)) < 0 ||
+ p_close(fd) < 0 ||
+ p_unlink(path.ptr) < 0 ||
+ p_symlink("testing", path.ptr) < 0 ||
+ p_lstat(path.ptr, &st) < 0)
+ symlinks_supported = false;
+ else
+ symlinks_supported = (S_ISLNK(st.st_mode) != 0);
+
+ (void)p_unlink(path.ptr);
+ git_buf_free(&path);
+
+ return symlinks_supported;
+}
+
+static int create_empty_file(const char *path, mode_t mode)
+{
+ int fd;
+
+ if ((fd = p_creat(path, mode)) < 0) {
+ giterr_set(GITERR_OS, "Error while creating '%s'", path);
+ return -1;
+ }
+
+ if (p_close(fd) < 0) {
+ giterr_set(GITERR_OS, "Error while closing '%s'", path);
+ return -1;
+ }
- free(repo);
+ return 0;
}
-int git_repository_discover(char *repository_path, size_t size, const char *start_path, int across_fs, const char *ceiling_dirs)
+static int repo_local_config(
+ git_config **out,
+ git_buf *config_dir,
+ git_repository *repo,
+ const char *repo_dir)
{
- int error, ceiling_offset;
- char bare_path[GIT_PATH_MAX];
- char normal_path[GIT_PATH_MAX];
- char *found_path;
- dev_t current_device = 0;
+ int error = 0;
+ git_config *parent;
+ const char *cfg_path;
- assert(start_path && repository_path);
+ if (git_buf_joinpath(config_dir, repo_dir, GIT_CONFIG_FILENAME_INREPO) < 0)
+ return -1;
+ cfg_path = git_buf_cstr(config_dir);
- error = git_path_prettify_dir(bare_path, start_path, NULL);
- if (error < GIT_SUCCESS)
+ /* make LOCAL config if missing */
+ if (!git_path_isfile(cfg_path) &&
+ (error = create_empty_file(cfg_path, GIT_CONFIG_FILE_MODE)) < 0)
return error;
- if (!across_fs) {
- error = retrieve_device(&current_device, bare_path);
- if (error < GIT_SUCCESS)
- return error;
+ /* if no repo, just open that file directly */
+ if (!repo)
+ return git_config_open_ondisk(out, cfg_path);
+
+ /* otherwise, open parent config and get that level */
+ if ((error = git_repository_config__weakptr(&parent, repo)) < 0)
+ return error;
+
+ if (git_config_open_level(out, parent, GIT_CONFIG_LEVEL_LOCAL) < 0) {
+ giterr_clear();
+
+ if (!(error = git_config_add_file_ondisk(
+ parent, cfg_path, GIT_CONFIG_LEVEL_LOCAL, false)))
+ error = git_config_open_level(out, parent, GIT_CONFIG_LEVEL_LOCAL);
}
- ceiling_offset = retrieve_ceiling_directories_offset(bare_path, ceiling_dirs);
- git_path_join(normal_path, bare_path, DOT_GIT);
+ git_config_free(parent);
- while(1) {
- /**
- * If the `.git` file is regular instead of
- * a directory, it should contain the path of the actual git repository
- */
- if (git_futils_isfile(normal_path) == GIT_SUCCESS) {
- error = read_gitfile(repository_path, normal_path, bare_path);
+ return error;
+}
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Unable to read git file `%s`", normal_path);
+static int repo_init_fs_configs(
+ git_config *cfg,
+ const char *cfg_path,
+ const char *repo_dir,
+ const char *work_dir,
+ bool update_ignorecase)
+{
+ int error = 0;
- error = quickcheck_repository_dir(repository_path);
- if (error < GIT_SUCCESS)
- return git__throw(GIT_ENOTFOUND, "The `.git` file found at '%s' points"
- "to an inexisting Git folder", normal_path);
+ if (!work_dir)
+ work_dir = repo_dir;
- return GIT_SUCCESS;
- }
+ if ((error = git_config_set_bool(
+ cfg, "core.filemode", is_chmod_supported(cfg_path))) < 0)
+ return error;
- /**
- * If the `.git` file is a folder, we check inside of it
- */
- if (git_futils_isdir(normal_path) == GIT_SUCCESS) {
- error = quickcheck_repository_dir(normal_path);
- if (error == GIT_SUCCESS) {
- found_path = normal_path;
- break;
- }
- }
+ if (!are_symlinks_supported(work_dir)) {
+ if ((error = git_config_set_bool(cfg, "core.symlinks", false)) < 0)
+ return error;
+ } else if (git_config_delete_entry(cfg, "core.symlinks") < 0)
+ giterr_clear();
+
+ if (update_ignorecase) {
+ if (is_filesystem_case_insensitive(repo_dir)) {
+ if ((error = git_config_set_bool(cfg, "core.ignorecase", true)) < 0)
+ return error;
+ } else if (git_config_delete_entry(cfg, "core.ignorecase") < 0)
+ giterr_clear();
+ }
- /**
- * Otherwise, the repository may be bare, let's check
- * the root anyway
- */
- error = quickcheck_repository_dir(bare_path);
- if (error == GIT_SUCCESS) {
- found_path = bare_path;
- break;
- }
+#ifdef GIT_USE_ICONV
+ if ((error = git_config_set_bool(
+ cfg, "core.precomposeunicode",
+ git_path_does_fs_decompose_unicode(work_dir))) < 0)
+ return error;
+ /* on non-iconv platforms, don't even set core.precomposeunicode */
+#endif
+
+ return 0;
+}
- if (git_path_dirname_r(normal_path, sizeof(normal_path), bare_path) < GIT_SUCCESS)
- return git__throw(GIT_EOSERR, "Failed to dirname '%s'", bare_path);
+static int repo_init_config(
+ const char *repo_dir,
+ const char *work_dir,
+ uint32_t flags,
+ uint32_t mode)
+{
+ int error = 0;
+ git_buf cfg_path = GIT_BUF_INIT;
+ git_config *config = NULL;
+ bool is_bare = ((flags & GIT_REPOSITORY_INIT_BARE) != 0);
+ bool is_reinit = ((flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0);
- if (!across_fs) {
- dev_t new_device;
- error = retrieve_device(&new_device, normal_path);
+ if ((error = repo_local_config(&config, &cfg_path, NULL, repo_dir)) < 0)
+ goto cleanup;
- if (error < GIT_SUCCESS || current_device != new_device) {
- return git__throw(GIT_ENOTAREPO,"Not a git repository (or any parent up to mount parent %s)\n"
- "Stopping at filesystem boundary.", bare_path);
- }
- current_device = new_device;
- }
+ if (is_reinit && (error = check_repositoryformatversion(config)) < 0)
+ goto cleanup;
+
+#define SET_REPO_CONFIG(TYPE, NAME, VAL) do { \
+ if ((error = git_config_set_##TYPE(config, NAME, VAL)) < 0) \
+ goto cleanup; } while (0)
+
+ SET_REPO_CONFIG(bool, "core.bare", is_bare);
+ SET_REPO_CONFIG(int32, "core.repositoryformatversion", GIT_REPO_VERSION);
+
+ if ((error = repo_init_fs_configs(
+ config, cfg_path.ptr, repo_dir, work_dir, !is_reinit)) < 0)
+ goto cleanup;
- strcpy(bare_path, normal_path);
- git_path_join(normal_path, bare_path, DOT_GIT);
+ if (!is_bare) {
+ SET_REPO_CONFIG(bool, "core.logallrefupdates", true);
- // nothing has been found, lets try the parent directory
- if (bare_path[ceiling_offset] == '\0') {
- return git__throw(GIT_ENOTAREPO,"Not a git repository (or any of the parent directories): %s", start_path);
+ if (!(flags & GIT_REPOSITORY_INIT__NATURAL_WD))
+ SET_REPO_CONFIG(string, "core.worktree", work_dir);
+ else if (is_reinit) {
+ if (git_config_delete_entry(config, "core.worktree") < 0)
+ giterr_clear();
}
}
- if (size < (strlen(found_path) + 2) * sizeof(char)) {
- return git__throw(GIT_EOVERFLOW, "The repository buffer is not long enough to handle the repository path `%s`", found_path);
+ if (mode == GIT_REPOSITORY_INIT_SHARED_GROUP) {
+ SET_REPO_CONFIG(int32, "core.sharedrepository", 1);
+ SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true);
+ }
+ else if (mode == GIT_REPOSITORY_INIT_SHARED_ALL) {
+ SET_REPO_CONFIG(int32, "core.sharedrepository", 2);
+ SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true);
}
- git_path_join(repository_path, found_path, "");
- return GIT_SUCCESS;
+cleanup:
+ git_buf_free(&cfg_path);
+ git_config_free(config);
+
+ return error;
}
-git_odb *git_repository_database(git_repository *repo)
+static int repo_reinit_submodule_fs(git_submodule *sm, const char *n, void *p)
{
- assert(repo);
- return repo->db;
+ git_repository *smrepo = NULL;
+ GIT_UNUSED(n); GIT_UNUSED(p);
+
+ if (git_submodule_open(&smrepo, sm) < 0 ||
+ git_repository_reinit_filesystem(smrepo, true) < 0)
+ giterr_clear();
+ git_repository_free(smrepo);
+
+ return 0;
}
-static int repo_init_reinit(const char *repository_path, int is_bare)
+int git_repository_reinit_filesystem(git_repository *repo, int recurse)
{
- /* TODO: reinit the repository */
- return git__throw(GIT_ENOTIMPLEMENTED,
- "Failed to reinitialize the %srepository at '%s'. "
- "This feature is not yet implemented",
- is_bare ? "bare" : "", repository_path);
+ int error = 0;
+ git_buf path = GIT_BUF_INIT;
+ git_config *config = NULL;
+ const char *repo_dir = git_repository_path(repo);
+
+ if (!(error = repo_local_config(&config, &path, repo, repo_dir)))
+ error = repo_init_fs_configs(
+ config, path.ptr, repo_dir, git_repository_workdir(repo), true);
+
+ git_config_free(config);
+ git_buf_free(&path);
+
+ git_repository__cvar_cache_clear(repo);
+
+ if (!repo->is_bare && recurse)
+ (void)git_submodule_foreach(repo, repo_reinit_submodule_fs, NULL);
+
+ return error;
}
-static int repo_init_createhead(git_repository *repo)
+static int repo_write_template(
+ const char *git_dir,
+ bool allow_overwrite,
+ const char *file,
+ mode_t mode,
+ bool hidden,
+ const char *content)
{
- git_reference *head_reference;
- return git_reference_create_symbolic(&head_reference, repo, GIT_HEAD_FILE, GIT_REFS_HEADS_MASTER_FILE, 0);
+ git_buf path = GIT_BUF_INIT;
+ int fd, error = 0, flags;
+
+ if (git_buf_joinpath(&path, git_dir, file) < 0)
+ return -1;
+
+ if (allow_overwrite)
+ flags = O_WRONLY | O_CREAT | O_TRUNC;
+ else
+ flags = O_WRONLY | O_CREAT | O_EXCL;
+
+ fd = p_open(git_buf_cstr(&path), flags, mode);
+
+ if (fd >= 0) {
+ error = p_write(fd, content, strlen(content));
+
+ p_close(fd);
+ }
+ else if (errno != EEXIST)
+ error = fd;
+
+#ifdef GIT_WIN32
+ if (!error && hidden) {
+ if (git_win32__sethidden(path.ptr) < 0)
+ error = -1;
+ }
+#else
+ GIT_UNUSED(hidden);
+#endif
+
+ git_buf_free(&path);
+
+ if (error)
+ giterr_set(GITERR_OS,
+ "Failed to initialize repository with template '%s'", file);
+
+ return error;
}
-static int repo_init_structure(const char *git_dir, int is_bare)
+static int repo_write_gitlink(
+ const char *in_dir, const char *to_repo)
{
- const int mode = 0755; /* or 0777 ? */
int error;
+ git_buf buf = GIT_BUF_INIT;
+ struct stat st;
+
+ git_path_dirname_r(&buf, to_repo);
+ git_path_to_dir(&buf);
+ if (git_buf_oom(&buf))
+ return -1;
+
+ /* don't write gitlink to natural workdir */
+ if (git__suffixcmp(to_repo, "/" DOT_GIT "/") == 0 &&
+ strcmp(in_dir, buf.ptr) == 0)
+ {
+ error = GIT_PASSTHROUGH;
+ goto cleanup;
+ }
- char temp_path[GIT_PATH_MAX];
+ if ((error = git_buf_joinpath(&buf, in_dir, DOT_GIT)) < 0)
+ goto cleanup;
- if (git_futils_mkdir_r(git_dir, mode))
- return git__throw(GIT_ERROR, "Failed to initialize repository structure. Could not mkdir");
+ if (!p_stat(buf.ptr, &st) && !S_ISREG(st.st_mode)) {
+ giterr_set(GITERR_REPOSITORY,
+ "Cannot overwrite gitlink file into path '%s'", in_dir);
+ error = GIT_EEXISTS;
+ goto cleanup;
+ }
- /* Hides the ".git" directory */
- if (!is_bare) {
+ git_buf_clear(&buf);
+
+ error = git_buf_printf(&buf, "%s %s", GIT_FILE_CONTENT_PREFIX, to_repo);
+
+ if (!error)
+ error = repo_write_template(in_dir, true, DOT_GIT, 0666, true, buf.ptr);
+
+cleanup:
+ git_buf_free(&buf);
+ return error;
+}
+
+static mode_t pick_dir_mode(git_repository_init_options *opts)
+{
+ if (opts->mode == GIT_REPOSITORY_INIT_SHARED_UMASK)
+ return 0777;
+ if (opts->mode == GIT_REPOSITORY_INIT_SHARED_GROUP)
+ return (0775 | S_ISGID);
+ if (opts->mode == GIT_REPOSITORY_INIT_SHARED_ALL)
+ return (0777 | S_ISGID);
+ return opts->mode;
+}
+
+#include "repo_template.h"
+
+static int repo_init_structure(
+ const char *repo_dir,
+ const char *work_dir,
+ git_repository_init_options *opts)
+{
+ int error = 0;
+ repo_template_item *tpl;
+ bool external_tpl =
+ ((opts->flags & GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE) != 0);
+ mode_t dmode = pick_dir_mode(opts);
+ bool chmod = opts->mode != GIT_REPOSITORY_INIT_SHARED_UMASK;
+
+ /* Hide the ".git" directory */
#ifdef GIT_WIN32
- error = p_hide_directory__w32(git_dir);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to initialize repository structure");
+ if ((opts->flags & GIT_REPOSITORY_INIT__HAS_DOTGIT) != 0) {
+ if (git_win32__sethidden(repo_dir) < 0) {
+ giterr_set(GITERR_OS,
+ "Failed to mark Git repository folder as hidden");
+ return -1;
+ }
+ }
#endif
+
+ /* Create the .git gitlink if appropriate */
+ if ((opts->flags & GIT_REPOSITORY_INIT_BARE) == 0 &&
+ (opts->flags & GIT_REPOSITORY_INIT__NATURAL_WD) == 0)
+ {
+ if (repo_write_gitlink(work_dir, repo_dir) < 0)
+ return -1;
}
- /* Creates the '/objects/info/' directory */
- git_path_join(temp_path, git_dir, GIT_OBJECTS_INFO_DIR);
- error = git_futils_mkdir_r(temp_path, mode);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to initialize repository structure");
+ /* Copy external template if requested */
+ if (external_tpl) {
+ git_config *cfg = NULL;
+ const char *tdir = NULL;
+ bool default_template = false;
+ git_buf template_buf = GIT_BUF_INIT;
+
+ if (opts->template_path)
+ tdir = opts->template_path;
+ else if ((error = git_config_open_default(&cfg)) >= 0) {
+ error = git_config_get_string(&tdir, cfg, "init.templatedir");
+ giterr_clear();
+ }
- /* Creates the '/objects/pack/' directory */
- git_path_join(temp_path, git_dir, GIT_OBJECTS_PACK_DIR);
- error = p_mkdir(temp_path, mode);
- if (error < GIT_SUCCESS)
- return git__throw(error, "Unable to create `%s` folder", temp_path);
+ if (!tdir) {
+ if (!(error = git_sysdir_find_template_dir(&template_buf)))
+ tdir = template_buf.ptr;
+ default_template = true;
+ }
+
+ if (tdir) {
+ uint32_t cpflags = GIT_CPDIR_COPY_SYMLINKS | GIT_CPDIR_SIMPLE_TO_MODE;
+ if (opts->mode != GIT_REPOSITORY_INIT_SHARED_UMASK)
+ cpflags |= GIT_CPDIR_CHMOD_DIRS;
+ error = git_futils_cp_r(tdir, repo_dir, cpflags, dmode);
+ }
+
+ git_buf_free(&template_buf);
+ git_config_free(cfg);
+
+ if (error < 0) {
+ if (!default_template)
+ return error;
- /* Creates the '/refs/heads/' directory */
- git_path_join(temp_path, git_dir, GIT_REFS_HEADS_DIR);
- error = git_futils_mkdir_r(temp_path, mode);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to initialize repository structure");
+ /* if template was default, ignore error and use internal */
+ giterr_clear();
+ external_tpl = false;
+ error = 0;
+ }
+ }
- /* Creates the '/refs/tags/' directory */
- git_path_join(temp_path, git_dir, GIT_REFS_TAGS_DIR);
- error = p_mkdir(temp_path, mode);
- if (error < GIT_SUCCESS)
- return git__throw(error, "Unable to create `%s` folder", temp_path);
+ /* Copy internal template
+ * - always ensure existence of dirs
+ * - only create files if no external template was specified
+ */
+ for (tpl = repo_template; !error && tpl->path; ++tpl) {
+ if (!tpl->content) {
+ uint32_t mkdir_flags = GIT_MKDIR_PATH;
+ if (chmod)
+ mkdir_flags |= GIT_MKDIR_CHMOD;
+
+ error = git_futils_mkdir(
+ tpl->path, repo_dir, dmode, mkdir_flags);
+ }
+ else if (!external_tpl) {
+ const char *content = tpl->content;
- /* TODO: what's left? templates? */
+ if (opts->description && strcmp(tpl->path, GIT_DESC_FILE) == 0)
+ content = opts->description;
- return GIT_SUCCESS;
+ error = repo_write_template(
+ repo_dir, false, tpl->path, tpl->mode, false, content);
+ }
+ }
+
+ return error;
}
-int git_repository_init(git_repository **repo_out, const char *path, unsigned is_bare)
+static int mkdir_parent(git_buf *buf, uint32_t mode, bool skip2)
{
- int error = GIT_SUCCESS;
- git_repository *repo = NULL;
- char repository_path[GIT_PATH_MAX];
+ /* When making parent directories during repository initialization
+ * don't try to set gid or grant world write access
+ */
+ return git_futils_mkdir(
+ buf->ptr, NULL, mode & ~(S_ISGID | 0002),
+ GIT_MKDIR_PATH | GIT_MKDIR_VERIFY_DIR |
+ (skip2 ? GIT_MKDIR_SKIP_LAST2 : GIT_MKDIR_SKIP_LAST));
+}
- assert(repo_out && path);
+static int repo_init_directories(
+ git_buf *repo_path,
+ git_buf *wd_path,
+ const char *given_repo,
+ git_repository_init_options *opts)
+{
+ int error = 0;
+ bool is_bare, add_dotgit, has_dotgit, natural_wd;
+ mode_t dirmode;
+
+ /* There are three possible rules for what we are allowed to create:
+ * - MKPATH means anything we need
+ * - MKDIR means just the .git directory and its parent and the workdir
+ * - Neither means only the .git directory can be created
+ *
+ * There are 5 "segments" of path that we might need to deal with:
+ * 1. The .git directory
+ * 2. The parent of the .git directory
+ * 3. Everything above the parent of the .git directory
+ * 4. The working directory (often the same as #2)
+ * 5. Everything above the working directory (often the same as #3)
+ *
+ * For all directories created, we start with the init_mode value for
+ * permissions and then strip off bits in some cases:
+ *
+ * For MKPATH, we create #3 (and #5) paths without S_ISGID or S_IWOTH
+ * For MKPATH and MKDIR, we create #2 (and #4) without S_ISGID
+ * For all rules, we create #1 using the untouched init_mode
+ */
+
+ /* set up repo path */
+
+ is_bare = ((opts->flags & GIT_REPOSITORY_INIT_BARE) != 0);
+
+ add_dotgit =
+ (opts->flags & GIT_REPOSITORY_INIT_NO_DOTGIT_DIR) == 0 &&
+ !is_bare &&
+ git__suffixcmp(given_repo, "/" DOT_GIT) != 0 &&
+ git__suffixcmp(given_repo, "/" GIT_DIR) != 0;
+
+ if (git_buf_joinpath(repo_path, given_repo, add_dotgit ? GIT_DIR : "") < 0)
+ return -1;
+
+ has_dotgit = (git__suffixcmp(repo_path->ptr, "/" GIT_DIR) == 0);
+ if (has_dotgit)
+ opts->flags |= GIT_REPOSITORY_INIT__HAS_DOTGIT;
+
+ /* set up workdir path */
- git_path_join(repository_path, path, is_bare ? "" : GIT_DIR);
+ if (!is_bare) {
+ if (opts->workdir_path) {
+ if (git_path_join_unrooted(
+ wd_path, opts->workdir_path, repo_path->ptr, NULL) < 0)
+ return -1;
+ } else if (has_dotgit) {
+ if (git_path_dirname_r(wd_path, repo_path->ptr) < 0)
+ return -1;
+ } else {
+ giterr_set(GITERR_REPOSITORY, "Cannot pick working directory"
+ " for non-bare repository that isn't a '.git' directory");
+ return -1;
+ }
- if (git_futils_isdir(repository_path)) {
- if (quickcheck_repository_dir(repository_path) == GIT_SUCCESS)
- return repo_init_reinit(repository_path, is_bare);
+ if (git_path_to_dir(wd_path) < 0)
+ return -1;
+ } else {
+ git_buf_clear(wd_path);
}
- error = repo_init_structure(repository_path, is_bare);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ natural_wd =
+ has_dotgit &&
+ wd_path->size > 0 &&
+ wd_path->size + strlen(GIT_DIR) == repo_path->size &&
+ memcmp(repo_path->ptr, wd_path->ptr, wd_path->size) == 0;
+ if (natural_wd)
+ opts->flags |= GIT_REPOSITORY_INIT__NATURAL_WD;
- repo = repository_alloc();
- if (repo == NULL) {
- error = GIT_ENOMEM;
- goto cleanup;
+ /* create directories as needed / requested */
+
+ dirmode = pick_dir_mode(opts);
+
+ if ((opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0) {
+ /* create path #5 */
+ if (wd_path->size > 0 &&
+ (error = mkdir_parent(wd_path, dirmode, false)) < 0)
+ return error;
+
+ /* create path #3 (if not the same as #5) */
+ if (!natural_wd &&
+ (error = mkdir_parent(repo_path, dirmode, has_dotgit)) < 0)
+ return error;
}
- error = guess_repository_dirs(repo, repository_path);
- if (error < GIT_SUCCESS)
- goto cleanup;
+ if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 ||
+ (opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0)
+ {
+ /* create path #4 */
+ if (wd_path->size > 0 &&
+ (error = git_futils_mkdir(
+ wd_path->ptr, NULL, dirmode & ~S_ISGID,
+ GIT_MKDIR_VERIFY_DIR)) < 0)
+ return error;
+
+ /* create path #2 (if not the same as #4) */
+ if (!natural_wd &&
+ (error = git_futils_mkdir(
+ repo_path->ptr, NULL, dirmode & ~S_ISGID,
+ GIT_MKDIR_VERIFY_DIR | GIT_MKDIR_SKIP_LAST)) < 0)
+ return error;
+ }
+
+ if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 ||
+ (opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0 ||
+ has_dotgit)
+ {
+ /* create path #1 */
+ error = git_futils_mkdir(repo_path->ptr, NULL, dirmode,
+ GIT_MKDIR_VERIFY_DIR | ((dirmode & S_ISGID) ? GIT_MKDIR_CHMOD : 0));
+ }
+
+ /* prettify both directories now that they are created */
+
+ if (!error) {
+ error = git_path_prettify_dir(repo_path, repo_path->ptr, NULL);
- assert(repo->is_bare == is_bare);
+ if (!error && wd_path->size > 0)
+ error = git_path_prettify_dir(wd_path, wd_path->ptr, NULL);
+ }
- error = init_odb(repo);
- if (error < GIT_SUCCESS)
+ return error;
+}
+
+static int repo_init_create_origin(git_repository *repo, const char *url)
+{
+ int error;
+ git_remote *remote;
+
+ if (!(error = git_remote_create(&remote, repo, GIT_REMOTE_ORIGIN, url))) {
+ git_remote_free(remote);
+ }
+
+ return error;
+}
+
+int git_repository_init(
+ git_repository **repo_out, const char *path, unsigned is_bare)
+{
+ git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
+
+ opts.flags = GIT_REPOSITORY_INIT_MKPATH; /* don't love this default */
+ if (is_bare)
+ opts.flags |= GIT_REPOSITORY_INIT_BARE;
+
+ return git_repository_init_ext(repo_out, path, &opts);
+}
+
+int git_repository_init_ext(
+ git_repository **out,
+ const char *given_repo,
+ git_repository_init_options *opts)
+{
+ int error;
+ git_buf repo_path = GIT_BUF_INIT, wd_path = GIT_BUF_INIT;
+
+ assert(out && given_repo && opts);
+
+ GITERR_CHECK_VERSION(opts, GIT_REPOSITORY_INIT_OPTIONS_VERSION, "git_repository_init_options");
+
+ error = repo_init_directories(&repo_path, &wd_path, given_repo, opts);
+ if (error < 0)
goto cleanup;
- error = repo_init_createhead(repo);
- if (error < GIT_SUCCESS)
+ if (valid_repository_path(&repo_path)) {
+
+ if ((opts->flags & GIT_REPOSITORY_INIT_NO_REINIT) != 0) {
+ giterr_set(GITERR_REPOSITORY,
+ "Attempt to reinitialize '%s'", given_repo);
+ error = GIT_EEXISTS;
+ goto cleanup;
+ }
+
+ opts->flags |= GIT_REPOSITORY_INIT__IS_REINIT;
+
+ error = repo_init_config(
+ repo_path.ptr, wd_path.ptr, opts->flags, opts->mode);
+
+ /* TODO: reinitialize the templates */
+ }
+ else {
+ if (!(error = repo_init_structure(
+ repo_path.ptr, wd_path.ptr, opts)) &&
+ !(error = repo_init_config(
+ repo_path.ptr, wd_path.ptr, opts->flags, opts->mode)))
+ error = repo_init_create_head(
+ repo_path.ptr, opts->initial_head);
+ }
+ if (error < 0)
goto cleanup;
- /* should never fail */
- assert(check_repository_dirs(repo) == GIT_SUCCESS);
+ error = git_repository_open(out, repo_path.ptr);
- *repo_out = repo;
- return GIT_SUCCESS;
+ if (!error && opts->origin_url)
+ error = repo_init_create_origin(*out, opts->origin_url);
cleanup:
- git_repository_free(repo);
- return git__rethrow(error, "Failed to (re)init the repository `%s`", path);
+ git_buf_free(&repo_path);
+ git_buf_free(&wd_path);
+
+ return error;
}
int git_repository_head_detached(git_repository *repo)
{
git_reference *ref;
- int error;
- size_t GIT_UNUSED(_size);
- git_otype type;
+ git_odb *odb = NULL;
+ int exists;
- error = git_reference_lookup(&ref, repo, GIT_HEAD_FILE);
- if (error < GIT_SUCCESS)
- return error;
+ if (git_repository_odb__weakptr(&odb, repo) < 0)
+ return -1;
- if (git_reference_type(ref) == GIT_REF_SYMBOLIC)
- return 0;
+ if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0)
+ return -1;
- error = git_odb_read_header(&_size, &type, repo->db, git_reference_oid(ref));
- if (error < GIT_SUCCESS)
- return error;
+ if (git_reference_type(ref) == GIT_REF_SYMBOLIC) {
+ git_reference_free(ref);
+ return 0;
+ }
- if (type != GIT_OBJ_COMMIT)
- return git__throw(GIT_EOBJCORRUPTED, "HEAD is not a commit");
+ exists = git_odb_exists(odb, git_reference_target(ref));
- return 1;
+ git_reference_free(ref);
+ return exists;
}
-int git_repository_head_orphan(git_repository *repo)
+int git_repository_head(git_reference **head_out, git_repository *repo)
{
- git_reference *ref;
+ git_reference *head;
int error;
- error = git_reference_lookup(&ref, repo, GIT_HEAD_FILE);
- if (error < GIT_SUCCESS)
+ if ((error = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0)
return error;
- if (git_reference_type(ref) == GIT_REF_OID)
+ if (git_reference_type(head) == GIT_REF_OID) {
+ *head_out = head;
return 0;
+ }
- error = git_reference_resolve(&ref, ref);
+ error = git_reference_lookup_resolved(head_out, repo, git_reference_symbolic_target(head), -1);
+ git_reference_free(head);
- return error == GIT_ENOTFOUND ? 1 : error;
+ return error == GIT_ENOTFOUND ? GIT_EUNBORNBRANCH : error;
}
-int git_repository_is_empty(git_repository *repo)
+int git_repository_head_unborn(git_repository *repo)
{
- git_reference *head, *branch;
+ git_reference *ref = NULL;
int error;
- error = git_reference_lookup(&head, repo, "HEAD");
- if (error < GIT_SUCCESS)
- return git__throw(error, "Corrupted repository. HEAD does not exist");
+ error = git_repository_head(&ref, repo);
+ git_reference_free(ref);
- if (git_reference_type(head) != GIT_REF_SYMBOLIC)
- return 0;
+ if (error == GIT_EUNBORNBRANCH)
+ return 1;
- if (strcmp(git_reference_target(head), "refs/heads/master") != 0)
+ if (error < 0)
+ return -1;
+
+ return 0;
+}
+
+static int at_least_one_cb(const char *refname, void *payload)
+{
+ GIT_UNUSED(refname);
+ GIT_UNUSED(payload);
+ return GIT_PASSTHROUGH;
+}
+
+static int repo_contains_no_reference(git_repository *repo)
+{
+ int error = git_reference_foreach_name(repo, &at_least_one_cb, NULL);
+
+ if (error == GIT_PASSTHROUGH)
return 0;
- error = git_reference_resolve(&branch, head);
- return error == GIT_ENOTFOUND ? 1 : error;
+ if (!error)
+ return 1;
+
+ return error;
}
-const char *git_repository_path(git_repository *repo, git_repository_pathid id)
+int git_repository_is_empty(git_repository *repo)
{
- assert(repo);
+ git_reference *head = NULL;
+ int is_empty = 0;
- switch (id) {
- case GIT_REPO_PATH:
- return repo->path_repository;
+ if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0)
+ return -1;
- case GIT_REPO_PATH_INDEX:
- return repo->path_index;
+ if (git_reference_type(head) == GIT_REF_SYMBOLIC)
+ is_empty =
+ (strcmp(git_reference_symbolic_target(head),
+ GIT_REFS_HEADS_DIR "master") == 0) &&
+ repo_contains_no_reference(repo);
- case GIT_REPO_PATH_ODB:
- return repo->path_odb;
+ git_reference_free(head);
- case GIT_REPO_PATH_WORKDIR:
- return repo->path_workdir;
+ return is_empty;
+}
+
+const char *git_repository_path(git_repository *repo)
+{
+ assert(repo);
+ return repo->path_repository;
+}
+
+const char *git_repository_workdir(git_repository *repo)
+{
+ assert(repo);
- default:
+ if (repo->is_bare)
return NULL;
+
+ return repo->workdir;
+}
+
+int git_repository_set_workdir(
+ git_repository *repo, const char *workdir, int update_gitlink)
+{
+ int error = 0;
+ git_buf path = GIT_BUF_INIT;
+
+ assert(repo && workdir);
+
+ if (git_path_prettify_dir(&path, workdir, NULL) < 0)
+ return -1;
+
+ if (repo->workdir && strcmp(repo->workdir, path.ptr) == 0)
+ return 0;
+
+ if (update_gitlink) {
+ git_config *config;
+
+ if (git_repository_config__weakptr(&config, repo) < 0)
+ return -1;
+
+ error = repo_write_gitlink(path.ptr, git_repository_path(repo));
+
+ /* passthrough error means gitlink is unnecessary */
+ if (error == GIT_PASSTHROUGH)
+ error = git_config_delete_entry(config, "core.worktree");
+ else if (!error)
+ error = git_config_set_string(config, "core.worktree", path.ptr);
+
+ if (!error)
+ error = git_config_set_bool(config, "core.bare", false);
+ }
+
+ if (!error) {
+ char *old_workdir = repo->workdir;
+
+ repo->workdir = git_buf_detach(&path);
+ repo->is_bare = 0;
+
+ git__free(old_workdir);
}
+
+ return error;
}
int git_repository_is_bare(git_repository *repo)
@@ -790,3 +1664,338 @@ int git_repository_is_bare(git_repository *repo)
assert(repo);
return repo->is_bare;
}
+
+int git_repository_head_tree(git_tree **tree, git_repository *repo)
+{
+ git_reference *head;
+ git_object *obj;
+ int error;
+
+ if ((error = git_repository_head(&head, repo)) < 0)
+ return error;
+
+ if ((error = git_reference_peel(&obj, head, GIT_OBJ_TREE)) < 0)
+ goto cleanup;
+
+ *tree = (git_tree *)obj;
+
+cleanup:
+ git_reference_free(head);
+ return error;
+}
+
+int git_repository_message(git_buf *out, git_repository *repo)
+{
+ git_buf path = GIT_BUF_INIT;
+ struct stat st;
+ int error;
+
+ git_buf_sanitize(out);
+
+ if (git_buf_joinpath(&path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)
+ return -1;
+
+ if ((error = p_stat(git_buf_cstr(&path), &st)) < 0) {
+ if (errno == ENOENT)
+ error = GIT_ENOTFOUND;
+ giterr_set(GITERR_OS, "Could not access message file");
+ } else {
+ error = git_futils_readbuffer(out, git_buf_cstr(&path));
+ }
+
+ git_buf_free(&path);
+
+ return error;
+}
+
+int git_repository_message_remove(git_repository *repo)
+{
+ git_buf path = GIT_BUF_INIT;
+ int error;
+
+ if (git_buf_joinpath(&path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)
+ return -1;
+
+ error = p_unlink(git_buf_cstr(&path));
+ git_buf_free(&path);
+
+ return error;
+}
+
+int git_repository_hashfile(
+ git_oid *out,
+ git_repository *repo,
+ const char *path,
+ git_otype type,
+ const char *as_path)
+{
+ int error;
+ git_filter_list *fl = NULL;
+ git_file fd = -1;
+ git_off_t len;
+ git_buf full_path = GIT_BUF_INIT;
+
+ assert(out && path && repo); /* as_path can be NULL */
+
+ /* At some point, it would be nice if repo could be NULL to just
+ * apply filter rules defined in system and global files, but for
+ * now that is not possible because git_filters_load() needs it.
+ */
+
+ error = git_path_join_unrooted(
+ &full_path, path, repo ? git_repository_workdir(repo) : NULL, NULL);
+ if (error < 0)
+ return error;
+
+ if (!as_path)
+ as_path = path;
+
+ /* passing empty string for "as_path" indicated --no-filters */
+ if (strlen(as_path) > 0) {
+ error = git_filter_list_load(
+ &fl, repo, NULL, as_path,
+ GIT_FILTER_TO_ODB, GIT_FILTER_OPT_DEFAULT);
+ if (error < 0)
+ return error;
+ } else {
+ error = 0;
+ }
+
+ /* at this point, error is a count of the number of loaded filters */
+
+ fd = git_futils_open_ro(full_path.ptr);
+ if (fd < 0) {
+ error = fd;
+ goto cleanup;
+ }
+
+ len = git_futils_filesize(fd);
+ if (len < 0) {
+ error = (int)len;
+ goto cleanup;
+ }
+
+ if (!git__is_sizet(len)) {
+ giterr_set(GITERR_OS, "File size overflow for 32-bit systems");
+ error = -1;
+ goto cleanup;
+ }
+
+ error = git_odb__hashfd_filtered(out, fd, (size_t)len, type, fl);
+
+cleanup:
+ if (fd >= 0)
+ p_close(fd);
+ git_filter_list_free(fl);
+ git_buf_free(&full_path);
+
+ return error;
+}
+
+static bool looks_like_a_branch(const char *refname)
+{
+ return git__prefixcmp(refname, GIT_REFS_HEADS_DIR) == 0;
+}
+
+int git_repository_set_head(
+ git_repository* repo,
+ const char* refname,
+ const git_signature *signature,
+ const char *log_message)
+{
+ git_reference *ref,
+ *new_head = NULL;
+ int error;
+
+ assert(repo && refname);
+
+ error = git_reference_lookup(&ref, repo, refname);
+ if (error < 0 && error != GIT_ENOTFOUND)
+ return error;
+
+ if (!error) {
+ if (git_reference_is_branch(ref)) {
+ error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE,
+ git_reference_name(ref), true, signature, log_message);
+ } else {
+ error = git_repository_set_head_detached(repo, git_reference_target(ref),
+ signature, log_message);
+ }
+ } else if (looks_like_a_branch(refname)) {
+ error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE, refname,
+ true, signature, log_message);
+ }
+
+ git_reference_free(ref);
+ git_reference_free(new_head);
+ return error;
+}
+
+int git_repository_set_head_detached(
+ git_repository* repo,
+ const git_oid* commitish,
+ const git_signature *signature,
+ const char *log_message)
+{
+ int error;
+ git_object *object,
+ *peeled = NULL;
+ git_reference *new_head = NULL;
+
+ assert(repo && commitish);
+
+ if ((error = git_object_lookup(&object, repo, commitish, GIT_OBJ_ANY)) < 0)
+ return error;
+
+ if ((error = git_object_peel(&peeled, object, GIT_OBJ_COMMIT)) < 0)
+ goto cleanup;
+
+ error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_object_id(peeled), true, signature, log_message);
+
+cleanup:
+ git_object_free(object);
+ git_object_free(peeled);
+ git_reference_free(new_head);
+ return error;
+}
+
+int git_repository_detach_head(
+ git_repository* repo,
+ const git_signature *signature,
+ const char *reflog_message)
+{
+ git_reference *old_head = NULL,
+ *new_head = NULL;
+ git_object *object = NULL;
+ int error;
+
+ assert(repo);
+
+ if ((error = git_repository_head(&old_head, repo)) < 0)
+ return error;
+
+ if ((error = git_object_lookup(&object, repo, git_reference_target(old_head), GIT_OBJ_COMMIT)) < 0)
+ goto cleanup;
+
+ error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_reference_target(old_head),
+ 1, signature, reflog_message);
+
+cleanup:
+ git_object_free(object);
+ git_reference_free(old_head);
+ git_reference_free(new_head);
+ return error;
+}
+
+/**
+ * Loosely ported from git.git
+ * https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh#L198-289
+ */
+int git_repository_state(git_repository *repo)
+{
+ git_buf repo_path = GIT_BUF_INIT;
+ int state = GIT_REPOSITORY_STATE_NONE;
+
+ assert(repo);
+
+ if (git_buf_puts(&repo_path, repo->path_repository) < 0)
+ return -1;
+
+ if (git_path_contains_file(&repo_path, GIT_REBASE_MERGE_INTERACTIVE_FILE))
+ state = GIT_REPOSITORY_STATE_REBASE_INTERACTIVE;
+ else if (git_path_contains_dir(&repo_path, GIT_REBASE_MERGE_DIR))
+ state = GIT_REPOSITORY_STATE_REBASE_MERGE;
+ else if (git_path_contains_file(&repo_path, GIT_REBASE_APPLY_REBASING_FILE))
+ state = GIT_REPOSITORY_STATE_REBASE;
+ else if (git_path_contains_file(&repo_path, GIT_REBASE_APPLY_APPLYING_FILE))
+ state = GIT_REPOSITORY_STATE_APPLY_MAILBOX;
+ else if (git_path_contains_dir(&repo_path, GIT_REBASE_APPLY_DIR))
+ state = GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE;
+ else if (git_path_contains_file(&repo_path, GIT_MERGE_HEAD_FILE))
+ state = GIT_REPOSITORY_STATE_MERGE;
+ else if(git_path_contains_file(&repo_path, GIT_REVERT_HEAD_FILE))
+ state = GIT_REPOSITORY_STATE_REVERT;
+ else if(git_path_contains_file(&repo_path, GIT_CHERRY_PICK_HEAD_FILE))
+ state = GIT_REPOSITORY_STATE_CHERRY_PICK;
+ else if(git_path_contains_file(&repo_path, GIT_BISECT_LOG_FILE))
+ state = GIT_REPOSITORY_STATE_BISECT;
+
+ git_buf_free(&repo_path);
+ return state;
+}
+
+int git_repository__cleanup_files(
+ git_repository *repo, const char *files[], size_t files_len)
+{
+ git_buf buf = GIT_BUF_INIT;
+ size_t i;
+ int error;
+
+ for (error = 0, i = 0; !error && i < files_len; ++i) {
+ const char *path;
+
+ if (git_buf_joinpath(&buf, repo->path_repository, files[i]) < 0)
+ return -1;
+
+ path = git_buf_cstr(&buf);
+
+ if (git_path_isfile(path)) {
+ error = p_unlink(path);
+ } else if (git_path_isdir(path)) {
+ error = git_futils_rmdir_r(path, NULL,
+ GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS);
+ }
+
+ git_buf_clear(&buf);
+ }
+
+ git_buf_free(&buf);
+ return error;
+}
+
+static const char *state_files[] = {
+ GIT_MERGE_HEAD_FILE,
+ GIT_MERGE_MODE_FILE,
+ GIT_MERGE_MSG_FILE,
+ GIT_REVERT_HEAD_FILE,
+ GIT_CHERRY_PICK_HEAD_FILE,
+ GIT_BISECT_LOG_FILE,
+ GIT_REBASE_MERGE_DIR,
+ GIT_REBASE_APPLY_DIR,
+};
+
+int git_repository_state_cleanup(git_repository *repo)
+{
+ assert(repo);
+
+ return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files));
+}
+
+int git_repository_is_shallow(git_repository *repo)
+{
+ git_buf path = GIT_BUF_INIT;
+ struct stat st;
+ int error;
+
+ git_buf_joinpath(&path, repo->path_repository, "shallow");
+ error = git_path_lstat(path.ptr, &st);
+ git_buf_free(&path);
+
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ return 0;
+ }
+
+ if (error < 0)
+ return error;
+ return st.st_size == 0 ? 0 : 1;
+}
+
+int git_repository_init_init_options(
+ git_repository_init_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_repository_init_options,
+ GIT_REPOSITORY_INIT_OPTIONS_INIT);
+ return 0;
+}
diff --git a/src/repository.h b/src/repository.h
index 2e0b9e352..aba16a016 100644
--- a/src/repository.h
+++ b/src/repository.h
@@ -1,3 +1,9 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_repository_h__
#define INCLUDE_repository_h__
@@ -6,44 +12,164 @@
#include "git2/odb.h"
#include "git2/repository.h"
#include "git2/object.h"
+#include "git2/config.h"
-#include "hashtable.h"
-#include "index.h"
#include "cache.h"
#include "refs.h"
#include "buffer.h"
+#include "object.h"
+#include "attrcache.h"
+#include "submodule.h"
+#include "diff_driver.h"
#define DOT_GIT ".git"
#define GIT_DIR DOT_GIT "/"
-#define GIT_OBJECTS_DIR "objects/"
-#define GIT_INDEX_FILE "index"
+#define GIT_DIR_MODE 0755
+#define GIT_BARE_DIR_MODE 0777
-struct git_object {
- git_cached_obj cached;
- git_repository *repo;
- git_otype type;
+/** Cvar cache identifiers */
+typedef enum {
+ GIT_CVAR_AUTO_CRLF = 0, /* core.autocrlf */
+ GIT_CVAR_EOL, /* core.eol */
+ GIT_CVAR_SYMLINKS, /* core.symlinks */
+ GIT_CVAR_IGNORECASE, /* core.ignorecase */
+ GIT_CVAR_FILEMODE, /* core.filemode */
+ GIT_CVAR_IGNORESTAT, /* core.ignorestat */
+ GIT_CVAR_TRUSTCTIME, /* core.trustctime */
+ GIT_CVAR_ABBREV, /* core.abbrev */
+ GIT_CVAR_PRECOMPOSE, /* core.precomposeunicode */
+ GIT_CVAR_SAFE_CRLF, /* core.safecrlf */
+ GIT_CVAR_LOGALLREFUPDATES, /* core.logallrefupdates */
+ GIT_CVAR_CACHE_MAX
+} git_cvar_cached;
+
+/**
+ * CVAR value enumerations
+ *
+ * These are the values that are actually stored in the cvar cache, instead
+ * of their string equivalents. These values are internal and symbolic;
+ * make sure that none of them is set to `-1`, since that is the unique
+ * identifier for "not cached"
+ */
+typedef enum {
+ /* The value hasn't been loaded from the cache yet */
+ GIT_CVAR_NOT_CACHED = -1,
+
+ /* core.safecrlf: false, 'fail', 'warn' */
+ GIT_SAFE_CRLF_FALSE = 0,
+ GIT_SAFE_CRLF_FAIL = 1,
+ GIT_SAFE_CRLF_WARN = 2,
+
+ /* core.autocrlf: false, true, 'input; */
+ GIT_AUTO_CRLF_FALSE = 0,
+ GIT_AUTO_CRLF_TRUE = 1,
+ GIT_AUTO_CRLF_INPUT = 2,
+ GIT_AUTO_CRLF_DEFAULT = GIT_AUTO_CRLF_FALSE,
+
+ /* core.eol: unset, 'crlf', 'lf', 'native' */
+ GIT_EOL_UNSET = 0,
+ GIT_EOL_CRLF = 1,
+ GIT_EOL_LF = 2,
+#ifdef GIT_WIN32
+ GIT_EOL_NATIVE = GIT_EOL_CRLF,
+#else
+ GIT_EOL_NATIVE = GIT_EOL_LF,
+#endif
+ GIT_EOL_DEFAULT = GIT_EOL_NATIVE,
+
+ /* core.symlinks: bool */
+ GIT_SYMLINKS_DEFAULT = GIT_CVAR_TRUE,
+ /* core.ignorecase */
+ GIT_IGNORECASE_DEFAULT = GIT_CVAR_FALSE,
+ /* core.filemode */
+ GIT_FILEMODE_DEFAULT = GIT_CVAR_TRUE,
+ /* core.ignorestat */
+ GIT_IGNORESTAT_DEFAULT = GIT_CVAR_FALSE,
+ /* core.trustctime */
+ GIT_TRUSTCTIME_DEFAULT = GIT_CVAR_TRUE,
+ /* core.abbrev */
+ GIT_ABBREV_DEFAULT = 7,
+ /* core.precomposeunicode */
+ GIT_PRECOMPOSE_DEFAULT = GIT_CVAR_FALSE,
+ /* core.safecrlf */
+ GIT_SAFE_CRLF_DEFAULT = GIT_CVAR_FALSE,
+ /* core.logallrefupdates */
+ GIT_LOGALLREFUPDATES_UNSET = 2,
+ GIT_LOGALLREFUPDATES_DEFAULT = GIT_LOGALLREFUPDATES_UNSET,
+} git_cvar_value;
+
+/* internal repository init flags */
+enum {
+ GIT_REPOSITORY_INIT__HAS_DOTGIT = (1u << 16),
+ GIT_REPOSITORY_INIT__NATURAL_WD = (1u << 17),
+ GIT_REPOSITORY_INIT__IS_REINIT = (1u << 18),
};
+/** Internal structure for repository object */
struct git_repository {
- git_odb *db;
+ git_odb *_odb;
+ git_refdb *_refdb;
+ git_config *_config;
+ git_index *_index;
+ git_submodule_cache *_submodules;
git_cache objects;
- git_refcache references;
+ git_attr_cache *attrcache;
+ git_diff_driver_registry *diff_drivers;
char *path_repository;
- char *path_index;
- char *path_odb;
- char *path_workdir;
+ char *workdir;
+ char *namespace;
unsigned is_bare:1;
unsigned int lru_counter;
+
+ git_cvar_value cvar_cache[GIT_CVAR_CACHE_MAX];
};
-/* fully free the object; internal method, do not
- * export */
-void git_object__free(void *object);
+GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo)
+{
+ return repo->attrcache;
+}
+
+int git_repository_head_tree(git_tree **tree, git_repository *repo);
+
+/*
+ * Weak pointers to repository internals.
+ *
+ * The returned pointers do not need to be freed. Do not keep
+ * permanent references to these (i.e. between API calls), since they may
+ * become invalidated if the user replaces a repository internal.
+ */
+int git_repository_config__weakptr(git_config **out, git_repository *repo);
+int git_repository_odb__weakptr(git_odb **out, git_repository *repo);
+int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo);
+int git_repository_index__weakptr(git_index **out, git_repository *repo);
+
+/*
+ * CVAR cache
+ *
+ * Efficient access to the most used config variables of a repository.
+ * The cache is cleared everytime the config backend is replaced.
+ */
+int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar);
+void git_repository__cvar_cache_clear(git_repository *repo);
+
+GIT_INLINE(int) git_repository__ensure_not_bare(
+ git_repository *repo,
+ const char *operation_name)
+{
+ if (!git_repository_is_bare(repo))
+ return 0;
+
+ giterr_set(
+ GITERR_REPOSITORY,
+ "Cannot %s. This operation is not allowed against bare repositories.",
+ operation_name);
+
+ return GIT_EBAREREPO;
+}
-int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header);
-void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid);
+int git_repository__cleanup_files(git_repository *repo, const char *files[], size_t files_len);
#endif
diff --git a/src/reset.c b/src/reset.c
new file mode 100644
index 000000000..248c91d3a
--- /dev/null
+++ b/src/reset.c
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "commit.h"
+#include "tag.h"
+#include "merge.h"
+#include "diff.h"
+#include "git2/reset.h"
+#include "git2/checkout.h"
+#include "git2/merge.h"
+#include "git2/refs.h"
+
+#define ERROR_MSG "Cannot perform reset"
+
+int git_reset_default(
+ git_repository *repo,
+ git_object *target,
+ git_strarray* pathspecs)
+{
+ git_object *commit = NULL;
+ git_tree *tree = NULL;
+ git_diff *diff = NULL;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ size_t i, max_i;
+ git_index_entry entry;
+ int error;
+ git_index *index = NULL;
+
+ assert(pathspecs != NULL && pathspecs->count > 0);
+
+ memset(&entry, 0, sizeof(git_index_entry));
+
+ if ((error = git_repository_index(&index, repo)) < 0)
+ goto cleanup;
+
+ if (target) {
+ if (git_object_owner(target) != repo) {
+ giterr_set(GITERR_OBJECT,
+ "%s_default - The given target does not belong to this repository.", ERROR_MSG);
+ return -1;
+ }
+
+ if ((error = git_object_peel(&commit, target, GIT_OBJ_COMMIT)) < 0 ||
+ (error = git_commit_tree(&tree, (git_commit *)commit)) < 0)
+ goto cleanup;
+ }
+
+ opts.pathspec = *pathspecs;
+ opts.flags = GIT_DIFF_REVERSE;
+
+ if ((error = git_diff_tree_to_index(
+ &diff, repo, tree, index, &opts)) < 0)
+ goto cleanup;
+
+ for (i = 0, max_i = git_diff_num_deltas(diff); i < max_i; ++i) {
+ const git_diff_delta *delta = git_diff_get_delta(diff, i);
+
+ assert(delta->status == GIT_DELTA_ADDED ||
+ delta->status == GIT_DELTA_MODIFIED ||
+ delta->status == GIT_DELTA_DELETED);
+
+ error = git_index_conflict_remove(index, delta->old_file.path);
+ if (error < 0) {
+ if (delta->status == GIT_DELTA_ADDED && error == GIT_ENOTFOUND)
+ giterr_clear();
+ else
+ goto cleanup;
+ }
+
+ if (delta->status == GIT_DELTA_DELETED) {
+ if ((error = git_index_remove(index, delta->old_file.path, 0)) < 0)
+ goto cleanup;
+ } else {
+ entry.mode = delta->new_file.mode;
+ git_oid_cpy(&entry.id, &delta->new_file.id);
+ entry.path = (char *)delta->new_file.path;
+
+ if ((error = git_index_add(index, &entry)) < 0)
+ goto cleanup;
+ }
+ }
+
+ error = git_index_write(index);
+
+cleanup:
+ git_object_free(commit);
+ git_tree_free(tree);
+ git_index_free(index);
+ git_diff_free(diff);
+
+ return error;
+}
+
+int git_reset(
+ git_repository *repo,
+ git_object *target,
+ git_reset_t reset_type,
+ git_signature *signature,
+ const char *log_message)
+{
+ git_object *commit = NULL;
+ git_index *index = NULL;
+ git_tree *tree = NULL;
+ int error = 0;
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_buf log_message_buf = GIT_BUF_INIT;
+
+ assert(repo && target);
+
+ if (git_object_owner(target) != repo) {
+ giterr_set(GITERR_OBJECT,
+ "%s - The given target does not belong to this repository.", ERROR_MSG);
+ return -1;
+ }
+
+ if (reset_type != GIT_RESET_SOFT &&
+ (error = git_repository__ensure_not_bare(repo,
+ reset_type == GIT_RESET_MIXED ? "reset mixed" : "reset hard")) < 0)
+ return error;
+
+ if ((error = git_object_peel(&commit, target, GIT_OBJ_COMMIT)) < 0 ||
+ (error = git_repository_index(&index, repo)) < 0 ||
+ (error = git_commit_tree(&tree, (git_commit *)commit)) < 0)
+ goto cleanup;
+
+ if (reset_type == GIT_RESET_SOFT &&
+ (git_repository_state(repo) == GIT_REPOSITORY_STATE_MERGE ||
+ git_index_has_conflicts(index)))
+ {
+ giterr_set(GITERR_OBJECT, "%s (soft) in the middle of a merge.", ERROR_MSG);
+ error = GIT_EUNMERGED;
+ goto cleanup;
+ }
+
+ if (log_message)
+ git_buf_sets(&log_message_buf, log_message);
+ else
+ git_buf_sets(&log_message_buf, "reset: moving");
+
+ /* move HEAD to the new target */
+ if ((error = git_reference__update_terminal(repo, GIT_HEAD_FILE,
+ git_object_id(commit), signature, git_buf_cstr(&log_message_buf))) < 0)
+ goto cleanup;
+
+ if (reset_type == GIT_RESET_HARD) {
+ /* overwrite working directory with HEAD */
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_SKIP_UNMERGED;
+
+ if ((error = git_checkout_tree(repo, (git_object *)tree, &opts)) < 0)
+ goto cleanup;
+ }
+
+ if (reset_type > GIT_RESET_SOFT) {
+ /* reset index to the target content */
+
+ if ((error = git_index_read_tree(index, tree)) < 0 ||
+ (error = git_index_write(index)) < 0)
+ goto cleanup;
+
+ if ((error = git_repository_state_cleanup(repo)) < 0) {
+ giterr_set(GITERR_INDEX, "%s - failed to clean up merge data", ERROR_MSG);
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ git_object_free(commit);
+ git_index_free(index);
+ git_tree_free(tree);
+ git_buf_free(&log_message_buf);
+
+ return error;
+}
diff --git a/src/revert.c b/src/revert.c
new file mode 100644
index 000000000..9c587724b
--- /dev/null
+++ b/src/revert.c
@@ -0,0 +1,228 @@
+/*
+* Copyright (C) the libgit2 contributors. All rights reserved.
+*
+* 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 "filebuf.h"
+#include "merge.h"
+
+#include "git2/types.h"
+#include "git2/merge.h"
+#include "git2/revert.h"
+#include "git2/commit.h"
+#include "git2/sys/commit.h"
+
+#define GIT_REVERT_FILE_MODE 0666
+
+static int write_revert_head(
+ git_repository *repo,
+ const char *commit_oidstr)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ int error = 0;
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_REVERT_HEAD_FILE)) >= 0 &&
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_REVERT_FILE_MODE)) >= 0 &&
+ (error = git_filebuf_printf(&file, "%s\n", commit_oidstr)) >= 0)
+ error = git_filebuf_commit(&file);
+
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ return error;
+}
+
+static int write_merge_msg(
+ git_repository *repo,
+ const char *commit_oidstr,
+ const char *commit_msgline)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf file_path = GIT_BUF_INIT;
+ int error = 0;
+
+ if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 ||
+ (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_REVERT_FILE_MODE)) < 0 ||
+ (error = git_filebuf_printf(&file, "Revert \"%s\"\n\nThis reverts commit %s.\n",
+ commit_msgline, commit_oidstr)) < 0)
+ goto cleanup;
+
+ error = git_filebuf_commit(&file);
+
+cleanup:
+ if (error < 0)
+ git_filebuf_cleanup(&file);
+
+ git_buf_free(&file_path);
+
+ return error;
+}
+
+static int revert_normalize_opts(
+ git_repository *repo,
+ git_revert_options *opts,
+ const git_revert_options *given,
+ const char *their_label)
+{
+ int error = 0;
+ unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE_CREATE |
+ GIT_CHECKOUT_ALLOW_CONFLICTS;
+
+ GIT_UNUSED(repo);
+
+ if (given != NULL)
+ memcpy(opts, given, sizeof(git_revert_options));
+ else {
+ git_revert_options default_opts = GIT_REVERT_OPTIONS_INIT;
+ memcpy(opts, &default_opts, sizeof(git_revert_options));
+ }
+
+ if (!opts->checkout_opts.checkout_strategy)
+ opts->checkout_opts.checkout_strategy = default_checkout_strategy;
+
+ if (!opts->checkout_opts.our_label)
+ opts->checkout_opts.our_label = "HEAD";
+
+ if (!opts->checkout_opts.their_label)
+ opts->checkout_opts.their_label = their_label;
+
+ return error;
+}
+
+static int revert_state_cleanup(git_repository *repo)
+{
+ const char *state_files[] = { GIT_REVERT_HEAD_FILE, GIT_MERGE_MSG_FILE };
+
+ return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files));
+}
+
+static int revert_seterr(git_commit *commit, const char *fmt)
+{
+ char commit_oidstr[GIT_OID_HEXSZ + 1];
+
+ git_oid_fmt(commit_oidstr, git_commit_id(commit));
+ commit_oidstr[GIT_OID_HEXSZ] = '\0';
+
+ giterr_set(GITERR_REVERT, fmt, commit_oidstr);
+
+ return -1;
+}
+
+int git_revert_commit(
+ git_index **out,
+ git_repository *repo,
+ git_commit *revert_commit,
+ git_commit *our_commit,
+ unsigned int mainline,
+ const git_merge_options *merge_opts)
+{
+ git_commit *parent_commit = NULL;
+ git_tree *parent_tree = NULL, *our_tree = NULL, *revert_tree = NULL;
+ int parent = 0, error = 0;
+
+ assert(out && repo && revert_commit && our_commit);
+
+ if (git_commit_parentcount(revert_commit) > 1) {
+ if (!mainline)
+ return revert_seterr(revert_commit,
+ "Mainline branch is not specified but %s is a merge commit");
+
+ parent = mainline;
+ } else {
+ if (mainline)
+ return revert_seterr(revert_commit,
+ "Mainline branch specified but %s is not a merge commit");
+
+ parent = git_commit_parentcount(revert_commit);
+ }
+
+ if (parent &&
+ ((error = git_commit_parent(&parent_commit, revert_commit, (parent - 1))) < 0 ||
+ (error = git_commit_tree(&parent_tree, parent_commit)) < 0))
+ goto done;
+
+ if ((error = git_commit_tree(&revert_tree, revert_commit)) < 0 ||
+ (error = git_commit_tree(&our_tree, our_commit)) < 0)
+ goto done;
+
+ error = git_merge_trees(out, repo, revert_tree, our_tree, parent_tree, merge_opts);
+
+done:
+ git_tree_free(parent_tree);
+ git_tree_free(our_tree);
+ git_tree_free(revert_tree);
+ git_commit_free(parent_commit);
+
+ return error;
+}
+
+int git_revert(
+ git_repository *repo,
+ git_commit *commit,
+ const git_revert_options *given_opts)
+{
+ git_revert_options opts;
+ git_reference *our_ref = NULL;
+ git_commit *our_commit = NULL;
+ char commit_oidstr[GIT_OID_HEXSZ + 1];
+ const char *commit_msg;
+ git_buf their_label = GIT_BUF_INIT;
+ git_index *index_new = NULL, *index_repo = NULL;
+ int error;
+
+ assert(repo && commit);
+
+ GITERR_CHECK_VERSION(given_opts, GIT_REVERT_OPTIONS_VERSION, "git_revert_options");
+
+ if ((error = git_repository__ensure_not_bare(repo, "revert")) < 0)
+ return error;
+
+ git_oid_fmt(commit_oidstr, git_commit_id(commit));
+ commit_oidstr[GIT_OID_HEXSZ] = '\0';
+
+ if ((commit_msg = git_commit_summary(commit)) == NULL) {
+ error = -1;
+ goto on_error;
+ }
+
+ if ((error = git_buf_printf(&their_label, "parent of %.7s... %s", commit_oidstr, commit_msg)) < 0 ||
+ (error = revert_normalize_opts(repo, &opts, given_opts, git_buf_cstr(&their_label))) < 0 ||
+ (error = write_revert_head(repo, commit_oidstr)) < 0 ||
+ (error = write_merge_msg(repo, commit_oidstr, commit_msg)) < 0 ||
+ (error = git_repository_head(&our_ref, repo)) < 0 ||
+ (error = git_reference_peel((git_object **)&our_commit, our_ref, GIT_OBJ_COMMIT)) < 0 ||
+ (error = git_revert_commit(&index_new, repo, commit, our_commit, opts.mainline, &opts.merge_opts)) < 0 ||
+ (error = git_merge__indexes(repo, index_new)) < 0 ||
+ (error = git_repository_index(&index_repo, repo)) < 0 ||
+ (error = git_merge__append_conflicts_to_merge_msg(repo, index_repo)) < 0 ||
+ (error = git_checkout_index(repo, index_repo, &opts.checkout_opts)) < 0)
+ goto on_error;
+
+ goto done;
+
+on_error:
+ revert_state_cleanup(repo);
+
+done:
+ git_index_free(index_new);
+ git_index_free(index_repo);
+ git_commit_free(our_commit);
+ git_reference_free(our_ref);
+ git_buf_free(&their_label);
+
+ return error;
+}
+
+int git_revert_init_options(git_revert_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_revert_options, GIT_REVERT_OPTIONS_INIT);
+ return 0;
+}
diff --git a/src/revparse.c b/src/revparse.c
new file mode 100644
index 000000000..60872e187
--- /dev/null
+++ b/src/revparse.c
@@ -0,0 +1,916 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include <assert.h>
+
+#include "common.h"
+#include "buffer.h"
+#include "tree.h"
+#include "refdb.h"
+
+#include "git2.h"
+
+static int maybe_sha_or_abbrev(git_object** out, git_repository *repo, const char *spec, size_t speclen)
+{
+ git_oid oid;
+
+ if (git_oid_fromstrn(&oid, spec, speclen) < 0)
+ return GIT_ENOTFOUND;
+
+ return git_object_lookup_prefix(out, repo, &oid, speclen, GIT_OBJ_ANY);
+}
+
+static int maybe_sha(git_object** out, git_repository *repo, const char *spec)
+{
+ size_t speclen = strlen(spec);
+
+ if (speclen != GIT_OID_HEXSZ)
+ return GIT_ENOTFOUND;
+
+ return maybe_sha_or_abbrev(out, repo, spec, speclen);
+}
+
+static int maybe_abbrev(git_object** out, git_repository *repo, const char *spec)
+{
+ size_t speclen = strlen(spec);
+
+ return maybe_sha_or_abbrev(out, repo, spec, speclen);
+}
+
+static int build_regex(regex_t *regex, const char *pattern)
+{
+ int error;
+
+ if (*pattern == '\0') {
+ giterr_set(GITERR_REGEX, "Empty pattern");
+ return GIT_EINVALIDSPEC;
+ }
+
+ error = regcomp(regex, pattern, REG_EXTENDED);
+ if (!error)
+ return 0;
+
+ error = giterr_set_regex(regex, error);
+
+ regfree(regex);
+
+ return error;
+}
+
+static int maybe_describe(git_object**out, git_repository *repo, const char *spec)
+{
+ const char *substr;
+ int error;
+ regex_t regex;
+
+ substr = strstr(spec, "-g");
+
+ if (substr == NULL)
+ return GIT_ENOTFOUND;
+
+ if (build_regex(&regex, ".+-[0-9]+-g[0-9a-fA-F]+") < 0)
+ return -1;
+
+ error = regexec(&regex, spec, 0, NULL, 0);
+ regfree(&regex);
+
+ if (error)
+ return GIT_ENOTFOUND;
+
+ return maybe_abbrev(out, repo, substr+2);
+}
+
+static int revparse_lookup_object(
+ git_object **object_out,
+ git_reference **reference_out,
+ git_repository *repo,
+ const char *spec)
+{
+ int error;
+ git_reference *ref;
+
+ if ((error = maybe_sha(object_out, repo, spec)) != GIT_ENOTFOUND)
+ return error;
+
+ error = git_reference_dwim(&ref, repo, spec);
+ if (!error) {
+
+ error = git_object_lookup(
+ object_out, repo, git_reference_target(ref), GIT_OBJ_ANY);
+
+ if (!error)
+ *reference_out = ref;
+
+ return error;
+ }
+
+ if (error != GIT_ENOTFOUND)
+ return error;
+
+ if ((strlen(spec) < GIT_OID_HEXSZ) &&
+ ((error = maybe_abbrev(object_out, repo, spec)) != GIT_ENOTFOUND))
+ return error;
+
+ if ((error = maybe_describe(object_out, repo, spec)) != GIT_ENOTFOUND)
+ return error;
+
+ giterr_set(GITERR_REFERENCE, "Revspec '%s' not found.", spec);
+ return GIT_ENOTFOUND;
+}
+
+static int try_parse_numeric(int *n, const char *curly_braces_content)
+{
+ int32_t content;
+ const char *end_ptr;
+
+ if (git__strtol32(&content, curly_braces_content, &end_ptr, 10) < 0)
+ return -1;
+
+ if (*end_ptr != '\0')
+ return -1;
+
+ *n = (int)content;
+ return 0;
+}
+
+static int retrieve_previously_checked_out_branch_or_revision(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position)
+{
+ git_reference *ref = NULL;
+ git_reflog *reflog = NULL;
+ regex_t preg;
+ int error = -1;
+ size_t i, numentries, cur;
+ const git_reflog_entry *entry;
+ const char *msg;
+ regmatch_t regexmatches[2];
+ git_buf buf = GIT_BUF_INIT;
+
+ cur = position;
+
+ if (*identifier != '\0' || *base_ref != NULL)
+ return GIT_EINVALIDSPEC;
+
+ if (build_regex(&preg, "checkout: moving from (.*) to .*") < 0)
+ return -1;
+
+ if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0)
+ goto cleanup;
+
+ if (git_reflog_read(&reflog, repo, GIT_HEAD_FILE) < 0)
+ goto cleanup;
+
+ numentries = git_reflog_entrycount(reflog);
+
+ for (i = 0; i < numentries; i++) {
+ entry = git_reflog_entry_byindex(reflog, i);
+ msg = git_reflog_entry_message(entry);
+ if (!msg)
+ continue;
+
+ if (regexec(&preg, msg, 2, regexmatches, 0))
+ continue;
+
+ cur--;
+
+ if (cur > 0)
+ continue;
+
+ git_buf_put(&buf, msg+regexmatches[1].rm_so, regexmatches[1].rm_eo - regexmatches[1].rm_so);
+
+ if ((error = git_reference_dwim(base_ref, repo, git_buf_cstr(&buf))) == 0)
+ goto cleanup;
+
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto cleanup;
+
+ error = maybe_abbrev(out, repo, git_buf_cstr(&buf));
+
+ goto cleanup;
+ }
+
+ error = GIT_ENOTFOUND;
+
+cleanup:
+ git_reference_free(ref);
+ git_buf_free(&buf);
+ regfree(&preg);
+ git_reflog_free(reflog);
+ return error;
+}
+
+static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, size_t identifier)
+{
+ git_reflog *reflog;
+ int error = -1;
+ size_t numentries;
+ const git_reflog_entry *entry;
+ bool search_by_pos = (identifier <= 100000000);
+
+ if (git_reflog_read(&reflog, git_reference_owner(ref), git_reference_name(ref)) < 0)
+ return -1;
+
+ numentries = git_reflog_entrycount(reflog);
+
+ if (search_by_pos) {
+ if (numentries < identifier + 1) {
+ giterr_set(
+ GITERR_REFERENCE,
+ "Reflog for '%s' has only %"PRIuZ" entries, asked for %"PRIuZ,
+ git_reference_name(ref), numentries, identifier);
+
+ error = GIT_ENOTFOUND;
+ goto cleanup;
+ }
+
+ entry = git_reflog_entry_byindex(reflog, identifier);
+ git_oid_cpy(oid, git_reflog_entry_id_new(entry));
+ error = 0;
+ goto cleanup;
+
+ } else {
+ size_t i;
+ git_time commit_time;
+
+ for (i = 0; i < numentries; i++) {
+ entry = git_reflog_entry_byindex(reflog, i);
+ commit_time = git_reflog_entry_committer(entry)->when;
+
+ if (commit_time.time > (git_time_t)identifier)
+ continue;
+
+ git_oid_cpy(oid, git_reflog_entry_id_new(entry));
+ error = 0;
+ goto cleanup;
+ }
+
+ error = GIT_ENOTFOUND;
+ }
+
+cleanup:
+ git_reflog_free(reflog);
+ return error;
+}
+
+static int retrieve_revobject_from_reflog(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position)
+{
+ git_reference *ref;
+ git_oid oid;
+ int error = -1;
+
+ if (*base_ref == NULL) {
+ if ((error = git_reference_dwim(&ref, repo, identifier)) < 0)
+ return error;
+ } else {
+ ref = *base_ref;
+ *base_ref = NULL;
+ }
+
+ if (position == 0) {
+ error = git_object_lookup(out, repo, git_reference_target(ref), GIT_OBJ_ANY);
+ goto cleanup;
+ }
+
+ if ((error = retrieve_oid_from_reflog(&oid, ref, position)) < 0)
+ goto cleanup;
+
+ error = git_object_lookup(out, repo, &oid, GIT_OBJ_ANY);
+
+cleanup:
+ git_reference_free(ref);
+ return error;
+}
+
+static int retrieve_remote_tracking_reference(git_reference **base_ref, const char *identifier, git_repository *repo)
+{
+ git_reference *tracking, *ref;
+ int error = -1;
+
+ if (*base_ref == NULL) {
+ if ((error = git_reference_dwim(&ref, repo, identifier)) < 0)
+ return error;
+ } else {
+ ref = *base_ref;
+ *base_ref = NULL;
+ }
+
+ if (!git_reference_is_branch(ref)) {
+ error = GIT_EINVALIDSPEC;
+ goto cleanup;
+ }
+
+ if ((error = git_branch_upstream(&tracking, ref)) < 0)
+ goto cleanup;
+
+ *base_ref = tracking;
+
+cleanup:
+ git_reference_free(ref);
+ return error;
+}
+
+static int handle_at_syntax(git_object **out, git_reference **ref, const char *spec, size_t identifier_len, git_repository* repo, const char *curly_braces_content)
+{
+ bool is_numeric;
+ int parsed = 0, error = -1;
+ git_buf identifier = GIT_BUF_INIT;
+ git_time_t timestamp;
+
+ assert(*out == NULL);
+
+ if (git_buf_put(&identifier, spec, identifier_len) < 0)
+ return -1;
+
+ is_numeric = !try_parse_numeric(&parsed, curly_braces_content);
+
+ if (*curly_braces_content == '-' && (!is_numeric || parsed == 0)) {
+ error = GIT_EINVALIDSPEC;
+ goto cleanup;
+ }
+
+ if (is_numeric) {
+ if (parsed < 0)
+ error = retrieve_previously_checked_out_branch_or_revision(out, ref, repo, git_buf_cstr(&identifier), -parsed);
+ else
+ error = retrieve_revobject_from_reflog(out, ref, repo, git_buf_cstr(&identifier), parsed);
+
+ goto cleanup;
+ }
+
+ if (!strcmp(curly_braces_content, "u") || !strcmp(curly_braces_content, "upstream")) {
+ error = retrieve_remote_tracking_reference(ref, git_buf_cstr(&identifier), repo);
+
+ goto cleanup;
+ }
+
+ if (git__date_parse(&timestamp, curly_braces_content) < 0)
+ goto cleanup;
+
+ error = retrieve_revobject_from_reflog(out, ref, repo, git_buf_cstr(&identifier), (size_t)timestamp);
+
+cleanup:
+ git_buf_free(&identifier);
+ return error;
+}
+
+static git_otype parse_obj_type(const char *str)
+{
+ if (!strcmp(str, "commit"))
+ return GIT_OBJ_COMMIT;
+
+ if (!strcmp(str, "tree"))
+ return GIT_OBJ_TREE;
+
+ if (!strcmp(str, "blob"))
+ return GIT_OBJ_BLOB;
+
+ if (!strcmp(str, "tag"))
+ return GIT_OBJ_TAG;
+
+ return GIT_OBJ_BAD;
+}
+
+static int dereference_to_non_tag(git_object **out, git_object *obj)
+{
+ if (git_object_type(obj) == GIT_OBJ_TAG)
+ return git_tag_peel(out, (git_tag *)obj);
+
+ return git_object_dup(out, obj);
+}
+
+static int handle_caret_parent_syntax(git_object **out, git_object *obj, int n)
+{
+ git_object *temp_commit = NULL;
+ int error;
+
+ if ((error = git_object_peel(&temp_commit, obj, GIT_OBJ_COMMIT)) < 0)
+ return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ?
+ GIT_EINVALIDSPEC : error;
+
+ if (n == 0) {
+ *out = temp_commit;
+ return 0;
+ }
+
+ error = git_commit_parent((git_commit **)out, (git_commit*)temp_commit, n - 1);
+
+ git_object_free(temp_commit);
+ return error;
+}
+
+static int handle_linear_syntax(git_object **out, git_object *obj, int n)
+{
+ git_object *temp_commit = NULL;
+ int error;
+
+ if ((error = git_object_peel(&temp_commit, obj, GIT_OBJ_COMMIT)) < 0)
+ return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ?
+ GIT_EINVALIDSPEC : error;
+
+ error = git_commit_nth_gen_ancestor((git_commit **)out, (git_commit*)temp_commit, n);
+
+ git_object_free(temp_commit);
+ return error;
+}
+
+static int handle_colon_syntax(
+ git_object **out,
+ git_object *obj,
+ const char *path)
+{
+ git_object *tree;
+ int error = -1;
+ git_tree_entry *entry = NULL;
+
+ if ((error = git_object_peel(&tree, obj, GIT_OBJ_TREE)) < 0)
+ return error == GIT_ENOTFOUND ? GIT_EINVALIDSPEC : error;
+
+ if (*path == '\0') {
+ *out = tree;
+ return 0;
+ }
+
+ /*
+ * TODO: Handle the relative path syntax
+ * (:./relative/path and :../relative/path)
+ */
+ if ((error = git_tree_entry_bypath(&entry, (git_tree *)tree, path)) < 0)
+ goto cleanup;
+
+ error = git_tree_entry_to_object(out, git_object_owner(tree), entry);
+
+cleanup:
+ git_tree_entry_free(entry);
+ git_object_free(tree);
+
+ return error;
+}
+
+static int walk_and_search(git_object **out, git_revwalk *walk, regex_t *regex)
+{
+ int error;
+ git_oid oid;
+ git_object *obj;
+
+ while (!(error = git_revwalk_next(&oid, walk))) {
+
+ error = git_object_lookup(&obj, git_revwalk_repository(walk), &oid, GIT_OBJ_COMMIT);
+ if ((error < 0) && (error != GIT_ENOTFOUND))
+ return -1;
+
+ if (!regexec(regex, git_commit_message((git_commit*)obj), 0, NULL, 0)) {
+ *out = obj;
+ return 0;
+ }
+
+ git_object_free(obj);
+ }
+
+ if (error < 0 && error == GIT_ITEROVER)
+ error = GIT_ENOTFOUND;
+
+ return error;
+}
+
+static int handle_grep_syntax(git_object **out, git_repository *repo, const git_oid *spec_oid, const char *pattern)
+{
+ regex_t preg;
+ git_revwalk *walk = NULL;
+ int error;
+
+ if ((error = build_regex(&preg, pattern)) < 0)
+ return error;
+
+ if ((error = git_revwalk_new(&walk, repo)) < 0)
+ goto cleanup;
+
+ git_revwalk_sorting(walk, GIT_SORT_TIME);
+
+ if (spec_oid == NULL) {
+ if ((error = git_revwalk_push_glob(walk, "refs/*")) < 0)
+ goto cleanup;
+ } else if ((error = git_revwalk_push(walk, spec_oid)) < 0)
+ goto cleanup;
+
+ error = walk_and_search(out, walk, &preg);
+
+cleanup:
+ regfree(&preg);
+ git_revwalk_free(walk);
+
+ return error;
+}
+
+static int handle_caret_curly_syntax(git_object **out, git_object *obj, const char *curly_braces_content)
+{
+ git_otype expected_type;
+
+ if (*curly_braces_content == '\0')
+ return dereference_to_non_tag(out, obj);
+
+ if (*curly_braces_content == '/')
+ return handle_grep_syntax(out, git_object_owner(obj), git_object_id(obj), curly_braces_content + 1);
+
+ expected_type = parse_obj_type(curly_braces_content);
+
+ if (expected_type == GIT_OBJ_BAD)
+ return GIT_EINVALIDSPEC;
+
+ return git_object_peel(out, obj, expected_type);
+}
+
+static int extract_curly_braces_content(git_buf *buf, const char *spec, size_t *pos)
+{
+ git_buf_clear(buf);
+
+ assert(spec[*pos] == '^' || spec[*pos] == '@');
+
+ (*pos)++;
+
+ if (spec[*pos] == '\0' || spec[*pos] != '{')
+ return GIT_EINVALIDSPEC;
+
+ (*pos)++;
+
+ while (spec[*pos] != '}') {
+ if (spec[*pos] == '\0')
+ return GIT_EINVALIDSPEC;
+
+ git_buf_putc(buf, spec[(*pos)++]);
+ }
+
+ (*pos)++;
+
+ return 0;
+}
+
+static int extract_path(git_buf *buf, const char *spec, size_t *pos)
+{
+ git_buf_clear(buf);
+
+ assert(spec[*pos] == ':');
+
+ (*pos)++;
+
+ if (git_buf_puts(buf, spec + *pos) < 0)
+ return -1;
+
+ *pos += git_buf_len(buf);
+
+ return 0;
+}
+
+static int extract_how_many(int *n, const char *spec, size_t *pos)
+{
+ const char *end_ptr;
+ int parsed, accumulated;
+ char kind = spec[*pos];
+
+ assert(spec[*pos] == '^' || spec[*pos] == '~');
+
+ accumulated = 0;
+
+ do {
+ do {
+ (*pos)++;
+ accumulated++;
+ } while (spec[(*pos)] == kind && kind == '~');
+
+ if (git__isdigit(spec[*pos])) {
+ if (git__strtol32(&parsed, spec + *pos, &end_ptr, 10) < 0)
+ return GIT_EINVALIDSPEC;
+
+ accumulated += (parsed - 1);
+ *pos = end_ptr - spec;
+ }
+
+ } while (spec[(*pos)] == kind && kind == '~');
+
+ *n = accumulated;
+
+ return 0;
+}
+
+static int object_from_reference(git_object **object, git_reference *reference)
+{
+ git_reference *resolved = NULL;
+ int error;
+
+ if (git_reference_resolve(&resolved, reference) < 0)
+ return -1;
+
+ error = git_object_lookup(object, reference->db->repo, git_reference_target(resolved), GIT_OBJ_ANY);
+ git_reference_free(resolved);
+
+ return error;
+}
+
+static int ensure_base_rev_loaded(git_object **object, git_reference **reference, const char *spec, size_t identifier_len, git_repository *repo, bool allow_empty_identifier)
+{
+ int error;
+ git_buf identifier = GIT_BUF_INIT;
+
+ if (*object != NULL)
+ return 0;
+
+ if (*reference != NULL)
+ return object_from_reference(object, *reference);
+
+ if (!allow_empty_identifier && identifier_len == 0)
+ return GIT_EINVALIDSPEC;
+
+ if (git_buf_put(&identifier, spec, identifier_len) < 0)
+ return -1;
+
+ error = revparse_lookup_object(object, reference, repo, git_buf_cstr(&identifier));
+ git_buf_free(&identifier);
+
+ return error;
+}
+
+static int ensure_base_rev_is_not_known_yet(git_object *object)
+{
+ if (object == NULL)
+ return 0;
+
+ return GIT_EINVALIDSPEC;
+}
+
+static bool any_left_hand_identifier(git_object *object, git_reference *reference, size_t identifier_len)
+{
+ if (object != NULL)
+ return true;
+
+ if (reference != NULL)
+ return true;
+
+ if (identifier_len > 0)
+ return true;
+
+ return false;
+}
+
+static int ensure_left_hand_identifier_is_not_known_yet(git_object *object, git_reference *reference)
+{
+ if (!ensure_base_rev_is_not_known_yet(object) && reference == NULL)
+ return 0;
+
+ return GIT_EINVALIDSPEC;
+}
+
+int revparse__ext(
+ git_object **object_out,
+ git_reference **reference_out,
+ size_t *identifier_len_out,
+ git_repository *repo,
+ const char *spec)
+{
+ size_t pos = 0, identifier_len = 0;
+ int error = -1, n;
+ git_buf buf = GIT_BUF_INIT;
+
+ git_reference *reference = NULL;
+ git_object *base_rev = NULL;
+
+ bool should_return_reference = true;
+
+ assert(object_out && reference_out && repo && spec);
+
+ *object_out = NULL;
+ *reference_out = NULL;
+
+ while (spec[pos]) {
+ switch (spec[pos]) {
+ case '^':
+ should_return_reference = false;
+
+ if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
+ goto cleanup;
+
+ if (spec[pos+1] == '{') {
+ git_object *temp_object = NULL;
+
+ if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0)
+ goto cleanup;
+
+ if ((error = handle_caret_curly_syntax(&temp_object, base_rev, git_buf_cstr(&buf))) < 0)
+ goto cleanup;
+
+ git_object_free(base_rev);
+ base_rev = temp_object;
+ } else {
+ git_object *temp_object = NULL;
+
+ if ((error = extract_how_many(&n, spec, &pos)) < 0)
+ goto cleanup;
+
+ if ((error = handle_caret_parent_syntax(&temp_object, base_rev, n)) < 0)
+ goto cleanup;
+
+ git_object_free(base_rev);
+ base_rev = temp_object;
+ }
+ break;
+
+ case '~':
+ {
+ git_object *temp_object = NULL;
+
+ should_return_reference = false;
+
+ if ((error = extract_how_many(&n, spec, &pos)) < 0)
+ goto cleanup;
+
+ if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
+ goto cleanup;
+
+ if ((error = handle_linear_syntax(&temp_object, base_rev, n)) < 0)
+ goto cleanup;
+
+ git_object_free(base_rev);
+ base_rev = temp_object;
+ break;
+ }
+
+ case ':':
+ {
+ git_object *temp_object = NULL;
+
+ should_return_reference = false;
+
+ if ((error = extract_path(&buf, spec, &pos)) < 0)
+ goto cleanup;
+
+ if (any_left_hand_identifier(base_rev, reference, identifier_len)) {
+ if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, true)) < 0)
+ goto cleanup;
+
+ if ((error = handle_colon_syntax(&temp_object, base_rev, git_buf_cstr(&buf))) < 0)
+ goto cleanup;
+ } else {
+ if (*git_buf_cstr(&buf) == '/') {
+ if ((error = handle_grep_syntax(&temp_object, repo, NULL, git_buf_cstr(&buf) + 1)) < 0)
+ goto cleanup;
+ } else {
+
+ /*
+ * TODO: support merge-stage path lookup (":2:Makefile")
+ * and plain index blob lookup (:i-am/a/blob)
+ */
+ giterr_set(GITERR_INVALID, "Unimplemented");
+ error = GIT_ERROR;
+ goto cleanup;
+ }
+ }
+
+ git_object_free(base_rev);
+ base_rev = temp_object;
+ break;
+ }
+
+ case '@':
+ {
+ if (spec[pos+1] == '{') {
+ git_object *temp_object = NULL;
+
+ if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0)
+ goto cleanup;
+
+ if ((error = ensure_base_rev_is_not_known_yet(base_rev)) < 0)
+ goto cleanup;
+
+ if ((error = handle_at_syntax(&temp_object, &reference, spec, identifier_len, repo, git_buf_cstr(&buf))) < 0)
+ goto cleanup;
+
+ if (temp_object != NULL)
+ base_rev = temp_object;
+ break;
+ } else {
+ /* Fall through */
+ }
+ }
+
+ default:
+ if ((error = ensure_left_hand_identifier_is_not_known_yet(base_rev, reference)) < 0)
+ goto cleanup;
+
+ pos++;
+ identifier_len++;
+ }
+ }
+
+ if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
+ goto cleanup;
+
+ if (!should_return_reference) {
+ git_reference_free(reference);
+ reference = NULL;
+ }
+
+ *object_out = base_rev;
+ *reference_out = reference;
+ *identifier_len_out = identifier_len;
+ error = 0;
+
+cleanup:
+ if (error) {
+ if (error == GIT_EINVALIDSPEC)
+ giterr_set(GITERR_INVALID,
+ "Failed to parse revision specifier - Invalid pattern '%s'", spec);
+
+ git_object_free(base_rev);
+ git_reference_free(reference);
+ }
+
+ git_buf_free(&buf);
+ return error;
+}
+
+int git_revparse_ext(
+ git_object **object_out,
+ git_reference **reference_out,
+ git_repository *repo,
+ const char *spec)
+{
+ int error;
+ size_t identifier_len;
+ git_object *obj = NULL;
+ git_reference *ref = NULL;
+
+ if ((error = revparse__ext(&obj, &ref, &identifier_len, repo, spec)) < 0)
+ goto cleanup;
+
+ *object_out = obj;
+ *reference_out = ref;
+ GIT_UNUSED(identifier_len);
+
+ return 0;
+
+cleanup:
+ git_object_free(obj);
+ git_reference_free(ref);
+ return error;
+}
+
+int git_revparse_single(git_object **out, git_repository *repo, const char *spec)
+{
+ int error;
+ git_object *obj = NULL;
+ git_reference *ref = NULL;
+
+ *out = NULL;
+
+ if ((error = git_revparse_ext(&obj, &ref, repo, spec)) < 0)
+ goto cleanup;
+
+ git_reference_free(ref);
+
+ *out = obj;
+
+ return 0;
+
+cleanup:
+ git_object_free(obj);
+ git_reference_free(ref);
+ return error;
+}
+
+int git_revparse(
+ git_revspec *revspec,
+ git_repository *repo,
+ const char *spec)
+{
+ const char *dotdot;
+ int error = 0;
+
+ assert(revspec && repo && spec);
+
+ memset(revspec, 0x0, sizeof(*revspec));
+
+ if ((dotdot = strstr(spec, "..")) != NULL) {
+ char *lstr;
+ const char *rstr;
+ revspec->flags = GIT_REVPARSE_RANGE;
+
+ lstr = git__substrdup(spec, dotdot - spec);
+ rstr = dotdot + 2;
+ if (dotdot[2] == '.') {
+ revspec->flags |= GIT_REVPARSE_MERGE_BASE;
+ rstr++;
+ }
+
+ error = git_revparse_single(&revspec->from, repo, lstr);
+ if (!error)
+ error = git_revparse_single(&revspec->to, repo, rstr);
+
+ git__free((void*)lstr);
+ } else {
+ revspec->flags = GIT_REVPARSE_SINGLE;
+ error = git_revparse_single(&revspec->from, repo, spec);
+ }
+
+ return error;
+}
diff --git a/src/revwalk.c b/src/revwalk.c
index f7a56ccf5..7aedd1f44 100644
--- a/src/revwalk.c
+++ b/src/revwalk.c
@@ -1,509 +1,480 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
#include "commit.h"
#include "odb.h"
-#include "hashtable.h"
-#include "pqueue.h"
-
-#include "git2/revwalk.h"
-
-typedef struct commit_object {
- git_oid oid;
- uint32_t time;
- unsigned int seen:1,
- uninteresting:1,
- topo_delay:1,
- parsed:1;
+#include "pool.h"
- unsigned short in_degree;
- unsigned short out_degree;
+#include "revwalk.h"
+#include "git2/revparse.h"
+#include "merge.h"
- struct commit_object **parents;
-} commit_object;
-
-typedef struct commit_list {
- commit_object *item;
- struct commit_list *next;
-} commit_list;
-
-struct git_revwalk {
- git_repository *repo;
-
- git_hashtable *commits;
+git_commit_list_node *git_revwalk__commit_lookup(
+ git_revwalk *walk, const git_oid *oid)
+{
+ git_commit_list_node *commit;
+ khiter_t pos;
+ int ret;
- commit_list *iterator_topo;
- commit_list *iterator_rand;
- commit_list *iterator_reverse;
- git_pqueue iterator_time;
+ /* lookup and reserve space if not already present */
+ pos = kh_get(oid, walk->commits, oid);
+ if (pos != kh_end(walk->commits))
+ return kh_value(walk->commits, pos);
- int (*get_next)(commit_object **, git_revwalk *);
- int (*enqueue)(git_revwalk *, commit_object *);
+ commit = git_commit_list_alloc_node(walk);
+ if (commit == NULL)
+ return NULL;
- git_vector memory_alloc;
- size_t chunk_size;
+ git_oid_cpy(&commit->oid, oid);
- unsigned walking:1;
- unsigned int sorting;
-};
+ pos = kh_put(oid, walk->commits, &commit->oid, &ret);
+ assert(ret != 0);
+ kh_value(walk->commits, pos) = commit;
-commit_list *commit_list_insert(commit_object *item, commit_list **list_p)
-{
- commit_list *new_list = git__malloc(sizeof(commit_list));
- new_list->item = item;
- new_list->next = *list_p;
- *list_p = new_list;
- return new_list;
+ return commit;
}
-void commit_list_free(commit_list **list_p)
+static int mark_uninteresting(git_revwalk *walk, git_commit_list_node *commit)
{
- commit_list *list = *list_p;
+ int error;
+ unsigned short i;
+ git_array_t(git_commit_list_node *) pending = GIT_ARRAY_INIT;
+ git_commit_list_node **tmp;
- while (list) {
- commit_list *temp = list;
- list = temp->next;
- free(temp);
- }
+ assert(commit);
- *list_p = NULL;
-}
+ git_array_alloc(pending);
+ GITERR_CHECK_ARRAY(pending);
-commit_object *commit_list_pop(commit_list **stack)
-{
- commit_list *top = *stack;
- commit_object *item = top ? top->item : NULL;
+ do {
+ commit->uninteresting = 1;
- if (top) {
- *stack = top->next;
- free(top);
- }
- return item;
-}
+ if ((error = git_commit_list_parse(walk, commit)) < 0)
+ return error;
-static int commit_time_cmp(void *a, void *b)
-{
- commit_object *commit_a = (commit_object *)a;
- commit_object *commit_b = (commit_object *)b;
+ for (i = 0; i < commit->out_degree; ++i)
+ if (!commit->parents[i]->uninteresting) {
+ git_commit_list_node **node = git_array_alloc(pending);
+ GITERR_CHECK_ALLOC(node);
+ *node = commit->parents[i];
+ }
- return (commit_a->time < commit_b->time);
-}
+ tmp = git_array_pop(pending);
+ commit = tmp ? *tmp : NULL;
-static uint32_t object_table_hash(const void *key, int hash_id)
-{
- uint32_t r;
- const git_oid *id = key;
+ } while (git_array_size(pending) > 0);
- memcpy(&r, id->id + (hash_id * sizeof(uint32_t)), sizeof(r));
- return r;
-}
+ git_array_clear(pending);
-#define COMMITS_PER_CHUNK 128
-#define CHUNK_STEP 64
-#define PARENTS_PER_COMMIT ((CHUNK_STEP - sizeof(commit_object)) / sizeof(commit_object *))
+ return 0;
+}
-static int alloc_chunk(git_revwalk *walk)
+static int process_commit(git_revwalk *walk, git_commit_list_node *commit, int hide)
{
- void *chunk;
+ int error;
- chunk = git__calloc(COMMITS_PER_CHUNK, CHUNK_STEP);
- if (chunk == NULL)
- return GIT_ENOMEM;
+ if (!hide && walk->hide_cb)
+ hide = walk->hide_cb(&commit->oid, walk->hide_cb_payload);
- walk->chunk_size = 0;
- return git_vector_insert(&walk->memory_alloc, chunk);
-}
+ if (hide && mark_uninteresting(walk, commit) < 0)
+ return -1;
-static commit_object *alloc_commit(git_revwalk *walk)
-{
- unsigned char *chunk;
+ if (commit->seen)
+ return 0;
- if (walk->chunk_size == COMMITS_PER_CHUNK)
- alloc_chunk(walk);
+ commit->seen = 1;
- chunk = git_vector_get(&walk->memory_alloc, walk->memory_alloc.length - 1);
- chunk += (walk->chunk_size * CHUNK_STEP);
- walk->chunk_size++;
+ if ((error = git_commit_list_parse(walk, commit)) < 0)
+ return error;
- return (commit_object *)chunk;
-}
+ if (!hide)
+ return walk->enqueue(walk, commit);
-static commit_object **alloc_parents(commit_object *commit, size_t n_parents)
-{
- if (n_parents <= PARENTS_PER_COMMIT)
- return (commit_object **)((unsigned char *)commit + sizeof(commit_object));
-
- return git__malloc(n_parents * sizeof(commit_object *));
+ return 0;
}
-
-static commit_object *commit_lookup(git_revwalk *walk, const git_oid *oid)
+static int process_commit_parents(git_revwalk *walk, git_commit_list_node *commit)
{
- commit_object *commit;
-
- if ((commit = git_hashtable_lookup(walk->commits, oid)) != NULL)
- return commit;
+ unsigned short i, max;
+ int error = 0;
- commit = alloc_commit(walk);
- if (commit == NULL)
- return NULL;
+ max = commit->out_degree;
+ if (walk->first_parent && commit->out_degree)
+ max = 1;
- git_oid_cpy(&commit->oid, oid);
-
- if (git_hashtable_insert(walk->commits, &commit->oid, commit) < GIT_SUCCESS) {
- free(commit);
- return NULL;
- }
+ for (i = 0; i < max && !error; ++i)
+ error = process_commit(walk, commit->parents[i], commit->uninteresting);
- return commit;
+ return error;
}
-static int commit_quick_parse(git_revwalk *walk, commit_object *commit, git_rawobj *raw)
+static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting, int from_glob)
{
- const int parent_len = STRLEN("parent ") + GIT_OID_HEXSZ + 1;
+ git_oid commit_id;
+ int error;
+ git_object *obj, *oobj;
+ git_commit_list_node *commit;
- unsigned char *buffer = raw->data;
- unsigned char *buffer_end = buffer + raw->len;
- unsigned char *parents_start;
+ if ((error = git_object_lookup(&oobj, walk->repo, oid, GIT_OBJ_ANY)) < 0)
+ return error;
- int i, parents = 0;
- long commit_time;
+ error = git_object_peel(&obj, oobj, GIT_OBJ_COMMIT);
+ git_object_free(oobj);
- buffer += STRLEN("tree ") + GIT_OID_HEXSZ + 1;
+ if (error == GIT_ENOTFOUND) {
+ /* If this comes from e.g. push_glob("tags"), ignore this */
+ if (from_glob)
+ return 0;
- parents_start = buffer;
- while (buffer + parent_len < buffer_end && memcmp(buffer, "parent ", STRLEN("parent ")) == 0) {
- parents++;
- buffer += parent_len;
+ giterr_set(GITERR_INVALID, "Object is not a committish");
+ return -1;
}
+ if (error < 0)
+ return error;
- commit->parents = alloc_parents(commit, parents);
- if (commit->parents == NULL)
- return GIT_ENOMEM;
-
- buffer = parents_start;
- for (i = 0; i < parents; ++i) {
- git_oid oid;
+ git_oid_cpy(&commit_id, git_object_id(obj));
+ git_object_free(obj);
- if (git_oid_fromstr(&oid, (char *)buffer + STRLEN("parent ")) < GIT_SUCCESS)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Parent object is corrupted");
-
- commit->parents[i] = commit_lookup(walk, &oid);
- if (commit->parents[i] == NULL)
- return GIT_ENOMEM;
+ commit = git_revwalk__commit_lookup(walk, &commit_id);
+ if (commit == NULL)
+ return -1; /* error already reported by failed lookup */
- buffer += parent_len;
+ commit->uninteresting = uninteresting;
+ if (walk->one == NULL && !uninteresting) {
+ walk->one = commit;
+ } else {
+ if (git_vector_insert(&walk->twos, commit) < 0)
+ return -1;
}
- commit->out_degree = (unsigned short)parents;
-
- if ((buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Object is corrupted");
+ return 0;
+}
- buffer = memchr(buffer, '>', buffer_end - buffer);
- if (buffer == NULL)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Can't find author");
+int git_revwalk_push(git_revwalk *walk, const git_oid *oid)
+{
+ assert(walk && oid);
+ return push_commit(walk, oid, 0, false);
+}
- if (git__strtol32(&commit_time, (char *)buffer + 2, NULL, 10) < GIT_SUCCESS)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Can't parse commit time");
- commit->time = (time_t)commit_time;
- commit->parsed = 1;
- return GIT_SUCCESS;
+int git_revwalk_hide(git_revwalk *walk, const git_oid *oid)
+{
+ assert(walk && oid);
+ return push_commit(walk, oid, 1, false);
}
-static int commit_parse(git_revwalk *walk, commit_object *commit)
+static int push_ref(git_revwalk *walk, const char *refname, int hide, int from_glob)
{
- git_odb_object *obj;
- int error;
+ git_oid oid;
- if (commit->parsed)
- return GIT_SUCCESS;
+ if (git_reference_name_to_id(&oid, walk->repo, refname) < 0)
+ return -1;
- if ((error = git_odb_read(&obj, walk->repo->db, &commit->oid)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to parse commit. Can't read object");
+ return push_commit(walk, &oid, hide, from_glob);
+}
- if (obj->raw.type != GIT_OBJ_COMMIT) {
- git_odb_object_close(obj);
- return git__throw(GIT_EOBJTYPE, "Failed to parse commit. Object is no commit object");
+static int push_glob(git_revwalk *walk, const char *glob, int hide)
+{
+ int error = 0;
+ git_buf buf = GIT_BUF_INIT;
+ git_reference *ref;
+ git_reference_iterator *iter;
+ size_t wildcard;
+
+ assert(walk && glob);
+
+ /* refs/ is implied if not given in the glob */
+ if (git__prefixcmp(glob, GIT_REFS_DIR) != 0)
+ git_buf_joinpath(&buf, GIT_REFS_DIR, glob);
+ else
+ git_buf_puts(&buf, glob);
+ if (git_buf_oom(&buf))
+ return -1;
+
+ /* If no '?', '*' or '[' exist, we append '/ *' to the glob */
+ wildcard = strcspn(glob, "?*[");
+ if (!glob[wildcard])
+ git_buf_put(&buf, "/*", 2);
+
+ if ((error = git_reference_iterator_glob_new(&iter, walk->repo, buf.ptr)) < 0)
+ goto out;
+
+ while ((error = git_reference_next(&ref, iter)) == 0) {
+ error = push_ref(walk, git_reference_name(ref), hide, true);
+ git_reference_free(ref);
+ if (error < 0)
+ break;
}
+ git_reference_iterator_free(iter);
- error = commit_quick_parse(walk, commit, &obj->raw);
- git_odb_object_close(obj);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse commit");
+ if (error == GIT_ITEROVER)
+ error = 0;
+out:
+ git_buf_free(&buf);
+ return error;
}
-static void mark_uninteresting(commit_object *commit)
+int git_revwalk_push_glob(git_revwalk *walk, const char *glob)
{
- unsigned short i;
- assert(commit);
-
- commit->uninteresting = 1;
-
- for (i = 0; i < commit->out_degree; ++i)
- if (!commit->parents[i]->uninteresting)
- mark_uninteresting(commit->parents[i]);
+ assert(walk && glob);
+ return push_glob(walk, glob, 0);
}
-static int process_commit(git_revwalk *walk, commit_object *commit, int hide)
+int git_revwalk_hide_glob(git_revwalk *walk, const char *glob)
{
- int error;
-
- if (hide)
- mark_uninteresting(commit);
-
- if (commit->seen)
- return GIT_SUCCESS;
-
- commit->seen = 1;
-
- if ((error = commit_parse(walk, commit)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to process commit");
-
- return walk->enqueue(walk, commit);
+ assert(walk && glob);
+ return push_glob(walk, glob, 1);
}
-static int process_commit_parents(git_revwalk *walk, commit_object *commit)
+int git_revwalk_push_head(git_revwalk *walk)
{
- unsigned short i;
- int error = GIT_SUCCESS;
+ assert(walk);
+ return push_ref(walk, GIT_HEAD_FILE, 0, false);
+}
- for (i = 0; i < commit->out_degree && error == GIT_SUCCESS; ++i) {
- error = process_commit(walk, commit->parents[i], commit->uninteresting);
- }
+int git_revwalk_hide_head(git_revwalk *walk)
+{
+ assert(walk);
+ return push_ref(walk, GIT_HEAD_FILE, 1, false);
+}
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to process commit parents");
+int git_revwalk_push_ref(git_revwalk *walk, const char *refname)
+{
+ assert(walk && refname);
+ return push_ref(walk, refname, 0, false);
}
-static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting)
+int git_revwalk_push_range(git_revwalk *walk, const char *range)
{
- commit_object *commit;
+ git_revspec revspec;
+ int error = 0;
- commit = commit_lookup(walk, oid);
- if (commit == NULL)
- return git__throw(GIT_ENOTFOUND, "Failed to push commit. Object not found");
+ if ((error = git_revparse(&revspec, walk->repo, range)))
+ return error;
- return process_commit(walk, commit, uninteresting);
-}
+ if (revspec.flags & GIT_REVPARSE_MERGE_BASE) {
+ /* TODO: support "<commit>...<commit>" */
+ giterr_set(GITERR_INVALID, "Symmetric differences not implemented in revwalk");
+ return GIT_EINVALIDSPEC;
+ }
-int git_revwalk_push(git_revwalk *walk, const git_oid *oid)
-{
- assert(walk && oid);
- return push_commit(walk, oid, 0);
+ if ((error = push_commit(walk, git_object_id(revspec.from), 1, false)))
+ goto out;
+
+ error = push_commit(walk, git_object_id(revspec.to), 0, false);
+
+out:
+ git_object_free(revspec.from);
+ git_object_free(revspec.to);
+ return error;
}
-int git_revwalk_hide(git_revwalk *walk, const git_oid *oid)
+int git_revwalk_hide_ref(git_revwalk *walk, const char *refname)
{
- assert(walk && oid);
- return push_commit(walk, oid, 1);
+ assert(walk && refname);
+ return push_ref(walk, refname, 1, false);
}
-static int revwalk_enqueue_timesort(git_revwalk *walk, commit_object *commit)
+static int revwalk_enqueue_timesort(git_revwalk *walk, git_commit_list_node *commit)
{
return git_pqueue_insert(&walk->iterator_time, commit);
}
-static int revwalk_enqueue_unsorted(git_revwalk *walk, commit_object *commit)
+static int revwalk_enqueue_unsorted(git_revwalk *walk, git_commit_list_node *commit)
{
- return commit_list_insert(commit, &walk->iterator_rand) ? GIT_SUCCESS : GIT_ENOMEM;
+ return git_commit_list_insert(commit, &walk->iterator_rand) ? 0 : -1;
}
-static int revwalk_next_timesort(commit_object **object_out, git_revwalk *walk)
+static int revwalk_next_timesort(git_commit_list_node **object_out, git_revwalk *walk)
{
int error;
- commit_object *next;
-
- while ((next = git_pqueue_pop(&walk->iterator_time)) != NULL) {
- if ((error = process_commit_parents(walk, next)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to load next revision");
+ git_commit_list_node *next;
+ while ((next = git_pqueue_pop(&walk->iterator_time)) != NULL)
if (!next->uninteresting) {
+ if ((error = process_commit_parents(walk, next)) < 0)
+ return error;
+
*object_out = next;
- return GIT_SUCCESS;
+ return 0;
}
- }
- return git__throw(GIT_EREVWALKOVER, "Failed to load next revision");
+ giterr_clear();
+ return GIT_ITEROVER;
}
-static int revwalk_next_unsorted(commit_object **object_out, git_revwalk *walk)
+static int revwalk_next_unsorted(git_commit_list_node **object_out, git_revwalk *walk)
{
int error;
- commit_object *next;
-
- while ((next = commit_list_pop(&walk->iterator_rand)) != NULL) {
- if ((error = process_commit_parents(walk, next)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to load next revision");
+ git_commit_list_node *next;
+ while ((next = git_commit_list_pop(&walk->iterator_rand)) != NULL)
if (!next->uninteresting) {
+ if ((error = process_commit_parents(walk, next)) < 0)
+ return error;
+
*object_out = next;
- return GIT_SUCCESS;
+ return 0;
}
- }
- return git__throw(GIT_EREVWALKOVER, "Failed to load next revision");
+ giterr_clear();
+ return GIT_ITEROVER;
}
-static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk)
+static int revwalk_next_toposort(git_commit_list_node **object_out, git_revwalk *walk)
{
- commit_object *next;
- unsigned short i;
+ git_commit_list_node *next;
+ unsigned short i, max;
for (;;) {
- next = commit_list_pop(&walk->iterator_topo);
- if (next == NULL)
- return git__throw(GIT_EREVWALKOVER, "Failed to load next revision");
+ next = git_commit_list_pop(&walk->iterator_topo);
+ if (next == NULL) {
+ giterr_clear();
+ return GIT_ITEROVER;
+ }
if (next->in_degree > 0) {
next->topo_delay = 1;
continue;
}
- for (i = 0; i < next->out_degree; ++i) {
- commit_object *parent = next->parents[i];
+
+ max = next->out_degree;
+ if (walk->first_parent && next->out_degree)
+ max = 1;
+
+ for (i = 0; i < max; ++i) {
+ git_commit_list_node *parent = next->parents[i];
if (--parent->in_degree == 0 && parent->topo_delay) {
parent->topo_delay = 0;
- commit_list_insert(parent, &walk->iterator_topo);
+ if (git_commit_list_insert(parent, &walk->iterator_topo) == NULL)
+ return -1;
}
}
*object_out = next;
- return GIT_SUCCESS;
+ return 0;
}
}
-static int revwalk_next_reverse(commit_object **object_out, git_revwalk *walk)
+static int revwalk_next_reverse(git_commit_list_node **object_out, git_revwalk *walk)
{
- *object_out = commit_list_pop(&walk->iterator_reverse);
- return *object_out ? GIT_SUCCESS : GIT_EREVWALKOVER;
+ *object_out = git_commit_list_pop(&walk->iterator_reverse);
+ return *object_out ? 0 : GIT_ITEROVER;
}
static int prepare_walk(git_revwalk *walk)
{
int error;
- commit_object *next;
+ unsigned int i;
+ git_commit_list_node *next, *two;
+
+ /*
+ * If walk->one is NULL, there were no positive references,
+ * so we know that the walk is already over.
+ */
+ if (walk->one == NULL) {
+ giterr_clear();
+ return GIT_ITEROVER;
+ }
+
+ if (process_commit(walk, walk->one, walk->one->uninteresting) < 0)
+ return -1;
+
+ git_vector_foreach(&walk->twos, i, two) {
+ if (process_commit(walk, two, two->uninteresting) < 0)
+ return -1;
+ }
if (walk->sorting & GIT_SORT_TOPOLOGICAL) {
unsigned short i;
- while ((error = walk->get_next(&next, walk)) == GIT_SUCCESS) {
+ while ((error = walk->get_next(&next, walk)) == 0) {
for (i = 0; i < next->out_degree; ++i) {
- commit_object *parent = next->parents[i];
+ git_commit_list_node *parent = next->parents[i];
parent->in_degree++;
}
- commit_list_insert(next, &walk->iterator_topo);
+ if (git_commit_list_insert(next, &walk->iterator_topo) == NULL)
+ return -1;
}
- if (error != GIT_EREVWALKOVER)
- return git__rethrow(error, "Failed to prepare revision walk");
+ if (error != GIT_ITEROVER)
+ return error;
walk->get_next = &revwalk_next_toposort;
}
if (walk->sorting & GIT_SORT_REVERSE) {
- while ((error = walk->get_next(&next, walk)) == GIT_SUCCESS)
- commit_list_insert(next, &walk->iterator_reverse);
+ while ((error = walk->get_next(&next, walk)) == 0)
+ if (git_commit_list_insert(next, &walk->iterator_reverse) == NULL)
+ return -1;
- if (error != GIT_EREVWALKOVER)
- return git__rethrow(error, "Failed to prepare revision walk");
+ if (error != GIT_ITEROVER)
+ return error;
walk->get_next = &revwalk_next_reverse;
}
walk->walking = 1;
- return GIT_SUCCESS;
+ return 0;
}
-
-
-
int git_revwalk_new(git_revwalk **revwalk_out, git_repository *repo)
{
git_revwalk *walk;
walk = git__malloc(sizeof(git_revwalk));
- if (walk == NULL)
- return GIT_ENOMEM;
+ GITERR_CHECK_ALLOC(walk);
memset(walk, 0x0, sizeof(git_revwalk));
- walk->commits = git_hashtable_alloc(64,
- object_table_hash,
- (git_hash_keyeq_ptr)git_oid_cmp);
-
- if (walk->commits == NULL) {
- free(walk);
- return GIT_ENOMEM;
- }
+ walk->commits = git_oidmap_alloc();
+ GITERR_CHECK_ALLOC(walk->commits);
- git_pqueue_init(&walk->iterator_time, 8, commit_time_cmp);
- git_vector_init(&walk->memory_alloc, 8, NULL);
- alloc_chunk(walk);
+ if (git_pqueue_init(
+ &walk->iterator_time, 0, 8, git_commit_list_time_cmp) < 0 ||
+ git_vector_init(&walk->twos, 4, NULL) < 0 ||
+ git_pool_init(&walk->commit_pool, 1,
+ git_pool__suggest_items_per_page(COMMIT_ALLOC) * COMMIT_ALLOC) < 0)
+ return -1;
walk->get_next = &revwalk_next_unsorted;
walk->enqueue = &revwalk_enqueue_unsorted;
walk->repo = repo;
+ if (git_repository_odb(&walk->odb, repo) < 0) {
+ git_revwalk_free(walk);
+ return -1;
+ }
+
*revwalk_out = walk;
- return GIT_SUCCESS;
+ return 0;
}
void git_revwalk_free(git_revwalk *walk)
{
- unsigned int i;
- const void *GIT_UNUSED(_unused);
- commit_object *commit;
-
if (walk == NULL)
return;
git_revwalk_reset(walk);
+ git_odb_free(walk->odb);
- /* if the parent has more than PARENTS_PER_COMMIT parents,
- * we had to allocate a separate array for those parents.
- * make sure it's being free'd */
- GIT_HASHTABLE_FOREACH(walk->commits, _unused, commit, {
- if (commit->out_degree > PARENTS_PER_COMMIT)
- free(commit->parents);
- });
-
- git_hashtable_free(walk->commits);
+ git_oidmap_free(walk->commits);
+ git_pool_clear(&walk->commit_pool);
git_pqueue_free(&walk->iterator_time);
-
- for (i = 0; i < walk->memory_alloc.length; ++i)
- free(git_vector_get(&walk->memory_alloc, i));
-
- git_vector_free(&walk->memory_alloc);
- free(walk);
+ git_vector_free(&walk->twos);
+ git__free(walk);
}
git_repository *git_revwalk_repository(git_revwalk *walk)
@@ -530,49 +501,79 @@ void git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode)
}
}
+void git_revwalk_simplify_first_parent(git_revwalk *walk)
+{
+ walk->first_parent = 1;
+}
+
int git_revwalk_next(git_oid *oid, git_revwalk *walk)
{
int error;
- commit_object *next;
+ git_commit_list_node *next;
assert(walk && oid);
if (!walk->walking) {
- if ((error = prepare_walk(walk)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to load next revision");
+ if ((error = prepare_walk(walk)) < 0)
+ return error;
}
error = walk->get_next(&next, walk);
- if (error == GIT_EREVWALKOVER) {
+ if (error == GIT_ITEROVER) {
git_revwalk_reset(walk);
- return GIT_EREVWALKOVER;
+ giterr_clear();
+ return GIT_ITEROVER;
}
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to load next revision");
+ if (!error)
+ git_oid_cpy(oid, &next->oid);
- git_oid_cpy(oid, &next->oid);
- return GIT_SUCCESS;
+ return error;
}
void git_revwalk_reset(git_revwalk *walk)
{
- const void *GIT_UNUSED(_unused);
- commit_object *commit;
+ git_commit_list_node *commit;
assert(walk);
- GIT_HASHTABLE_FOREACH(walk->commits, _unused, commit,
+ kh_foreach_value(walk->commits, commit, {
commit->seen = 0;
commit->in_degree = 0;
commit->topo_delay = 0;
- );
+ commit->uninteresting = 0;
+ });
git_pqueue_clear(&walk->iterator_time);
- commit_list_free(&walk->iterator_topo);
- commit_list_free(&walk->iterator_rand);
- commit_list_free(&walk->iterator_reverse);
+ git_commit_list_free(&walk->iterator_topo);
+ git_commit_list_free(&walk->iterator_rand);
+ git_commit_list_free(&walk->iterator_reverse);
walk->walking = 0;
+
+ walk->one = NULL;
+ git_vector_clear(&walk->twos);
+}
+
+int git_revwalk_add_hide_cb(
+ git_revwalk *walk,
+ git_revwalk_hide_cb hide_cb,
+ void *payload)
+{
+ assert(walk);
+
+ if (walk->walking)
+ git_revwalk_reset(walk);
+
+ if (walk->hide_cb) {
+ /* There is already a callback added */
+ giterr_set(GITERR_INVALID, "There is already a callback added to hide commits in revision walker.");
+ return -1;
+ }
+
+ walk->hide_cb = hide_cb;
+ walk->hide_cb_payload = payload;
+
+ return 0;
}
diff --git a/src/revwalk.h b/src/revwalk.h
new file mode 100644
index 000000000..d81f97c01
--- /dev/null
+++ b/src/revwalk.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_revwalk_h__
+#define INCLUDE_revwalk_h__
+
+#include "git2/revwalk.h"
+#include "oidmap.h"
+#include "commit_list.h"
+#include "pqueue.h"
+#include "pool.h"
+#include "vector.h"
+
+GIT__USE_OIDMAP;
+
+struct git_revwalk {
+ git_repository *repo;
+ git_odb *odb;
+
+ git_oidmap *commits;
+ git_pool commit_pool;
+
+ git_commit_list *iterator_topo;
+ git_commit_list *iterator_rand;
+ git_commit_list *iterator_reverse;
+ git_pqueue iterator_time;
+
+ int (*get_next)(git_commit_list_node **, git_revwalk *);
+ int (*enqueue)(git_revwalk *, git_commit_list_node *);
+
+ unsigned walking:1,
+ first_parent: 1;
+ unsigned int sorting;
+
+ /* merge base calculation */
+ git_commit_list_node *one;
+ git_vector twos;
+
+ /* hide callback */
+ git_revwalk_hide_cb hide_cb;
+ void *hide_cb_payload;
+};
+
+git_commit_list_node *git_revwalk__commit_lookup(git_revwalk *walk, const git_oid *oid);
+
+#endif
diff --git a/src/settings.c b/src/settings.c
new file mode 100644
index 000000000..1a21ea024
--- /dev/null
+++ b/src/settings.c
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include <git2.h>
+#include "common.h"
+#include "sysdir.h"
+#include "cache.h"
+
+void git_libgit2_version(int *major, int *minor, int *rev)
+{
+ *major = LIBGIT2_VER_MAJOR;
+ *minor = LIBGIT2_VER_MINOR;
+ *rev = LIBGIT2_VER_REVISION;
+}
+
+int git_libgit2_features()
+{
+ return 0
+#ifdef GIT_THREADS
+ | GIT_FEATURE_THREADS
+#endif
+#if defined(GIT_SSL) || defined(GIT_WINHTTP)
+ | GIT_FEATURE_HTTPS
+#endif
+#if defined(GIT_SSH)
+ | GIT_FEATURE_SSH
+#endif
+ ;
+}
+
+/* Declarations for tuneable settings */
+extern size_t git_mwindow__window_size;
+extern size_t git_mwindow__mapped_limit;
+
+static int config_level_to_sysdir(int config_level)
+{
+ int val = -1;
+
+ switch (config_level) {
+ case GIT_CONFIG_LEVEL_SYSTEM: val = GIT_SYSDIR_SYSTEM; break;
+ case GIT_CONFIG_LEVEL_XDG: val = GIT_SYSDIR_XDG; break;
+ case GIT_CONFIG_LEVEL_GLOBAL: val = GIT_SYSDIR_GLOBAL; break;
+ default:
+ giterr_set(
+ GITERR_INVALID, "Invalid config path selector %d", config_level);
+ }
+
+ return val;
+}
+
+int git_libgit2_opts(int key, ...)
+{
+ int error = 0;
+ va_list ap;
+
+ va_start(ap, key);
+
+ switch (key) {
+ case GIT_OPT_SET_MWINDOW_SIZE:
+ git_mwindow__window_size = va_arg(ap, size_t);
+ break;
+
+ case GIT_OPT_GET_MWINDOW_SIZE:
+ *(va_arg(ap, size_t *)) = git_mwindow__window_size;
+ break;
+
+ case GIT_OPT_SET_MWINDOW_MAPPED_LIMIT:
+ git_mwindow__mapped_limit = va_arg(ap, size_t);
+ break;
+
+ case GIT_OPT_GET_MWINDOW_MAPPED_LIMIT:
+ *(va_arg(ap, size_t *)) = git_mwindow__mapped_limit;
+ break;
+
+ case GIT_OPT_GET_SEARCH_PATH:
+ if ((error = config_level_to_sysdir(va_arg(ap, int))) >= 0) {
+ git_buf *out = va_arg(ap, git_buf *);
+ const git_buf *tmp;
+
+ git_buf_sanitize(out);
+ if ((error = git_sysdir_get(&tmp, error)) < 0)
+ break;
+
+ error = git_buf_sets(out, tmp->ptr);
+ }
+ break;
+
+ case GIT_OPT_SET_SEARCH_PATH:
+ if ((error = config_level_to_sysdir(va_arg(ap, int))) >= 0)
+ error = git_sysdir_set(error, va_arg(ap, const char *));
+ break;
+
+ case GIT_OPT_SET_CACHE_OBJECT_LIMIT:
+ {
+ git_otype type = (git_otype)va_arg(ap, int);
+ size_t size = va_arg(ap, size_t);
+ error = git_cache_set_max_object_size(type, size);
+ break;
+ }
+
+ case GIT_OPT_SET_CACHE_MAX_SIZE:
+ git_cache__max_storage = va_arg(ap, ssize_t);
+ break;
+
+ case GIT_OPT_ENABLE_CACHING:
+ git_cache__enabled = (va_arg(ap, int) != 0);
+ break;
+
+ case GIT_OPT_GET_CACHED_MEMORY:
+ *(va_arg(ap, ssize_t *)) = git_cache__current_storage.val;
+ *(va_arg(ap, ssize_t *)) = git_cache__max_storage;
+ break;
+
+ case GIT_OPT_GET_TEMPLATE_PATH:
+ {
+ git_buf *out = va_arg(ap, git_buf *);
+ const git_buf *tmp;
+
+ git_buf_sanitize(out);
+ if ((error = git_sysdir_get(&tmp, GIT_SYSDIR_TEMPLATE)) < 0)
+ break;
+
+ error = git_buf_sets(out, tmp->ptr);
+ }
+ break;
+
+ case GIT_OPT_SET_TEMPLATE_PATH:
+ error = git_sysdir_set(GIT_SYSDIR_TEMPLATE, va_arg(ap, const char *));
+ break;
+ }
+
+ va_end(ap);
+
+ return error;
+}
+
diff --git a/src/sha1.h b/src/sha1.h
deleted file mode 100644
index 558d6aece..000000000
--- a/src/sha1.h
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * SHA1 routine optimized to do word accesses rather than byte accesses,
- * and to avoid unnecessary copies into the context array.
- *
- * This was initially based on the Mozilla SHA1 implementation, although
- * none of the original Mozilla code remains.
- */
-
-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, unsigned long 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
diff --git a/src/sha1_lookup.c b/src/sha1_lookup.c
index 6ac00c5aa..c6b561340 100644
--- a/src/sha1_lookup.c
+++ b/src/sha1_lookup.c
@@ -1,101 +1,83 @@
/*
- * This file is basically taken from git code.
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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 "sha1_lookup.h"
#include "common.h"
+#include "oid.h"
/*
* Conventional binary search loop looks like this:
*
* unsigned lo, hi;
- * do {
- * unsigned mi = (lo + hi) / 2;
- * int cmp = "entry pointed at by mi" minus "target";
- * if (!cmp)
- * return (mi is the wanted one)
- * if (cmp > 0)
- * hi = mi; "mi is larger than target"
- * else
- * lo = mi+1; "mi is smaller than target"
- * } while (lo < hi);
+ * do {
+ * unsigned mi = (lo + hi) / 2;
+ * int cmp = "entry pointed at by mi" minus "target";
+ * if (!cmp)
+ * return (mi is the wanted one)
+ * if (cmp > 0)
+ * hi = mi; "mi is larger than target"
+ * else
+ * lo = mi+1; "mi is smaller than target"
+ * } while (lo < hi);
*
* The invariants are:
*
* - When entering the loop, lo points at a slot that is never
- * above the target (it could be at the target), hi points at a
- * slot that is guaranteed to be above the target (it can never
- * be at the target).
+ * above the target (it could be at the target), hi points at a
+ * slot that is guaranteed to be above the target (it can never
+ * be at the target).
*
* - We find a point 'mi' between lo and hi (mi could be the same
- * as lo, but never can be as same as hi), and check if it hits
- * the target. There are three cases:
+ * as lo, but never can be as same as hi), and check if it hits
+ * the target. There are three cases:
*
- * - if it is a hit, we are happy.
+ * - if it is a hit, we are happy.
*
- * - if it is strictly higher than the target, we set it to hi,
- * and repeat the search.
+ * - if it is strictly higher than the target, we set it to hi,
+ * and repeat the search.
*
- * - if it is strictly lower than the target, we update lo to
- * one slot after it, because we allow lo to be at the target.
+ * - if it is strictly lower than the target, we update lo to
+ * one slot after it, because we allow lo to be at the target.
*
- * If the loop exits, there is no matching entry.
+ * If the loop exits, there is no matching entry.
*
* When choosing 'mi', we do not have to take the "middle" but
* anywhere in between lo and hi, as long as lo <= mi < hi is
- * satisfied. When we somehow know that the distance between the
+ * satisfied. When we somehow know that the distance between the
* target and lo is much shorter than the target and hi, we could
* pick mi that is much closer to lo than the midway.
*
* Now, we can take advantage of the fact that SHA-1 is a good hash
* function, and as long as there are enough entries in the table, we
- * can expect uniform distribution. An entry that begins with for
+ * can expect uniform distribution. An entry that begins with for
* example "deadbeef..." is much likely to appear much later than in
- * the midway of the table. It can reasonably be expected to be near
+ * the midway of the table. It can reasonably be expected to be near
* 87% (222/256) from the top of the table.
*
- * However, we do not want to pick "mi" too precisely. If the entry at
+ * However, we do not want to pick "mi" too precisely. If the entry at
* the 87% in the above example turns out to be higher than the target
* we are looking for, we would end up narrowing the search space down
* only by 13%, instead of 50% we would get if we did a simple binary
- * search. So we would want to hedge our bets by being less aggressive.
+ * search. So we would want to hedge our bets by being less aggressive.
*
* The table at "table" holds at least "nr" entries of "elem_size"
- * bytes each. Each entry has the SHA-1 key at "key_offset". The
- * table is sorted by the SHA-1 key of the entries. The caller wants
+ * bytes each. Each entry has the SHA-1 key at "key_offset". The
+ * table is sorted by the SHA-1 key of the entries. The caller wants
* to find the entry with "key", and knows that the entry at "lo" is
* not higher than the entry it is looking for, and that the entry at
* "hi" is higher than the entry it is looking for.
*/
int sha1_entry_pos(const void *table,
- size_t elem_size,
- size_t key_offset,
- unsigned lo, unsigned hi, unsigned nr,
- const unsigned char *key)
+ size_t elem_size,
+ size_t key_offset,
+ unsigned lo, unsigned hi, unsigned nr,
+ const unsigned char *key)
{
const unsigned char *base = (const unsigned char*)table;
const unsigned char *hi_key, *lo_key;
@@ -127,7 +109,54 @@ int sha1_entry_pos(const void *table,
* byte 0 thru (ofs-1) are the same between
* lo and hi; ofs is the first byte that is
* different.
+ *
+ * If ofs==20, then no bytes are different,
+ * meaning we have entries with duplicate
+ * keys. We know that we are in a solid run
+ * of this entry (because the entries are
+ * sorted, and our lo and hi are the same,
+ * there can be nothing but this single key
+ * in between). So we can stop the search.
+ * Either one of these entries is it (and
+ * we do not care which), or we do not have
+ * it.
+ *
+ * Furthermore, we know that one of our
+ * endpoints must be the edge of the run of
+ * duplicates. For example, given this
+ * sequence:
+ *
+ * idx 0 1 2 3 4 5
+ * key A C C C C D
+ *
+ * If we are searching for "B", we might
+ * hit the duplicate run at lo=1, hi=3
+ * (e.g., by first mi=3, then mi=0). But we
+ * can never have lo > 1, because B < C.
+ * That is, if our key is less than the
+ * run, we know that "lo" is the edge, but
+ * we can say nothing of "hi". Similarly,
+ * if our key is greater than the run, we
+ * know that "hi" is the edge, but we can
+ * say nothing of "lo".
+ *
+ * Therefore if we do not find it, we also
+ * know where it would go if it did exist:
+ * just on the far side of the edge that we
+ * know about.
*/
+ if (ofs == 20) {
+ mi = lo;
+ mi_key = base + elem_size * mi + key_offset;
+ cmp = memcmp(mi_key, key, 20);
+ if (!cmp)
+ return mi;
+ if (cmp < 0)
+ return -1 - hi;
+ else
+ return -1 - lo;
+ }
+
hiv = hi_key[ofs_0];
if (ofs_0 < 19)
hiv = (hiv << 8) | hi_key[ofs_0+1];
@@ -157,7 +186,7 @@ int sha1_entry_pos(const void *table,
* end up narrowing the search space by a smaller
* amount (i.e. the distance between 'mi' and 'hi')
* than what we would have (i.e. about half of 'lo'
- * and 'hi'). Hedge our bets to pick 'mi' less
+ * and 'hi'). Hedge our bets to pick 'mi' less
* aggressively, i.e. make 'mi' a bit closer to the
* middle than we would otherwise pick.
*/
@@ -173,11 +202,12 @@ int sha1_entry_pos(const void *table,
#ifdef INDEX_DEBUG_LOOKUP
printf("lo %u hi %u rg %u mi %u ", lo, hi, range, mi);
printf("ofs %u lov %x, hiv %x, kyv %x\n",
- ofs_0, lov, hiv, kyv);
+ ofs_0, lov, hiv, kyv);
#endif
if (!(lo <= mi && mi < hi)) {
- return git__throw(GIT_ERROR, "Assertion failure. Binary search invariant is false");
+ giterr_set(GITERR_INVALID, "Assertion failure. Binary search invariant is false");
+ return -1;
}
mi_key = base + elem_size * mi + key_offset;
@@ -194,3 +224,26 @@ int sha1_entry_pos(const void *table,
} while (lo < hi);
return -((int)lo)-1;
}
+
+int sha1_position(const void *table,
+ size_t stride,
+ unsigned lo, unsigned hi,
+ const unsigned char *key)
+{
+ const unsigned char *base = table;
+
+ do {
+ unsigned mi = (lo + hi) / 2;
+ int cmp = git_oid__hashcmp(base + mi * stride, key);
+
+ if (!cmp)
+ return mi;
+
+ if (cmp > 0)
+ hi = mi;
+ else
+ lo = mi+1;
+ } while (lo < hi);
+
+ return -((int)lo)-1;
+}
diff --git a/src/sha1_lookup.h b/src/sha1_lookup.h
index 5caa2f5ed..3799620c7 100644
--- a/src/sha1_lookup.h
+++ b/src/sha1_lookup.h
@@ -1,12 +1,23 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_lookup_h__
#define INCLUDE_sha1_lookup_h__
#include <stdlib.h>
int sha1_entry_pos(const void *table,
- size_t elem_size,
- size_t key_offset,
- unsigned lo, unsigned hi, unsigned nr,
- const unsigned char *key);
+ size_t elem_size,
+ size_t key_offset,
+ unsigned lo, unsigned hi, unsigned nr,
+ const unsigned char *key);
+
+int sha1_position(const void *table,
+ size_t stride,
+ unsigned lo, unsigned hi,
+ const unsigned char *key);
#endif
diff --git a/src/signature.c b/src/signature.c
index 327efe247..2545b7519 100644
--- a/src/signature.c
+++ b/src/signature.c
@@ -1,335 +1,228 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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 "signature.h"
#include "repository.h"
#include "git2/common.h"
+#include "posix.h"
void git_signature_free(git_signature *sig)
{
if (sig == NULL)
return;
- free(sig->name);
- free(sig->email);
- free(sig);
+ git__free(sig->name);
+ sig->name = NULL;
+ git__free(sig->email);
+ sig->email = NULL;
+ git__free(sig);
}
-static const char *skip_leading_spaces(const char *buffer, const char *buffer_end)
+static int signature_error(const char *msg)
{
- while (*buffer == ' ' && buffer < buffer_end)
- buffer++;
-
- return buffer;
+ giterr_set(GITERR_INVALID, "Failed to parse signature - %s", msg);
+ return -1;
}
-static const char *skip_trailing_spaces(const char *buffer_start, const char *buffer_end)
+static bool contains_angle_brackets(const char *input)
{
- while (*buffer_end == ' ' && buffer_end > buffer_start)
- buffer_end--;
-
- return buffer_end;
+ return strchr(input, '<') != NULL || strchr(input, '>') != NULL;
}
-static int process_trimming(const char *input, char **storage, const char *input_end, int fail_when_empty)
+static char *extract_trimmed(const char *ptr, size_t len)
{
- const char *left, *right;
- int trimmed_input_length;
-
- left = skip_leading_spaces(input, input_end);
- right = skip_trailing_spaces(input, input_end - 1);
-
- if (right < left) {
- if (fail_when_empty)
- return git__throw(GIT_EINVALIDARGS, "Failed to trim. Input is either empty or only contains spaces");
- else
- right = left - 1;
+ while (len && git__isspace(ptr[0])) {
+ ptr++; len--;
}
- trimmed_input_length = right - left + 1;
-
- *storage = git__malloc(trimmed_input_length + 1);
- if (*storage == NULL)
- return GIT_ENOMEM;
-
- memcpy(*storage, left, trimmed_input_length);
- (*storage)[trimmed_input_length] = 0;
+ while (len && git__isspace(ptr[len - 1])) {
+ len--;
+ }
- return GIT_SUCCESS;
+ return git__substrdup(ptr, len);
}
int git_signature_new(git_signature **sig_out, const char *name, const char *email, git_time_t time, int offset)
{
- int error;
git_signature *p = NULL;
assert(name && email);
*sig_out = NULL;
- if ((p = git__malloc(sizeof(git_signature))) == NULL) {
- error = GIT_ENOMEM;
- goto cleanup;
+ if (contains_angle_brackets(name) ||
+ contains_angle_brackets(email)) {
+ return signature_error(
+ "Neither `name` nor `email` should contain angle brackets chars.");
}
- memset(p, 0x0, sizeof(git_signature));
+ p = git__calloc(1, sizeof(git_signature));
+ GITERR_CHECK_ALLOC(p);
- error = process_trimming(name, &p->name, name + strlen(name), 1);
- if (error < GIT_SUCCESS) {
- git__rethrow(GIT_EINVALIDARGS, "Failed to create signature. 'name' argument is invalid");
- goto cleanup;
- }
+ p->name = extract_trimmed(name, strlen(name));
+ p->email = extract_trimmed(email, strlen(email));
+
+ if (p->name == NULL || p->email == NULL)
+ return -1; /* oom */
- error = process_trimming(email, &p->email, email + strlen(email), 1);
- if (error < GIT_SUCCESS) {
- git__rethrow(GIT_EINVALIDARGS, "Failed to create signature. 'email' argument is invalid");
- goto cleanup;
+ if (p->name[0] == '\0') {
+ git_signature_free(p);
+ return signature_error("Signature cannot have an empty name");
}
p->when.time = time;
p->when.offset = offset;
*sig_out = p;
-
- return error;
-
-cleanup:
- git_signature_free(p);
- return error;
+ return 0;
}
-git_signature *git_signature_dup(const git_signature *sig)
+int git_signature_dup(git_signature **dest, const git_signature *source)
{
- git_signature *new;
- if (git_signature_new(&new, sig->name, sig->email, sig->when.time, sig->when.offset) < GIT_SUCCESS)
- return NULL;
- return new;
+ git_signature *signature;
+
+ if (source == NULL)
+ return 0;
+
+ signature = git__calloc(1, sizeof(git_signature));
+ GITERR_CHECK_ALLOC(signature);
+
+ signature->name = git__strdup(source->name);
+ GITERR_CHECK_ALLOC(signature->name);
+
+ signature->email = git__strdup(source->email);
+ GITERR_CHECK_ALLOC(signature->email);
+
+ signature->when.time = source->when.time;
+ signature->when.offset = source->when.offset;
+
+ *dest = signature;
+
+ return 0;
}
int git_signature_now(git_signature **sig_out, const char *name, const char *email)
{
- int error;
time_t now;
time_t offset;
- struct tm *utc_tm, *local_tm;
+ struct tm *utc_tm;
git_signature *sig;
-
-#ifndef GIT_WIN32
- struct tm _utc, _local;
-#endif
+ struct tm _utc;
*sig_out = NULL;
- time(&now);
-
- /**
- * On Win32, `gmtime_r` doesn't exist but
- * `gmtime` is threadsafe, so we can use that
+ /*
+ * Get the current time as seconds since the epoch and
+ * transform that into a tm struct containing the time at
+ * UTC. Give that to mktime which considers it a local time
+ * (tm_isdst = -1 asks it to take DST into account) and gives
+ * us that time as seconds since the epoch. The difference
+ * between its return value and 'now' is our offset to UTC.
*/
-#ifdef GIT_WIN32
- utc_tm = gmtime(&now);
- local_tm = localtime(&now);
-#else
- utc_tm = gmtime_r(&now, &_utc);
- local_tm = localtime_r(&now, &_local);
-#endif
-
- offset = mktime(local_tm) - mktime(utc_tm);
+ time(&now);
+ utc_tm = p_gmtime_r(&now, &_utc);
+ utc_tm->tm_isdst = -1;
+ offset = (time_t)difftime(now, mktime(utc_tm));
offset /= 60;
- /* mktime takes care of setting tm_isdst correctly */
- if (local_tm->tm_isdst)
- offset += 60;
-
- if ((error = git_signature_new(&sig, name, email, now, (int)offset)) < GIT_SUCCESS)
- return error;
+ if (git_signature_new(&sig, name, email, now, (int)offset) < 0)
+ return -1;
*sig_out = sig;
- return error;
-}
-
-static int parse_timezone_offset(const char *buffer, int *offset_out)
-{
- long dec_offset;
- int mins, hours, offset;
-
- const char *offset_start;
- const char *offset_end;
-
- offset_start = buffer;
-
- if (*offset_start == '\n') {
- *offset_out = 0;
- return GIT_SUCCESS;
- }
-
- if (offset_start[0] != '-' && offset_start[0] != '+')
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. It doesn't start with '+' or '-'");
-
- if (offset_start[1] < '0' || offset_start[1] > '9')
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset.");
-
- if (git__strtol32(&dec_offset, offset_start + 1, &offset_end, 10) < GIT_SUCCESS)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. It isn't a number");
-
- if (offset_end - offset_start != 5)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. Invalid length");
-
- if (dec_offset > 1400)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. Value too large");
-
- hours = dec_offset / 100;
- mins = dec_offset % 100;
-
- if (hours > 14) // see http://www.worldtimezone.com/faq.html
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. Hour value too large");
-
- if (mins > 59)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. Minute value too large");
-
- offset = (hours * 60) + mins;
-
- if (offset_start[0] == '-')
- offset *= -1;
-
- *offset_out = offset;
-
- return GIT_SUCCESS;
-}
-
-int process_next_token(const char **buffer_out, char **storage,
- const char *token_end, const char *right_boundary)
-{
- int error = process_trimming(*buffer_out, storage, token_end, 0);
- if (error < GIT_SUCCESS)
- return error;
-
- *buffer_out = token_end + 1;
-
- if (*buffer_out > right_boundary)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Signature too short");
-
- return GIT_SUCCESS;
-}
-
-const char *scan_for_previous_token(const char *buffer, const char *left_boundary)
-{
- const char *start;
-
- if (buffer <= left_boundary)
- return NULL;
-
- start = skip_trailing_spaces(left_boundary, buffer);
-
- /* Search for previous occurence of space */
- while (start[-1] != ' ' && start > left_boundary)
- start--;
-
- return start;
+ return 0;
}
-int parse_time(git_time_t *time_out, const char *buffer)
+int git_signature_default(git_signature **out, git_repository *repo)
{
- long time;
int error;
+ git_config *cfg;
+ const char *user_name, *user_email;
- if (*buffer == '+' || *buffer == '-')
- return git__throw(GIT_ERROR, "Failed while parsing time. '%s' rather look like a timezone offset.", buffer);
-
- error = git__strtol32(&time, buffer, &buffer, 10);
-
- if (error < GIT_SUCCESS)
+ if ((error = git_repository_config_snapshot(&cfg, repo)) < 0)
return error;
- *time_out = (git_time_t)time;
+ if (!(error = git_config_get_string(&user_name, cfg, "user.name")) &&
+ !(error = git_config_get_string(&user_email, cfg, "user.email")))
+ error = git_signature_now(out, user_name, user_email);
- return GIT_SUCCESS;
+ git_config_free(cfg);
+ return error;
}
int git_signature__parse(git_signature *sig, const char **buffer_out,
const char *buffer_end, const char *header, char ender)
{
const char *buffer = *buffer_out;
- const char *line_end, *name_end, *email_end, *tz_start, *time_start;
- int error = GIT_SUCCESS;
+ const char *email_start, *email_end;
- memset(sig, 0x0, sizeof(git_signature));
+ memset(sig, 0, sizeof(git_signature));
- if ((line_end = memchr(buffer, ender, buffer_end - buffer)) == NULL)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. No newline given");
+ if ((buffer_end = memchr(buffer, ender, buffer_end - buffer)) == NULL)
+ return signature_error("no newline given");
if (header) {
const size_t header_len = strlen(header);
- if (memcmp(buffer, header, header_len) != 0)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Expected prefix '%s' doesn't match actual", header);
+ if (buffer + header_len >= buffer_end || memcmp(buffer, header, header_len) != 0)
+ return signature_error("expected prefix doesn't match actual");
buffer += header_len;
}
- if (buffer > line_end)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Signature too short");
-
- if ((name_end = strchr(buffer, '<')) == NULL)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Cannot find '<' in signature");
-
- if ((email_end = strchr(buffer, '>')) == NULL)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Cannot find '>' in signature");
-
- if (email_end < name_end)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Malformed e-mail");
-
- error = process_next_token(&buffer, &sig->name, name_end, line_end);
- if (error < GIT_SUCCESS)
- return error;
-
- error = process_next_token(&buffer, &sig->email, email_end, line_end);
- if (error < GIT_SUCCESS)
- return error;
-
- tz_start = scan_for_previous_token(line_end - 1, buffer);
-
- if (tz_start == NULL)
- goto clean_exit; /* No timezone nor date */
-
- time_start = scan_for_previous_token(tz_start - 1, buffer);
- if (time_start == NULL || parse_time(&sig->when.time, time_start) < GIT_SUCCESS) {
- /* The tz_start might point at the time */
- parse_time(&sig->when.time, tz_start);
- goto clean_exit;
- }
-
- if (parse_timezone_offset(tz_start, &sig->when.offset) < GIT_SUCCESS) {
- sig->when.time = 0; /* Bogus timezone, we reset the time */
+ email_start = git__memrchr(buffer, '<', buffer_end - buffer);
+ email_end = git__memrchr(buffer, '>', buffer_end - buffer);
+
+ if (!email_start || !email_end || email_end <= email_start)
+ return signature_error("malformed e-mail");
+
+ email_start += 1;
+ sig->name = extract_trimmed(buffer, email_start - buffer - 1);
+ sig->email = extract_trimmed(email_start, email_end - email_start);
+
+ /* Do we even have a time at the end of the signature? */
+ if (email_end + 2 < buffer_end) {
+ const char *time_start = email_end + 2;
+ const char *time_end;
+
+ if (git__strtol64(&sig->when.time, time_start, &time_end, 10) < 0)
+ return signature_error("invalid Unix timestamp");
+
+ /* do we have a timezone? */
+ if (time_end + 1 < buffer_end) {
+ int offset, hours, mins;
+ const char *tz_start, *tz_end;
+
+ tz_start = time_end + 1;
+
+ if ((tz_start[0] != '-' && tz_start[0] != '+') ||
+ git__strtol32(&offset, tz_start + 1, &tz_end, 10) < 0) {
+ //malformed timezone, just assume it's zero
+ offset = 0;
+ }
+
+ hours = offset / 100;
+ mins = offset % 100;
+
+ /*
+ * only store timezone if it's not overflowing;
+ * see http://www.worldtimezone.com/faq.html
+ */
+ if (hours < 14 && mins < 59) {
+ sig->when.offset = (hours * 60) + mins;
+ if (tz_start[0] == '-')
+ sig->when.offset = -sig->when.offset;
+ }
+ }
}
-clean_exit:
- *buffer_out = line_end + 1;
- return GIT_SUCCESS;
+ *buffer_out = buffer_end + 1;
+ return 0;
}
void git_signature__writebuf(git_buf *buf, const char *header, const git_signature *sig)
@@ -337,6 +230,8 @@ void git_signature__writebuf(git_buf *buf, const char *header, const git_signatu
int offset, hours, mins;
char sign;
+ assert(buf && sig);
+
offset = sig->when.offset;
sign = (sig->when.offset < 0) ? '-' : '+';
diff --git a/src/signature.h b/src/signature.h
index 2fe6cd7c9..24655cbf5 100644
--- a/src/signature.h
+++ b/src/signature.h
@@ -1,3 +1,9 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_signature_h__
#define INCLUDE_signature_h__
diff --git a/src/sortedcache.c b/src/sortedcache.c
new file mode 100644
index 000000000..c6b226153
--- /dev/null
+++ b/src/sortedcache.c
@@ -0,0 +1,378 @@
+#include "sortedcache.h"
+
+GIT__USE_STRMAP;
+
+int git_sortedcache_new(
+ git_sortedcache **out,
+ size_t item_path_offset,
+ git_sortedcache_free_item_fn free_item,
+ void *free_item_payload,
+ git_vector_cmp item_cmp,
+ const char *path)
+{
+ git_sortedcache *sc;
+ size_t pathlen;
+
+ pathlen = path ? strlen(path) : 0;
+
+ sc = git__calloc(sizeof(git_sortedcache) + pathlen + 1, 1);
+ GITERR_CHECK_ALLOC(sc);
+
+ if (git_pool_init(&sc->pool, 1, 0) < 0 ||
+ git_vector_init(&sc->items, 4, item_cmp) < 0 ||
+ git_strmap_alloc(&sc->map) < 0)
+ goto fail;
+
+ if (git_rwlock_init(&sc->lock)) {
+ giterr_set(GITERR_OS, "Failed to initialize lock");
+ goto fail;
+ }
+
+ sc->item_path_offset = item_path_offset;
+ sc->free_item = free_item;
+ sc->free_item_payload = free_item_payload;
+ GIT_REFCOUNT_INC(sc);
+ if (pathlen)
+ memcpy(sc->path, path, pathlen);
+
+ *out = sc;
+ return 0;
+
+fail:
+ git_strmap_free(sc->map);
+ git_vector_free(&sc->items);
+ git_pool_clear(&sc->pool);
+ git__free(sc);
+ return -1;
+}
+
+void git_sortedcache_incref(git_sortedcache *sc)
+{
+ GIT_REFCOUNT_INC(sc);
+}
+
+const char *git_sortedcache_path(git_sortedcache *sc)
+{
+ return sc->path;
+}
+
+static void sortedcache_clear(git_sortedcache *sc)
+{
+ git_strmap_clear(sc->map);
+
+ if (sc->free_item) {
+ size_t i;
+ void *item;
+
+ git_vector_foreach(&sc->items, i, item) {
+ sc->free_item(sc->free_item_payload, item);
+ }
+ }
+
+ git_vector_clear(&sc->items);
+
+ git_pool_clear(&sc->pool);
+}
+
+static void sortedcache_free(git_sortedcache *sc)
+{
+ /* acquire write lock to make sure everyone else is done */
+ if (git_sortedcache_wlock(sc) < 0)
+ return;
+
+ sortedcache_clear(sc);
+ git_vector_free(&sc->items);
+ git_strmap_free(sc->map);
+
+ git_sortedcache_wunlock(sc);
+
+ git_rwlock_free(&sc->lock);
+ git__free(sc);
+}
+
+void git_sortedcache_free(git_sortedcache *sc)
+{
+ if (!sc)
+ return;
+ GIT_REFCOUNT_DEC(sc, sortedcache_free);
+}
+
+static int sortedcache_copy_item(void *payload, void *tgt_item, void *src_item)
+{
+ git_sortedcache *sc = payload;
+ /* path will already have been copied by upsert */
+ memcpy(tgt_item, src_item, sc->item_path_offset);
+ return 0;
+}
+
+/* copy a sorted cache */
+int git_sortedcache_copy(
+ git_sortedcache **out,
+ git_sortedcache *src,
+ bool lock,
+ int (*copy_item)(void *payload, void *tgt_item, void *src_item),
+ void *payload)
+{
+ int error = 0;
+ git_sortedcache *tgt;
+ size_t i;
+ void *src_item, *tgt_item;
+
+ /* just use memcpy if no special copy fn is passed in */
+ if (!copy_item) {
+ copy_item = sortedcache_copy_item;
+ payload = src;
+ }
+
+ if ((error = git_sortedcache_new(
+ &tgt, src->item_path_offset,
+ src->free_item, src->free_item_payload,
+ src->items._cmp, src->path)) < 0)
+ return error;
+
+ if (lock && git_sortedcache_rlock(src) < 0) {
+ git_sortedcache_free(tgt);
+ return -1;
+ }
+
+ git_vector_foreach(&src->items, i, src_item) {
+ char *path = ((char *)src_item) + src->item_path_offset;
+
+ if ((error = git_sortedcache_upsert(&tgt_item, tgt, path)) < 0 ||
+ (error = copy_item(payload, tgt_item, src_item)) < 0)
+ break;
+ }
+
+ if (lock)
+ git_sortedcache_runlock(src);
+ if (error)
+ git_sortedcache_free(tgt);
+
+ *out = !error ? tgt : NULL;
+
+ return error;
+}
+
+/* lock sortedcache while making modifications */
+int git_sortedcache_wlock(git_sortedcache *sc)
+{
+ GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */
+
+ if (git_rwlock_wrlock(&sc->lock) < 0) {
+ giterr_set(GITERR_OS, "Unable to acquire write lock on cache");
+ return -1;
+ }
+ return 0;
+}
+
+/* unlock sorted cache when done with modifications */
+void git_sortedcache_wunlock(git_sortedcache *sc)
+{
+ git_vector_sort(&sc->items);
+ git_rwlock_wrunlock(&sc->lock);
+}
+
+/* lock sortedcache for read */
+int git_sortedcache_rlock(git_sortedcache *sc)
+{
+ GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */
+
+ if (git_rwlock_rdlock(&sc->lock) < 0) {
+ giterr_set(GITERR_OS, "Unable to acquire read lock on cache");
+ return -1;
+ }
+ return 0;
+}
+
+/* unlock sorted cache when done reading */
+void git_sortedcache_runlock(git_sortedcache *sc)
+{
+ GIT_UNUSED(sc); /* prevent warning when compiled w/o threads */
+ git_rwlock_rdunlock(&sc->lock);
+}
+
+/* if the file has changed, lock cache and load file contents into buf;
+ * returns <0 on error, >0 if file has not changed
+ */
+int git_sortedcache_lockandload(git_sortedcache *sc, git_buf *buf)
+{
+ int error, fd;
+
+ if ((error = git_sortedcache_wlock(sc)) < 0)
+ return error;
+
+ if ((error = git_futils_filestamp_check(&sc->stamp, sc->path)) <= 0)
+ goto unlock;
+
+ if (!git__is_sizet(sc->stamp.size)) {
+ giterr_set(GITERR_INVALID, "Unable to load file larger than size_t");
+ error = -1;
+ goto unlock;
+ }
+
+ if ((fd = git_futils_open_ro(sc->path)) < 0) {
+ error = fd;
+ goto unlock;
+ }
+
+ if (buf)
+ error = git_futils_readbuffer_fd(buf, fd, (size_t)sc->stamp.size);
+
+ (void)p_close(fd);
+
+ if (error < 0)
+ goto unlock;
+
+ return 1; /* return 1 -> file needs reload and was successfully loaded */
+
+unlock:
+ git_sortedcache_wunlock(sc);
+ return error;
+}
+
+void git_sortedcache_updated(git_sortedcache *sc)
+{
+ /* update filestamp to latest value */
+ git_futils_filestamp_check(&sc->stamp, sc->path);
+}
+
+/* release all items in sorted cache */
+int git_sortedcache_clear(git_sortedcache *sc, bool wlock)
+{
+ if (wlock && git_sortedcache_wlock(sc) < 0)
+ return -1;
+
+ sortedcache_clear(sc);
+
+ if (wlock)
+ git_sortedcache_wunlock(sc);
+
+ return 0;
+}
+
+/* find and/or insert item, returning pointer to item data */
+int git_sortedcache_upsert(void **out, git_sortedcache *sc, const char *key)
+{
+ int error = 0;
+ khiter_t pos;
+ void *item;
+ size_t keylen, itemlen;
+ char *item_key;
+
+ pos = git_strmap_lookup_index(sc->map, key);
+ if (git_strmap_valid_index(sc->map, pos)) {
+ item = git_strmap_value_at(sc->map, pos);
+ goto done;
+ }
+
+ keylen = strlen(key);
+ itemlen = sc->item_path_offset + keylen + 1;
+ itemlen = (itemlen + 7) & ~7;
+
+ if ((item = git_pool_mallocz(&sc->pool, (uint32_t)itemlen)) == NULL) {
+ /* don't use GITERR_CHECK_ALLOC b/c of lock */
+ error = -1;
+ goto done;
+ }
+
+ /* one strange thing is that even if the vector or hash table insert
+ * fail, there is no way to free the pool item so we just abandon it
+ */
+
+ item_key = ((char *)item) + sc->item_path_offset;
+ memcpy(item_key, key, keylen);
+
+ pos = kh_put(str, sc->map, item_key, &error);
+ if (error < 0)
+ goto done;
+
+ if (!error)
+ kh_key(sc->map, pos) = item_key;
+ kh_val(sc->map, pos) = item;
+
+ error = git_vector_insert(&sc->items, item);
+ if (error < 0)
+ git_strmap_delete_at(sc->map, pos);
+
+done:
+ if (out)
+ *out = !error ? item : NULL;
+ return error;
+}
+
+/* lookup item by key */
+void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key)
+{
+ khiter_t pos = git_strmap_lookup_index(sc->map, key);
+ if (git_strmap_valid_index(sc->map, pos))
+ return git_strmap_value_at(sc->map, pos);
+ return NULL;
+}
+
+/* find out how many items are in the cache */
+size_t git_sortedcache_entrycount(const git_sortedcache *sc)
+{
+ return git_vector_length(&sc->items);
+}
+
+/* lookup item by index */
+void *git_sortedcache_entry(git_sortedcache *sc, size_t pos)
+{
+ /* make sure the items are sorted so this gets the correct item */
+ if (!git_vector_is_sorted(&sc->items))
+ git_vector_sort(&sc->items);
+
+ return git_vector_get(&sc->items, pos);
+}
+
+/* helper struct so bsearch callback can know offset + key value for cmp */
+struct sortedcache_magic_key {
+ size_t offset;
+ const char *key;
+};
+
+static int sortedcache_magic_cmp(const void *key, const void *value)
+{
+ const struct sortedcache_magic_key *magic = key;
+ const char *value_key = ((const char *)value) + magic->offset;
+ return strcmp(magic->key, value_key);
+}
+
+/* lookup index of item by key */
+int git_sortedcache_lookup_index(
+ size_t *out, git_sortedcache *sc, const char *key)
+{
+ struct sortedcache_magic_key magic;
+
+ magic.offset = sc->item_path_offset;
+ magic.key = key;
+
+ return git_vector_bsearch2(out, &sc->items, sortedcache_magic_cmp, &magic);
+}
+
+/* remove entry from cache */
+int git_sortedcache_remove(git_sortedcache *sc, size_t pos)
+{
+ char *item;
+ khiter_t mappos;
+
+ /* because of pool allocation, this can't actually remove the item,
+ * but we can remove it from the items vector and the hash table.
+ */
+
+ if ((item = git_vector_get(&sc->items, pos)) == NULL) {
+ giterr_set(GITERR_INVALID, "Removing item out of range");
+ return GIT_ENOTFOUND;
+ }
+
+ (void)git_vector_remove(&sc->items, pos);
+
+ mappos = git_strmap_lookup_index(sc->map, item + sc->item_path_offset);
+ git_strmap_delete_at(sc->map, mappos);
+
+ if (sc->free_item)
+ sc->free_item(sc->free_item_payload, item);
+
+ return 0;
+}
+
diff --git a/src/sortedcache.h b/src/sortedcache.h
new file mode 100644
index 000000000..4cacad62b
--- /dev/null
+++ b/src/sortedcache.h
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_sorted_cache_h__
+#define INCLUDE_sorted_cache_h__
+
+#include "util.h"
+#include "fileops.h"
+#include "vector.h"
+#include "thread-utils.h"
+#include "pool.h"
+#include "strmap.h"
+
+#include <stddef.h>
+
+/*
+ * The purpose of this data structure is to cache the parsed contents of a
+ * file (a.k.a. the backing file) where each item in the file can be
+ * identified by a key string and you want to both look them up by name
+ * and traverse them in sorted order. Each item is assumed to itself end
+ * in a GIT_FLEX_ARRAY.
+ */
+
+typedef void (*git_sortedcache_free_item_fn)(void *payload, void *item);
+
+typedef struct {
+ git_refcount rc;
+ git_rwlock lock;
+ size_t item_path_offset;
+ git_sortedcache_free_item_fn free_item;
+ void *free_item_payload;
+ git_pool pool;
+ git_vector items;
+ git_strmap *map;
+ git_futils_filestamp stamp;
+ char path[GIT_FLEX_ARRAY];
+} git_sortedcache;
+
+/* Create a new sortedcache
+ *
+ * Even though every sortedcache stores items with a GIT_FLEX_ARRAY at
+ * the end containing their key string, you have to provide the item_cmp
+ * sorting function because the sorting function doesn't get a payload
+ * and therefore can't know the offset to the item key string. :-(
+ *
+ * @param out The allocated git_sortedcache
+ * @param item_path_offset Offset to the GIT_FLEX_ARRAY item key in the
+ * struct - use offsetof(struct mine, key-field) to get this
+ * @param free_item Optional callback to free each item
+ * @param free_item_payload Optional payload passed to free_item callback
+ * @param item_cmp Compare the keys of two items
+ * @param path The path to the backing store file for this cache; this
+ * may be NULL. The cache makes it easy to load this and check
+ * if it has been modified since the last load and/or write.
+ */
+int git_sortedcache_new(
+ git_sortedcache **out,
+ size_t item_path_offset, /* use offsetof(struct, path-field) macro */
+ git_sortedcache_free_item_fn free_item,
+ void *free_item_payload,
+ git_vector_cmp item_cmp,
+ const char *path);
+
+/* Copy a sorted cache
+ *
+ * - `copy_item` can be NULL to just use memcpy
+ * - if `lock`, grabs read lock on `src` during copy and releases after
+ */
+int git_sortedcache_copy(
+ git_sortedcache **out,
+ git_sortedcache *src,
+ bool lock,
+ int (*copy_item)(void *payload, void *tgt_item, void *src_item),
+ void *payload);
+
+/* Free sorted cache (first calling `free_item` callbacks)
+ *
+ * Don't call on a locked collection - it may acquire a write lock
+ */
+void git_sortedcache_free(git_sortedcache *sc);
+
+/* Increment reference count - balance with call to free */
+void git_sortedcache_incref(git_sortedcache *sc);
+
+/* Get the pathname associated with this cache at creation time */
+const char *git_sortedcache_path(git_sortedcache *sc);
+
+/*
+ * CACHE WRITE FUNCTIONS
+ *
+ * The following functions require you to have a writer lock to make the
+ * modification. Some of the functions take a `wlock` parameter and
+ * will optionally lock and unlock for you if that is passed as true.
+ *
+ */
+
+/* Lock sortedcache for write */
+int git_sortedcache_wlock(git_sortedcache *sc);
+
+/* Unlock sorted cache when done with write */
+void git_sortedcache_wunlock(git_sortedcache *sc);
+
+/* Lock cache and load backing file into a buffer.
+ *
+ * This grabs a write lock on the cache then looks at the modification
+ * time and size of the file on disk.
+ *
+ * If the file appears to have changed, this loads the file contents into
+ * the buffer and returns a positive value leaving the cache locked - the
+ * caller should parse the file content, update the cache as needed, then
+ * release the lock. NOTE: In this case, the caller MUST unlock the cache.
+ *
+ * If the file appears to be unchanged, then this automatically releases
+ * the lock on the cache, clears the buffer, and returns 0.
+ *
+ * @return 0 if up-to-date, 1 if out-of-date, <0 on error
+ */
+int git_sortedcache_lockandload(git_sortedcache *sc, git_buf *buf);
+
+/* Refresh file timestamp after write completes
+ * You should already be holding the write lock when you call this.
+ */
+void git_sortedcache_updated(git_sortedcache *sc);
+
+/* Release all items in sorted cache
+ *
+ * If `wlock` is true, grabs write lock and releases when done, otherwise
+ * you should already be holding a write lock when you call this.
+ */
+int git_sortedcache_clear(git_sortedcache *sc, bool wlock);
+
+/* Find and/or insert item, returning pointer to item data.
+ * You should already be holding the write lock when you call this.
+ */
+int git_sortedcache_upsert(
+ void **out, git_sortedcache *sc, const char *key);
+
+/* Removes entry at pos from cache
+ * You should already be holding the write lock when you call this.
+ */
+int git_sortedcache_remove(git_sortedcache *sc, size_t pos);
+
+/*
+ * CACHE READ FUNCTIONS
+ *
+ * The following functions access items in the cache. To prevent the
+ * results from being invalidated before they can be used, you should be
+ * holding either a read lock or a write lock when using these functions.
+ *
+ */
+
+/* Lock sortedcache for read */
+int git_sortedcache_rlock(git_sortedcache *sc);
+
+/* Unlock sorted cache when done with read */
+void git_sortedcache_runlock(git_sortedcache *sc);
+
+/* Lookup item by key - returns NULL if not found */
+void *git_sortedcache_lookup(const git_sortedcache *sc, const char *key);
+
+/* Get how many items are in the cache
+ *
+ * You can call this function without holding a lock, but be aware
+ * that it may change before you use it.
+ */
+size_t git_sortedcache_entrycount(const git_sortedcache *sc);
+
+/* Lookup item by index - returns NULL if out of range */
+void *git_sortedcache_entry(git_sortedcache *sc, size_t pos);
+
+/* Lookup index of item by key - returns GIT_ENOTFOUND if not found */
+int git_sortedcache_lookup_index(
+ size_t *out, git_sortedcache *sc, const char *key);
+
+#endif
diff --git a/src/stash.c b/src/stash.c
new file mode 100644
index 000000000..86e0a627c
--- /dev/null
+++ b/src/stash.c
@@ -0,0 +1,648 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "repository.h"
+#include "commit.h"
+#include "tree.h"
+#include "reflog.h"
+#include "git2/diff.h"
+#include "git2/stash.h"
+#include "git2/status.h"
+#include "git2/checkout.h"
+#include "git2/index.h"
+#include "signature.h"
+
+static int create_error(int error, const char *msg)
+{
+ giterr_set(GITERR_STASH, "Cannot stash changes - %s", msg);
+ return error;
+}
+
+static int retrieve_head(git_reference **out, git_repository *repo)
+{
+ int error = git_repository_head(out, repo);
+
+ if (error == GIT_EUNBORNBRANCH)
+ return create_error(error, "You do not have the initial commit yet.");
+
+ return error;
+}
+
+static int append_abbreviated_oid(git_buf *out, const git_oid *b_commit)
+{
+ char *formatted_oid;
+
+ formatted_oid = git_oid_allocfmt(b_commit);
+ GITERR_CHECK_ALLOC(formatted_oid);
+
+ git_buf_put(out, formatted_oid, 7);
+ git__free(formatted_oid);
+
+ return git_buf_oom(out) ? -1 : 0;
+}
+
+static int append_commit_description(git_buf *out, git_commit* commit)
+{
+ const char *message;
+ size_t pos = 0, len;
+
+ if (append_abbreviated_oid(out, git_commit_id(commit)) < 0)
+ return -1;
+
+ message = git_commit_message(commit);
+ len = strlen(message);
+
+ /* TODO: Replace with proper commit short message
+ * when git_commit_message_short() is implemented.
+ */
+ while (pos < len && message[pos] != '\n')
+ pos++;
+
+ git_buf_putc(out, ' ');
+ git_buf_put(out, message, pos);
+ git_buf_putc(out, '\n');
+
+ return git_buf_oom(out) ? -1 : 0;
+}
+
+static int retrieve_base_commit_and_message(
+ git_commit **b_commit,
+ git_buf *stash_message,
+ git_repository *repo)
+{
+ git_reference *head = NULL;
+ int error;
+
+ if ((error = retrieve_head(&head, repo)) < 0)
+ return error;
+
+ if (strcmp("HEAD", git_reference_name(head)) == 0)
+ error = git_buf_puts(stash_message, "(no branch): ");
+ else
+ error = git_buf_printf(
+ stash_message,
+ "%s: ",
+ git_reference_name(head) + strlen(GIT_REFS_HEADS_DIR));
+ if (error < 0)
+ goto cleanup;
+
+ if ((error = git_commit_lookup(
+ b_commit, repo, git_reference_target(head))) < 0)
+ goto cleanup;
+
+ if ((error = append_commit_description(stash_message, *b_commit)) < 0)
+ goto cleanup;
+
+cleanup:
+ git_reference_free(head);
+ return error;
+}
+
+static int build_tree_from_index(git_tree **out, git_index *index)
+{
+ int error;
+ git_oid i_tree_oid;
+
+ if ((error = git_index_write_tree(&i_tree_oid, index)) < 0)
+ return -1;
+
+ return git_tree_lookup(out, git_index_owner(index), &i_tree_oid);
+}
+
+static int commit_index(
+ git_commit **i_commit,
+ git_index *index,
+ const git_signature *stasher,
+ const char *message,
+ const git_commit *parent)
+{
+ git_tree *i_tree = NULL;
+ git_oid i_commit_oid;
+ git_buf msg = GIT_BUF_INIT;
+ int error;
+
+ if ((error = build_tree_from_index(&i_tree, index)) < 0)
+ goto cleanup;
+
+ if ((error = git_buf_printf(&msg, "index on %s\n", message)) < 0)
+ goto cleanup;
+
+ if ((error = git_commit_create(
+ &i_commit_oid,
+ git_index_owner(index),
+ NULL,
+ stasher,
+ stasher,
+ NULL,
+ git_buf_cstr(&msg),
+ i_tree,
+ 1,
+ &parent)) < 0)
+ goto cleanup;
+
+ error = git_commit_lookup(i_commit, git_index_owner(index), &i_commit_oid);
+
+cleanup:
+ git_tree_free(i_tree);
+ git_buf_free(&msg);
+ return error;
+}
+
+struct stash_update_rules {
+ bool include_changed;
+ bool include_untracked;
+ bool include_ignored;
+};
+
+static int stash_update_index_from_diff(
+ git_index *index,
+ const git_diff *diff,
+ struct stash_update_rules *data)
+{
+ int error = 0;
+ size_t d, max_d = git_diff_num_deltas(diff);
+
+ for (d = 0; !error && d < max_d; ++d) {
+ const char *add_path = NULL;
+ const git_diff_delta *delta = git_diff_get_delta(diff, d);
+
+ switch (delta->status) {
+ case GIT_DELTA_IGNORED:
+ if (data->include_ignored)
+ add_path = delta->new_file.path;
+ break;
+
+ case GIT_DELTA_UNTRACKED:
+ if (data->include_untracked &&
+ delta->new_file.mode != GIT_FILEMODE_TREE)
+ add_path = delta->new_file.path;
+ break;
+
+ case GIT_DELTA_ADDED:
+ case GIT_DELTA_MODIFIED:
+ if (data->include_changed)
+ add_path = delta->new_file.path;
+ break;
+
+ case GIT_DELTA_DELETED:
+ if (data->include_changed &&
+ !git_index_find(NULL, index, delta->old_file.path))
+ error = git_index_remove(index, delta->old_file.path, 0);
+ break;
+
+ default:
+ /* Unimplemented */
+ giterr_set(
+ GITERR_INVALID,
+ "Cannot update index. Unimplemented status (%d)",
+ delta->status);
+ return -1;
+ }
+
+ if (add_path != NULL)
+ error = git_index_add_bypath(index, add_path);
+ }
+
+ return error;
+}
+
+static int build_untracked_tree(
+ git_tree **tree_out,
+ git_index *index,
+ git_commit *i_commit,
+ uint32_t flags)
+{
+ git_tree *i_tree = NULL;
+ git_diff *diff = NULL;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ struct stash_update_rules data = {0};
+ int error;
+
+ git_index_clear(index);
+
+ if (flags & GIT_STASH_INCLUDE_UNTRACKED) {
+ opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED |
+ GIT_DIFF_RECURSE_UNTRACKED_DIRS;
+ data.include_untracked = true;
+ }
+
+ if (flags & GIT_STASH_INCLUDE_IGNORED) {
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED;
+ data.include_ignored = true;
+ }
+
+ if ((error = git_commit_tree(&i_tree, i_commit)) < 0)
+ goto cleanup;
+
+ if ((error = git_diff_tree_to_workdir(
+ &diff, git_index_owner(index), i_tree, &opts)) < 0)
+ goto cleanup;
+
+ if ((error = stash_update_index_from_diff(index, diff, &data)) < 0)
+ goto cleanup;
+
+ error = build_tree_from_index(tree_out, index);
+
+cleanup:
+ git_diff_free(diff);
+ git_tree_free(i_tree);
+ return error;
+}
+
+static int commit_untracked(
+ git_commit **u_commit,
+ git_index *index,
+ const git_signature *stasher,
+ const char *message,
+ git_commit *i_commit,
+ uint32_t flags)
+{
+ git_tree *u_tree = NULL;
+ git_oid u_commit_oid;
+ git_buf msg = GIT_BUF_INIT;
+ int error;
+
+ if ((error = build_untracked_tree(&u_tree, index, i_commit, flags)) < 0)
+ goto cleanup;
+
+ if ((error = git_buf_printf(&msg, "untracked files on %s\n", message)) < 0)
+ goto cleanup;
+
+ if ((error = git_commit_create(
+ &u_commit_oid,
+ git_index_owner(index),
+ NULL,
+ stasher,
+ stasher,
+ NULL,
+ git_buf_cstr(&msg),
+ u_tree,
+ 0,
+ NULL)) < 0)
+ goto cleanup;
+
+ error = git_commit_lookup(u_commit, git_index_owner(index), &u_commit_oid);
+
+cleanup:
+ git_tree_free(u_tree);
+ git_buf_free(&msg);
+ return error;
+}
+
+static int build_workdir_tree(
+ git_tree **tree_out,
+ git_index *index,
+ git_commit *b_commit)
+{
+ git_repository *repo = git_index_owner(index);
+ git_tree *b_tree = NULL;
+ git_diff *diff = NULL;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ struct stash_update_rules data = {0};
+ int error;
+
+ opts.flags = GIT_DIFF_IGNORE_SUBMODULES;
+
+ if ((error = git_commit_tree(&b_tree, b_commit)) < 0)
+ goto cleanup;
+
+ if ((error = git_diff_tree_to_workdir_with_index(
+ &diff, repo, b_tree, &opts)) < 0)
+ goto cleanup;
+
+ data.include_changed = true;
+
+ if ((error = stash_update_index_from_diff(index, diff, &data)) < 0)
+ goto cleanup;
+
+ error = build_tree_from_index(tree_out, index);
+
+cleanup:
+ git_diff_free(diff);
+ git_tree_free(b_tree);
+
+ return error;
+}
+
+static int commit_worktree(
+ git_oid *w_commit_oid,
+ git_index *index,
+ const git_signature *stasher,
+ const char *message,
+ git_commit *i_commit,
+ git_commit *b_commit,
+ git_commit *u_commit)
+{
+ int error = 0;
+ git_tree *w_tree = NULL, *i_tree = NULL;
+ const git_commit *parents[] = { NULL, NULL, NULL };
+
+ parents[0] = b_commit;
+ parents[1] = i_commit;
+ parents[2] = u_commit;
+
+ if ((error = git_commit_tree(&i_tree, i_commit)) < 0)
+ goto cleanup;
+
+ if ((error = git_index_read_tree(index, i_tree)) < 0)
+ goto cleanup;
+
+ if ((error = build_workdir_tree(&w_tree, index, b_commit)) < 0)
+ goto cleanup;
+
+ error = git_commit_create(
+ w_commit_oid,
+ git_index_owner(index),
+ NULL,
+ stasher,
+ stasher,
+ NULL,
+ message,
+ w_tree,
+ u_commit ? 3 : 2,
+ parents);
+
+cleanup:
+ git_tree_free(i_tree);
+ git_tree_free(w_tree);
+ return error;
+}
+
+static int prepare_worktree_commit_message(
+ git_buf* msg,
+ const char *user_message)
+{
+ git_buf buf = GIT_BUF_INIT;
+ int error;
+
+ if ((error = git_buf_set(&buf, git_buf_cstr(msg), git_buf_len(msg))) < 0)
+ return error;
+
+ git_buf_clear(msg);
+
+ if (!user_message)
+ git_buf_printf(msg, "WIP on %s", git_buf_cstr(&buf));
+ else {
+ const char *colon;
+
+ if ((colon = strchr(git_buf_cstr(&buf), ':')) == NULL)
+ goto cleanup;
+
+ git_buf_puts(msg, "On ");
+ git_buf_put(msg, git_buf_cstr(&buf), colon - buf.ptr);
+ git_buf_printf(msg, ": %s\n", user_message);
+ }
+
+ error = (git_buf_oom(msg) || git_buf_oom(&buf)) ? -1 : 0;
+
+cleanup:
+ git_buf_free(&buf);
+
+ return error;
+}
+
+static int update_reflog(
+ git_oid *w_commit_oid,
+ git_repository *repo,
+ const git_signature *stasher,
+ const char *message)
+{
+ git_reference *stash;
+ int error;
+
+ if ((error = git_reference_ensure_log(repo, GIT_REFS_STASH_FILE)) < 0)
+ return error;
+
+ error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, w_commit_oid, 1, stasher, message);
+
+ git_reference_free(stash);
+
+ 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 GIT_PASSTHROUGH;
+}
+
+static int ensure_there_are_changes_to_stash(
+ git_repository *repo,
+ bool include_untracked_files,
+ bool include_ignored_files)
+{
+ int error;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+
+ opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
+ opts.flags = GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
+
+ 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_PASSTHROUGH)
+ 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,
+ bool remove_ignored)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ if (remove_untracked)
+ opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED;
+
+ if (remove_ignored)
+ opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_IGNORED;
+
+ return git_checkout_tree(repo, (git_object *)commit, &opts);
+}
+
+int git_stash_save(
+ git_oid *out,
+ git_repository *repo,
+ const git_signature *stasher,
+ const char *message,
+ uint32_t flags)
+{
+ git_index *index = NULL;
+ git_commit *b_commit = NULL, *i_commit = NULL, *u_commit = NULL;
+ git_buf msg = GIT_BUF_INIT;
+ int error;
+
+ assert(out && repo && stasher);
+
+ if ((error = git_repository__ensure_not_bare(repo, "stash save")) < 0)
+ return error;
+
+ if ((error = retrieve_base_commit_and_message(&b_commit, &msg, repo)) < 0)
+ goto cleanup;
+
+ if ((error = ensure_there_are_changes_to_stash(
+ repo,
+ (flags & GIT_STASH_INCLUDE_UNTRACKED) != 0,
+ (flags & GIT_STASH_INCLUDE_IGNORED) != 0)) < 0)
+ goto cleanup;
+
+ if ((error = git_repository_index(&index, repo)) < 0)
+ goto cleanup;
+
+ if ((error = commit_index(
+ &i_commit, index, stasher, git_buf_cstr(&msg), b_commit)) < 0)
+ goto cleanup;
+
+ if ((flags & (GIT_STASH_INCLUDE_UNTRACKED | GIT_STASH_INCLUDE_IGNORED)) &&
+ (error = commit_untracked(
+ &u_commit, index, stasher, git_buf_cstr(&msg),
+ i_commit, flags)) < 0)
+ goto cleanup;
+
+ if ((error = prepare_worktree_commit_message(&msg, message)) < 0)
+ goto cleanup;
+
+ if ((error = commit_worktree(
+ out, index, stasher, git_buf_cstr(&msg),
+ i_commit, b_commit, u_commit)) < 0)
+ goto cleanup;
+
+ git_buf_rtrim(&msg);
+
+ if ((error = update_reflog(out, repo, stasher, git_buf_cstr(&msg))) < 0)
+ goto cleanup;
+
+ if ((error = reset_index_and_workdir(
+ repo,
+ ((flags & GIT_STASH_KEEP_INDEX) != 0) ? i_commit : b_commit,
+ (flags & GIT_STASH_INCLUDE_UNTRACKED) != 0,
+ (flags & GIT_STASH_INCLUDE_IGNORED) != 0)) < 0)
+ goto cleanup;
+
+cleanup:
+
+ git_buf_free(&msg);
+ git_commit_free(i_commit);
+ git_commit_free(b_commit);
+ git_commit_free(u_commit);
+ git_index_free(index);
+
+ return error;
+}
+
+int git_stash_foreach(
+ git_repository *repo,
+ git_stash_cb callback,
+ void *payload)
+{
+ git_reference *stash;
+ git_reflog *reflog = NULL;
+ int error;
+ size_t i, max;
+ const git_reflog_entry *entry;
+
+ error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE);
+ if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ return 0;
+ }
+ if (error < 0)
+ goto cleanup;
+
+ if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0)
+ goto cleanup;
+
+ max = git_reflog_entrycount(reflog);
+ for (i = 0; i < max; i++) {
+ entry = git_reflog_entry_byindex(reflog, i);
+
+ error = callback(i,
+ git_reflog_entry_message(entry),
+ git_reflog_entry_id_new(entry),
+ payload);
+
+ if (error) {
+ giterr_set_after_callback(error);
+ break;
+ }
+ }
+
+cleanup:
+ git_reference_free(stash);
+ git_reflog_free(reflog);
+ return error;
+}
+
+int git_stash_drop(
+ git_repository *repo,
+ size_t index)
+{
+ git_reference *stash;
+ git_reflog *reflog = NULL;
+ size_t max;
+ int error;
+
+ if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0)
+ return error;
+
+ if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0)
+ goto cleanup;
+
+ max = git_reflog_entrycount(reflog);
+
+ if (index > max - 1) {
+ error = GIT_ENOTFOUND;
+ giterr_set(GITERR_STASH, "No stashed state at position %" PRIuZ, index);
+ goto cleanup;
+ }
+
+ if ((error = git_reflog_drop(reflog, index, true)) < 0)
+ goto cleanup;
+
+ if ((error = git_reflog_write(reflog)) < 0)
+ goto cleanup;
+
+ if (max == 1) {
+ error = git_reference_delete(stash);
+ git_reference_free(stash);
+ stash = NULL;
+ } else if (index == 0) {
+ const git_reflog_entry *entry;
+
+ entry = git_reflog_entry_byindex(reflog, 0);
+
+ git_reference_free(stash);
+ if ((error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, &entry->oid_cur, 1, NULL, NULL) < 0))
+ goto cleanup;
+
+ /* We need to undo the writing that we just did */
+ error = git_reflog_write(reflog);
+ }
+
+cleanup:
+ git_reference_free(stash);
+ git_reflog_free(reflog);
+ return error;
+}
diff --git a/src/status.c b/src/status.c
index d9613c129..8d7612f72 100644
--- a/src/status.c
+++ b/src/status.c
@@ -1,26 +1,8 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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"
@@ -29,333 +11,538 @@
#include "hash.h"
#include "vector.h"
#include "tree.h"
+#include "status.h"
#include "git2/status.h"
+#include "repository.h"
+#include "ignore.h"
+#include "index.h"
-struct status_entry {
- char path[GIT_PATH_MAX];
+#include "git2/diff.h"
+#include "diff.h"
- git_index_time mtime;
+static unsigned int index_delta2status(const git_diff_delta *head2idx)
+{
+ git_status_t st = GIT_STATUS_CURRENT;
+
+ switch (head2idx->status) {
+ case GIT_DELTA_ADDED:
+ case GIT_DELTA_COPIED:
+ st = GIT_STATUS_INDEX_NEW;
+ break;
+ case GIT_DELTA_DELETED:
+ st = GIT_STATUS_INDEX_DELETED;
+ break;
+ case GIT_DELTA_MODIFIED:
+ st = GIT_STATUS_INDEX_MODIFIED;
+ break;
+ case GIT_DELTA_RENAMED:
+ st = GIT_STATUS_INDEX_RENAMED;
+
+ if (!git_oid_equal(&head2idx->old_file.id, &head2idx->new_file.id))
+ st |= GIT_STATUS_INDEX_MODIFIED;
+ break;
+ case GIT_DELTA_TYPECHANGE:
+ st = GIT_STATUS_INDEX_TYPECHANGE;
+ break;
+ default:
+ break;
+ }
- git_oid head_oid;
- git_oid index_oid;
- git_oid wt_oid;
+ return st;
+}
- unsigned int status_flags:6;
-};
+static unsigned int workdir_delta2status(
+ git_diff *diff, git_diff_delta *idx2wd)
+{
+ git_status_t st = GIT_STATUS_CURRENT;
+
+ switch (idx2wd->status) {
+ case GIT_DELTA_ADDED:
+ case GIT_DELTA_COPIED:
+ case GIT_DELTA_UNTRACKED:
+ st = GIT_STATUS_WT_NEW;
+ break;
+ case GIT_DELTA_DELETED:
+ st = GIT_STATUS_WT_DELETED;
+ break;
+ case GIT_DELTA_MODIFIED:
+ st = GIT_STATUS_WT_MODIFIED;
+ break;
+ case GIT_DELTA_IGNORED:
+ st = GIT_STATUS_IGNORED;
+ break;
+ case GIT_DELTA_RENAMED:
+ st = GIT_STATUS_WT_RENAMED;
+
+ if (!git_oid_equal(&idx2wd->old_file.id, &idx2wd->new_file.id)) {
+ /* if OIDs don't match, we might need to calculate them now to
+ * discern between RENAMED vs RENAMED+MODIFED
+ */
+ if (git_oid_iszero(&idx2wd->old_file.id) &&
+ diff->old_src == GIT_ITERATOR_TYPE_WORKDIR &&
+ !git_diff__oid_for_file(
+ &idx2wd->old_file.id, diff, idx2wd->old_file.path,
+ idx2wd->old_file.mode, idx2wd->old_file.size))
+ idx2wd->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
+
+ if (git_oid_iszero(&idx2wd->new_file.id) &&
+ diff->new_src == GIT_ITERATOR_TYPE_WORKDIR &&
+ !git_diff__oid_for_file(
+ &idx2wd->new_file.id, diff, idx2wd->new_file.path,
+ idx2wd->new_file.mode, idx2wd->new_file.size))
+ idx2wd->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
+
+ if (!git_oid_equal(&idx2wd->old_file.id, &idx2wd->new_file.id))
+ st |= GIT_STATUS_WT_MODIFIED;
+ }
+ break;
+ case GIT_DELTA_TYPECHANGE:
+ st = GIT_STATUS_WT_TYPECHANGE;
+ break;
+ default:
+ break;
+ }
+
+ return st;
+}
-static int status_cmp(const void *a, const void *b)
+static bool status_is_included(
+ git_status_list *status,
+ git_diff_delta *head2idx,
+ git_diff_delta *idx2wd)
{
- const struct status_entry *entry_a = (const struct status_entry *)(a);
- const struct status_entry *entry_b = (const struct status_entry *)(b);
+ if (!(status->opts.flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES))
+ return 1;
- return strcmp(entry_a->path, entry_b->path);
+ /* if excluding submodules and this is a submodule everywhere */
+ if (head2idx) {
+ if (head2idx->status != GIT_DELTA_ADDED &&
+ head2idx->old_file.mode != GIT_FILEMODE_COMMIT)
+ return 1;
+ if (head2idx->status != GIT_DELTA_DELETED &&
+ head2idx->new_file.mode != GIT_FILEMODE_COMMIT)
+ return 1;
+ }
+ if (idx2wd) {
+ if (idx2wd->status != GIT_DELTA_ADDED &&
+ idx2wd->old_file.mode != GIT_FILEMODE_COMMIT)
+ return 1;
+ if (idx2wd->status != GIT_DELTA_DELETED &&
+ idx2wd->new_file.mode != GIT_FILEMODE_COMMIT)
+ return 1;
+ }
+
+ /* only get here if every valid mode is GIT_FILEMODE_COMMIT */
+ return 0;
}
-static int status_srch(const void *key, const void *array_member)
+static git_status_t status_compute(
+ git_status_list *status,
+ git_diff_delta *head2idx,
+ git_diff_delta *idx2wd)
{
- const char *path = (const char *)key;
- const struct status_entry *entry = (const struct status_entry *)(array_member);
+ git_status_t st = GIT_STATUS_CURRENT;
+
+ if (head2idx)
+ st |= index_delta2status(head2idx);
+
+ if (idx2wd)
+ st |= workdir_delta2status(status->idx2wd, idx2wd);
- return strcmp(path, entry->path);
+ return st;
}
-static int find_status_entry(git_vector *entries, const char *path)
+static int status_collect(
+ git_diff_delta *head2idx,
+ git_diff_delta *idx2wd,
+ void *payload)
{
- return git_vector_bsearch2(entries, status_srch, path);
+ git_status_list *status = payload;
+ git_status_entry *status_entry;
+
+ if (!status_is_included(status, head2idx, idx2wd))
+ return 0;
+
+ status_entry = git__malloc(sizeof(git_status_entry));
+ GITERR_CHECK_ALLOC(status_entry);
+
+ status_entry->status = status_compute(status, head2idx, idx2wd);
+ status_entry->head_to_index = head2idx;
+ status_entry->index_to_workdir = idx2wd;
+
+ return git_vector_insert(&status->paired, status_entry);
}
-static struct status_entry *new_status_entry(git_vector *entries, const char *path)
+GIT_INLINE(int) status_entry_cmp_base(
+ const void *a,
+ const void *b,
+ int (*strcomp)(const char *a, const char *b))
{
- struct status_entry *e = git__malloc(sizeof(struct status_entry));
- memset(e, 0x0, sizeof(struct status_entry));
- if (entries != NULL)
- git_vector_insert(entries, e);
- strcpy(e->path, path);
- return e;
+ const git_status_entry *entry_a = a;
+ const git_status_entry *entry_b = b;
+ const git_diff_delta *delta_a, *delta_b;
+
+ delta_a = entry_a->index_to_workdir ? entry_a->index_to_workdir :
+ entry_a->head_to_index;
+ delta_b = entry_b->index_to_workdir ? entry_b->index_to_workdir :
+ entry_b->head_to_index;
+
+ if (!delta_a && delta_b)
+ return -1;
+ if (delta_a && !delta_b)
+ return 1;
+ if (!delta_a && !delta_b)
+ return 0;
+
+ return strcomp(delta_a->new_file.path, delta_b->new_file.path);
}
-static void recurse_tree_entries(git_tree *tree, git_vector *entries, char *path)
+static int status_entry_icmp(const void *a, const void *b)
{
- int i, cnt, idx;
- struct status_entry *e;
- char file_path[GIT_PATH_MAX];
- git_tree *subtree;
+ return status_entry_cmp_base(a, b, git__strcasecmp);
+}
- cnt = git_tree_entrycount(tree);
- for (i = 0; i < cnt; ++i) {
- const git_tree_entry *tree_entry = git_tree_entry_byindex(tree, i);
+static int status_entry_cmp(const void *a, const void *b)
+{
+ return status_entry_cmp_base(a, b, git__strcmp);
+}
- git_path_join(file_path, path, tree_entry->filename);
+static git_status_list *git_status_list_alloc(git_index *index)
+{
+ git_status_list *status = NULL;
+ int (*entrycmp)(const void *a, const void *b);
- if (git_tree_lookup(&subtree, tree->object.repo, &tree_entry->oid) == GIT_SUCCESS) {
- recurse_tree_entries(subtree, entries, file_path);
- git_tree_close(subtree);
- continue;
- }
+ if (!(status = git__calloc(1, sizeof(git_status_list))))
+ return NULL;
- if ((idx = find_status_entry(entries, file_path)) != GIT_ENOTFOUND)
- e = (struct status_entry *)git_vector_get(entries, idx);
- else
- e = new_status_entry(entries, file_path);
+ entrycmp = index->ignore_case ? status_entry_icmp : status_entry_cmp;
- git_oid_cpy(&e->head_oid, &tree_entry->oid);
+ if (git_vector_init(&status->paired, 0, entrycmp) < 0) {
+ git__free(status);
+ return NULL;
}
+
+ return status;
}
-static void recurse_tree_entry(git_tree *tree, struct status_entry *e, const char *path)
+static int status_validate_options(const git_status_options *opts)
{
- char *dir_sep;
- char buffer[GIT_PATH_MAX];
- const git_tree_entry *tree_entry;
- git_tree *subtree;
-
- strcpy(buffer, path);
-
- dir_sep = strchr(buffer, '/');
- if (dir_sep) {
- *dir_sep = '\0';
-
- tree_entry = git_tree_entry_byname(tree, buffer);
- if (tree_entry != NULL) {
- if (git_tree_lookup(&subtree, tree->object.repo, &tree_entry->oid) == GIT_SUCCESS) {
- recurse_tree_entry(subtree, e, dir_sep+1);
- git_tree_close(subtree);
- return;
- }
- }
- }
+ if (!opts)
+ return 0;
+
+ GITERR_CHECK_VERSION(opts, GIT_STATUS_OPTIONS_VERSION, "git_status_options");
- tree_entry = git_tree_entry_byname(tree, path);
- if (tree_entry != NULL) {
- git_oid_cpy(&e->head_oid, &tree_entry->oid);
+ if (opts->show > GIT_STATUS_SHOW_WORKDIR_ONLY) {
+ giterr_set(GITERR_INVALID, "Unknown status 'show' option");
+ return -1;
}
-}
-struct status_st {
- union {
- git_vector *vector;
- struct status_entry *e;
- } entry;
+ if ((opts->flags & GIT_STATUS_OPT_NO_REFRESH) != 0 &&
+ (opts->flags & GIT_STATUS_OPT_UPDATE_INDEX) != 0) {
+ giterr_set(GITERR_INVALID, "Updating index from status "
+ "is not allowed when index refresh is disabled");
+ return -1;
+ }
- int workdir_path_len;
-};
+ return 0;
+}
-static int dirent_cb(void *state, char *full_path)
+int git_status_list_new(
+ git_status_list **out,
+ git_repository *repo,
+ const git_status_options *opts)
{
- int idx;
- struct status_entry *e;
- struct status_st *st = (struct status_st *)state;
- char *file_path = full_path + st->workdir_path_len;
- struct stat filest;
- git_oid oid;
-
- if ((git_futils_isdir(full_path) == GIT_SUCCESS) && (!strcmp(".git", file_path)))
- return 0;
+ git_index *index = NULL;
+ git_status_list *status = NULL;
+ git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options findopt = GIT_DIFF_FIND_OPTIONS_INIT;
+ git_tree *head = NULL;
+ git_status_show_t show =
+ opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
+ int error = 0;
+ unsigned int flags = opts ? opts->flags : GIT_STATUS_OPT_DEFAULTS;
+
+ *out = NULL;
+
+ if (status_validate_options(opts) < 0)
+ return -1;
+
+ if ((error = git_repository__ensure_not_bare(repo, "status")) < 0 ||
+ (error = git_repository_index(&index, repo)) < 0)
+ return error;
+
+ /* if there is no HEAD, that's okay - we'll make an empty iterator */
+ if ((error = git_repository_head_tree(&head, repo)) < 0) {
+ if (error != GIT_ENOTFOUND && error != GIT_EUNBORNBRANCH)
+ goto done;
+ giterr_clear();
+ }
- if (git_futils_isdir(full_path) == GIT_SUCCESS)
- return git_futils_direach(full_path, GIT_PATH_MAX, dirent_cb, state);
+ /* refresh index from disk unless prevented */
+ if ((flags & GIT_STATUS_OPT_NO_REFRESH) == 0 &&
+ git_index_read(index, false) < 0)
+ giterr_clear();
- if ((idx = find_status_entry(st->entry.vector, file_path)) != GIT_ENOTFOUND) {
- e = (struct status_entry *)git_vector_get(st->entry.vector, idx);
+ status = git_status_list_alloc(index);
+ GITERR_CHECK_ALLOC(status);
- if (p_stat(full_path, &filest) < 0)
- return git__throw(GIT_EOSERR, "Failed to read file %s", full_path);
+ if (opts) {
+ memcpy(&status->opts, opts, sizeof(git_status_options));
+ memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec));
+ }
- if (e->mtime.seconds == (git_time_t)filest.st_mtime) {
- git_oid_cpy(&e->wt_oid, &e->index_oid);
- return 0;
- }
- } else {
- e = new_status_entry(st->entry.vector, file_path);
+ diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE;
+ findopt.flags = GIT_DIFF_FIND_FOR_UNTRACKED;
+
+ if ((flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED;
+ if ((flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_IGNORED;
+ if ((flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED;
+ if ((flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
+ if ((flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH;
+ if ((flags & GIT_STATUS_OPT_RECURSE_IGNORED_DIRS) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_IGNORED_DIRS;
+ if ((flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES;
+ if ((flags & GIT_STATUS_OPT_UPDATE_INDEX) != 0)
+ diffopt.flags = diffopt.flags | GIT_DIFF_UPDATE_INDEX;
+
+ if ((flags & GIT_STATUS_OPT_RENAMES_FROM_REWRITES) != 0)
+ findopt.flags = findopt.flags |
+ GIT_DIFF_FIND_AND_BREAK_REWRITES |
+ GIT_DIFF_FIND_RENAMES_FROM_REWRITES |
+ GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY;
+
+ if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) {
+ if ((error = git_diff_tree_to_index(
+ &status->head2idx, repo, head, index, &diffopt)) < 0)
+ goto done;
+
+ if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 &&
+ (error = git_diff_find_similar(status->head2idx, &findopt)) < 0)
+ goto done;
}
- git_odb_hashfile(&oid, full_path, GIT_OBJ_BLOB);
- git_oid_cpy(&e->wt_oid, &oid);
+ if (show != GIT_STATUS_SHOW_INDEX_ONLY) {
+ if ((error = git_diff_index_to_workdir(
+ &status->idx2wd, repo, index, &diffopt)) < 0)
+ goto done;
- return 0;
-}
+ if ((flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 &&
+ (error = git_diff_find_similar(status->idx2wd, &findopt)) < 0)
+ goto done;
+ }
-static int single_dirent_cb(void *state, char *full_path)
-{
- struct status_st *st = (struct status_st *)state;
- struct status_entry *e = st->entry.e;
+ error = git_diff__paired_foreach(
+ status->head2idx, status->idx2wd, status_collect, status);
+ if (error < 0)
+ goto done;
+
+ if (flags & GIT_STATUS_OPT_SORT_CASE_SENSITIVELY)
+ git_vector_set_cmp(&status->paired, status_entry_cmp);
+ if (flags & GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY)
+ git_vector_set_cmp(&status->paired, status_entry_icmp);
+
+ if ((flags &
+ (GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
+ GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR |
+ GIT_STATUS_OPT_SORT_CASE_SENSITIVELY |
+ GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY)) != 0)
+ git_vector_sort(&status->paired);
+
+done:
+ if (error < 0) {
+ git_status_list_free(status);
+ status = NULL;
+ }
- char *file_path = full_path + st->workdir_path_len;
- struct stat filest;
- git_oid oid;
+ *out = status;
- if ((git_futils_isdir(full_path) == GIT_SUCCESS) && (!strcmp(".git", file_path)))
- return 0;
+ git_tree_free(head);
+ git_index_free(index);
- if (git_futils_isdir(full_path) == GIT_SUCCESS)
- return git_futils_direach(full_path, GIT_PATH_MAX, single_dirent_cb, state);
+ return error;
+}
- if (!strcmp(file_path, e->path)) {
- if (p_stat(full_path, &filest) < 0)
- return git__throw(GIT_EOSERR, "Failed to read file %s", full_path);
+size_t git_status_list_entrycount(git_status_list *status)
+{
+ assert(status);
- if (e->mtime.seconds == (git_time_t)filest.st_mtime) {
- git_oid_cpy(&e->wt_oid, &e->index_oid);
- return 1;
- }
+ return status->paired.length;
+}
- git_odb_hashfile(&oid, full_path, GIT_OBJ_BLOB);
- git_oid_cpy(&e->wt_oid, &oid);
- return 1;
- }
+const git_status_entry *git_status_byindex(git_status_list *status, size_t i)
+{
+ assert(status);
- return 0;
+ return git_vector_get(&status->paired, i);
}
-static int set_status_flags(struct status_entry *e)
+void git_status_list_free(git_status_list *status)
{
- git_oid zero;
- int head_zero, index_zero, wt_zero;
+ if (status == NULL)
+ return;
- memset(&zero, 0x0, sizeof(git_oid));
+ git_diff_free(status->head2idx);
+ git_diff_free(status->idx2wd);
- head_zero = git_oid_cmp(&zero, &e->head_oid);
- index_zero = git_oid_cmp(&zero, &e->index_oid);
- wt_zero = git_oid_cmp(&zero, &e->wt_oid);
+ git_vector_free_deep(&status->paired);
- if (head_zero == 0 && index_zero == 0 && wt_zero == 0)
- return GIT_ENOTFOUND;
+ git__memzero(status, sizeof(*status));
+ git__free(status);
+}
- if (head_zero == 0 && index_zero != 0)
- e->status_flags |= GIT_STATUS_INDEX_NEW;
- else if (index_zero == 0 && head_zero != 0)
- e->status_flags |= GIT_STATUS_INDEX_DELETED;
- else if (git_oid_cmp(&e->head_oid, &e->index_oid) != 0)
- e->status_flags |= GIT_STATUS_INDEX_MODIFIED;
+int git_status_foreach_ext(
+ git_repository *repo,
+ const git_status_options *opts,
+ git_status_cb cb,
+ void *payload)
+{
+ git_status_list *status;
+ const git_status_entry *status_entry;
+ size_t i;
+ int error = 0;
+
+ if ((error = git_status_list_new(&status, repo, opts)) < 0)
+ return error;
+
+ git_vector_foreach(&status->paired, i, status_entry) {
+ const char *path = status_entry->head_to_index ?
+ status_entry->head_to_index->old_file.path :
+ status_entry->index_to_workdir->old_file.path;
+
+ if ((error = cb(path, status_entry->status, payload)) != 0) {
+ giterr_set_after_callback(error);
+ break;
+ }
+ }
- if (index_zero == 0 && wt_zero != 0)
- e->status_flags |= GIT_STATUS_WT_NEW;
- else if (wt_zero == 0 && index_zero != 0)
- e->status_flags |= GIT_STATUS_WT_DELETED;
- else if (git_oid_cmp(&e->index_oid, &e->wt_oid) != 0)
- e->status_flags |= GIT_STATUS_WT_MODIFIED;
+ git_status_list_free(status);
- return GIT_SUCCESS;
+ return error;
}
-int git_status_foreach(git_repository *repo, int (*callback)(const char *, unsigned int, void *), void *payload)
+int git_status_foreach(git_repository *repo, git_status_cb cb, void *payload)
{
- git_vector entries;
- struct status_entry *e;
- git_index *index;
- unsigned int i, cnt;
- git_index_entry *index_entry;
- char temp_path[GIT_PATH_MAX];
- int error;
- git_tree *tree;
- struct status_st dirent_st;
+ return git_status_foreach_ext(repo, NULL, cb, payload);
+}
- git_reference *head_ref, *resolved_head_ref;
- git_commit *head_commit;
+struct status_file_info {
+ char *expected;
+ unsigned int count;
+ unsigned int status;
+ int fnm_flags;
+ int ambiguous;
+};
- git_repository_index(&index, repo);
+static int get_one_status(const char *path, unsigned int status, void *data)
+{
+ struct status_file_info *sfi = data;
+ int (*strcomp)(const char *a, const char *b);
- cnt = git_index_entrycount(index);
- git_vector_init(&entries, cnt, status_cmp);
- for (i = 0; i < cnt; ++i) {
- index_entry = git_index_get(index, i);
+ sfi->count++;
+ sfi->status = status;
- e = new_status_entry(&entries, index_entry->path);
- git_oid_cpy(&e->index_oid, &index_entry->oid);
- e->mtime = index_entry->mtime;
- }
- git_index_free(index);
+ strcomp = (sfi->fnm_flags & FNM_CASEFOLD) ? git__strcasecmp : git__strcmp;
- git_reference_lookup(&head_ref, repo, GIT_HEAD_FILE);
- git_reference_resolve(&resolved_head_ref, head_ref);
+ if (sfi->count > 1 ||
+ (strcomp(sfi->expected, path) != 0 &&
+ p_fnmatch(sfi->expected, path, sfi->fnm_flags) != 0))
+ {
+ sfi->ambiguous = true;
+ return GIT_EAMBIGUOUS; /* giterr_set will be done by caller */
+ }
- git_commit_lookup(&head_commit, repo, git_reference_oid(resolved_head_ref));
+ return 0;
+}
- // recurse through tree entries
- git_commit_tree(&tree, head_commit);
- recurse_tree_entries(tree, &entries, "");
- git_tree_close(tree);
- git_commit_close(head_commit);
+int git_status_file(
+ unsigned int *status_flags,
+ git_repository *repo,
+ const char *path)
+{
+ int error;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ struct status_file_info sfi = {0};
+ git_index *index;
- dirent_st.workdir_path_len = strlen(repo->path_workdir);
- dirent_st.entry.vector = &entries;
- strcpy(temp_path, repo->path_workdir);
- git_futils_direach(temp_path, GIT_PATH_MAX, dirent_cb, &dirent_st);
+ assert(status_flags && repo && path);
- for (i = 0; i < entries.length; ++i) {
- e = (struct status_entry *)git_vector_get(&entries, i);
+ if ((error = git_repository_index__weakptr(&index, repo)) < 0)
+ return error;
- set_status_flags(e);
- }
+ if ((sfi.expected = git__strdup(path)) == NULL)
+ return -1;
+ if (index->ignore_case)
+ sfi.fnm_flags = FNM_CASEFOLD;
+ opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
+ opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED |
+ GIT_STATUS_OPT_RECURSE_IGNORED_DIRS |
+ GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS |
+ GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
+ opts.pathspec.count = 1;
+ opts.pathspec.strings = &sfi.expected;
- for (i = 0; i < entries.length; ++i) {
- e = (struct status_entry *)git_vector_get(&entries, i);
+ error = git_status_foreach_ext(repo, &opts, get_one_status, &sfi);
- if ((error = callback(e->path, e->status_flags, payload)) < GIT_SUCCESS)
- return git__throw(error, "Failed to list statuses. User callback failed");
+ if (error < 0 && sfi.ambiguous) {
+ giterr_set(GITERR_INVALID,
+ "Ambiguous path '%s' given to git_status_file", sfi.expected);
+ error = GIT_EAMBIGUOUS;
}
- for (i = 0; i < entries.length; ++i) {
- e = (struct status_entry *)git_vector_get(&entries, i);
- free(e);
+ if (!error && !sfi.count) {
+ giterr_set(GITERR_INVALID,
+ "Attempt to get status of nonexistent file '%s'", path);
+ error = GIT_ENOTFOUND;
}
- git_vector_free(&entries);
- return GIT_SUCCESS;
+ *status_flags = sfi.status;
+
+ git__free(sfi.expected);
+
+ return error;
}
-int git_status_file(unsigned int *status_flags, git_repository *repo, const char *path)
+int git_status_should_ignore(
+ int *ignored,
+ git_repository *repo,
+ const char *path)
{
- struct status_entry *e;
- git_index *index;
- git_index_entry *index_entry;
- char temp_path[GIT_PATH_MAX];
- int idx, error;
- git_tree *tree;
- git_reference *head_ref, *resolved_head_ref;
- git_commit *head_commit;
- struct status_st dirent_st;
-
- assert(status_flags);
-
- e = new_status_entry(NULL, path);
-
- // Find file in Index
- git_repository_index(&index, repo);
- idx = git_index_find(index, path);
- if (idx >= 0) {
- index_entry = git_index_get(index, idx);
- git_oid_cpy(&e->index_oid, &index_entry->oid);
- e->mtime = index_entry->mtime;
- }
- git_index_free(index);
-
- // Find file in HEAD
- git_reference_lookup(&head_ref, repo, GIT_HEAD_FILE);
- git_reference_resolve(&resolved_head_ref, head_ref);
+ return git_ignore_path_is_ignored(ignored, repo, path);
+}
- git_commit_lookup(&head_commit, repo, git_reference_oid(resolved_head_ref));
+int git_status_init_options(git_status_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_status_options, GIT_STATUS_OPTIONS_INIT);
+ return 0;
+}
- git_commit_tree(&tree, head_commit);
- recurse_tree_entry(tree, e, path);
- git_tree_close(tree);
- git_commit_close(head_commit);
+int git_status_list_get_perfdata(
+ git_diff_perfdata *out, const git_status_list *status)
+{
+ assert(out);
+ GITERR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata");
- // Find file in Workdir
- dirent_st.workdir_path_len = strlen(repo->path_workdir);
- dirent_st.entry.e = e;
- strcpy(temp_path, repo->path_workdir);
- git_futils_direach(temp_path, GIT_PATH_MAX, single_dirent_cb, &dirent_st);
+ out->stat_calls = 0;
+ out->oid_calculations = 0;
- if ((error = set_status_flags(e)) < GIT_SUCCESS) {
- free(e);
- return git__throw(error, "Nonexistent file");
+ if (status->head2idx) {
+ out->stat_calls += status->head2idx->perf.stat_calls;
+ out->oid_calculations += status->head2idx->perf.oid_calculations;
+ }
+ if (status->idx2wd) {
+ out->stat_calls += status->idx2wd->perf.stat_calls;
+ out->oid_calculations += status->idx2wd->perf.oid_calculations;
}
- *status_flags = e->status_flags;
-
- free(e);
-
- return GIT_SUCCESS;
+ return 0;
}
+
diff --git a/src/status.h b/src/status.h
new file mode 100644
index 000000000..33008b89c
--- /dev/null
+++ b/src/status.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_status_h__
+#define INCLUDE_status_h__
+
+#include "diff.h"
+#include "git2/status.h"
+#include "git2/diff.h"
+
+struct git_status_list {
+ git_status_options opts;
+
+ git_diff *head2idx;
+ git_diff *idx2wd;
+
+ git_vector paired;
+};
+
+#endif
diff --git a/src/strmap.c b/src/strmap.c
new file mode 100644
index 000000000..b26a13d1f
--- /dev/null
+++ b/src/strmap.c
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "strmap.h"
+
+int git_strmap_next(
+ void **data,
+ git_strmap_iter* iter,
+ git_strmap *map)
+{
+ if (!map)
+ return GIT_ERROR;
+
+ while (*iter != git_strmap_end(map)) {
+ if (!(git_strmap_has_data(map, *iter))) {
+ ++(*iter);
+ continue;
+ }
+
+ *data = git_strmap_value_at(map, *iter);
+
+ ++(*iter);
+
+ return GIT_OK;
+ }
+
+ return GIT_ITEROVER;
+}
diff --git a/src/strmap.h b/src/strmap.h
new file mode 100644
index 000000000..8985aaf7e
--- /dev/null
+++ b/src/strmap.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_strmap_h__
+#define INCLUDE_strmap_h__
+
+#include "common.h"
+
+#define kmalloc git__malloc
+#define kcalloc git__calloc
+#define krealloc git__realloc
+#define kfree git__free
+#include "khash.h"
+
+__KHASH_TYPE(str, const char *, void *);
+typedef khash_t(str) git_strmap;
+typedef khiter_t git_strmap_iter;
+
+#define GIT__USE_STRMAP \
+ __KHASH_IMPL(str, static kh_inline, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal)
+
+#define git_strmap_alloc(hp) \
+ ((*(hp) = kh_init(str)) == NULL) ? giterr_set_oom(), -1 : 0
+
+#define git_strmap_free(h) kh_destroy(str, h), h = NULL
+#define git_strmap_clear(h) kh_clear(str, h)
+
+#define git_strmap_num_entries(h) kh_size(h)
+
+#define git_strmap_lookup_index(h, k) kh_get(str, h, k)
+#define git_strmap_valid_index(h, idx) (idx != kh_end(h))
+
+#define git_strmap_exists(h, k) (kh_get(str, h, k) != kh_end(h))
+#define git_strmap_has_data(h, idx) kh_exist(h, idx)
+
+#define git_strmap_key(h, idx) kh_key(h, idx)
+#define git_strmap_value_at(h, idx) kh_val(h, idx)
+#define git_strmap_set_value_at(h, idx, v) kh_val(h, idx) = v
+#define git_strmap_delete_at(h, idx) kh_del(str, h, idx)
+
+#define git_strmap_insert(h, key, val, rval) do { \
+ khiter_t __pos = kh_put(str, h, key, &rval); \
+ if (rval >= 0) { \
+ if (rval == 0) kh_key(h, __pos) = key; \
+ kh_val(h, __pos) = val; \
+ } } while (0)
+
+#define git_strmap_insert2(h, key, val, oldv, rval) do { \
+ khiter_t __pos = kh_put(str, h, key, &rval); \
+ if (rval >= 0) { \
+ if (rval == 0) { \
+ oldv = kh_val(h, __pos); \
+ kh_key(h, __pos) = key; \
+ } else { oldv = NULL; } \
+ kh_val(h, __pos) = val; \
+ } } while (0)
+
+#define git_strmap_delete(h, key) do { \
+ khiter_t __pos = git_strmap_lookup_index(h, key); \
+ if (git_strmap_valid_index(h, __pos)) \
+ git_strmap_delete_at(h, __pos); } while (0)
+
+#define git_strmap_foreach kh_foreach
+#define git_strmap_foreach_value kh_foreach_value
+
+#define git_strmap_begin kh_begin
+#define git_strmap_end kh_end
+
+int git_strmap_next(
+ void **data,
+ git_strmap_iter* iter,
+ git_strmap *map);
+
+#endif
diff --git a/src/strnlen.h b/src/strnlen.h
new file mode 100644
index 000000000..fdd7fe39c
--- /dev/null
+++ b/src/strnlen.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_strlen_h__
+#define INCLUDE_strlen_h__
+
+#if defined(__MINGW32__) || defined(__sun) || defined(__APPLE__) || defined(__MidnightBSD__)
+# define NO_STRNLEN
+#endif
+
+#ifdef NO_STRNLEN
+GIT_INLINE(size_t) p_strnlen(const char *s, size_t maxlen) {
+ const char *end = memchr(s, 0, maxlen);
+ return end ? (size_t)(end - s) : maxlen;
+}
+#else
+# define p_strnlen strnlen
+#endif
+
+#endif
diff --git a/src/submodule.c b/src/submodule.c
new file mode 100644
index 000000000..b1291df8e
--- /dev/null
+++ b/src/submodule.c
@@ -0,0 +1,1977 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "git2/config.h"
+#include "git2/sys/config.h"
+#include "git2/types.h"
+#include "git2/index.h"
+#include "buffer.h"
+#include "buf_text.h"
+#include "vector.h"
+#include "posix.h"
+#include "config_file.h"
+#include "config.h"
+#include "repository.h"
+#include "submodule.h"
+#include "tree.h"
+#include "iterator.h"
+#include "path.h"
+#include "index.h"
+
+#define GIT_MODULES_FILE ".gitmodules"
+
+static git_cvar_map _sm_update_map[] = {
+ {GIT_CVAR_STRING, "checkout", GIT_SUBMODULE_UPDATE_CHECKOUT},
+ {GIT_CVAR_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE},
+ {GIT_CVAR_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE},
+ {GIT_CVAR_STRING, "none", GIT_SUBMODULE_UPDATE_NONE},
+ {GIT_CVAR_FALSE, NULL, GIT_SUBMODULE_UPDATE_NONE},
+ {GIT_CVAR_TRUE, NULL, GIT_SUBMODULE_UPDATE_CHECKOUT},
+};
+
+static git_cvar_map _sm_ignore_map[] = {
+ {GIT_CVAR_STRING, "none", GIT_SUBMODULE_IGNORE_NONE},
+ {GIT_CVAR_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED},
+ {GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY},
+ {GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL},
+ {GIT_CVAR_FALSE, NULL, GIT_SUBMODULE_IGNORE_NONE},
+ {GIT_CVAR_TRUE, NULL, GIT_SUBMODULE_IGNORE_ALL},
+};
+
+static git_cvar_map _sm_recurse_map[] = {
+ {GIT_CVAR_STRING, "on-demand", GIT_SUBMODULE_RECURSE_ONDEMAND},
+ {GIT_CVAR_FALSE, NULL, GIT_SUBMODULE_RECURSE_NO},
+ {GIT_CVAR_TRUE, NULL, GIT_SUBMODULE_RECURSE_YES},
+};
+
+enum {
+ CACHE_OK = 0,
+ CACHE_REFRESH = 1,
+ CACHE_FLUSH = 2
+};
+enum {
+ GITMODULES_EXISTING = 0,
+ GITMODULES_CREATE = 1,
+};
+
+static kh_inline khint_t str_hash_no_trailing_slash(const char *s)
+{
+ khint_t h;
+
+ for (h = 0; *s; ++s)
+ if (s[1] != '\0' || *s != '/')
+ h = (h << 5) - h + *s;
+
+ return h;
+}
+
+static kh_inline int str_equal_no_trailing_slash(const char *a, const char *b)
+{
+ size_t alen = a ? strlen(a) : 0;
+ size_t blen = b ? strlen(b) : 0;
+
+ if (alen > 0 && a[alen - 1] == '/')
+ alen--;
+ if (blen > 0 && b[blen - 1] == '/')
+ blen--;
+
+ return (alen == blen && strncmp(a, b, alen) == 0);
+}
+
+__KHASH_IMPL(
+ str, static kh_inline, const char *, void *, 1,
+ str_hash_no_trailing_slash, str_equal_no_trailing_slash);
+
+static int submodule_cache_init(git_repository *repo, int refresh);
+static void submodule_cache_free(git_submodule_cache *cache);
+
+static git_config_backend *open_gitmodules(git_submodule_cache *, int gitmod);
+static int get_url_base(git_buf *url, git_repository *repo);
+static int lookup_head_remote_key(git_buf *remote_key, git_repository *repo);
+static int submodule_get(git_submodule **, git_submodule_cache *, const char *, const char *);
+static int submodule_load_from_config(const git_config_entry *, void *);
+static int submodule_load_from_wd_lite(git_submodule *);
+static void submodule_get_index_status(unsigned int *, git_submodule *);
+static void submodule_get_wd_status(unsigned int *, git_submodule *, git_repository *, git_submodule_ignore_t);
+
+static int submodule_cmp(const void *a, const void *b)
+{
+ return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name);
+}
+
+static int submodule_config_key_trunc_puts(git_buf *key, const char *suffix)
+{
+ ssize_t idx = git_buf_rfind(key, '.');
+ git_buf_truncate(key, (size_t)(idx + 1));
+ return git_buf_puts(key, suffix);
+}
+
+/* lookup submodule or return ENOTFOUND if it doesn't exist */
+static int submodule_lookup(
+ git_submodule **out,
+ git_submodule_cache *cache,
+ const char *name,
+ const char *alternate)
+{
+ khiter_t pos;
+
+ /* lock cache */
+
+ pos = git_strmap_lookup_index(cache->submodules, name);
+
+ if (!git_strmap_valid_index(cache->submodules, pos) && alternate)
+ pos = git_strmap_lookup_index(cache->submodules, alternate);
+
+ if (!git_strmap_valid_index(cache->submodules, pos)) {
+ /* unlock cache */
+ return GIT_ENOTFOUND; /* don't set error - caller will cope */
+ }
+
+ if (out != NULL) {
+ git_submodule *sm = git_strmap_value_at(cache->submodules, pos);
+ GIT_REFCOUNT_INC(sm);
+ *out = sm;
+ }
+
+ /* unlock cache */
+
+ return 0;
+}
+
+/* clear a set of flags on all submodules */
+static void submodule_cache_clear_flags(
+ git_submodule_cache *cache, uint32_t mask)
+{
+ git_submodule *sm;
+ uint32_t inverted_mask = ~mask;
+
+ git_strmap_foreach_value(cache->submodules, sm, {
+ sm->flags &= inverted_mask;
+ });
+}
+
+/*
+ * PUBLIC APIS
+ */
+
+bool git_submodule__is_submodule(git_repository *repo, const char *name)
+{
+ git_strmap *map;
+
+ if (submodule_cache_init(repo, CACHE_OK) < 0) {
+ giterr_clear();
+ return false;
+ }
+
+ if (!repo->_submodules || !(map = repo->_submodules->submodules))
+ return false;
+
+ return git_strmap_valid_index(map, git_strmap_lookup_index(map, name));
+}
+
+static void submodule_set_lookup_error(int error, const char *name)
+{
+ if (!error)
+ return;
+
+ giterr_set(GITERR_SUBMODULE, (error == GIT_ENOTFOUND) ?
+ "No submodule named '%s'" :
+ "Submodule '%s' has not been added yet", name);
+}
+
+int git_submodule__lookup(
+ git_submodule **out, /* NULL if user only wants to test existence */
+ git_repository *repo,
+ const char *name) /* trailing slash is allowed */
+{
+ int error;
+
+ assert(repo && name);
+
+ if ((error = submodule_cache_init(repo, CACHE_OK)) < 0)
+ return error;
+
+ if ((error = submodule_lookup(out, repo->_submodules, name, NULL)) < 0)
+ submodule_set_lookup_error(error, name);
+
+ return error;
+}
+
+int git_submodule_lookup(
+ git_submodule **out, /* NULL if user only wants to test existence */
+ git_repository *repo,
+ const char *name) /* trailing slash is allowed */
+{
+ int error;
+
+ assert(repo && name);
+
+ if ((error = submodule_cache_init(repo, CACHE_REFRESH)) < 0)
+ return error;
+
+ if ((error = submodule_lookup(out, repo->_submodules, name, NULL)) < 0) {
+
+ /* check if a plausible submodule exists at path */
+ if (git_repository_workdir(repo)) {
+ git_buf path = GIT_BUF_INIT;
+
+ if (git_buf_join3(&path,
+ '/', git_repository_workdir(repo), name, DOT_GIT) < 0)
+ return -1;
+
+ if (git_path_exists(path.ptr))
+ error = GIT_EEXISTS;
+
+ git_buf_free(&path);
+ }
+
+ submodule_set_lookup_error(error, name);
+ }
+
+ return error;
+}
+
+static void submodule_free_dup(void *sm)
+{
+ git_submodule_free(sm);
+}
+
+int git_submodule_foreach(
+ git_repository *repo,
+ int (*callback)(git_submodule *sm, const char *name, void *payload),
+ void *payload)
+{
+ int error;
+ size_t i;
+ git_submodule *sm;
+ git_submodule_cache *cache;
+ git_vector snapshot = GIT_VECTOR_INIT;
+
+ assert(repo && callback);
+
+ if ((error = submodule_cache_init(repo, CACHE_REFRESH)) < 0)
+ return error;
+
+ cache = repo->_submodules;
+
+ if (git_mutex_lock(&cache->lock) < 0) {
+ giterr_set(GITERR_OS, "Unable to acquire lock on submodule cache");
+ return -1;
+ }
+
+ if (!(error = git_vector_init(
+ &snapshot, kh_size(cache->submodules), submodule_cmp))) {
+
+ git_strmap_foreach_value(cache->submodules, sm, {
+ if ((error = git_vector_insert(&snapshot, sm)) < 0)
+ break;
+ GIT_REFCOUNT_INC(sm);
+ });
+ }
+
+ git_mutex_unlock(&cache->lock);
+
+ if (error < 0)
+ goto done;
+
+ git_vector_uniq(&snapshot, submodule_free_dup);
+
+ git_vector_foreach(&snapshot, i, sm) {
+ if ((error = callback(sm, sm->name, payload)) != 0) {
+ giterr_set_after_callback(error);
+ break;
+ }
+ }
+
+done:
+ git_vector_foreach(&snapshot, i, sm)
+ git_submodule_free(sm);
+ git_vector_free(&snapshot);
+
+ return error;
+}
+
+void git_submodule_cache_free(git_repository *repo)
+{
+ git_submodule_cache *cache;
+
+ assert(repo);
+
+ if ((cache = git__swap(repo->_submodules, NULL)) != NULL)
+ submodule_cache_free(cache);
+}
+
+int git_submodule_add_setup(
+ git_submodule **out,
+ git_repository *repo,
+ const char *url,
+ const char *path,
+ int use_gitlink)
+{
+ int error = 0;
+ git_config_backend *mods = NULL;
+ git_submodule *sm = NULL;
+ git_buf name = GIT_BUF_INIT, real_url = GIT_BUF_INIT;
+ git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT;
+ git_repository *subrepo = NULL;
+
+ assert(repo && url && path);
+
+ /* see if there is already an entry for this submodule */
+
+ if (git_submodule_lookup(NULL, repo, path) < 0)
+ giterr_clear();
+ else {
+ giterr_set(GITERR_SUBMODULE,
+ "Attempt to add submodule '%s' that already exists", path);
+ return GIT_EEXISTS;
+ }
+
+ /* resolve parameters */
+ if ((error = git_submodule_resolve_url(&real_url, repo, url)) < 0)
+ goto cleanup;
+
+ /* validate and normalize path */
+
+ if (git__prefixcmp(path, git_repository_workdir(repo)) == 0)
+ path += strlen(git_repository_workdir(repo));
+
+ if (git_path_root(path) >= 0) {
+ giterr_set(GITERR_SUBMODULE, "Submodule path must be a relative path");
+ error = -1;
+ goto cleanup;
+ }
+
+ /* update .gitmodules */
+
+ if (!(mods = open_gitmodules(repo->_submodules, GITMODULES_CREATE))) {
+ giterr_set(GITERR_SUBMODULE,
+ "Adding submodules to a bare repository is not supported");
+ return -1;
+ }
+
+ if ((error = git_buf_printf(&name, "submodule.%s.path", path)) < 0 ||
+ (error = git_config_file_set_string(mods, name.ptr, path)) < 0)
+ goto cleanup;
+
+ if ((error = submodule_config_key_trunc_puts(&name, "url")) < 0 ||
+ (error = git_config_file_set_string(mods, name.ptr, real_url.ptr)) < 0)
+ goto cleanup;
+
+ git_buf_clear(&name);
+
+ /* init submodule repository and add origin remote as needed */
+
+ error = git_buf_joinpath(&name, git_repository_workdir(repo), path);
+ if (error < 0)
+ goto cleanup;
+
+ /* New style: sub-repo goes in <repo-dir>/modules/<name>/ with a
+ * gitlink in the sub-repo workdir directory to that repository
+ *
+ * Old style: sub-repo goes directly into repo/<name>/.git/
+ */
+
+ initopt.flags = GIT_REPOSITORY_INIT_MKPATH |
+ GIT_REPOSITORY_INIT_NO_REINIT;
+ initopt.origin_url = real_url.ptr;
+
+ if (git_path_exists(name.ptr) &&
+ git_path_contains(&name, DOT_GIT))
+ {
+ /* repo appears to already exist - reinit? */
+ }
+ else if (use_gitlink) {
+ git_buf repodir = GIT_BUF_INIT;
+
+ error = git_buf_join3(
+ &repodir, '/', git_repository_path(repo), "modules", path);
+ if (error < 0)
+ goto cleanup;
+
+ initopt.workdir_path = name.ptr;
+ initopt.flags |= GIT_REPOSITORY_INIT_NO_DOTGIT_DIR;
+
+ error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt);
+
+ git_buf_free(&repodir);
+ }
+ else {
+ error = git_repository_init_ext(&subrepo, name.ptr, &initopt);
+ }
+ if (error < 0)
+ goto cleanup;
+
+ /* add submodule to hash and "reload" it */
+
+ if (git_mutex_lock(&repo->_submodules->lock) < 0) {
+ giterr_set(GITERR_OS, "Unable to acquire lock on submodule cache");
+ error = -1;
+ goto cleanup;
+ }
+
+ if (!(error = submodule_get(&sm, repo->_submodules, path, NULL)) &&
+ !(error = git_submodule_reload(sm, false)))
+ error = git_submodule_init(sm, false);
+
+ git_mutex_unlock(&repo->_submodules->lock);
+
+cleanup:
+ if (error && sm) {
+ git_submodule_free(sm);
+ sm = NULL;
+ }
+ if (out != NULL)
+ *out = sm;
+
+ git_config_file_free(mods);
+ git_repository_free(subrepo);
+ git_buf_free(&real_url);
+ git_buf_free(&name);
+
+ return error;
+}
+
+int git_submodule_add_finalize(git_submodule *sm)
+{
+ int error;
+ git_index *index;
+
+ assert(sm);
+
+ if ((error = git_repository_index__weakptr(&index, sm->repo)) < 0 ||
+ (error = git_index_add_bypath(index, GIT_MODULES_FILE)) < 0)
+ return error;
+
+ return git_submodule_add_to_index(sm, true);
+}
+
+int git_submodule_add_to_index(git_submodule *sm, int write_index)
+{
+ int error;
+ git_repository *sm_repo = NULL;
+ git_index *index;
+ git_buf path = GIT_BUF_INIT;
+ git_commit *head;
+ git_index_entry entry;
+ struct stat st;
+
+ assert(sm);
+
+ /* force reload of wd OID by git_submodule_open */
+ sm->flags = sm->flags & ~GIT_SUBMODULE_STATUS__WD_OID_VALID;
+
+ if ((error = git_repository_index__weakptr(&index, sm->repo)) < 0 ||
+ (error = git_buf_joinpath(
+ &path, git_repository_workdir(sm->repo), sm->path)) < 0 ||
+ (error = git_submodule_open(&sm_repo, sm)) < 0)
+ goto cleanup;
+
+ /* read stat information for submodule working directory */
+ if (p_stat(path.ptr, &st) < 0) {
+ giterr_set(GITERR_SUBMODULE,
+ "Cannot add submodule without working directory");
+ error = -1;
+ goto cleanup;
+ }
+
+ memset(&entry, 0, sizeof(entry));
+ entry.path = sm->path;
+ git_index_entry__init_from_stat(
+ &entry, &st, !(git_index_caps(index) & GIT_INDEXCAP_NO_FILEMODE));
+
+ /* calling git_submodule_open will have set sm->wd_oid if possible */
+ if ((sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) == 0) {
+ giterr_set(GITERR_SUBMODULE,
+ "Cannot add submodule without HEAD to index");
+ error = -1;
+ goto cleanup;
+ }
+ git_oid_cpy(&entry.id, &sm->wd_oid);
+
+ if ((error = git_commit_lookup(&head, sm_repo, &sm->wd_oid)) < 0)
+ goto cleanup;
+
+ entry.ctime.seconds = git_commit_time(head);
+ entry.ctime.nanoseconds = 0;
+ entry.mtime.seconds = git_commit_time(head);
+ entry.mtime.nanoseconds = 0;
+
+ git_commit_free(head);
+
+ /* add it */
+ error = git_index_add(index, &entry);
+
+ /* write it, if requested */
+ if (!error && write_index) {
+ error = git_index_write(index);
+
+ if (!error)
+ git_oid_cpy(&sm->index_oid, &sm->wd_oid);
+ }
+
+cleanup:
+ git_repository_free(sm_repo);
+ git_buf_free(&path);
+ return error;
+}
+
+const char *git_submodule_ignore_to_str(git_submodule_ignore_t ignore)
+{
+ int i;
+ for (i = 0; i < (int)ARRAY_SIZE(_sm_ignore_map); ++i)
+ if (_sm_ignore_map[i].map_value == ignore)
+ return _sm_ignore_map[i].str_match;
+ return NULL;
+}
+
+const char *git_submodule_update_to_str(git_submodule_update_t update)
+{
+ int i;
+ for (i = 0; i < (int)ARRAY_SIZE(_sm_update_map); ++i)
+ if (_sm_update_map[i].map_value == update)
+ return _sm_update_map[i].str_match;
+ return NULL;
+}
+
+const char *git_submodule_recurse_to_str(git_submodule_recurse_t recurse)
+{
+ int i;
+ for (i = 0; i < (int)ARRAY_SIZE(_sm_recurse_map); ++i)
+ if (_sm_recurse_map[i].map_value == recurse)
+ return _sm_recurse_map[i].str_match;
+ return NULL;
+}
+
+int git_submodule_save(git_submodule *submodule)
+{
+ int error = 0;
+ git_config_backend *mods;
+ git_buf key = GIT_BUF_INIT;
+ const char *val;
+
+ assert(submodule);
+
+ mods = open_gitmodules(submodule->repo->_submodules, GITMODULES_CREATE);
+ if (!mods) {
+ giterr_set(GITERR_SUBMODULE,
+ "Adding submodules to a bare repository is not supported");
+ return -1;
+ }
+
+ if ((error = git_buf_printf(&key, "submodule.%s.", submodule->name)) < 0)
+ goto cleanup;
+
+ /* save values for path, url, update, ignore, fetchRecurseSubmodules */
+
+ if ((error = submodule_config_key_trunc_puts(&key, "path")) < 0 ||
+ (error = git_config_file_set_string(mods, key.ptr, submodule->path)) < 0)
+ goto cleanup;
+
+ if ((error = submodule_config_key_trunc_puts(&key, "url")) < 0 ||
+ (error = git_config_file_set_string(mods, key.ptr, submodule->url)) < 0)
+ goto cleanup;
+
+ if ((error = submodule_config_key_trunc_puts(&key, "branch")) < 0 ||
+ (error = git_config_file_set_string(mods, key.ptr, submodule->branch)) < 0)
+ goto cleanup;
+
+ if (!(error = submodule_config_key_trunc_puts(&key, "update")) &&
+ (val = git_submodule_update_to_str(submodule->update)) != NULL)
+ error = git_config_file_set_string(mods, key.ptr, val);
+ if (error < 0)
+ goto cleanup;
+
+ if (!(error = submodule_config_key_trunc_puts(&key, "ignore")) &&
+ (val = git_submodule_ignore_to_str(submodule->ignore)) != NULL)
+ error = git_config_file_set_string(mods, key.ptr, val);
+ if (error < 0)
+ goto cleanup;
+
+ if (!(error = submodule_config_key_trunc_puts(&key, "fetchRecurseSubmodules")) &&
+ (val = git_submodule_recurse_to_str(submodule->fetch_recurse)) != NULL)
+ error = git_config_file_set_string(mods, key.ptr, val);
+ if (error < 0)
+ goto cleanup;
+
+ /* update internal defaults */
+
+ submodule->ignore_default = submodule->ignore;
+ submodule->update_default = submodule->update;
+ submodule->fetch_recurse_default = submodule->fetch_recurse;
+ submodule->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG;
+
+cleanup:
+ git_config_file_free(mods);
+ git_buf_free(&key);
+
+ return error;
+}
+
+git_repository *git_submodule_owner(git_submodule *submodule)
+{
+ assert(submodule);
+ return submodule->repo;
+}
+
+const char *git_submodule_name(git_submodule *submodule)
+{
+ assert(submodule);
+ return submodule->name;
+}
+
+const char *git_submodule_path(git_submodule *submodule)
+{
+ assert(submodule);
+ return submodule->path;
+}
+
+const char *git_submodule_url(git_submodule *submodule)
+{
+ assert(submodule);
+ return submodule->url;
+}
+
+int git_submodule_resolve_url(git_buf *out, git_repository *repo, const char *url)
+{
+ int error = 0;
+
+ assert(out && repo && url);
+
+ git_buf_sanitize(out);
+
+ if (git_path_is_relative(url)) {
+ if (!(error = get_url_base(out, repo)))
+ error = git_path_apply_relative(out, url);
+ } else if (strchr(url, ':') != NULL || url[0] == '/') {
+ error = git_buf_sets(out, url);
+ } else {
+ giterr_set(GITERR_SUBMODULE, "Invalid format for submodule URL");
+ error = -1;
+ }
+
+ return error;
+}
+
+const char *git_submodule_branch(git_submodule *submodule)
+{
+ assert(submodule);
+ return submodule->branch;
+}
+
+int git_submodule_set_url(git_submodule *submodule, const char *url)
+{
+ assert(submodule && url);
+
+ git__free(submodule->url);
+
+ submodule->url = git__strdup(url);
+ GITERR_CHECK_ALLOC(submodule->url);
+
+ return 0;
+}
+
+const git_oid *git_submodule_index_id(git_submodule *submodule)
+{
+ assert(submodule);
+
+ if (submodule->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID)
+ return &submodule->index_oid;
+ else
+ return NULL;
+}
+
+const git_oid *git_submodule_head_id(git_submodule *submodule)
+{
+ assert(submodule);
+
+ if (submodule->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID)
+ return &submodule->head_oid;
+ else
+ return NULL;
+}
+
+const git_oid *git_submodule_wd_id(git_submodule *submodule)
+{
+ assert(submodule);
+
+ /* load unless we think we have a valid oid */
+ if (!(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) {
+ git_repository *subrepo;
+
+ /* calling submodule open grabs the HEAD OID if possible */
+ if (!git_submodule_open_bare(&subrepo, submodule))
+ git_repository_free(subrepo);
+ else
+ giterr_clear();
+ }
+
+ if (submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)
+ return &submodule->wd_oid;
+ else
+ return NULL;
+}
+
+git_submodule_ignore_t git_submodule_ignore(git_submodule *submodule)
+{
+ assert(submodule);
+ return (submodule->ignore < GIT_SUBMODULE_IGNORE_NONE) ?
+ GIT_SUBMODULE_IGNORE_NONE : submodule->ignore;
+}
+
+git_submodule_ignore_t git_submodule_set_ignore(
+ git_submodule *submodule, git_submodule_ignore_t ignore)
+{
+ git_submodule_ignore_t old;
+
+ assert(submodule);
+
+ if (ignore == GIT_SUBMODULE_IGNORE_RESET)
+ ignore = submodule->ignore_default;
+
+ old = submodule->ignore;
+ submodule->ignore = ignore;
+ return old;
+}
+
+git_submodule_update_t git_submodule_update(git_submodule *submodule)
+{
+ assert(submodule);
+ return (submodule->update < GIT_SUBMODULE_UPDATE_CHECKOUT) ?
+ GIT_SUBMODULE_UPDATE_CHECKOUT : submodule->update;
+}
+
+git_submodule_update_t git_submodule_set_update(
+ git_submodule *submodule, git_submodule_update_t update)
+{
+ git_submodule_update_t old;
+
+ assert(submodule);
+
+ if (update == GIT_SUBMODULE_UPDATE_RESET)
+ update = submodule->update_default;
+
+ old = submodule->update;
+ submodule->update = update;
+ return old;
+}
+
+git_submodule_recurse_t git_submodule_fetch_recurse_submodules(
+ git_submodule *submodule)
+{
+ assert(submodule);
+ return submodule->fetch_recurse;
+}
+
+git_submodule_recurse_t git_submodule_set_fetch_recurse_submodules(
+ git_submodule *submodule,
+ git_submodule_recurse_t fetch_recurse_submodules)
+{
+ git_submodule_recurse_t old;
+
+ assert(submodule);
+
+ if (fetch_recurse_submodules == GIT_SUBMODULE_RECURSE_RESET)
+ fetch_recurse_submodules = submodule->fetch_recurse_default;
+
+ old = submodule->fetch_recurse;
+ submodule->fetch_recurse = fetch_recurse_submodules;
+ return old;
+}
+
+int git_submodule_init(git_submodule *sm, int overwrite)
+{
+ int error;
+ const char *val;
+ git_buf key = GIT_BUF_INIT;
+ git_config *cfg = NULL;
+
+ if (!sm->url) {
+ giterr_set(GITERR_SUBMODULE,
+ "No URL configured for submodule '%s'", sm->name);
+ return -1;
+ }
+
+ if ((error = git_repository_config(&cfg, sm->repo)) < 0)
+ return error;
+
+ /* write "submodule.NAME.url" */
+
+ if ((error = git_buf_printf(&key, "submodule.%s.url", sm->name)) < 0 ||
+ (error = git_config__update_entry(
+ cfg, key.ptr, sm->url, overwrite != 0, false)) < 0)
+ goto cleanup;
+
+ /* write "submodule.NAME.update" if not default */
+
+ val = (sm->update == GIT_SUBMODULE_UPDATE_CHECKOUT) ?
+ NULL : git_submodule_update_to_str(sm->update);
+
+ if ((error = git_buf_printf(&key, "submodule.%s.update", sm->name)) < 0 ||
+ (error = git_config__update_entry(
+ cfg, key.ptr, val, overwrite != 0, false)) < 0)
+ goto cleanup;
+
+ /* success */
+
+cleanup:
+ git_config_free(cfg);
+ git_buf_free(&key);
+
+ return error;
+}
+
+int git_submodule_sync(git_submodule *sm)
+{
+ int error = 0;
+ git_config *cfg = NULL;
+ git_buf key = GIT_BUF_INIT;
+ git_repository *smrepo = NULL;
+
+ if (!sm->url) {
+ giterr_set(GITERR_SUBMODULE,
+ "No URL configured for submodule '%s'", sm->name);
+ return -1;
+ }
+
+ /* copy URL over to config only if it already exists */
+
+ if (!(error = git_repository_config__weakptr(&cfg, sm->repo)) &&
+ !(error = git_buf_printf(&key, "submodule.%s.url", sm->name)))
+ error = git_config__update_entry(cfg, key.ptr, sm->url, true, true);
+
+ /* if submodule exists in the working directory, update remote url */
+
+ if (!error &&
+ (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) != 0 &&
+ !(error = git_submodule_open(&smrepo, sm)))
+ {
+ git_buf remote_name = GIT_BUF_INIT;
+
+ if ((error = git_repository_config__weakptr(&cfg, smrepo)) < 0)
+ /* return error from reading submodule config */;
+ else if ((error = lookup_head_remote_key(&remote_name, smrepo)) < 0) {
+ giterr_clear();
+ error = git_buf_sets(&key, "branch.origin.remote");
+ } else {
+ error = git_buf_join3(
+ &key, '.', "branch", remote_name.ptr, "remote");
+ git_buf_free(&remote_name);
+ }
+
+ if (!error)
+ error = git_config__update_entry(cfg, key.ptr, sm->url, true, false);
+
+ git_repository_free(smrepo);
+ }
+
+ git_buf_free(&key);
+
+ return error;
+}
+
+static int git_submodule__open(
+ git_repository **subrepo, git_submodule *sm, bool bare)
+{
+ int error;
+ git_buf path = GIT_BUF_INIT;
+ unsigned int flags = GIT_REPOSITORY_OPEN_NO_SEARCH;
+ const char *wd;
+
+ assert(sm && subrepo);
+
+ if (git_repository__ensure_not_bare(
+ sm->repo, "open submodule repository") < 0)
+ return GIT_EBAREREPO;
+
+ wd = git_repository_workdir(sm->repo);
+
+ if (git_buf_joinpath(&path, wd, sm->path) < 0 ||
+ git_buf_joinpath(&path, path.ptr, DOT_GIT) < 0)
+ return -1;
+
+ sm->flags = sm->flags &
+ ~(GIT_SUBMODULE_STATUS_IN_WD |
+ GIT_SUBMODULE_STATUS__WD_OID_VALID |
+ GIT_SUBMODULE_STATUS__WD_SCANNED);
+
+ if (bare)
+ flags |= GIT_REPOSITORY_OPEN_BARE;
+
+ error = git_repository_open_ext(subrepo, path.ptr, flags, wd);
+
+ /* if we opened the submodule successfully, grab HEAD OID, etc. */
+ if (!error) {
+ sm->flags |= GIT_SUBMODULE_STATUS_IN_WD |
+ GIT_SUBMODULE_STATUS__WD_SCANNED;
+
+ if (!git_reference_name_to_id(&sm->wd_oid, *subrepo, GIT_HEAD_FILE))
+ sm->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID;
+ else
+ giterr_clear();
+ } else if (git_path_exists(path.ptr)) {
+ sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED |
+ GIT_SUBMODULE_STATUS_IN_WD;
+ } else {
+ git_buf_rtruncate_at_char(&path, '/'); /* remove "/.git" */
+
+ if (git_path_isdir(path.ptr))
+ sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED;
+ }
+
+ git_buf_free(&path);
+
+ return error;
+}
+
+int git_submodule_open_bare(git_repository **subrepo, git_submodule *sm)
+{
+ return git_submodule__open(subrepo, sm, true);
+}
+
+int git_submodule_open(git_repository **subrepo, git_submodule *sm)
+{
+ return git_submodule__open(subrepo, sm, false);
+}
+
+int git_submodule_reload_all(git_repository *repo, int force)
+{
+ return submodule_cache_init(repo, force ? CACHE_FLUSH : CACHE_REFRESH);
+}
+
+static void submodule_update_from_index_entry(
+ git_submodule *sm, const git_index_entry *ie)
+{
+ bool already_found = (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) != 0;
+
+ if (!S_ISGITLINK(ie->mode)) {
+ if (!already_found)
+ sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE;
+ } else {
+ if (already_found)
+ sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES;
+ else
+ git_oid_cpy(&sm->index_oid, &ie->id);
+
+ sm->flags |= GIT_SUBMODULE_STATUS_IN_INDEX |
+ GIT_SUBMODULE_STATUS__INDEX_OID_VALID;
+ }
+}
+
+static int submodule_update_index(git_submodule *sm)
+{
+ git_index *index;
+ const git_index_entry *ie;
+
+ if (git_repository_index__weakptr(&index, sm->repo) < 0)
+ return -1;
+
+ sm->flags = sm->flags &
+ ~(GIT_SUBMODULE_STATUS_IN_INDEX |
+ GIT_SUBMODULE_STATUS__INDEX_OID_VALID);
+
+ if (!(ie = git_index_get_bypath(index, sm->path, 0)))
+ return 0;
+
+ submodule_update_from_index_entry(sm, ie);
+
+ return 0;
+}
+
+static void submodule_update_from_head_data(
+ git_submodule *sm, mode_t mode, const git_oid *id)
+{
+ if (!S_ISGITLINK(mode))
+ sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE;
+ else {
+ git_oid_cpy(&sm->head_oid, id);
+
+ sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD |
+ GIT_SUBMODULE_STATUS__HEAD_OID_VALID;
+ }
+}
+
+static int submodule_update_head(git_submodule *submodule)
+{
+ git_tree *head = NULL;
+ git_tree_entry *te = NULL;
+
+ submodule->flags = submodule->flags &
+ ~(GIT_SUBMODULE_STATUS_IN_HEAD |
+ GIT_SUBMODULE_STATUS__HEAD_OID_VALID);
+
+ /* if we can't look up file in current head, then done */
+ if (git_repository_head_tree(&head, submodule->repo) < 0 ||
+ git_tree_entry_bypath(&te, head, submodule->path) < 0)
+ giterr_clear();
+ else
+ submodule_update_from_head_data(submodule, te->attr, &te->oid);
+
+ git_tree_entry_free(te);
+ git_tree_free(head);
+ return 0;
+}
+
+
+int git_submodule_reload(git_submodule *sm, int force)
+{
+ int error = 0;
+ git_config_backend *mods;
+ git_submodule_cache *cache;
+
+ GIT_UNUSED(force);
+
+ assert(sm);
+
+ cache = sm->repo->_submodules;
+
+ /* refresh index data */
+ if ((error = submodule_update_index(sm)) < 0)
+ return error;
+
+ /* refresh HEAD tree data */
+ if ((error = submodule_update_head(sm)) < 0)
+ return error;
+
+ /* done if bare */
+ if (git_repository_is_bare(sm->repo))
+ return error;
+
+ /* refresh config data */
+ mods = open_gitmodules(cache, GITMODULES_EXISTING);
+ if (mods != NULL) {
+ git_buf path = GIT_BUF_INIT;
+
+ git_buf_sets(&path, "submodule\\.");
+ git_buf_text_puts_escape_regex(&path, sm->name);
+ git_buf_puts(&path, ".*");
+
+ if (git_buf_oom(&path))
+ error = -1;
+ else
+ error = git_config_file_foreach_match(
+ mods, path.ptr, submodule_load_from_config, cache);
+
+ git_buf_free(&path);
+ git_config_file_free(mods);
+
+ if (error < 0)
+ return error;
+ }
+
+ /* refresh wd data */
+ sm->flags &=
+ ~(GIT_SUBMODULE_STATUS_IN_WD | GIT_SUBMODULE_STATUS__WD_OID_VALID |
+ GIT_SUBMODULE_STATUS__WD_FLAGS);
+
+ return submodule_load_from_wd_lite(sm);
+}
+
+static void submodule_copy_oid_maybe(
+ git_oid *tgt, const git_oid *src, bool valid)
+{
+ if (tgt) {
+ if (valid)
+ memcpy(tgt, src, sizeof(*tgt));
+ else
+ memset(tgt, 0, sizeof(*tgt));
+ }
+}
+
+int git_submodule__status(
+ unsigned int *out_status,
+ git_oid *out_head_id,
+ git_oid *out_index_id,
+ git_oid *out_wd_id,
+ git_submodule *sm,
+ git_submodule_ignore_t ign)
+{
+ unsigned int status;
+ git_repository *smrepo = NULL;
+
+ if (ign < GIT_SUBMODULE_IGNORE_NONE)
+ ign = sm->ignore;
+
+ /* only return location info if ignore == all */
+ if (ign == GIT_SUBMODULE_IGNORE_ALL) {
+ *out_status = (sm->flags & GIT_SUBMODULE_STATUS__IN_FLAGS);
+ return 0;
+ }
+
+ /* refresh the index OID */
+ if (submodule_update_index(sm) < 0)
+ return -1;
+
+ /* refresh the HEAD OID */
+ if (submodule_update_head(sm) < 0)
+ return -1;
+
+ /* for ignore == dirty, don't scan the working directory */
+ if (ign == GIT_SUBMODULE_IGNORE_DIRTY) {
+ /* git_submodule_open_bare will load WD OID data */
+ if (git_submodule_open_bare(&smrepo, sm) < 0)
+ giterr_clear();
+ else
+ git_repository_free(smrepo);
+ smrepo = NULL;
+ } else if (git_submodule_open(&smrepo, sm) < 0) {
+ giterr_clear();
+ smrepo = NULL;
+ }
+
+ status = GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(sm->flags);
+
+ submodule_get_index_status(&status, sm);
+ submodule_get_wd_status(&status, sm, smrepo, ign);
+
+ git_repository_free(smrepo);
+
+ *out_status = status;
+
+ submodule_copy_oid_maybe(out_head_id, &sm->head_oid,
+ (sm->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID) != 0);
+ submodule_copy_oid_maybe(out_index_id, &sm->index_oid,
+ (sm->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID) != 0);
+ submodule_copy_oid_maybe(out_wd_id, &sm->wd_oid,
+ (sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) != 0);
+
+ return 0;
+}
+
+int git_submodule_status(unsigned int *status, git_submodule *sm)
+{
+ assert(status && sm);
+
+ return git_submodule__status(status, NULL, NULL, NULL, sm, 0);
+}
+
+int git_submodule_location(unsigned int *location, git_submodule *sm)
+{
+ assert(location && sm);
+
+ return git_submodule__status(
+ location, NULL, NULL, NULL, sm, GIT_SUBMODULE_IGNORE_ALL);
+}
+
+
+/*
+ * INTERNAL FUNCTIONS
+ */
+
+static int submodule_alloc(
+ git_submodule **out, git_submodule_cache *cache, const char *name)
+{
+ size_t namelen;
+ git_submodule *sm;
+
+ if (!name || !(namelen = strlen(name))) {
+ giterr_set(GITERR_SUBMODULE, "Invalid submodule name");
+ return -1;
+ }
+
+ sm = git__calloc(1, sizeof(git_submodule));
+ GITERR_CHECK_ALLOC(sm);
+
+ sm->name = sm->path = git__strdup(name);
+ if (!sm->name) {
+ git__free(sm);
+ return -1;
+ }
+
+ GIT_REFCOUNT_INC(sm);
+ sm->ignore = sm->ignore_default = GIT_SUBMODULE_IGNORE_NONE;
+ sm->update = sm->update_default = GIT_SUBMODULE_UPDATE_CHECKOUT;
+ sm->fetch_recurse = sm->fetch_recurse_default = GIT_SUBMODULE_RECURSE_NO;
+ sm->repo = cache->repo;
+ sm->branch = NULL;
+
+ *out = sm;
+ return 0;
+}
+
+static void submodule_cache_remove_item(
+ git_submodule_cache *cache,
+ git_submodule *item,
+ bool free_after_remove)
+{
+ git_strmap *map;
+ const char *name, *alt;
+
+ if (!cache || !(map = cache->submodules) || !item)
+ return;
+
+ name = item->name;
+ alt = (item->path != item->name) ? item->path : NULL;
+
+ for (; name; name = alt, alt = NULL) {
+ khiter_t pos = git_strmap_lookup_index(map, name);
+ git_submodule *found;
+
+ if (!git_strmap_valid_index(map, pos))
+ continue;
+
+ found = git_strmap_value_at(map, pos);
+
+ if (found != item)
+ continue;
+
+ git_strmap_set_value_at(map, pos, NULL);
+ git_strmap_delete_at(map, pos);
+
+ if (free_after_remove)
+ git_submodule_free(found);
+ }
+}
+
+static void submodule_release(git_submodule *sm)
+{
+ if (!sm)
+ return;
+
+ if (sm->repo) {
+ git_submodule_cache *cache = sm->repo->_submodules;
+ sm->repo = NULL;
+ submodule_cache_remove_item(cache, sm, false);
+ }
+
+ if (sm->path != sm->name)
+ git__free(sm->path);
+ git__free(sm->name);
+ git__free(sm->url);
+ git__free(sm->branch);
+ git__memzero(sm, sizeof(*sm));
+ git__free(sm);
+}
+
+void git_submodule_free(git_submodule *sm)
+{
+ if (!sm)
+ return;
+ GIT_REFCOUNT_DEC(sm, submodule_release);
+}
+
+static int submodule_get(
+ git_submodule **out,
+ git_submodule_cache *cache,
+ const char *name,
+ const char *alternate)
+{
+ int error = 0;
+ khiter_t pos;
+ git_submodule *sm;
+
+ pos = git_strmap_lookup_index(cache->submodules, name);
+
+ if (!git_strmap_valid_index(cache->submodules, pos) && alternate)
+ pos = git_strmap_lookup_index(cache->submodules, alternate);
+
+ if (!git_strmap_valid_index(cache->submodules, pos)) {
+ if ((error = submodule_alloc(&sm, cache, name)) < 0)
+ return error;
+
+ /* insert value at name - if another thread beats us to it, then use
+ * their record and release our own.
+ */
+ pos = kh_put(str, cache->submodules, sm->name, &error);
+
+ if (error < 0)
+ goto done;
+ else if (error == 0) {
+ git_submodule_free(sm);
+ sm = git_strmap_value_at(cache->submodules, pos);
+ } else {
+ error = 0;
+ git_strmap_set_value_at(cache->submodules, pos, sm);
+ }
+ } else {
+ sm = git_strmap_value_at(cache->submodules, pos);
+ }
+
+done:
+ if (error < 0)
+ git_submodule_free(sm);
+ else if (out) {
+ GIT_REFCOUNT_INC(sm);
+ *out = sm;
+ }
+
+ return error;
+}
+
+static int submodule_config_error(const char *property, const char *value)
+{
+ giterr_set(GITERR_INVALID,
+ "Invalid value for submodule '%s' property: '%s'", property, value);
+ return -1;
+}
+
+int git_submodule_parse_ignore(git_submodule_ignore_t *out, const char *value)
+{
+ int val;
+
+ if (git_config_lookup_map_value(
+ &val, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value) < 0) {
+ *out = GIT_SUBMODULE_IGNORE_NONE;
+ return submodule_config_error("ignore", value);
+ }
+
+ *out = (git_submodule_ignore_t)val;
+ return 0;
+}
+
+int git_submodule_parse_update(git_submodule_update_t *out, const char *value)
+{
+ int val;
+
+ if (git_config_lookup_map_value(
+ &val, _sm_update_map, ARRAY_SIZE(_sm_update_map), value) < 0) {
+ *out = GIT_SUBMODULE_UPDATE_CHECKOUT;
+ return submodule_config_error("update", value);
+ }
+
+ *out = (git_submodule_update_t)val;
+ return 0;
+}
+
+int git_submodule_parse_recurse(git_submodule_recurse_t *out, const char *value)
+{
+ int val;
+
+ if (git_config_lookup_map_value(
+ &val, _sm_recurse_map, ARRAY_SIZE(_sm_recurse_map), value) < 0) {
+ *out = GIT_SUBMODULE_RECURSE_YES;
+ return submodule_config_error("recurse", value);
+ }
+
+ *out = (git_submodule_recurse_t)val;
+ return 0;
+}
+
+static int submodule_load_from_config(
+ const git_config_entry *entry, void *payload)
+{
+ git_submodule_cache *cache = payload;
+ const char *namestart, *property;
+ const char *key = entry->name, *value = entry->value, *path;
+ char *alternate = NULL, *replaced = NULL;
+ git_buf name = GIT_BUF_INIT;
+ git_submodule *sm = NULL;
+ int error = 0;
+
+ if (git__prefixcmp(key, "submodule.") != 0)
+ return 0;
+
+ namestart = key + strlen("submodule.");
+ property = strrchr(namestart, '.');
+
+ if (!property || (property == namestart))
+ return 0;
+
+ property++;
+ path = !strcasecmp(property, "path") ? value : NULL;
+
+ if ((error = git_buf_set(&name, namestart, property - namestart - 1)) < 0 ||
+ (error = submodule_get(&sm, cache, name.ptr, path)) < 0)
+ goto done;
+
+ sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG;
+
+ /* Only from config might we get differing names & paths. If so, then
+ * update the submodule and insert under the alternative key.
+ */
+
+ /* TODO: if case insensitive filesystem, then the following strcmps
+ * should be strcasecmp
+ */
+
+ if (strcmp(sm->name, name.ptr) != 0) { /* name changed */
+ if (!strcmp(sm->path, name.ptr)) { /* already set as path */
+ replaced = sm->name;
+ sm->name = sm->path;
+ } else {
+ if (sm->name != sm->path)
+ replaced = sm->name;
+ alternate = sm->name = git_buf_detach(&name);
+ }
+ }
+ else if (path && strcmp(path, sm->path) != 0) { /* path changed */
+ if (!strcmp(sm->name, value)) { /* already set as name */
+ replaced = sm->path;
+ sm->path = sm->name;
+ } else {
+ if (sm->path != sm->name)
+ replaced = sm->path;
+ if ((alternate = git__strdup(value)) == NULL) {
+ error = -1;
+ goto done;
+ }
+ sm->path = alternate;
+ }
+ }
+
+ /* Deregister under name being replaced */
+ if (replaced) {
+ git_strmap_delete(cache->submodules, replaced);
+ git_submodule_free(sm);
+ git__free(replaced);
+ }
+
+ /* Insert under alternate key */
+ if (alternate) {
+ void *old_sm = NULL;
+ git_strmap_insert2(cache->submodules, alternate, sm, old_sm, error);
+
+ if (error < 0)
+ goto done;
+ if (error > 0)
+ error = 0;
+
+ GIT_REFCOUNT_INC(sm); /* increase refcount for new key */
+
+ /* if we replaced an old module under this key, release the old one */
+ if (old_sm && ((git_submodule *)old_sm) != sm) {
+ git_submodule_free(old_sm);
+ /* TODO: log warning about multiple submodules with same path */
+ }
+ }
+
+ /* TODO: Look up path in index and if it is present but not a GITLINK
+ * then this should be deleted (at least to match git's behavior)
+ */
+
+ if (path)
+ goto done;
+
+ /* copy other properties into submodule entry */
+ if (strcasecmp(property, "url") == 0) {
+ git__free(sm->url);
+ sm->url = NULL;
+
+ if (value != NULL && (sm->url = git__strdup(value)) == NULL) {
+ error = -1;
+ goto done;
+ }
+ }
+ else if (strcasecmp(property, "branch") == 0) {
+ git__free(sm->branch);
+ sm->branch = NULL;
+
+ if (value != NULL && (sm->branch = git__strdup(value)) == NULL) {
+ error = -1;
+ goto done;
+ }
+ }
+ else if (strcasecmp(property, "update") == 0) {
+ if ((error = git_submodule_parse_update(&sm->update, value)) < 0)
+ goto done;
+ sm->update_default = sm->update;
+ }
+ else if (strcasecmp(property, "fetchRecurseSubmodules") == 0) {
+ if ((error = git_submodule_parse_recurse(&sm->fetch_recurse, value)) < 0)
+ goto done;
+ sm->fetch_recurse_default = sm->fetch_recurse;
+ }
+ else if (strcasecmp(property, "ignore") == 0) {
+ if ((error = git_submodule_parse_ignore(&sm->ignore, value)) < 0)
+ goto done;
+ sm->ignore_default = sm->ignore;
+ }
+ /* ignore other unknown submodule properties */
+
+done:
+ git_submodule_free(sm); /* offset refcount inc from submodule_get() */
+ git_buf_free(&name);
+ return error;
+}
+
+static int submodule_load_from_wd_lite(git_submodule *sm)
+{
+ git_buf path = GIT_BUF_INIT;
+
+ if (git_buf_joinpath(&path, git_repository_workdir(sm->repo), sm->path) < 0)
+ return -1;
+
+ if (git_path_isdir(path.ptr))
+ sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED;
+
+ if (git_path_contains(&path, DOT_GIT))
+ sm->flags |= GIT_SUBMODULE_STATUS_IN_WD;
+
+ git_buf_free(&path);
+ return 0;
+}
+
+static int submodule_cache_refresh_from_index(
+ git_submodule_cache *cache, git_index *idx)
+{
+ int error;
+ git_iterator *i;
+ const git_index_entry *entry;
+
+ if ((error = git_iterator_for_index(&i, idx, 0, NULL, NULL)) < 0)
+ return error;
+
+ while (!(error = git_iterator_advance(&entry, i))) {
+ khiter_t pos = git_strmap_lookup_index(cache->submodules, entry->path);
+ git_submodule *sm;
+
+ if (git_strmap_valid_index(cache->submodules, pos)) {
+ sm = git_strmap_value_at(cache->submodules, pos);
+
+ if (S_ISGITLINK(entry->mode))
+ submodule_update_from_index_entry(sm, entry);
+ else
+ sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE;
+ } else if (S_ISGITLINK(entry->mode)) {
+ if (!submodule_get(&sm, cache, entry->path, NULL)) {
+ submodule_update_from_index_entry(sm, entry);
+ git_submodule_free(sm);
+ }
+ }
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ git_iterator_free(i);
+
+ return error;
+}
+
+static int submodule_cache_refresh_from_head(
+ git_submodule_cache *cache, git_tree *head)
+{
+ int error;
+ git_iterator *i;
+ const git_index_entry *entry;
+
+ if ((error = git_iterator_for_tree(&i, head, 0, NULL, NULL)) < 0)
+ return error;
+
+ while (!(error = git_iterator_advance(&entry, i))) {
+ khiter_t pos = git_strmap_lookup_index(cache->submodules, entry->path);
+ git_submodule *sm;
+
+ if (git_strmap_valid_index(cache->submodules, pos)) {
+ sm = git_strmap_value_at(cache->submodules, pos);
+
+ if (S_ISGITLINK(entry->mode))
+ submodule_update_from_head_data(sm, entry->mode, &entry->id);
+ else
+ sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE;
+ } else if (S_ISGITLINK(entry->mode)) {
+ if (!submodule_get(&sm, cache, entry->path, NULL)) {
+ submodule_update_from_head_data(
+ sm, entry->mode, &entry->id);
+ git_submodule_free(sm);
+ }
+ }
+ }
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+
+ git_iterator_free(i);
+
+ return error;
+}
+
+static git_config_backend *open_gitmodules(
+ git_submodule_cache *cache,
+ int okay_to_create)
+{
+ const char *workdir = git_repository_workdir(cache->repo);
+ git_buf path = GIT_BUF_INIT;
+ git_config_backend *mods = NULL;
+
+ if (workdir != NULL) {
+ if (git_buf_joinpath(&path, workdir, GIT_MODULES_FILE) != 0)
+ return NULL;
+
+ if (okay_to_create || git_path_isfile(path.ptr)) {
+ /* git_config_file__ondisk should only fail if OOM */
+ if (git_config_file__ondisk(&mods, path.ptr) < 0)
+ mods = NULL;
+ /* open should only fail here if the file is malformed */
+ else if (git_config_file_open(mods, GIT_CONFIG_LEVEL_LOCAL) < 0) {
+ git_config_file_free(mods);
+ mods = NULL;
+ }
+ }
+ }
+
+ git_buf_free(&path);
+
+ return mods;
+}
+
+static void submodule_cache_free(git_submodule_cache *cache)
+{
+ git_submodule *sm;
+
+ if (!cache)
+ return;
+
+ git_strmap_foreach_value(cache->submodules, sm, {
+ sm->repo = NULL; /* disconnect from repo */
+ git_submodule_free(sm);
+ });
+ git_strmap_free(cache->submodules);
+
+ git_buf_free(&cache->gitmodules_path);
+ git_mutex_free(&cache->lock);
+ git__free(cache);
+}
+
+static int submodule_cache_alloc(
+ git_submodule_cache **out, git_repository *repo)
+{
+ git_submodule_cache *cache = git__calloc(1, sizeof(git_submodule_cache));
+ GITERR_CHECK_ALLOC(cache);
+
+ if (git_mutex_init(&cache->lock) < 0) {
+ giterr_set(GITERR_OS, "Unable to initialize submodule cache lock");
+ git__free(cache);
+ return -1;
+ }
+
+ if (git_strmap_alloc(&cache->submodules) < 0) {
+ submodule_cache_free(cache);
+ return -1;
+ }
+
+ cache->repo = repo;
+ git_buf_init(&cache->gitmodules_path, 0);
+
+ *out = cache;
+ return 0;
+}
+
+static int submodule_cache_refresh(git_submodule_cache *cache, int refresh)
+{
+ int error = 0, update_index, update_head, update_gitmod;
+ git_index *idx = NULL;
+ git_tree *head = NULL;
+ const char *wd = NULL;
+ git_buf path = GIT_BUF_INIT;
+ git_submodule *sm;
+ git_config_backend *mods = NULL;
+ uint32_t mask;
+
+ if (!cache || !cache->repo || !refresh)
+ return 0;
+
+ if (git_mutex_lock(&cache->lock) < 0) {
+ giterr_set(GITERR_OS, "Unable to acquire lock on submodule cache");
+ return -1;
+ }
+
+ /* get sources that we will need to check */
+
+ if (git_repository_index(&idx, cache->repo) < 0)
+ giterr_clear();
+ if (git_repository_head_tree(&head, cache->repo) < 0)
+ giterr_clear();
+
+ wd = git_repository_workdir(cache->repo);
+ if (wd && (error = git_buf_joinpath(&path, wd, GIT_MODULES_FILE)) < 0)
+ goto cleanup;
+
+ /* check for invalidation */
+
+ if (refresh == CACHE_FLUSH)
+ update_index = update_head = update_gitmod = true;
+ else {
+ update_index =
+ !idx || git_index__changed_relative_to(idx, &cache->index_stamp);
+ update_head =
+ !head || !git_oid_equal(&cache->head_id, git_tree_id(head));
+
+ update_gitmod = (wd != NULL) ?
+ git_futils_filestamp_check(&cache->gitmodules_stamp, path.ptr) :
+ (cache->gitmodules_stamp.mtime != 0);
+ }
+
+ /* clear submodule flags that are to be refreshed */
+
+ mask = 0;
+ if (!idx || update_index)
+ mask |= GIT_SUBMODULE_STATUS_IN_INDEX |
+ GIT_SUBMODULE_STATUS__INDEX_FLAGS |
+ GIT_SUBMODULE_STATUS__INDEX_OID_VALID |
+ GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES;
+ if (!head || update_head)
+ mask |= GIT_SUBMODULE_STATUS_IN_HEAD |
+ GIT_SUBMODULE_STATUS__HEAD_OID_VALID;
+ if (update_gitmod)
+ mask |= GIT_SUBMODULE_STATUS_IN_CONFIG;
+ if (mask != 0)
+ mask |= GIT_SUBMODULE_STATUS_IN_WD |
+ GIT_SUBMODULE_STATUS__WD_SCANNED |
+ GIT_SUBMODULE_STATUS__WD_FLAGS |
+ GIT_SUBMODULE_STATUS__WD_OID_VALID;
+ else
+ goto cleanup; /* nothing to do */
+
+ submodule_cache_clear_flags(cache, mask);
+
+ /* add back submodule information from index */
+
+ if (idx && update_index) {
+ if ((error = submodule_cache_refresh_from_index(cache, idx)) < 0)
+ goto cleanup;
+
+ git_futils_filestamp_set(
+ &cache->index_stamp, git_index__filestamp(idx));
+ }
+
+ /* add submodule information from HEAD */
+
+ if (head && update_head) {
+ if ((error = submodule_cache_refresh_from_head(cache, head)) < 0)
+ goto cleanup;
+
+ git_oid_cpy(&cache->head_id, git_tree_id(head));
+ }
+
+ /* add submodule information from .gitmodules */
+
+ if (wd && update_gitmod > 0) {
+ if ((mods = open_gitmodules(cache, false)) != NULL &&
+ (error = git_config_file_foreach(
+ mods, submodule_load_from_config, cache)) < 0)
+ goto cleanup;
+ }
+
+ /* shallow scan submodules in work tree as needed */
+
+ if (wd && mask != 0) {
+ git_strmap_foreach_value(cache->submodules, sm, {
+ submodule_load_from_wd_lite(sm);
+ });
+ }
+
+ /* remove submodules that no longer exist */
+
+ git_strmap_foreach_value(cache->submodules, sm, {
+ /* purge unless in HEAD, index, or .gitmodules; no sm for wd only */
+ if (sm != NULL &&
+ !(sm->flags &
+ (GIT_SUBMODULE_STATUS_IN_HEAD |
+ GIT_SUBMODULE_STATUS_IN_INDEX |
+ GIT_SUBMODULE_STATUS_IN_CONFIG)))
+ submodule_cache_remove_item(cache, sm, true);
+ });
+
+cleanup:
+ git_config_file_free(mods);
+
+ /* TODO: if we got an error, mark submodule config as invalid? */
+
+ git_mutex_unlock(&cache->lock);
+
+ git_index_free(idx);
+ git_tree_free(head);
+ git_buf_free(&path);
+
+ return error;
+}
+
+static int submodule_cache_init(git_repository *repo, int cache_refresh)
+{
+ int error = 0;
+ git_submodule_cache *cache = NULL;
+
+ /* if submodules already exist, just refresh as requested */
+ if (repo->_submodules)
+ return submodule_cache_refresh(repo->_submodules, cache_refresh);
+
+ /* otherwise create a new cache, load it, and atomically swap it in */
+ if (!(error = submodule_cache_alloc(&cache, repo)) &&
+ !(error = submodule_cache_refresh(cache, CACHE_FLUSH)))
+ cache = git__compare_and_swap(&repo->_submodules, NULL, cache);
+
+ /* might have raced with another thread to set cache, so free if needed */
+ if (cache)
+ submodule_cache_free(cache);
+
+ return error;
+}
+
+/* Lookup name of remote of the local tracking branch HEAD points to */
+static int lookup_head_remote_key(git_buf *remote_name, git_repository *repo)
+{
+ int error;
+ git_reference *head = NULL;
+ git_buf upstream_name = GIT_BUF_INIT;
+
+ /* lookup and dereference HEAD */
+ if ((error = git_repository_head(&head, repo)) < 0)
+ return error;
+
+ /* lookup remote tracking branch of HEAD */
+ if (!(error = git_branch_upstream_name(
+ &upstream_name, repo, git_reference_name(head))))
+ {
+ /* lookup remote of remote tracking branch */
+ error = git_branch_remote_name(remote_name, repo, upstream_name.ptr);
+
+ git_buf_free(&upstream_name);
+ }
+
+ git_reference_free(head);
+
+ return error;
+}
+
+/* Lookup the remote of the local tracking branch HEAD points to */
+static int lookup_head_remote(git_remote **remote, git_repository *repo)
+{
+ int error;
+ git_buf remote_name = GIT_BUF_INIT;
+
+ /* lookup remote of remote tracking branch name */
+ if (!(error = lookup_head_remote_key(&remote_name, repo)))
+ error = git_remote_load(remote, repo, remote_name.ptr);
+
+ git_buf_free(&remote_name);
+
+ return error;
+}
+
+/* Lookup remote, either from HEAD or fall back on origin */
+static int lookup_default_remote(git_remote **remote, git_repository *repo)
+{
+ int error = lookup_head_remote(remote, repo);
+
+ /* if that failed, use 'origin' instead */
+ if (error == GIT_ENOTFOUND)
+ error = git_remote_load(remote, repo, "origin");
+
+ if (error == GIT_ENOTFOUND)
+ giterr_set(
+ GITERR_SUBMODULE,
+ "Cannot get default remote for submodule - no local tracking "
+ "branch for HEAD and origin does not exist");
+
+ return error;
+}
+
+static int get_url_base(git_buf *url, git_repository *repo)
+{
+ int error;
+ git_remote *remote = NULL;
+
+ if (!(error = lookup_default_remote(&remote, repo))) {
+ error = git_buf_sets(url, git_remote_url(remote));
+ git_remote_free(remote);
+ }
+ else if (error == GIT_ENOTFOUND) {
+ /* if repository does not have a default remote, use workdir instead */
+ giterr_clear();
+ error = git_buf_sets(url, git_repository_workdir(repo));
+ }
+
+ return error;
+}
+
+static void submodule_get_index_status(unsigned int *status, git_submodule *sm)
+{
+ const git_oid *head_oid = git_submodule_head_id(sm);
+ const git_oid *index_oid = git_submodule_index_id(sm);
+
+ *status = *status & ~GIT_SUBMODULE_STATUS__INDEX_FLAGS;
+
+ if (!head_oid) {
+ if (index_oid)
+ *status |= GIT_SUBMODULE_STATUS_INDEX_ADDED;
+ }
+ else if (!index_oid)
+ *status |= GIT_SUBMODULE_STATUS_INDEX_DELETED;
+ else if (!git_oid_equal(head_oid, index_oid))
+ *status |= GIT_SUBMODULE_STATUS_INDEX_MODIFIED;
+}
+
+static void submodule_get_wd_status(
+ unsigned int *status,
+ git_submodule *sm,
+ git_repository *sm_repo,
+ git_submodule_ignore_t ign)
+{
+ const git_oid *index_oid = git_submodule_index_id(sm);
+ const git_oid *wd_oid =
+ (sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) ? &sm->wd_oid : NULL;
+ git_tree *sm_head = NULL;
+ git_index *index = NULL;
+ git_diff_options opt = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff;
+
+ *status = *status & ~GIT_SUBMODULE_STATUS__WD_FLAGS;
+
+ if (!index_oid) {
+ if (wd_oid)
+ *status |= GIT_SUBMODULE_STATUS_WD_ADDED;
+ }
+ else if (!wd_oid) {
+ if ((sm->flags & GIT_SUBMODULE_STATUS__WD_SCANNED) != 0 &&
+ (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0)
+ *status |= GIT_SUBMODULE_STATUS_WD_UNINITIALIZED;
+ else
+ *status |= GIT_SUBMODULE_STATUS_WD_DELETED;
+ }
+ else if (!git_oid_equal(index_oid, wd_oid))
+ *status |= GIT_SUBMODULE_STATUS_WD_MODIFIED;
+
+ /* if we have no repo, then we're done */
+ if (!sm_repo)
+ return;
+
+ /* the diffs below could be optimized with an early termination
+ * option to the git_diff functions, but for now this is sufficient
+ * (and certainly no worse that what core git does).
+ */
+
+ if (ign == GIT_SUBMODULE_IGNORE_NONE)
+ opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
+
+ (void)git_repository_index__weakptr(&index, sm_repo);
+
+ /* if we don't have an unborn head, check diff with index */
+ if (git_repository_head_tree(&sm_head, sm_repo) < 0)
+ giterr_clear();
+ else {
+ /* perform head to index diff on submodule */
+ if (git_diff_tree_to_index(&diff, sm_repo, sm_head, index, &opt) < 0)
+ giterr_clear();
+ else {
+ if (git_diff_num_deltas(diff) > 0)
+ *status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED;
+ git_diff_free(diff);
+ diff = NULL;
+ }
+
+ git_tree_free(sm_head);
+ }
+
+ /* perform index-to-workdir diff on submodule */
+ if (git_diff_index_to_workdir(&diff, sm_repo, index, &opt) < 0)
+ giterr_clear();
+ else {
+ size_t untracked =
+ git_diff_num_deltas_of_type(diff, GIT_DELTA_UNTRACKED);
+
+ if (untracked > 0)
+ *status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED;
+
+ if (git_diff_num_deltas(diff) != untracked)
+ *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED;
+
+ git_diff_free(diff);
+ diff = NULL;
+ }
+}
diff --git a/src/submodule.h b/src/submodule.h
new file mode 100644
index 000000000..a6182beca
--- /dev/null
+++ b/src/submodule.h
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_submodule_h__
+#define INCLUDE_submodule_h__
+
+#include "git2/submodule.h"
+#include "git2/repository.h"
+#include "fileops.h"
+
+/* Notes:
+ *
+ * Submodule information can be in four places: the index, the config files
+ * (both .git/config and .gitmodules), the HEAD tree, and the working
+ * directory.
+ *
+ * In the index:
+ * - submodule is found by path
+ * - may be missing, present, or of the wrong type
+ * - will have an oid if present
+ *
+ * In the HEAD tree:
+ * - submodule is found by path
+ * - may be missing, present, or of the wrong type
+ * - will have an oid if present
+ *
+ * In the config files:
+ * - submodule is found by submodule "name" which is usually the path
+ * - may be missing or present
+ * - will have a name, path, url, and other properties
+ *
+ * In the working directory:
+ * - submodule is found by path
+ * - may be missing, an empty directory, a checked out directory,
+ * or of the wrong type
+ * - if checked out, will have a HEAD oid
+ * - if checked out, will have git history that can be used to compare oids
+ * - if checked out, may have modified files and/or untracked files
+ */
+
+/**
+ * Description of submodule
+ *
+ * This record describes a submodule found in a repository. There should be
+ * an entry for every submodule found in the HEAD and index, and for every
+ * submodule described in .gitmodules. The fields are as follows:
+ *
+ * - `rc` tracks the refcount of how many hash table entries in the
+ * git_submodule_cache there are for this submodule. It only comes into
+ * play if the name and path of the submodule differ.
+ *
+ * - `name` is the name of the submodule from .gitmodules.
+ * - `path` is the path to the submodule from the repo root. It is almost
+ * always the same as `name`.
+ * - `url` is the url for the submodule.
+ * - `update` is a git_submodule_update_t value - see gitmodules(5) update.
+ * - `update_default` is the update value from the config
+ * - `ignore` is a git_submodule_ignore_t value - see gitmodules(5) ignore.
+ * - `ignore_default` is the ignore value from the config
+ * - `fetch_recurse` is a git_submodule_recurse_t value - see gitmodules(5)
+ * fetchRecurseSubmodules.
+ * - `fetch_recurse_default` is the recurse value from the config
+ *
+ * - `repo` is the parent repository that contains this submodule.
+ * - `flags` after for internal use, tracking where this submodule has been
+ * found (head, index, config, workdir) and known status info, etc.
+ * - `head_oid` is the SHA1 for the submodule path in the repo HEAD.
+ * - `index_oid` is the SHA1 for the submodule recorded in the index.
+ * - `wd_oid` is the SHA1 for the HEAD of the checked out submodule.
+ *
+ * If the submodule has been added to .gitmodules but not yet git added,
+ * then the `index_oid` will be zero but still marked valid. If the
+ * submodule has been deleted, but the delete has not been committed yet,
+ * then the `index_oid` will be set, but the `url` will be NULL.
+ */
+struct git_submodule {
+ git_refcount rc;
+
+ /* information from config */
+ char *name;
+ char *path; /* important: may just point to "name" string */
+ char *url;
+ char *branch;
+ git_submodule_update_t update;
+ git_submodule_update_t update_default;
+ git_submodule_ignore_t ignore;
+ git_submodule_ignore_t ignore_default;
+ git_submodule_recurse_t fetch_recurse;
+ git_submodule_recurse_t fetch_recurse_default;
+
+ /* internal information */
+ git_repository *repo;
+ uint32_t flags;
+ git_oid head_oid;
+ git_oid index_oid;
+ git_oid wd_oid;
+};
+
+/**
+ * The git_submodule_cache stores known submodules along with timestamps,
+ * etc. about when they were loaded
+ */
+typedef struct {
+ git_repository *repo;
+ git_strmap *submodules;
+ git_mutex lock;
+
+ /* cache invalidation data */
+ git_oid head_id;
+ git_futils_filestamp index_stamp;
+ git_buf gitmodules_path;
+ git_futils_filestamp gitmodules_stamp;
+ git_futils_filestamp config_stamp;
+} git_submodule_cache;
+
+/* Force revalidation of submodule data cache (alloc as needed) */
+extern int git_submodule_cache_refresh(git_repository *repo);
+
+/* Release all submodules */
+extern void git_submodule_cache_free(git_repository *repo);
+
+/* Additional flags on top of public GIT_SUBMODULE_STATUS values */
+enum {
+ GIT_SUBMODULE_STATUS__WD_SCANNED = (1u << 20),
+ GIT_SUBMODULE_STATUS__HEAD_OID_VALID = (1u << 21),
+ GIT_SUBMODULE_STATUS__INDEX_OID_VALID = (1u << 22),
+ GIT_SUBMODULE_STATUS__WD_OID_VALID = (1u << 23),
+ GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE = (1u << 24),
+ GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE = (1u << 25),
+ GIT_SUBMODULE_STATUS__WD_NOT_SUBMODULE = (1u << 26),
+ GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES = (1u << 27),
+};
+
+#define GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(S) \
+ ((S) & ~(0xFFFFFFFFu << 20))
+
+/* Internal submodule check does not attempt to refresh cached data */
+extern bool git_submodule__is_submodule(git_repository *repo, const char *name);
+
+/* Internal lookup does not attempt to refresh cached data */
+extern int git_submodule__lookup(
+ git_submodule **out, git_repository *repo, const char *path);
+
+/* Internal status fn returns status and optionally the various OIDs */
+extern int git_submodule__status(
+ unsigned int *out_status,
+ git_oid *out_head_id,
+ git_oid *out_index_id,
+ git_oid *out_wd_id,
+ git_submodule *sm,
+ git_submodule_ignore_t ign);
+
+/* Open submodule repository as bare repo for quick HEAD check, etc. */
+extern int git_submodule_open_bare(
+ git_repository **repo,
+ git_submodule *submodule);
+
+extern int git_submodule_parse_ignore(
+ git_submodule_ignore_t *out, const char *value);
+extern int git_submodule_parse_update(
+ git_submodule_update_t *out, const char *value);
+
+extern const char *git_submodule_ignore_to_str(git_submodule_ignore_t);
+extern const char *git_submodule_update_to_str(git_submodule_update_t);
+extern const char *git_submodule_recurse_to_str(git_submodule_recurse_t);
+
+#endif
diff --git a/src/sysdir.c b/src/sysdir.c
new file mode 100644
index 000000000..cd94a8b57
--- /dev/null
+++ b/src/sysdir.c
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "sysdir.h"
+#include "global.h"
+#include "buffer.h"
+#include "path.h"
+#include <ctype.h>
+#if GIT_WIN32
+#include "win32/findfile.h"
+#endif
+
+static int git_sysdir_guess_system_dirs(git_buf *out)
+{
+#ifdef GIT_WIN32
+ return git_win32__find_system_dirs(out, L"etc\\");
+#else
+ return git_buf_sets(out, "/etc");
+#endif
+}
+
+static int git_sysdir_guess_global_dirs(git_buf *out)
+{
+#ifdef GIT_WIN32
+ return git_win32__find_global_dirs(out);
+#else
+ return git_buf_sets(out, getenv("HOME"));
+#endif
+}
+
+static int git_sysdir_guess_xdg_dirs(git_buf *out)
+{
+#ifdef GIT_WIN32
+ return git_win32__find_xdg_dirs(out);
+#else
+ const char *env = NULL;
+
+ if ((env = getenv("XDG_CONFIG_HOME")) != NULL)
+ return git_buf_joinpath(out, env, "git");
+ else if ((env = getenv("HOME")) != NULL)
+ return git_buf_joinpath(out, env, ".config/git");
+
+ git_buf_clear(out);
+ return 0;
+#endif
+}
+
+static int git_sysdir_guess_template_dirs(git_buf *out)
+{
+#ifdef GIT_WIN32
+ return git_win32__find_system_dirs(out, L"share\\git-core\\templates");
+#else
+ return git_buf_sets(out, "/usr/share/git-core/templates");
+#endif
+}
+
+typedef int (*git_sysdir_guess_cb)(git_buf *out);
+
+static git_buf git_sysdir__dirs[GIT_SYSDIR__MAX] =
+ { GIT_BUF_INIT, GIT_BUF_INIT, GIT_BUF_INIT, GIT_BUF_INIT };
+
+static git_sysdir_guess_cb git_sysdir__dir_guess[GIT_SYSDIR__MAX] = {
+ git_sysdir_guess_system_dirs,
+ git_sysdir_guess_global_dirs,
+ git_sysdir_guess_xdg_dirs,
+ git_sysdir_guess_template_dirs,
+};
+
+static int git_sysdir__dirs_shutdown_set = 0;
+
+int git_sysdir_global_init(void)
+{
+ git_sysdir_t i;
+ const git_buf *path;
+ int error = 0;
+
+ for (i = 0; !error && i < GIT_SYSDIR__MAX; i++)
+ error = git_sysdir_get(&path, i);
+
+ return error;
+}
+
+void git_sysdir_global_shutdown(void)
+{
+ int i;
+ for (i = 0; i < GIT_SYSDIR__MAX; ++i)
+ git_buf_free(&git_sysdir__dirs[i]);
+
+ git_sysdir__dirs_shutdown_set = 0;
+}
+
+static int git_sysdir_check_selector(git_sysdir_t which)
+{
+ if (which < GIT_SYSDIR__MAX)
+ return 0;
+
+ giterr_set(GITERR_INVALID, "config directory selector out of range");
+ return -1;
+}
+
+
+int git_sysdir_get(const git_buf **out, git_sysdir_t which)
+{
+ assert(out);
+
+ *out = NULL;
+
+ GITERR_CHECK_ERROR(git_sysdir_check_selector(which));
+
+ if (!git_buf_len(&git_sysdir__dirs[which])) {
+ /* prepare shutdown if we're going to need it */
+ if (!git_sysdir__dirs_shutdown_set) {
+ git__on_shutdown(git_sysdir_global_shutdown);
+ git_sysdir__dirs_shutdown_set = 1;
+ }
+
+ GITERR_CHECK_ERROR(
+ git_sysdir__dir_guess[which](&git_sysdir__dirs[which]));
+ }
+
+ *out = &git_sysdir__dirs[which];
+ return 0;
+}
+
+int git_sysdir_get_str(
+ char *out,
+ size_t outlen,
+ git_sysdir_t which)
+{
+ const git_buf *path = NULL;
+
+ GITERR_CHECK_ERROR(git_sysdir_check_selector(which));
+ GITERR_CHECK_ERROR(git_sysdir_get(&path, which));
+
+ if (!out || path->size >= outlen) {
+ giterr_set(GITERR_NOMEMORY, "Buffer is too short for the path");
+ return GIT_EBUFS;
+ }
+
+ git_buf_copy_cstr(out, outlen, path);
+ return 0;
+}
+
+#define PATH_MAGIC "$PATH"
+
+int git_sysdir_set(git_sysdir_t which, const char *search_path)
+{
+ const char *expand_path = NULL;
+ git_buf merge = GIT_BUF_INIT;
+
+ GITERR_CHECK_ERROR(git_sysdir_check_selector(which));
+
+ if (search_path != NULL)
+ expand_path = strstr(search_path, PATH_MAGIC);
+
+ /* init with default if not yet done and needed (ignoring error) */
+ if ((!search_path || expand_path) &&
+ !git_buf_len(&git_sysdir__dirs[which]))
+ git_sysdir__dir_guess[which](&git_sysdir__dirs[which]);
+
+ /* if $PATH is not referenced, then just set the path */
+ if (!expand_path)
+ return git_buf_sets(&git_sysdir__dirs[which], search_path);
+
+ /* otherwise set to join(before $PATH, old value, after $PATH) */
+ if (expand_path > search_path)
+ git_buf_set(&merge, search_path, expand_path - search_path);
+
+ if (git_buf_len(&git_sysdir__dirs[which]))
+ git_buf_join(&merge, GIT_PATH_LIST_SEPARATOR,
+ merge.ptr, git_sysdir__dirs[which].ptr);
+
+ expand_path += strlen(PATH_MAGIC);
+ if (*expand_path)
+ git_buf_join(&merge, GIT_PATH_LIST_SEPARATOR, merge.ptr, expand_path);
+
+ git_buf_swap(&git_sysdir__dirs[which], &merge);
+ git_buf_free(&merge);
+
+ return git_buf_oom(&git_sysdir__dirs[which]) ? -1 : 0;
+}
+
+static int git_sysdir_find_in_dirlist(
+ git_buf *path,
+ const char *name,
+ git_sysdir_t which,
+ const char *label)
+{
+ size_t len;
+ const char *scan, *next = NULL;
+ const git_buf *syspath;
+
+ GITERR_CHECK_ERROR(git_sysdir_get(&syspath, which));
+ if (!syspath || !git_buf_len(syspath))
+ goto done;
+
+ for (scan = git_buf_cstr(syspath); scan; scan = next) {
+ /* find unescaped separator or end of string */
+ for (next = scan; *next; ++next) {
+ if (*next == GIT_PATH_LIST_SEPARATOR &&
+ (next <= scan || next[-1] != '\\'))
+ break;
+ }
+
+ len = (size_t)(next - scan);
+ next = (*next ? next + 1 : NULL);
+ if (!len)
+ continue;
+
+ GITERR_CHECK_ERROR(git_buf_set(path, scan, len));
+ if (name)
+ GITERR_CHECK_ERROR(git_buf_joinpath(path, path->ptr, name));
+
+ if (git_path_exists(path->ptr))
+ return 0;
+ }
+
+done:
+ git_buf_free(path);
+ giterr_set(GITERR_OS, "The %s file '%s' doesn't exist", label, name);
+ return GIT_ENOTFOUND;
+}
+
+int git_sysdir_find_system_file(git_buf *path, const char *filename)
+{
+ return git_sysdir_find_in_dirlist(
+ path, filename, GIT_SYSDIR_SYSTEM, "system");
+}
+
+int git_sysdir_find_global_file(git_buf *path, const char *filename)
+{
+ return git_sysdir_find_in_dirlist(
+ path, filename, GIT_SYSDIR_GLOBAL, "global");
+}
+
+int git_sysdir_find_xdg_file(git_buf *path, const char *filename)
+{
+ return git_sysdir_find_in_dirlist(
+ path, filename, GIT_SYSDIR_XDG, "global/xdg");
+}
+
+int git_sysdir_find_template_dir(git_buf *path)
+{
+ return git_sysdir_find_in_dirlist(
+ path, NULL, GIT_SYSDIR_TEMPLATE, "template");
+}
+
diff --git a/src/sysdir.h b/src/sysdir.h
new file mode 100644
index 000000000..f1bbf0bae
--- /dev/null
+++ b/src/sysdir.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_sysdir_h__
+#define INCLUDE_sysdir_h__
+
+#include "common.h"
+#include "posix.h"
+#include "buffer.h"
+
+/**
+ * Find a "global" file (i.e. one in a user's home directory).
+ *
+ * @param path buffer to write the full path into
+ * @param filename name of file to find in the home directory
+ * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error
+ */
+extern int git_sysdir_find_global_file(git_buf *path, const char *filename);
+
+/**
+ * Find an "XDG" file (i.e. one in user's XDG config path).
+ *
+ * @param path buffer to write the full path into
+ * @param filename name of file to find in the home directory
+ * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error
+ */
+extern int git_sysdir_find_xdg_file(git_buf *path, const char *filename);
+
+/**
+ * Find a "system" file (i.e. one shared for all users of the system).
+ *
+ * @param path buffer to write the full path into
+ * @param filename name of file to find in the home directory
+ * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error
+ */
+extern int git_sysdir_find_system_file(git_buf *path, const char *filename);
+
+/**
+ * Find template directory.
+ *
+ * @param path buffer to write the full path into
+ * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error
+ */
+extern int git_sysdir_find_template_dir(git_buf *path);
+
+typedef enum {
+ GIT_SYSDIR_SYSTEM = 0,
+ GIT_SYSDIR_GLOBAL = 1,
+ GIT_SYSDIR_XDG = 2,
+ GIT_SYSDIR_TEMPLATE = 3,
+ GIT_SYSDIR__MAX = 4,
+} git_sysdir_t;
+
+/**
+ * Configures global data for configuration file search paths.
+ *
+ * @return 0 on success, <0 on failure
+ */
+extern int git_sysdir_global_init(void);
+
+/**
+ * Get the search path for global/system/xdg files
+ *
+ * @param out pointer to git_buf containing search path
+ * @param which which list of paths to return
+ * @return 0 on success, <0 on failure
+ */
+extern int git_sysdir_get(const git_buf **out, git_sysdir_t which);
+
+/**
+ * Get search path into a preallocated buffer
+ *
+ * @param out String buffer to write into
+ * @param outlen Size of string buffer
+ * @param which Which search path to return
+ * @return 0 on success, GIT_EBUFS if out is too small, <0 on other failure
+ */
+
+extern int git_sysdir_get_str(char *out, size_t outlen, git_sysdir_t which);
+
+/**
+ * Set search paths for global/system/xdg files
+ *
+ * The first occurrence of the magic string "$PATH" in the new value will
+ * be replaced with the old value of the search path.
+ *
+ * @param which Which search path to modify
+ * @param paths New search path (separated by GIT_PATH_LIST_SEPARATOR)
+ * @return 0 on success, <0 on failure (allocation error)
+ */
+extern int git_sysdir_set(git_sysdir_t which, const char *paths);
+
+/**
+ * Free the configuration file search paths.
+ */
+extern void git_sysdir_global_shutdown(void);
+
+#endif /* INCLUDE_sysdir_h__ */
diff --git a/src/tag.c b/src/tag.c
index 1a79d92df..d7b531d34 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -1,102 +1,88 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
#include "commit.h"
#include "tag.h"
#include "signature.h"
+#include "message.h"
#include "git2/object.h"
#include "git2/repository.h"
#include "git2/signature.h"
+#include "git2/odb_backend.h"
-void git_tag__free(git_tag *tag)
+void git_tag__free(void *_tag)
{
+ git_tag *tag = _tag;
git_signature_free(tag->tagger);
- free(tag->message);
- free(tag->tag_name);
- free(tag);
-}
-
-const git_oid *git_tag_id(git_tag *c)
-{
- return git_object_id((git_object *)c);
+ git__free(tag->message);
+ git__free(tag->tag_name);
+ git__free(tag);
}
-int git_tag_target(git_object **target, git_tag *t)
+int git_tag_target(git_object **target, const git_tag *t)
{
assert(t);
return git_object_lookup(target, t->object.repo, &t->target, t->type);
}
-const git_oid *git_tag_target_oid(git_tag *t)
+const git_oid *git_tag_target_id(const git_tag *t)
{
assert(t);
return &t->target;
}
-git_otype git_tag_type(git_tag *t)
+git_otype git_tag_target_type(const git_tag *t)
{
assert(t);
return t->type;
}
-const char *git_tag_name(git_tag *t)
+const char *git_tag_name(const git_tag *t)
{
assert(t);
return t->tag_name;
}
-const git_signature *git_tag_tagger(git_tag *t)
+const git_signature *git_tag_tagger(const git_tag *t)
{
return t->tagger;
}
-const char *git_tag_message(git_tag *t)
+const char *git_tag_message(const git_tag *t)
{
assert(t);
return t->message;
}
-static int parse_tag_buffer(git_tag *tag, const char *buffer, const char *buffer_end)
+static int tag_error(const char *str)
+{
+ giterr_set(GITERR_TAG, "Failed to parse tag. %s", str);
+ return -1;
+}
+
+static int tag_parse(git_tag *tag, const char *buffer, const char *buffer_end)
{
static const char *tag_types[] = {
NULL, "commit\n", "tree\n", "blob\n", "tag\n"
};
- unsigned int i, text_len;
+ unsigned int i;
+ size_t text_len;
char *search;
- int error;
- if ((error = git_oid__parse(&tag->target, &buffer, buffer_end, "object ")) < 0)
- return git__rethrow(error, "Failed to parse tag. Object field invalid");
+ if (git_oid__parse(&tag->target, &buffer, buffer_end, "object ") < 0)
+ return tag_error("Object field invalid");
if (buffer + 5 >= buffer_end)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Object too short");
+ return tag_error("Object too short");
if (memcmp(buffer, "type ", 5) != 0)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Type field not found");
+ return tag_error("Type field not found");
buffer += 5;
tag->type = GIT_OBJ_BAD;
@@ -105,7 +91,7 @@ static int parse_tag_buffer(git_tag *tag, const char *buffer, const char *buffer
size_t type_length = strlen(tag_types[i]);
if (buffer + type_length >= buffer_end)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Object too short");
+ return tag_error("Object too short");
if (memcmp(buffer, tag_types[i], type_length) == 0) {
tag->type = i;
@@ -115,69 +101,98 @@ static int parse_tag_buffer(git_tag *tag, const char *buffer, const char *buffer
}
if (tag->type == GIT_OBJ_BAD)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Invalid object type");
+ return tag_error("Invalid object type");
if (buffer + 4 >= buffer_end)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Object too short");
+ return tag_error("Object too short");
if (memcmp(buffer, "tag ", 4) != 0)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Tag field not found");
+ return tag_error("Tag field not found");
buffer += 4;
search = memchr(buffer, '\n', buffer_end - buffer);
if (search == NULL)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Object too short");
+ return tag_error("Object too short");
text_len = search - buffer;
tag->tag_name = git__malloc(text_len + 1);
- if (tag->tag_name == NULL)
- return GIT_ENOMEM;
+ GITERR_CHECK_ALLOC(tag->tag_name);
memcpy(tag->tag_name, buffer, text_len);
tag->tag_name[text_len] = '\0';
buffer = search + 1;
- tag->tagger = git__malloc(sizeof(git_signature));
- if (tag->tagger == NULL)
- return GIT_ENOMEM;
+ tag->tagger = NULL;
+ if (buffer < buffer_end && *buffer != '\n') {
+ tag->tagger = git__malloc(sizeof(git_signature));
+ GITERR_CHECK_ALLOC(tag->tagger);
- if ((error = git_signature__parse(tag->tagger, &buffer, buffer_end, "tagger ", '\n')) != 0) {
- free(tag->tag_name);
- git_signature_free(tag->tagger);
- return git__rethrow(error, "Failed to parse tag");
+ if (git_signature__parse(tag->tagger, &buffer, buffer_end, "tagger ", '\n') < 0)
+ return -1;
}
- if( *buffer != '\n' )
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. 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);
- if (tag->message == NULL)
- return GIT_ENOMEM;
+ 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 GIT_SUCCESS;
+ return 0;
}
-static int retrieve_tag_reference(git_reference **tag_reference_out, char *ref_name_out, git_repository *repo, const char *tag_name)
+int git_tag__parse(void *_tag, git_odb_object *odb_obj)
+{
+ git_tag *tag = _tag;
+ const char *buffer = git_odb_object_data(odb_obj);
+ const char *buffer_end = buffer + git_odb_object_size(odb_obj);
+
+ return tag_parse(tag, buffer, buffer_end);
+}
+
+static int retrieve_tag_reference(
+ git_reference **tag_reference_out,
+ git_buf *ref_name_out,
+ git_repository *repo,
+ const char *tag_name)
{
git_reference *tag_ref;
int error;
- git_path_join(ref_name_out, GIT_REFS_TAGS_DIR, tag_name);
- error = git_reference_lookup(&tag_ref, repo, ref_name_out);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to retrieve tag reference");
+ *tag_reference_out = NULL;
+
+ if (git_buf_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name) < 0)
+ return -1;
+
+ error = git_reference_lookup(&tag_ref, repo, ref_name_out->ptr);
+ if (error < 0)
+ return error; /* Be it not foundo or corrupted */
*tag_reference_out = tag_ref;
- return GIT_SUCCESS;
+ return 0;
+}
+
+static int retrieve_tag_reference_oid(
+ git_oid *oid,
+ git_buf *ref_name_out,
+ git_repository *repo,
+ const char *tag_name)
+{
+ if (git_buf_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name) < 0)
+ return -1;
+
+ return git_reference_name_to_id(oid, repo, ref_name_out->ptr);
}
static int write_tag_annotation(
@@ -188,28 +203,31 @@ static int write_tag_annotation(
const git_signature *tagger,
const char *message)
{
- int error = GIT_SUCCESS;
git_buf tag = GIT_BUF_INIT;
+ git_odb *odb;
git_oid__writebuf(&tag, "object ", git_object_id(target));
git_buf_printf(&tag, "type %s\n", git_object_type2string(git_object_type(target)));
git_buf_printf(&tag, "tag %s\n", tag_name);
git_signature__writebuf(&tag, "tagger ", tagger);
git_buf_putc(&tag, '\n');
- git_buf_puts(&tag, message);
- if (git_buf_oom(&tag)) {
- git_buf_free(&tag);
- return git__throw(GIT_ENOMEM, "Not enough memory to build the tag data");
- }
+ if (git_buf_puts(&tag, message) < 0)
+ goto on_error;
- error = git_odb_write(oid, git_repository_database(repo), tag.ptr, tag.size, GIT_OBJ_TAG);
- git_buf_free(&tag);
+ if (git_repository_odb__weakptr(&odb, repo) < 0)
+ goto on_error;
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to create tag annotation");
+ if (git_odb_write(oid, odb, tag.ptr, tag.size, GIT_OBJ_TAG) < 0)
+ goto on_error;
- return error;
+ git_buf_free(&tag);
+ return 0;
+
+on_error:
+ git_buf_free(&tag);
+ giterr_set(GITERR_OBJECT, "Failed to create tag annotation.");
+ return -1;
}
static int git_tag_create__internal(
@@ -223,70 +241,75 @@ static int git_tag_create__internal(
int create_tag_annotation)
{
git_reference *new_ref = NULL;
- char ref_name[GIT_REFNAME_MAX];
+ git_buf ref_name = GIT_BUF_INIT;
- int error, should_update_ref = 0;
+ int error;
assert(repo && tag_name && target);
assert(!create_tag_annotation || (tagger && message));
- if (git_object_owner(target) != repo)
- return git__throw(GIT_EINVALIDARGS, "The given target does not belong to this repository");
-
- error = retrieve_tag_reference(&new_ref, ref_name, repo, tag_name);
-
- switch (error) {
- case GIT_SUCCESS:
- case GIT_ENOTFOUND:
- break;
-
- default:
- return git__rethrow(error, "Failed to create tag");
+ if (git_object_owner(target) != repo) {
+ giterr_set(GITERR_INVALID, "The given target does not belong to this repository");
+ return -1;
}
+ error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag_name);
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto cleanup;
+
/** Ensure the tag name doesn't conflict with an already existing
- * reference unless overwriting has explictly been requested **/
- if (new_ref != NULL) {
- if (!allow_ref_overwrite) {
- git_oid_cpy(oid, git_reference_oid(new_ref));
- return git__throw(GIT_EEXISTS, "Tag already exists");
- } else {
- should_update_ref = 1;
- }
+ * reference unless overwriting has explictly been requested **/
+ if (error == 0 && !allow_ref_overwrite) {
+ git_buf_free(&ref_name);
+ giterr_set(GITERR_TAG, "Tag already exists");
+ return GIT_EEXISTS;
}
if (create_tag_annotation) {
- if ((error = write_tag_annotation(oid, repo, tag_name, target, tagger, message)) < GIT_SUCCESS)
- return error;
+ if (write_tag_annotation(oid, repo, tag_name, target, tagger, message) < 0)
+ return -1;
} else
git_oid_cpy(oid, git_object_id(target));
- if (!should_update_ref)
- error = git_reference_create_oid(&new_ref, repo, ref_name, oid, 0);
- else
- error = git_reference_set_oid(new_ref, oid);
+ error = git_reference_create(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite, NULL, NULL);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create tag");
+cleanup:
+ git_reference_free(new_ref);
+ git_buf_free(&ref_name);
+ return error;
}
int git_tag_create(
- git_oid *oid,
- git_repository *repo,
- const char *tag_name,
- const git_object *target,
- const git_signature *tagger,
- const char *message,
- int allow_ref_overwrite)
+ git_oid *oid,
+ git_repository *repo,
+ const char *tag_name,
+ const git_object *target,
+ const git_signature *tagger,
+ const char *message,
+ int allow_ref_overwrite)
{
return git_tag_create__internal(oid, repo, tag_name, target, tagger, message, allow_ref_overwrite, 1);
}
+int git_tag_annotation_create(
+ git_oid *oid,
+ git_repository *repo,
+ const char *tag_name,
+ const git_object *target,
+ const git_signature *tagger,
+ const char *message)
+{
+ assert(oid && repo && tag_name && target && tagger && message);
+
+ return write_tag_annotation(oid, repo, tag_name, target, tagger, message);
+}
+
int git_tag_create_lightweight(
- git_oid *oid,
- git_repository *repo,
- const char *tag_name,
- const git_object *target,
- int allow_ref_overwrite)
+ git_oid *oid,
+ git_repository *repo,
+ const char *tag_name,
+ const git_object *target,
+ int allow_ref_overwrite)
{
return git_tag_create__internal(oid, repo, tag_name, target, NULL, NULL, allow_ref_overwrite, 0);
}
@@ -294,114 +317,159 @@ int git_tag_create_lightweight(
int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *buffer, int allow_ref_overwrite)
{
git_tag tag;
- int error, should_update_ref = 0;
+ int error;
+ git_odb *odb;
git_odb_stream *stream;
git_odb_object *target_obj;
- git_reference *new_ref;
- char ref_name[GIT_REFNAME_MAX];
+ git_reference *new_ref = NULL;
+ git_buf ref_name = GIT_BUF_INIT;
assert(oid && buffer);
memset(&tag, 0, sizeof(tag));
+ if (git_repository_odb__weakptr(&odb, repo) < 0)
+ return -1;
+
/* validate the buffer */
- if ((error = parse_tag_buffer(&tag, buffer, buffer + strlen(buffer))) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to create tag");
+ if (tag_parse(&tag, buffer, buffer + strlen(buffer)) < 0)
+ return -1;
/* validate the target */
- if ((error = git_odb_read(&target_obj, repo->db, &tag.target)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to create tag");
+ if (git_odb_read(&target_obj, odb, &tag.target) < 0)
+ goto on_error;
- if (tag.type != target_obj->raw.type)
- return git__throw(error, "The type for the given target is invalid");
-
- git_odb_object_close(target_obj);
-
- error = retrieve_tag_reference(&new_ref, ref_name, repo, tag.tag_name);
+ if (tag.type != target_obj->cached.type) {
+ giterr_set(GITERR_TAG, "The type for the given target is invalid");
+ goto on_error;
+ }
- switch (error) {
- case GIT_SUCCESS:
- case GIT_ENOTFOUND:
- break;
+ error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag.tag_name);
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto on_error;
- default:
- return git__rethrow(error, "Failed to create tag");
- }
+ /* We don't need these objects after this */
+ git_signature_free(tag.tagger);
+ git__free(tag.tag_name);
+ git__free(tag.message);
+ git_odb_object_free(target_obj);
/** Ensure the tag name doesn't conflict with an already existing
- * reference unless overwriting has explictly been requested **/
- if (new_ref != NULL) {
- if (!allow_ref_overwrite) {
- git_oid_cpy(oid, git_reference_oid(new_ref));
- return git__throw(GIT_EEXISTS, "Tag already exists");
- } else {
- should_update_ref = 1;
- }
+ * reference unless overwriting has explictly been requested **/
+ if (error == 0 && !allow_ref_overwrite) {
+ giterr_set(GITERR_TAG, "Tag already exists");
+ return GIT_EEXISTS;
}
/* write the buffer */
- if ((error = git_odb_open_wstream(&stream, repo->db, strlen(buffer), GIT_OBJ_TAG)) < GIT_SUCCESS)
- return git__rethrow(error, "Failed to create tag");
+ if ((error = git_odb_open_wstream(
+ &stream, odb, strlen(buffer), GIT_OBJ_TAG)) < 0)
+ return error;
- stream->write(stream, buffer, strlen(buffer));
+ if (!(error = git_odb_stream_write(stream, buffer, strlen(buffer))))
+ error = git_odb_stream_finalize_write(oid, stream);
- error = stream->finalize_write(oid, stream);
- stream->free(stream);
+ git_odb_stream_free(stream);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to create tag");
+ if (error < 0) {
+ git_buf_free(&ref_name);
+ return error;
+ }
- if (!should_update_ref)
- error = git_reference_create_oid(&new_ref, repo, ref_name, oid, 0);
- else
- error = git_reference_set_oid(new_ref, oid);
+ error = git_reference_create(
+ &new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite, NULL, NULL);
- git_signature_free(tag.tagger);
- free(tag.tag_name);
- free(tag.message);
+ git_reference_free(new_ref);
+ git_buf_free(&ref_name);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create tag");
+ return error;
+
+on_error:
+ git_signature_free(tag.tagger);
+ git__free(tag.tag_name);
+ git__free(tag.message);
+ git_odb_object_free(target_obj);
+ return -1;
}
int git_tag_delete(git_repository *repo, const char *tag_name)
{
- int error;
git_reference *tag_ref;
- char ref_name[GIT_REFNAME_MAX];
+ git_buf ref_name = GIT_BUF_INIT;
+ int error;
+
+ error = retrieve_tag_reference(&tag_ref, &ref_name, repo, tag_name);
+
+ git_buf_free(&ref_name);
+
+ if (error < 0)
+ return error;
- error = retrieve_tag_reference(&tag_ref, ref_name, repo, tag_name);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to delete tag");
+ if ((error = git_reference_delete(tag_ref)) == 0)
+ git_reference_free(tag_ref);
- return git_reference_delete(tag_ref);
+ return error;
+}
+
+typedef struct {
+ git_repository *repo;
+ git_tag_foreach_cb cb;
+ void *cb_data;
+} tag_cb_data;
+
+static int tags_cb(const char *ref, void *data)
+{
+ int error;
+ git_oid oid;
+ tag_cb_data *d = (tag_cb_data *)data;
+
+ if (git__prefixcmp(ref, GIT_REFS_TAGS_DIR) != 0)
+ return 0; /* no tag */
+
+ if (!(error = git_reference_name_to_id(&oid, d->repo, ref))) {
+ if ((error = d->cb(ref, &oid, d->cb_data)) != 0)
+ giterr_set_after_callback_function(error, "git_tag_foreach");
+ }
+
+ return error;
}
-int git_tag__parse(git_tag *tag, git_odb_object *obj)
+int git_tag_foreach(git_repository *repo, git_tag_foreach_cb cb, void *cb_data)
{
- assert(tag);
- return parse_tag_buffer(tag, obj->raw.data, (char *)obj->raw.data + obj->raw.len);
+ tag_cb_data data;
+
+ assert(repo && cb);
+
+ data.cb = cb;
+ data.cb_data = cb_data;
+ data.repo = repo;
+
+ return git_reference_foreach_name(repo, &tags_cb, &data);
}
typedef struct {
- git_vector *taglist;
- const char *pattern;
+ git_vector *taglist;
+ const char *pattern;
} tag_filter_data;
-#define GIT_REFS_TAGS_DIR_LEN STRLEN(GIT_REFS_TAGS_DIR)
+#define GIT_REFS_TAGS_DIR_LEN strlen(GIT_REFS_TAGS_DIR)
-static int tag_list_cb(const char *tag_name, void *payload)
+static int tag_list_cb(const char *tag_name, git_oid *oid, void *data)
{
- tag_filter_data *filter;
+ tag_filter_data *filter = (tag_filter_data *)data;
+ GIT_UNUSED(oid);
- if (git__prefixcmp(tag_name, GIT_REFS_TAGS_DIR) != 0)
- return GIT_SUCCESS;
+ if (!*filter->pattern ||
+ p_fnmatch(filter->pattern, tag_name + GIT_REFS_TAGS_DIR_LEN, 0) == 0)
+ {
+ char *matched = git__strdup(tag_name + GIT_REFS_TAGS_DIR_LEN);
+ GITERR_CHECK_ALLOC(matched);
- filter = (tag_filter_data *)payload;
- if (!*filter->pattern || p_fnmatch(filter->pattern, tag_name + GIT_REFS_TAGS_DIR_LEN, 0) == GIT_SUCCESS)
- return git_vector_insert(filter->taglist, git__strdup(tag_name));
+ return git_vector_insert(filter->taglist, matched);
+ }
- return GIT_SUCCESS;
+ return 0;
}
int git_tag_list_match(git_strarray *tag_names, const char *pattern, git_repository *repo)
@@ -412,24 +480,29 @@ int git_tag_list_match(git_strarray *tag_names, const char *pattern, git_reposit
assert(tag_names && repo && pattern);
- if (git_vector_init(&taglist, 8, NULL) < GIT_SUCCESS)
- return GIT_ENOMEM;
+ if ((error = git_vector_init(&taglist, 8, NULL)) < 0)
+ return error;
filter.taglist = &taglist;
filter.pattern = pattern;
- error = git_reference_foreach(repo, GIT_REF_OID|GIT_REF_PACKED, &tag_list_cb, (void *)&filter);
- if (error < GIT_SUCCESS) {
+ error = git_tag_foreach(repo, &tag_list_cb, (void *)&filter);
+
+ if (error < 0)
git_vector_free(&taglist);
- return git__rethrow(error, "Failed to list tags");
- }
- tag_names->strings = (char **)taglist.contents;
- tag_names->count = taglist.length;
- return GIT_SUCCESS;
+ tag_names->strings =
+ (char **)git_vector_detach(&tag_names->count, NULL, &taglist);
+
+ return 0;
}
int git_tag_list(git_strarray *tag_names, git_repository *repo)
{
return git_tag_list_match(tag_names, "", repo);
}
+
+int git_tag_peel(git_object **tag_target, const git_tag *tag)
+{
+ return git_object_peel(tag_target, (const git_object *)tag, GIT_OBJ_ANY);
+}
diff --git a/src/tag.h b/src/tag.h
index eddf8fa3a..d0cd393c7 100644
--- a/src/tag.h
+++ b/src/tag.h
@@ -1,3 +1,9 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_tag_h__
#define INCLUDE_tag_h__
@@ -16,7 +22,7 @@ struct git_tag {
char *message;
};
-void git_tag__free(git_tag *tag);
-int git_tag__parse(git_tag *tag, git_odb_object *obj);
+void git_tag__free(void *tag);
+int git_tag__parse(void *tag, git_odb_object *obj);
#endif
diff --git a/src/thread-utils.c b/src/thread-utils.c
index 5e8220f46..c3baf411a 100644
--- a/src/thread-utils.c
+++ b/src/thread-utils.c
@@ -1,11 +1,17 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "thread-utils.h"
#ifdef _WIN32
-# define WIN32_LEAN_AND_MEAN
-# include <windows.h>
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
#elif defined(hpux) || defined(__hpux) || defined(_hpux)
-# include <sys/pstat.h>
+# include <sys/pstat.h>
#endif
/*
@@ -14,11 +20,11 @@
* with this disgusting nest of #ifdefs.
*/
#ifndef _SC_NPROCESSORS_ONLN
-# ifdef _SC_NPROC_ONLN
-# define _SC_NPROCESSORS_ONLN _SC_NPROC_ONLN
-# elif defined _SC_CRAY_NCPU
-# define _SC_NPROCESSORS_ONLN _SC_CRAY_NCPU
-# endif
+# ifdef _SC_NPROC_ONLN
+# define _SC_NPROCESSORS_ONLN _SC_NPROC_ONLN
+# elif defined _SC_CRAY_NCPU
+# define _SC_NPROCESSORS_ONLN _SC_CRAY_NCPU
+# endif
#endif
int git_online_cpus(void)
diff --git a/src/thread-utils.h b/src/thread-utils.h
index 20d6022ea..daec14eeb 100644
--- a/src/thread-utils.h
+++ b/src/thread-utils.h
@@ -1,51 +1,131 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_thread_utils_h__
#define INCLUDE_thread_utils_h__
-
-
/* Common operations even if threading has been disabled */
typedef struct {
-#if defined(_MSC_VER)
+#if defined(GIT_WIN32)
volatile long val;
#else
volatile int val;
#endif
} git_atomic;
-GIT_INLINE(void) git_atomic_set(git_atomic *a, int val)
-{
- a->val = val;
-}
+#ifdef GIT_ARCH_64
+
+typedef struct {
+#if defined(GIT_WIN32)
+ __int64 val;
+#else
+ int64_t val;
+#endif
+} git_atomic64;
+
+typedef git_atomic64 git_atomic_ssize;
+
+#define git_atomic_ssize_add git_atomic64_add
+
+#else
+
+typedef git_atomic git_atomic_ssize;
+
+#define git_atomic_ssize_add git_atomic_add
+
+#endif
#ifdef GIT_THREADS
-#define git_thread pthread_t
-#define git_thread_create(thread, attr, start_routine, arg) pthread_create(thread, attr, start_routine, arg)
-#define git_thread_kill(thread) pthread_cancel(thread)
-#define git_thread_exit(status) pthread_exit(status)
-#define git_thread_join(id, status) pthread_join(id, status)
+#if !defined(GIT_WIN32)
+
+typedef struct {
+ pthread_t thread;
+} git_thread;
+
+#define git_thread_create(git_thread_ptr, attr, start_routine, arg) \
+ pthread_create(&(git_thread_ptr)->thread, attr, start_routine, arg)
+#define git_thread_join(git_thread_ptr, status) \
+ pthread_join((git_thread_ptr)->thread, status)
+
+#endif
+
+#if defined(GIT_WIN32)
+#define git_thread_yield() Sleep(0)
+#else
+#define git_thread_yield() sched_yield()
+#endif
/* Pthreads Mutex */
#define git_mutex pthread_mutex_t
-#define git_mutex_init(a) pthread_mutex_init(a, NULL)
-#define git_mutex_lock(a) pthread_mutex_lock(a)
+#define git_mutex_init(a) pthread_mutex_init(a, NULL)
+#define git_mutex_lock(a) pthread_mutex_lock(a)
#define git_mutex_unlock(a) pthread_mutex_unlock(a)
-#define git_mutex_free(a) pthread_mutex_destroy(a)
+#define git_mutex_free(a) pthread_mutex_destroy(a)
+
+/* Pthreads condition vars */
+#define git_cond pthread_cond_t
+#define git_cond_init(c) pthread_cond_init(c, NULL)
+#define git_cond_free(c) pthread_cond_destroy(c)
+#define git_cond_wait(c, l) pthread_cond_wait(c, l)
+#define git_cond_signal(c) pthread_cond_signal(c)
+#define git_cond_broadcast(c) pthread_cond_broadcast(c)
+
+/* Pthread (-ish) rwlock
+ *
+ * This differs from normal pthreads rwlocks in two ways:
+ * 1. Separate APIs for releasing read locks and write locks (as
+ * opposed to the pure POSIX API which only has one unlock fn)
+ * 2. You should not use recursive read locks (i.e. grabbing a read
+ * lock in a thread that already holds a read lock) because the
+ * Windows implementation doesn't support it
+ */
+#define git_rwlock pthread_rwlock_t
+#define git_rwlock_init(a) pthread_rwlock_init(a, NULL)
+#define git_rwlock_rdlock(a) pthread_rwlock_rdlock(a)
+#define git_rwlock_rdunlock(a) pthread_rwlock_rdunlock(a)
+#define git_rwlock_wrlock(a) pthread_rwlock_wrlock(a)
+#define git_rwlock_wrunlock(a) pthread_rwlock_wrunlock(a)
+#define git_rwlock_free(a) pthread_rwlock_destroy(a)
+#define GIT_RWLOCK_STATIC_INIT PTHREAD_RWLOCK_INITIALIZER
+
+#ifndef GIT_WIN32
+#define pthread_rwlock_rdunlock pthread_rwlock_unlock
+#define pthread_rwlock_wrunlock pthread_rwlock_unlock
+#endif
-/* Pthreads condition vars -- disabled by now */
-#define git_cond unsigned int //pthread_cond_t
-#define git_cond_init(c, a) (void)0 //pthread_cond_init(c, a)
-#define git_cond_free(c) (void)0 //pthread_cond_destroy(c)
-#define git_cond_wait(c, l) (void)0 //pthread_cond_wait(c, l)
-#define git_cond_signal(c) (void)0 //pthread_cond_signal(c)
-#define git_cond_broadcast(c) (void)0 //pthread_cond_broadcast(c)
+
+GIT_INLINE(void) git_atomic_set(git_atomic *a, int val)
+{
+#if defined(GIT_WIN32)
+ InterlockedExchange(&a->val, (LONG)val);
+#elif defined(__GNUC__)
+ __sync_lock_test_and_set(&a->val, val);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
GIT_INLINE(int) git_atomic_inc(git_atomic *a)
{
-#ifdef __GNUC__
- return __sync_add_and_fetch(&a->val, 1);
-#elif defined(_MSC_VER)
+#if defined(GIT_WIN32)
return InterlockedIncrement(&a->val);
+#elif defined(__GNUC__)
+ return __sync_add_and_fetch(&a->val, 1);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
+
+GIT_INLINE(int) git_atomic_add(git_atomic *a, int32_t addend)
+{
+#if defined(GIT_WIN32)
+ return InterlockedExchangeAdd(&a->val, addend);
+#elif defined(__GNUC__)
+ return __sync_add_and_fetch(&a->val, addend);
#else
# error "Unsupported architecture for atomic operations"
#endif
@@ -53,27 +133,65 @@ GIT_INLINE(int) git_atomic_inc(git_atomic *a)
GIT_INLINE(int) git_atomic_dec(git_atomic *a)
{
-#ifdef __GNUC__
- return __sync_sub_and_fetch(&a->val, 1);
-#elif defined(_MSC_VER)
+#if defined(GIT_WIN32)
return InterlockedDecrement(&a->val);
+#elif defined(__GNUC__)
+ return __sync_sub_and_fetch(&a->val, 1);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
+
+GIT_INLINE(void *) git___compare_and_swap(
+ void * volatile *ptr, void *oldval, void *newval)
+{
+ volatile void *foundval;
+#if defined(GIT_WIN32)
+ foundval = InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval);
+#elif defined(__GNUC__)
+ foundval = __sync_val_compare_and_swap(ptr, oldval, newval);
#else
# error "Unsupported architecture for atomic operations"
#endif
+ return (foundval == oldval) ? oldval : newval;
}
+GIT_INLINE(volatile void *) git___swap(
+ void * volatile *ptr, void *newval)
+{
+#if defined(GIT_WIN32)
+ return InterlockedExchangePointer(ptr, newval);
+#else
+ return __sync_lock_test_and_set(ptr, newval);
+#endif
+}
+
+#ifdef GIT_ARCH_64
+
+GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend)
+{
+#if defined(GIT_WIN32)
+ return InterlockedExchangeAdd64(&a->val, addend);
+#elif defined(__GNUC__)
+ return __sync_add_and_fetch(&a->val, addend);
+#else
+# error "Unsupported architecture for atomic operations"
+#endif
+}
+
+#endif
+
#else
#define git_thread unsigned int
-#define git_thread_create(thread, attr, start_routine, arg) (void)0
-#define git_thread_kill(thread) (void)0
-#define git_thread_exit(status) (void)0
+#define git_thread_create(thread, attr, start_routine, arg) 0
#define git_thread_join(id, status) (void)0
+#define git_thread_yield() (void)0
/* Pthreads Mutex */
#define git_mutex unsigned int
-#define git_mutex_init(a) (void)0
-#define git_mutex_lock(a) (void)0
+#define git_mutex_init(a) 0
+#define git_mutex_lock(a) 0
#define git_mutex_unlock(a) (void)0
#define git_mutex_free(a) (void)0
@@ -85,18 +203,89 @@ GIT_INLINE(int) git_atomic_dec(git_atomic *a)
#define git_cond_signal(c) (void)0
#define git_cond_broadcast(c) (void)0
+/* Pthreads rwlock */
+#define git_rwlock unsigned int
+#define git_rwlock_init(a) 0
+#define git_rwlock_rdlock(a) 0
+#define git_rwlock_rdunlock(a) (void)0
+#define git_rwlock_wrlock(a) 0
+#define git_rwlock_wrunlock(a) (void)0
+#define git_rwlock_free(a) (void)0
+#define GIT_RWLOCK_STATIC_INIT 0
+
+
+GIT_INLINE(void) git_atomic_set(git_atomic *a, int val)
+{
+ a->val = val;
+}
+
GIT_INLINE(int) git_atomic_inc(git_atomic *a)
{
return ++a->val;
}
+GIT_INLINE(int) git_atomic_add(git_atomic *a, int32_t addend)
+{
+ a->val += addend;
+ return a->val;
+}
+
GIT_INLINE(int) git_atomic_dec(git_atomic *a)
{
return --a->val;
}
+GIT_INLINE(void *) git___compare_and_swap(
+ void * volatile *ptr, void *oldval, void *newval)
+{
+ if (*ptr == oldval)
+ *ptr = newval;
+ else
+ oldval = newval;
+ return oldval;
+}
+
+GIT_INLINE(volatile void *) git___swap(
+ void * volatile *ptr, void *newval)
+{
+ volatile void *old = *ptr;
+ *ptr = newval;
+ return old;
+}
+
+#ifdef GIT_ARCH_64
+
+GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend)
+{
+ a->val += addend;
+ return a->val;
+}
+
+#endif
+
#endif
+GIT_INLINE(int) git_atomic_get(git_atomic *a)
+{
+ return (int)a->val;
+}
+
+/* Atomically replace oldval with newval
+ * @return oldval if it was replaced or newval if it was not
+ */
+#define git__compare_and_swap(P,O,N) \
+ git___compare_and_swap((void * volatile *)P, O, N)
+
+#define git__swap(ptr, val) (void *)git___swap((void * volatile *)&ptr, val)
+
extern int git_online_cpus(void);
+#if defined(GIT_THREADS) && defined(GIT_WIN32)
+# define GIT_MEMORY_BARRIER MemoryBarrier()
+#elif defined(GIT_THREADS)
+# define GIT_MEMORY_BARRIER __sync_synchronize()
+#else
+# define GIT_MEMORY_BARRIER /* noop */
+#endif
+
#endif /* INCLUDE_thread_utils_h__ */
diff --git a/src/trace.c b/src/trace.c
new file mode 100644
index 000000000..ee5039f56
--- /dev/null
+++ b/src/trace.c
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "buffer.h"
+#include "common.h"
+#include "global.h"
+#include "trace.h"
+#include "git2/trace.h"
+
+#ifdef GIT_TRACE
+
+struct git_trace_data git_trace__data = {0};
+
+#endif
+
+int git_trace_set(git_trace_level_t level, git_trace_callback callback)
+{
+#ifdef GIT_TRACE
+ assert(level == 0 || callback != NULL);
+
+ git_trace__data.level = level;
+ git_trace__data.callback = callback;
+ GIT_MEMORY_BARRIER;
+
+ return 0;
+#else
+ GIT_UNUSED(level);
+ GIT_UNUSED(callback);
+
+ giterr_set(GITERR_INVALID,
+ "This version of libgit2 was not built with tracing.");
+ return -1;
+#endif
+}
diff --git a/src/trace.h b/src/trace.h
new file mode 100644
index 000000000..4d4e3bf53
--- /dev/null
+++ b/src/trace.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_trace_h__
+#define INCLUDE_trace_h__
+
+#include <git2/trace.h>
+#include "buffer.h"
+
+#ifdef GIT_TRACE
+
+struct git_trace_data {
+ git_trace_level_t level;
+ git_trace_callback callback;
+};
+
+extern struct git_trace_data git_trace__data;
+
+GIT_INLINE(void) git_trace__write_fmt(
+ git_trace_level_t level,
+ const char *fmt, ...)
+{
+ git_trace_callback callback = git_trace__data.callback;
+ git_buf message = GIT_BUF_INIT;
+ va_list ap;
+
+ va_start(ap, fmt);
+ git_buf_vprintf(&message, fmt, ap);
+ va_end(ap);
+
+ callback(level, git_buf_cstr(&message));
+
+ git_buf_free(&message);
+}
+
+#define git_trace_level() (git_trace__data.level)
+#define git_trace(l, ...) { \
+ if (git_trace__data.level >= l && \
+ git_trace__data.callback != NULL) { \
+ git_trace__write_fmt(l, __VA_ARGS__); \
+ } \
+ }
+
+#else
+
+#define git_trace_level() ((void)0)
+#define git_trace(lvl, ...) ((void)0)
+
+#endif
+
+#endif
diff --git a/src/transport.c b/src/transport.c
index 204ef176f..2194b1864 100644
--- a/src/transport.c
+++ b/src/transport.c
@@ -1,91 +1,226 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
#include "common.h"
#include "git2/types.h"
-#include "git2/transport.h"
+#include "git2/remote.h"
#include "git2/net.h"
-#include "transport.h"
+#include "git2/transport.h"
+#include "path.h"
-struct {
+typedef struct transport_definition {
char *prefix;
+ unsigned priority;
git_transport_cb fn;
-} transports[] = {
- {"git://", git_transport_git},
- {"http://", git_transport_dummy},
- {"https://", git_transport_dummy},
- {"file://", git_transport_local},
- {"git+ssh://", git_transport_dummy},
- {"ssh+git://", git_transport_dummy},
- {NULL, 0}
+ void *param;
+} transport_definition;
+
+static 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 };
+#ifdef GIT_SSH
+static git_smart_subtransport_definition ssh_subtransport_definition = { git_smart_subtransport_ssh, 0 };
+#endif
+
+static transport_definition local_transport_definition = { "file://", 1, git_transport_local, NULL };
+#ifdef GIT_SSH
+static transport_definition ssh_transport_definition = { "ssh://", 1, git_transport_smart, &ssh_subtransport_definition };
+#else
+static transport_definition dummy_transport_definition = { NULL, 1, git_transport_dummy, NULL };
+#endif
+
+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},
+#ifdef GIT_SSH
+ {"ssh://", 1, git_transport_smart, &ssh_subtransport_definition},
+#endif
+ {NULL, 0, 0}
};
-static git_transport_cb transport_new_fn(const char *url)
+static git_vector additional_transports = GIT_VECTOR_INIT;
+
+#define GIT_TRANSPORT_COUNT (sizeof(transports)/sizeof(transports[0])) - 1
+
+static int transport_find_fn(const char *url, git_transport_cb *callback, void **param)
{
- int i = 0;
+ size_t i = 0;
+ unsigned priority = 0;
+ transport_definition *definition = NULL, *definition_iter;
- while (1) {
- if (transports[i].prefix == NULL)
- break;
+ // First, check to see if it's an obvious URL, which a URL scheme
+ for (i = 0; i < GIT_TRANSPORT_COUNT; ++i) {
+ definition_iter = &transports[i];
- if (!strncasecmp(url, transports[i].prefix, strlen(transports[i].prefix)))
- return transports[i].fn;
+ if (strncasecmp(url, definition_iter->prefix, strlen(definition_iter->prefix)))
+ continue;
- ++i;
+ if (definition_iter->priority > priority)
+ definition = definition_iter;
}
- /*
- * If we still haven't found the transport, we assume we mean a
- * local file.
- * TODO: Parse "example.com:project.git" as an SSH URL
- */
- return git_transport_local;
+ git_vector_foreach(&additional_transports, i, definition_iter) {
+ if (strncasecmp(url, definition_iter->prefix, strlen(definition_iter->prefix)))
+ continue;
+
+ if (definition_iter->priority > priority)
+ definition = definition_iter;
+ }
+
+#ifdef GIT_WIN32
+ /* On Windows, it might not be possible to discern between absolute local
+ * and ssh paths - first check if this is a valid local path that points
+ * to a directory and if so assume local path, else assume SSH */
+
+ /* Check to see if the path points to a file on the local file system */
+ if (!definition && git_path_exists(url) && git_path_isdir(url))
+ definition = &local_transport_definition;
+#endif
+
+ /* For other systems, perform the SSH check first, to avoid going to the
+ * filesystem if it is not necessary */
+
+ /* It could be a SSH remote path. Check to see if there's a :
+ * SSH is an unsupported transport mechanism in this version of libgit2 */
+ if (!definition && strrchr(url, ':'))
+#ifdef GIT_SSH
+ definition = &ssh_transport_definition;
+#else
+ definition = &dummy_transport_definition;
+#endif
+
+#ifndef GIT_WIN32
+ /* Check to see if the path points to a file on the local file system */
+ if (!definition && git_path_exists(url) && git_path_isdir(url))
+ definition = &local_transport_definition;
+#endif
+
+ if (!definition)
+ return -1;
+
+ *callback = definition->fn;
+ *param = definition->param;
+
+ return 0;
}
/**************
* Public API *
**************/
-int git_transport_dummy(git_transport **GIT_UNUSED(transport))
+int git_transport_dummy(git_transport **transport, git_remote *owner, void *param)
{
- GIT_UNUSED_ARG(transport);
- return git__throw(GIT_ENOTIMPLEMENTED, "This protocol isn't implemented. Sorry");
+ GIT_UNUSED(transport);
+ GIT_UNUSED(owner);
+ GIT_UNUSED(param);
+ giterr_set(GITERR_NET, "This transport isn't implemented. Sorry");
+ return -1;
}
-int git_transport_new(git_transport **out, const char *url)
+int git_transport_new(git_transport **out, git_remote *owner, const char *url)
{
git_transport_cb fn;
git_transport *transport;
+ void *param;
int error;
- fn = transport_new_fn(url);
-
- error = fn(&transport);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to create new transport");
+ if (transport_find_fn(url, &fn, &param) < 0) {
+ giterr_set(GITERR_NET, "Unsupported URL protocol");
+ return -1;
+ }
- transport->url = git__strdup(url);
- if (transport->url == NULL)
- return GIT_ENOMEM;
+ error = fn(&transport, owner, param);
+ if (error < 0)
+ return error;
*out = transport;
- return GIT_SUCCESS;
+ return 0;
+}
+
+int git_transport_register(
+ const char *prefix,
+ unsigned priority,
+ git_transport_cb cb,
+ void *param)
+{
+ transport_definition *d;
+
+ d = git__calloc(sizeof(transport_definition), 1);
+ GITERR_CHECK_ALLOC(d);
+
+ d->prefix = git__strdup(prefix);
+
+ if (!d->prefix)
+ goto on_error;
+
+ d->priority = priority;
+ d->fn = cb;
+ d->param = param;
+
+ if (git_vector_insert(&additional_transports, d) < 0)
+ goto on_error;
+
+ return 0;
+
+on_error:
+ git__free(d->prefix);
+ git__free(d);
+ return -1;
}
-int git_transport_connect(git_transport *transport, int direction)
+int git_transport_unregister(
+ const char *prefix,
+ unsigned priority)
{
- return transport->connect(transport, direction);
+ transport_definition *d;
+ unsigned i;
+
+ git_vector_foreach(&additional_transports, i, d) {
+ if (d->priority == priority && !strcasecmp(d->prefix, prefix)) {
+ if (git_vector_remove(&additional_transports, i) < 0)
+ return -1;
+
+ git__free(d->prefix);
+ git__free(d);
+
+ if (!additional_transports.length)
+ git_vector_free(&additional_transports);
+
+ return 0;
+ }
+ }
+
+ return GIT_ENOTFOUND;
}
-int git_transport_ls(git_transport *transport, git_headarray *array)
+/* from remote.h */
+int git_remote_valid_url(const char *url)
{
- return transport->ls(transport, array);
+ git_transport_cb fn;
+ void *param;
+
+ return !transport_find_fn(url, &fn, &param);
}
-int git_transport_close(git_transport *transport)
+int git_remote_supported_url(const char* url)
{
- return transport->close(transport);
+ git_transport_cb fn;
+ void *param;
+
+ if (transport_find_fn(url, &fn, &param) < 0)
+ return 0;
+
+ return fn != &git_transport_dummy;
}
-void git_transport_free(git_transport *transport)
+int git_transport_init(git_transport *opts, unsigned int version)
{
- transport->free(transport);
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_transport, GIT_TRANSPORT_INIT);
+ return 0;
}
diff --git a/src/transport.h b/src/transport.h
deleted file mode 100644
index b17d9a929..000000000
--- a/src/transport.h
+++ /dev/null
@@ -1,77 +0,0 @@
-#ifndef INCLUDE_transport_h__
-#define INCLUDE_transport_h__
-
-#include "git2/transport.h"
-#include "git2/net.h"
-#include "vector.h"
-
-/*
- * 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.
- *
- * If we just want to execute ls-remote, ->ls() gets
- * called. Otherwise, the have/want/need list needs to be built via
- * ->wanthaveneed(). We can then ->push() or ->pull(). 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 */
- int connected : 1;
- /**
- * Connect and store the remote heads
- */
- int (*connect)(struct git_transport *transport, int dir);
- /**
- * Give a list of references, useful for ls-remote
- */
- int (*ls)(struct git_transport *transport, git_headarray *headarray);
- /**
- * Calculate want/have/need. May not even be needed.
- */
- int (*wanthaveneed)(struct git_transport *transport, void *something);
- /**
- * Build the pack
- */
- int (*build_pack)(struct git_transport *transport);
- /**
- * Push the changes over
- */
- int (*push)(struct git_transport *transport);
- /**
- * Fetch the changes
- */
- int (*fetch)(struct git_transport *transport);
- /**
- * Close the connection
- */
- int (*close)(struct git_transport *transport);
- /**
- * Free the associated resources
- */
- void (*free)(struct git_transport *transport);
-};
-
-int git_transport_local(struct git_transport **transport);
-int git_transport_git(struct git_transport **transport);
-int git_transport_dummy(struct git_transport **transport);
-
-#endif
diff --git a/src/transport_git.c b/src/transport_git.c
deleted file mode 100644
index b07b98660..000000000
--- a/src/transport_git.c
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#include "git2/net.h"
-#include "git2/common.h"
-#include "git2/types.h"
-#include "git2/errors.h"
-
-#include "vector.h"
-#include "transport.h"
-#include "pkt.h"
-#include "common.h"
-#include "netops.h"
-
-typedef struct {
- git_transport parent;
- int socket;
- git_vector refs;
- git_remote_head **heads;
-} transport_git;
-
-/*
- * Create a git procol request.
- *
- * For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0
- */
-static int gen_proto(char **out, int *outlen, const char *cmd, const char *url)
-{
- char *delim, *repo, *ptr;
- char default_command[] = "git-upload-pack";
- char host[] = "host=";
- int len;
-
- delim = strchr(url, '/');
- if (delim == NULL)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to create proto-request: malformed URL");
-
- repo = delim;
-
- delim = strchr(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) + 2;
-
- *out = git__malloc(len);
- if (*out == NULL)
- return GIT_ENOMEM;
-
- *outlen = len - 1;
- ptr = *out;
- memset(ptr, 0x0, len);
- /* We expect the return value to be > len - 1 so don't bother checking it */
- snprintf(ptr, len -1, "%04x%s %s%c%s%s", len - 1, cmd, repo, 0, host, url);
-
- return GIT_SUCCESS;
-}
-
-static int send_request(int s, const char *cmd, const char *url)
-{
- int error, len;
- char *msg = NULL;
-
- error = gen_proto(&msg, &len, cmd, url);
- if (error < GIT_SUCCESS)
- goto cleanup;
-
- error = gitno_send(s, msg, len, 0);
-
-cleanup:
- free(msg);
- return error;
-}
-
-/* The URL should already have been stripped of the protocol */
-static int extract_host_and_port(char **host, char **port, const char *url)
-{
- char *colon, *slash, *delim;
- int error = GIT_SUCCESS;
-
- colon = strchr(url, ':');
- slash = strchr(url, '/');
-
- if (slash == NULL)
- return git__throw(GIT_EOBJCORRUPTED, "Malformed URL: missing /");
-
- if (colon == NULL) {
- *port = git__strdup(GIT_DEFAULT_PORT);
- } else {
- *port = git__strndup(colon + 1, slash - colon - 1);
- }
- if (*port == NULL)
- return GIT_ENOMEM;;
-
-
- delim = colon == NULL ? slash : colon;
- *host = git__strndup(url, delim - url);
- if (*host == NULL) {
- free(*port);
- error = GIT_ENOMEM;
- }
-
- 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)
-{
- int s = -1;
- char *host, *port;
- const char prefix[] = "git://";
- int error, connected = 0;
-
- if (!git__prefixcmp(url, prefix))
- url += STRLEN(prefix);
-
- error = extract_host_and_port(&host, &port, url);
- s = gitno_connect(host, port);
- connected = 1;
- error = send_request(s, NULL, url);
- t->socket = s;
-
- free(host);
- free(port);
-
- if (error < GIT_SUCCESS && s > 0)
- close(s);
- if (!connected)
- error = git__throw(GIT_EOSERR, "Failed to connect to any of the addresses");
-
- return error;
-}
-
-/*
- * Read from the socket and store the references in the vector
- */
-static int store_refs(transport_git *t)
-{
- gitno_buffer buf;
- int s = t->socket;
- git_vector *refs = &t->refs;
- int error = GIT_SUCCESS;
- char buffer[1024];
- const char *line_end, *ptr;
- git_pkt *pkt;
-
- gitno_buffer_setup(&buf, buffer, sizeof(buffer), s);
-
- while (1) {
- error = gitno_recv(&buf);
- if (error < GIT_SUCCESS)
- return git__rethrow(GIT_EOSERR, "Failed to receive data");
- if (error == GIT_SUCCESS) /* Orderly shutdown, so exit */
- return GIT_SUCCESS;
-
- ptr = buf.data;
- while (1) {
- if (buf.offset == 0)
- break;
- error = git_pkt_parse_line(&pkt, ptr, &line_end, buf.offset);
- /*
- * If the error is GIT_ESHORTBUFFER, it means the buffer
- * isn't long enough to satisfy the request. Break out and
- * wait for more input.
- * On any other error, fail.
- */
- if (error == GIT_ESHORTBUFFER) {
- break;
- }
- if (error < GIT_SUCCESS) {
- return error;
- }
-
- /* Get rid of the part we've used already */
- gitno_consume(&buf, line_end);
-
- error = git_vector_insert(refs, pkt);
- if (error < GIT_SUCCESS)
- return error;
-
- if (pkt->type == GIT_PKT_FLUSH)
- return GIT_SUCCESS;
-
- }
- }
-
- return error;
-}
-
-/*
- * 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)
-{
- transport_git *t = (transport_git *) transport;
- int error = GIT_SUCCESS;
-
- if (direction == GIT_DIR_PUSH)
- return git__throw(GIT_EINVALIDARGS, "Pushing is not supported with the git protocol");
-
- t->parent.direction = direction;
- error = git_vector_init(&t->refs, 16, NULL);
- if (error < GIT_SUCCESS)
- goto cleanup;
-
- /* Connect and ask for the refs */
- error = do_connect(t, transport->url);
- if (error < GIT_SUCCESS)
- return error;
-
- t->parent.connected = 1;
- error = store_refs(t);
-
-cleanup:
- if (error < GIT_SUCCESS) {
- git_vector_free(&t->refs);
- }
-
- return error;
-}
-
-static int git_ls(git_transport *transport, git_headarray *array)
-{
- transport_git *t = (transport_git *) transport;
- git_vector *refs = &t->refs;
- int len = 0;
- unsigned int i;
-
- array->heads = git__calloc(refs->length, sizeof(git_remote_head *));
- if (array->heads == NULL)
- return GIT_ENOMEM;
-
- for (i = 0; i < refs->length; ++i) {
- git_pkt *p = git_vector_get(refs, i);
- if (p->type != GIT_PKT_REF)
- continue;
-
- ++len;
- array->heads[i] = &(((git_pkt_ref *) p)->head);
- }
- array->len = len;
- t->heads = array->heads;
-
- return GIT_SUCCESS;
-}
-
-static int git_close(git_transport *transport)
-{
- transport_git *t = (transport_git*) transport;
- int s = t->socket;
- int error;
-
- /* Can't do anything if there's an error, so don't bother checking */
- git_pkt_send_flush(s);
- error = close(s);
- if (error < 0)
- error = git__throw(GIT_EOSERR, "Failed to close socket");
-
- return error;
-}
-
-static void git_free(git_transport *transport)
-{
- transport_git *t = (transport_git *) transport;
- git_vector *refs = &t->refs;
- unsigned int i;
-
- for (i = 0; i < refs->length; ++i) {
- git_pkt *p = git_vector_get(refs, i);
- git_pkt_free(p);
- }
-
- git_vector_free(refs);
- free(t->heads);
- free(t->parent.url);
- free(t);
-}
-
-int git_transport_git(git_transport **out)
-{
- transport_git *t;
-
- t = git__malloc(sizeof(transport_git));
- if (t == NULL)
- return GIT_ENOMEM;
-
- memset(t, 0x0, sizeof(transport_git));
-
- t->parent.connect = git_connect;
- t->parent.ls = git_ls;
- t->parent.close = git_close;
- t->parent.free = git_free;
-
- *out = (git_transport *) t;
-
- return GIT_SUCCESS;
-}
diff --git a/src/transport_local.c b/src/transport_local.c
deleted file mode 100644
index 350f2ea4a..000000000
--- a/src/transport_local.c
+++ /dev/null
@@ -1,222 +0,0 @@
-#include "common.h"
-#include "git2/types.h"
-#include "git2/transport.h"
-#include "git2/net.h"
-#include "git2/repository.h"
-#include "git2/object.h"
-#include "git2/tag.h"
-#include "refs.h"
-#include "transport.h"
-
-typedef struct {
- git_transport parent;
- git_repository *repo;
- git_vector *refs;
-} transport_local;
-
-static int cmp_refs(const void *a, const void *b)
-{
- const char *stra = (const char *)a;
- const char *strb = (const char *)b;
- return strcmp(stra, strb);
-}
-
-/*
- * 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 GIT_UNUSED(direction))
-{
- git_repository *repo;
- int error;
- transport_local *t = (transport_local *) transport;
- const char *path;
- const char file_prefix[] = "file://";
- GIT_UNUSED_ARG(direction);
-
- /* The repo layer doesn't want the prefix */
- if (!git__prefixcmp(transport->url, file_prefix))
- path = transport->url + STRLEN(file_prefix);
- else
- path = transport->url;
-
- error = git_repository_open(&repo, path);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to open remote");
-
- t->repo = repo;
- t->parent.connected = 1;
-
- return GIT_SUCCESS;
-}
-
-static int add_ref(const char *name, git_repository *repo, git_vector *vec)
-{
- const char peeled[] = "^{}";
- git_remote_head *head;
- git_reference *ref;
- git_object *obj = NULL;
- int error = GIT_SUCCESS, peel_len, ret;
-
- head = git__malloc(sizeof(git_remote_head));
- if (head == NULL)
- return GIT_ENOMEM;
-
- head->name = git__strdup(name);
- if (head->name == NULL) {
- error = GIT_ENOMEM;
- goto out;
- }
-
- error = git_reference_lookup(&ref, repo, name);
- if (error < GIT_SUCCESS)
- goto out;
-
- error = git_reference_resolve(&ref, ref);
- if (error < GIT_SUCCESS)
- goto out;
-
- git_oid_cpy(&head->oid, git_reference_oid(ref));
-
- error = git_vector_insert(vec, head);
- if (error < GIT_SUCCESS)
- goto out;
-
- /* If it's not a tag, we don't need to try to peel it */
- if (git__prefixcmp(name, GIT_REFS_TAGS_DIR))
- goto out;
-
- error = git_object_lookup(&obj, repo, &head->oid, GIT_OBJ_ANY);
- if (error < GIT_SUCCESS) {
- git__rethrow(error, "Failed to lookup object");
- }
-
- /* If it's not an annotated tag, just get out */
- if (git_object_type(obj) != GIT_OBJ_TAG)
- goto out;
-
- /* And if it's a tag, peel it, and add it to the list */
- head = git__malloc(sizeof(git_remote_head));
- peel_len = strlen(name) + STRLEN(peeled);
- head->name = git__malloc(peel_len + 1);
- ret = snprintf(head->name, peel_len + 1, "%s%s", name, peeled);
- if (ret >= peel_len + 1) {
- error = git__throw(GIT_ERROR, "The string is magically to long");
- }
-
- git_oid_cpy(&head->oid, git_tag_target_oid((git_tag *) obj));
-
- error = git_vector_insert(vec, head);
- if (error < GIT_SUCCESS)
- goto out;
-
- out:
- git_object_close(obj);
- if (error < GIT_SUCCESS) {
- free(head->name);
- free(head);
- }
- return error;
-}
-
-static int local_ls(git_transport *transport, git_headarray *array)
-{
- int error;
- unsigned int i;
- git_repository *repo;
- git_vector *vec;
- git_strarray refs;
- transport_local *t = (transport_local *) transport;
-
- assert(transport && transport->connected);
-
- repo = t->repo;
-
- error = git_reference_listall(&refs, repo, GIT_REF_LISTALL);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to list remote heads");
-
- vec = git__malloc(sizeof(git_vector));
- if (vec == NULL) {
- error = GIT_ENOMEM;
- goto out;
- }
-
- error = git_vector_init(vec, refs.count, NULL);
- if (error < GIT_SUCCESS)
- return error;
-
- /* Sort the references first */
- git__tsort((void **)refs.strings, refs.count, &cmp_refs);
-
- /* Add HEAD */
- error = add_ref(GIT_HEAD_FILE, repo, vec);
- if (error < GIT_SUCCESS)
- goto out;
-
- for (i = 0; i < refs.count; ++i) {
- error = add_ref(refs.strings[i], repo, vec);
- if (error < GIT_SUCCESS)
- goto out;
- }
-
- array->len = vec->length;
- array->heads = (git_remote_head **)vec->contents;
-
- t->refs = vec;
-
- out:
-
- git_strarray_free(&refs);
-
- return error;
-}
-
-static int local_close(git_transport *GIT_UNUSED(transport))
-{
- /* Nothing to do */
- GIT_UNUSED_ARG(transport);
- return GIT_SUCCESS;
-}
-
-static void local_free(git_transport *transport)
-{
- unsigned int i;
- transport_local *t = (transport_local *) transport;
- git_vector *vec = t->refs;
-
- assert(transport);
-
- for (i = 0; i < vec->length; ++i) {
- git_remote_head *h = git_vector_get(vec, i);
- free(h->name);
- free(h);
- }
- git_vector_free(vec);
- free(vec);
- git_repository_free(t->repo);
- free(t->parent.url);
- free(t);
-}
-
-/**************
- * Public API *
- **************/
-
-int git_transport_local(git_transport **out)
-{
- transport_local *t;
-
- t = git__malloc(sizeof(transport_local));
- if (t == NULL)
- return GIT_ENOMEM;
-
- t->parent.connect = local_connect;
- t->parent.ls = local_ls;
- t->parent.close = local_close;
- t->parent.free = local_free;
-
- *out = (git_transport *) t;
-
- return GIT_SUCCESS;
-}
diff --git a/src/transports/cred.c b/src/transports/cred.c
new file mode 100644
index 000000000..913ec36cc
--- /dev/null
+++ b/src/transports/cred.c
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2.h"
+#include "smart.h"
+#include "git2/cred_helpers.h"
+
+int git_cred_has_username(git_cred *cred)
+{
+ if (cred->credtype == GIT_CREDTYPE_DEFAULT)
+ return 0;
+
+ return 1;
+}
+
+static void plaintext_free(struct git_cred *cred)
+{
+ git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
+
+ git__free(c->username);
+
+ /* Zero the memory which previously held the password */
+ if (c->password) {
+ size_t pass_len = strlen(c->password);
+ git__memzero(c->password, 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;
+
+ assert(cred && username && password);
+
+ c = git__malloc(sizeof(git_cred_userpass_plaintext));
+ GITERR_CHECK_ALLOC(c);
+
+ c->parent.credtype = GIT_CREDTYPE_USERPASS_PLAINTEXT;
+ c->parent.free = plaintext_free;
+ c->username = git__strdup(username);
+
+ if (!c->username) {
+ git__free(c);
+ return -1;
+ }
+
+ c->password = git__strdup(password);
+
+ if (!c->password) {
+ git__free(c->username);
+ git__free(c);
+ return -1;
+ }
+
+ *cred = &c->parent;
+ return 0;
+}
+
+static void ssh_key_free(struct git_cred *cred)
+{
+ git_cred_ssh_key *c =
+ (git_cred_ssh_key *)cred;
+
+ git__free(c->username);
+
+ if (c->privatekey) {
+ /* Zero the memory which previously held the private key */
+ size_t key_len = strlen(c->privatekey);
+ git__memzero(c->privatekey, key_len);
+ git__free(c->privatekey);
+ }
+
+ if (c->passphrase) {
+ /* Zero the memory which previously held the passphrase */
+ size_t pass_len = strlen(c->passphrase);
+ git__memzero(c->passphrase, pass_len);
+ git__free(c->passphrase);
+ }
+
+ if (c->publickey) {
+ /* Zero the memory which previously held the public key */
+ size_t key_len = strlen(c->publickey);
+ git__memzero(c->publickey, key_len);
+ git__free(c->publickey);
+ }
+
+ git__free(c);
+}
+
+static void ssh_interactive_free(struct git_cred *cred)
+{
+ git_cred_ssh_interactive *c = (git_cred_ssh_interactive *)cred;
+
+ git__free(c->username);
+
+ git__free(c);
+}
+
+static void ssh_custom_free(struct git_cred *cred)
+{
+ git_cred_ssh_custom *c = (git_cred_ssh_custom *)cred;
+
+ git__free(c->username);
+
+ if (c->publickey) {
+ /* Zero the memory which previously held the publickey */
+ size_t key_len = strlen(c->publickey);
+ git__memzero(c->publickey, key_len);
+ git__free(c->publickey);
+ }
+
+ git__free(c);
+}
+
+static void default_free(struct git_cred *cred)
+{
+ git_cred_default *c = (git_cred_default *)cred;
+
+ git__free(c);
+}
+
+int git_cred_ssh_key_new(
+ git_cred **cred,
+ const char *username,
+ const char *publickey,
+ const char *privatekey,
+ const char *passphrase)
+{
+ git_cred_ssh_key *c;
+
+ assert(username && cred && privatekey);
+
+ c = git__calloc(1, sizeof(git_cred_ssh_key));
+ GITERR_CHECK_ALLOC(c);
+
+ c->parent.credtype = GIT_CREDTYPE_SSH_KEY;
+ c->parent.free = ssh_key_free;
+
+ c->username = git__strdup(username);
+ GITERR_CHECK_ALLOC(c->username);
+
+ c->privatekey = git__strdup(privatekey);
+ GITERR_CHECK_ALLOC(c->privatekey);
+
+ if (publickey) {
+ c->publickey = git__strdup(publickey);
+ GITERR_CHECK_ALLOC(c->publickey);
+ }
+
+ if (passphrase) {
+ c->passphrase = git__strdup(passphrase);
+ GITERR_CHECK_ALLOC(c->passphrase);
+ }
+
+ *cred = &c->parent;
+ return 0;
+}
+
+int git_cred_ssh_interactive_new(
+ git_cred **out,
+ const char *username,
+ git_cred_ssh_interactive_callback prompt_callback,
+ void *payload)
+{
+ git_cred_ssh_interactive *c;
+
+ assert(out && username && prompt_callback);
+
+ c = git__calloc(1, sizeof(git_cred_ssh_interactive));
+ GITERR_CHECK_ALLOC(c);
+
+ c->parent.credtype = GIT_CREDTYPE_SSH_INTERACTIVE;
+ c->parent.free = ssh_interactive_free;
+
+ c->username = git__strdup(username);
+ GITERR_CHECK_ALLOC(c->username);
+
+ c->prompt_callback = prompt_callback;
+ c->payload = payload;
+
+ *out = &c->parent;
+ return 0;
+}
+
+int git_cred_ssh_key_from_agent(git_cred **cred, const char *username) {
+ git_cred_ssh_key *c;
+
+ assert(username && cred);
+
+ c = git__calloc(1, sizeof(git_cred_ssh_key));
+ GITERR_CHECK_ALLOC(c);
+
+ c->parent.credtype = GIT_CREDTYPE_SSH_KEY;
+ c->parent.free = ssh_key_free;
+
+ c->username = git__strdup(username);
+ GITERR_CHECK_ALLOC(c->username);
+
+ c->privatekey = NULL;
+
+ *cred = &c->parent;
+ return 0;
+}
+
+int git_cred_ssh_custom_new(
+ git_cred **cred,
+ const char *username,
+ const char *publickey,
+ size_t publickey_len,
+ git_cred_sign_callback sign_callback,
+ void *payload)
+{
+ git_cred_ssh_custom *c;
+
+ assert(username && cred);
+
+ c = git__calloc(1, sizeof(git_cred_ssh_custom));
+ GITERR_CHECK_ALLOC(c);
+
+ c->parent.credtype = GIT_CREDTYPE_SSH_CUSTOM;
+ c->parent.free = ssh_custom_free;
+
+ c->username = git__strdup(username);
+ GITERR_CHECK_ALLOC(c->username);
+
+ if (publickey_len > 0) {
+ c->publickey = git__malloc(publickey_len);
+ GITERR_CHECK_ALLOC(c->publickey);
+
+ memcpy(c->publickey, publickey, publickey_len);
+ }
+
+ c->publickey_len = publickey_len;
+ c->sign_callback = sign_callback;
+ c->payload = payload;
+
+ *cred = &c->parent;
+ return 0;
+}
+
+int git_cred_default_new(git_cred **cred)
+{
+ git_cred_default *c;
+
+ assert(cred);
+
+ c = git__calloc(1, sizeof(git_cred_default));
+ GITERR_CHECK_ALLOC(c);
+
+ c->credtype = GIT_CREDTYPE_DEFAULT;
+ c->free = default_free;
+
+ *cred = c;
+ return 0;
+}
diff --git a/src/transports/cred_helpers.c b/src/transports/cred_helpers.c
new file mode 100644
index 000000000..d420e3e3c
--- /dev/null
+++ b/src/transports/cred_helpers.c
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "git2/cred_helpers.h"
+
+int git_cred_userpass(
+ git_cred **cred,
+ const char *url,
+ const char *user_from_url,
+ unsigned int allowed_types,
+ void *payload)
+{
+ git_cred_userpass_payload *userpass = (git_cred_userpass_payload*)payload;
+ const char *effective_username = NULL;
+
+ GIT_UNUSED(url);
+
+ if (!userpass || !userpass->password) return -1;
+
+ /* Username resolution: a username can be passed with the URL, the
+ * credentials payload, or both. Here's what we do. Note that if we get
+ * this far, we know that any password the url may contain has already
+ * failed at least once, so we ignore it.
+ *
+ * | Payload | URL | Used |
+ * +-------------+----------+-----------+
+ * | yes | no | payload |
+ * | yes | yes | payload |
+ * | no | yes | url |
+ * | no | no | FAIL |
+ */
+ if (userpass->username)
+ effective_username = userpass->username;
+ else if (user_from_url)
+ effective_username = user_from_url;
+ else
+ return -1;
+
+ if ((GIT_CREDTYPE_USERPASS_PLAINTEXT & allowed_types) == 0 ||
+ git_cred_userpass_plaintext_new(cred, effective_username, userpass->password) < 0)
+ return -1;
+
+ return 0;
+}
diff --git a/src/transports/git.c b/src/transports/git.c
new file mode 100644
index 000000000..21507c1c7
--- /dev/null
+++ b/src/transports/git.c
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2.h"
+#include "buffer.h"
+#include "netops.h"
+
+#define OWNING_SUBTRANSPORT(s) ((git_subtransport *)(s)->parent.subtransport)
+
+static const char prefix_git[] = "git://";
+static const char cmd_uploadpack[] = "git-upload-pack";
+static const char cmd_receivepack[] = "git-receive-pack";
+
+typedef struct {
+ git_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 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 host[] = "host=";
+ size_t len;
+
+ delim = strchr(url, '/');
+ if (delim == NULL) {
+ giterr_set(GITERR_NET, "Malformed URL");
+ return -1;
+ }
+
+ repo = delim;
+
+ delim = strchr(url, ':');
+ if (delim == NULL)
+ delim = strchr(url, '/');
+
+ len = 4 + strlen(cmd) + 1 + strlen(repo) + 1 + strlen(host) + (delim - url) + 1;
+
+ git_buf_grow(request, len);
+ git_buf_printf(request, "%04x%s %s%c%s",
+ (unsigned int)(len & 0x0FFFF), cmd, repo, 0, host);
+ git_buf_put(request, url, delim - url);
+ git_buf_putc(request, '\0');
+
+ if (git_buf_oom(request))
+ return -1;
+
+ return 0;
+}
+
+static int send_command(git_stream *s)
+{
+ int error;
+ git_buf request = GIT_BUF_INIT;
+
+ error = gen_proto(&request, s->cmd, s->url);
+ if (error < 0)
+ goto cleanup;
+
+ /* 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;
+}
+
+static int git_stream_read(
+ git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read)
+{
+ int error;
+ git_stream *s = (git_stream *)stream;
+ gitno_buffer buf;
+
+ *bytes_read = 0;
+
+ if (!s->sent_command && (error = send_command(s)) < 0)
+ return error;
+
+ gitno_buffer_setup(&s->socket, &buf, buffer, buf_size);
+
+ if ((error = gitno_recv(&buf)) < 0)
+ return error;
+
+ *bytes_read = buf.offset;
+
+ return 0;
+}
+
+static int git_stream_write(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ int error;
+ git_stream *s = (git_stream *)stream;
+
+ if (!s->sent_command && (error = send_command(s)) < 0)
+ return error;
+
+ 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);
+ }
+
+ git__free(s->url);
+ git__free(s);
+}
+
+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;
+
+ s = git__calloc(sizeof(git_stream), 1);
+ GITERR_CHECK_ALLOC(s);
+
+ s->parent.subtransport = &t->parent;
+ s->parent.read = git_stream_read;
+ s->parent.write = git_stream_write;
+ s->parent.free = git_stream_free;
+
+ s->cmd = cmd;
+ s->url = git__strdup(url);
+
+ if (!s->url) {
+ git__free(s);
+ return -1;
+ }
+
+ *stream = &s->parent;
+ return 0;
+}
+
+static int _git_uploadpack_ls(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL;
+ const char *stream_url = url;
+ git_stream *s;
+ int error;
+
+ *stream = NULL;
+
+ if (!git__prefixcmp(url, prefix_git))
+ stream_url += strlen(prefix_git);
+
+ if ((error = git_stream_alloc(t, stream_url, cmd_uploadpack, stream)) < 0)
+ return error;
+
+ s = (git_stream *)*stream;
+
+ if (!(error = gitno_extract_url_parts(
+ &host, &port, &path, &user, &pass, url, GIT_DEFAULT_PORT))) {
+
+ if (!(error = gitno_connect(&s->socket, host, port, 0)))
+ t->current_stream = s;
+
+ git__free(host);
+ git__free(port);
+ git__free(path);
+ git__free(user);
+ git__free(pass);
+ } else if (*stream)
+ git_stream_free(*stream);
+
+ return error;
+}
+
+static int _git_uploadpack(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ GIT_UNUSED(url);
+
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
+ }
+
+ giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK");
+ return -1;
+}
+
+static int _git_receivepack_ls(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL;
+ const char *stream_url = url;
+ git_stream *s;
+ int error;
+
+ *stream = NULL;
+ if (!git__prefixcmp(url, prefix_git))
+ stream_url += strlen(prefix_git);
+
+ if (git_stream_alloc(t, stream_url, cmd_receivepack, stream) < 0)
+ return -1;
+
+ s = (git_stream *)*stream;
+
+ if (!(error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, GIT_DEFAULT_PORT))) {
+ if (!(error = gitno_connect(&s->socket, host, port, 0)))
+ t->current_stream = s;
+
+ git__free(host);
+ git__free(port);
+ git__free(path);
+ git__free(user);
+ git__free(pass);
+ } else if (*stream)
+ git_stream_free(*stream);
+
+ return error;
+}
+
+static int _git_receivepack(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ GIT_UNUSED(url);
+
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
+ }
+
+ giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK");
+ return -1;
+}
+
+static int _git_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *subtransport,
+ const char *url,
+ git_smart_service_t action)
+{
+ git_subtransport *t = (git_subtransport *) subtransport;
+
+ switch (action) {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ return _git_uploadpack_ls(t, url, stream);
+
+ case GIT_SERVICE_UPLOADPACK:
+ return _git_uploadpack(t, url, stream);
+
+ case GIT_SERVICE_RECEIVEPACK_LS:
+ return _git_receivepack_ls(t, url, stream);
+
+ case GIT_SERVICE_RECEIVEPACK:
+ return _git_receivepack(t, url, stream);
+ }
+
+ *stream = NULL;
+ return -1;
+}
+
+static int _git_close(git_smart_subtransport *subtransport)
+{
+ git_subtransport *t = (git_subtransport *) subtransport;
+
+ assert(!t->current_stream);
+
+ GIT_UNUSED(t);
+
+ return 0;
+}
+
+static void _git_free(git_smart_subtransport *subtransport)
+{
+ git_subtransport *t = (git_subtransport *) subtransport;
+
+ assert(!t->current_stream);
+
+ git__free(t);
+}
+
+int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owner)
+{
+ git_subtransport *t;
+
+ if (!out)
+ return -1;
+
+ t = git__calloc(sizeof(git_subtransport), 1);
+ GITERR_CHECK_ALLOC(t);
+
+ t->owner = owner;
+ t->parent.action = _git_action;
+ t->parent.close = _git_close;
+ t->parent.free = _git_free;
+
+ *out = (git_smart_subtransport *) t;
+ return 0;
+}
diff --git a/src/transports/http.c b/src/transports/http.c
new file mode 100644
index 000000000..a7eff7365
--- /dev/null
+++ b/src/transports/http.c
@@ -0,0 +1,909 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef GIT_WINHTTP
+
+#include "git2.h"
+#include "http_parser.h"
+#include "buffer.h"
+#include "netops.h"
+#include "smart.h"
+
+static const char *upload_pack_service = "upload-pack";
+static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack";
+static const char *upload_pack_service_url = "/git-upload-pack";
+static const char *receive_pack_service = "receive-pack";
+static const char *receive_pack_ls_service_url = "/info/refs?service=git-receive-pack";
+static const char *receive_pack_service_url = "/git-receive-pack";
+static const char *get_verb = "GET";
+static const char *post_verb = "POST";
+static const char *basic_authtype = "Basic";
+
+#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport)
+
+#define PARSE_ERROR_GENERIC -1
+#define PARSE_ERROR_REPLAY -2
+
+#define CHUNK_SIZE 4096
+
+enum last_cb {
+ NONE,
+ FIELD,
+ VALUE
+};
+
+typedef enum {
+ GIT_HTTP_AUTH_BASIC = 1,
+} http_authmechanism_t;
+
+typedef struct {
+ git_smart_subtransport_stream parent;
+ const char *service;
+ const char *service_url;
+ char *redirect_url;
+ const char *verb;
+ char *chunk_buffer;
+ unsigned chunk_buffer_len;
+ unsigned sent_request : 1,
+ received_response : 1,
+ chunked : 1,
+ redirect_count : 3;
+} http_stream;
+
+typedef struct {
+ git_smart_subtransport parent;
+ transport_smart *owner;
+ gitno_socket socket;
+ gitno_connection_data connection_data;
+ git_cred *cred;
+ git_cred *url_cred;
+ http_authmechanism_t auth_mechanism;
+ bool connected;
+
+ /* Parser structures */
+ http_parser parser;
+ http_parser_settings settings;
+ gitno_buffer parse_buffer;
+ git_buf parse_header_name;
+ git_buf parse_header_value;
+ char parse_buffer_data[2048];
+ char *content_type;
+ char *location;
+ git_vector www_authenticate;
+ enum last_cb last_cb;
+ int parse_error;
+ unsigned parse_finished : 1;
+} http_subtransport;
+
+typedef struct {
+ http_stream *s;
+ http_subtransport *t;
+
+ /* Target buffer details from read() */
+ char *buffer;
+ size_t buf_size;
+ size_t *bytes_read;
+} parser_context;
+
+static int apply_basic_credential(git_buf *buf, git_cred *cred)
+{
+ git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
+ git_buf raw = GIT_BUF_INIT;
+ int error = -1;
+
+ git_buf_printf(&raw, "%s:%s", c->username, c->password);
+
+ if (git_buf_oom(&raw) ||
+ git_buf_puts(buf, "Authorization: Basic ") < 0 ||
+ git_buf_put_base64(buf, git_buf_cstr(&raw), raw.size) < 0 ||
+ git_buf_puts(buf, "\r\n") < 0)
+ goto on_error;
+
+ error = 0;
+
+on_error:
+ if (raw.size)
+ memset(raw.ptr, 0x0, raw.size);
+
+ git_buf_free(&raw);
+ return error;
+}
+
+static int gen_request(
+ git_buf *buf,
+ http_stream *s,
+ size_t content_length)
+{
+ http_subtransport *t = OWNING_SUBTRANSPORT(s);
+ const char *path = t->connection_data.path ? t->connection_data.path : "/";
+
+ git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, path, s->service_url);
+
+ git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n");
+ git_buf_printf(buf, "Host: %s\r\n", t->connection_data.host);
+
+ if (s->chunked || content_length > 0) {
+ git_buf_printf(buf, "Accept: application/x-git-%s-result\r\n", s->service);
+ git_buf_printf(buf, "Content-Type: application/x-git-%s-request\r\n", s->service);
+
+ if (s->chunked)
+ git_buf_puts(buf, "Transfer-Encoding: chunked\r\n");
+ else
+ git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n", content_length);
+ } else
+ git_buf_puts(buf, "Accept: */*\r\n");
+
+ /* Apply credentials to the request */
+ if (t->cred && t->cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT &&
+ t->auth_mechanism == GIT_HTTP_AUTH_BASIC &&
+ apply_basic_credential(buf, t->cred) < 0)
+ return -1;
+
+ /* Use url-parsed basic auth if username and password are both provided */
+ if (!t->cred && t->connection_data.user && t->connection_data.pass) {
+ if (!t->url_cred && git_cred_userpass_plaintext_new(&t->url_cred,
+ t->connection_data.user, t->connection_data.pass) < 0)
+ return -1;
+ if (apply_basic_credential(buf, t->url_cred) < 0) return -1;
+ }
+
+ git_buf_puts(buf, "\r\n");
+
+ if (git_buf_oom(buf))
+ return -1;
+
+ return 0;
+}
+
+static int parse_unauthorized_response(
+ git_vector *www_authenticate,
+ int *allowed_types,
+ http_authmechanism_t *auth_mechanism)
+{
+ unsigned i;
+ char *entry;
+
+ git_vector_foreach(www_authenticate, i, entry) {
+ if (!strncmp(entry, basic_authtype, 5) &&
+ (entry[5] == '\0' || entry[5] == ' ')) {
+ *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
+ *auth_mechanism = GIT_HTTP_AUTH_BASIC;
+ }
+ }
+
+ return 0;
+}
+
+static int on_header_ready(http_subtransport *t)
+{
+ git_buf *name = &t->parse_header_name;
+ git_buf *value = &t->parse_header_value;
+
+ if (!strcasecmp("Content-Type", git_buf_cstr(name))) {
+ if (!t->content_type) {
+ t->content_type = git__strdup(git_buf_cstr(value));
+ GITERR_CHECK_ALLOC(t->content_type);
+ }
+ }
+ else if (!strcmp("WWW-Authenticate", git_buf_cstr(name))) {
+ char *dup = git__strdup(git_buf_cstr(value));
+ GITERR_CHECK_ALLOC(dup);
+
+ git_vector_insert(&t->www_authenticate, dup);
+ }
+ else if (!strcasecmp("Location", git_buf_cstr(name))) {
+ if (!t->location) {
+ t->location = git__strdup(git_buf_cstr(value));
+ GITERR_CHECK_ALLOC(t->location);
+ }
+ }
+
+ return 0;
+}
+
+static int on_header_field(http_parser *parser, const char *str, size_t len)
+{
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
+
+ /* Both parse_header_name and parse_header_value are populated
+ * and ready for consumption */
+ if (VALUE == t->last_cb)
+ if (on_header_ready(t) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ if (NONE == t->last_cb || VALUE == t->last_cb)
+ git_buf_clear(&t->parse_header_name);
+
+ if (git_buf_put(&t->parse_header_name, str, len) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ t->last_cb = FIELD;
+ return 0;
+}
+
+static int on_header_value(http_parser *parser, const char *str, size_t len)
+{
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
+
+ assert(NONE != t->last_cb);
+
+ if (FIELD == t->last_cb)
+ git_buf_clear(&t->parse_header_value);
+
+ if (git_buf_put(&t->parse_header_value, str, len) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ t->last_cb = VALUE;
+ return 0;
+}
+
+static int on_headers_complete(http_parser *parser)
+{
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
+ http_stream *s = ctx->s;
+ git_buf buf = GIT_BUF_INIT;
+ int error = 0, no_callback = 0;
+
+ /* Both parse_header_name and parse_header_value are populated
+ * and ready for consumption. */
+ if (VALUE == t->last_cb)
+ if (on_header_ready(t) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ /* Check for an authentication failure. */
+
+ if (parser->status_code == 401 &&
+ get_verb == s->verb) {
+ if (!t->owner->cred_acquire_payload) {
+ no_callback = 1;
+ } else {
+ int allowed_types = 0;
+
+ if (parse_unauthorized_response(&t->www_authenticate,
+ &allowed_types, &t->auth_mechanism) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ if (allowed_types &&
+ (!t->cred || 0 == (t->cred->credtype & allowed_types))) {
+
+ error = t->owner->cred_acquire_cb(&t->cred,
+ t->owner->url,
+ t->connection_data.user,
+ allowed_types,
+ t->owner->cred_acquire_payload);
+
+ if (error == GIT_PASSTHROUGH) {
+ no_callback = 1;
+ } else if (error < 0) {
+ return PARSE_ERROR_GENERIC;
+ } else {
+ assert(t->cred);
+
+ /* Successfully acquired a credential. */
+ return t->parse_error = PARSE_ERROR_REPLAY;
+ }
+ }
+ }
+
+ if (no_callback) {
+ giterr_set(GITERR_NET, "authentication required but no callback set");
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+ }
+
+ /* Check for a redirect.
+ * Right now we only permit a redirect to the same hostname. */
+ if ((parser->status_code == 301 ||
+ parser->status_code == 302 ||
+ (parser->status_code == 303 && get_verb == s->verb) ||
+ parser->status_code == 307) &&
+ t->location) {
+
+ if (s->redirect_count >= 7) {
+ giterr_set(GITERR_NET, "Too many redirects");
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+
+ if (gitno_connection_data_from_url(&t->connection_data, t->location, s->service_url) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ /* Set the redirect URL on the stream. This is a transfer of
+ * ownership of the memory. */
+ if (s->redirect_url)
+ git__free(s->redirect_url);
+
+ s->redirect_url = t->location;
+ t->location = NULL;
+
+ t->connected = 0;
+ s->redirect_count++;
+
+ return t->parse_error = PARSE_ERROR_REPLAY;
+ }
+
+ /* Check for a 200 HTTP status code. */
+ if (parser->status_code != 200) {
+ giterr_set(GITERR_NET,
+ "Unexpected HTTP status code: %d",
+ parser->status_code);
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+
+ /* The response must contain a Content-Type header. */
+ if (!t->content_type) {
+ giterr_set(GITERR_NET, "No Content-Type header in response");
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+
+ /* The Content-Type header must match our expectation. */
+ if (get_verb == s->verb)
+ git_buf_printf(&buf,
+ "application/x-git-%s-advertisement",
+ ctx->s->service);
+ else
+ git_buf_printf(&buf,
+ "application/x-git-%s-result",
+ ctx->s->service);
+
+ if (git_buf_oom(&buf))
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ if (strcmp(t->content_type, git_buf_cstr(&buf))) {
+ git_buf_free(&buf);
+ giterr_set(GITERR_NET,
+ "Invalid Content-Type: %s",
+ t->content_type);
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+
+ git_buf_free(&buf);
+
+ return 0;
+}
+
+static int on_message_complete(http_parser *parser)
+{
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
+
+ t->parse_finished = 1;
+
+ return 0;
+}
+
+static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
+{
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
+
+ if (ctx->buf_size < len) {
+ giterr_set(GITERR_NET, "Can't fit data in the buffer");
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
+
+ memcpy(ctx->buffer, str, len);
+ *(ctx->bytes_read) += len;
+ ctx->buffer += len;
+ ctx->buf_size -= len;
+
+ return 0;
+}
+
+static void clear_parser_state(http_subtransport *t)
+{
+ http_parser_init(&t->parser, HTTP_RESPONSE);
+ gitno_buffer_setup(&t->socket,
+ &t->parse_buffer,
+ t->parse_buffer_data,
+ sizeof(t->parse_buffer_data));
+
+ t->last_cb = NONE;
+ t->parse_error = 0;
+ t->parse_finished = 0;
+
+ git_buf_free(&t->parse_header_name);
+ git_buf_init(&t->parse_header_name, 0);
+
+ git_buf_free(&t->parse_header_value);
+ git_buf_init(&t->parse_header_value, 0);
+
+ git__free(t->content_type);
+ t->content_type = NULL;
+
+ git__free(t->location);
+ t->location = NULL;
+
+ git_vector_free_deep(&t->www_authenticate);
+}
+
+static int write_chunk(gitno_socket *socket, const char *buffer, size_t len)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ /* Chunk header */
+ git_buf_printf(&buf, "%X\r\n", (unsigned)len);
+
+ if (git_buf_oom(&buf))
+ return -1;
+
+ if (gitno_send(socket, buf.ptr, buf.size, 0) < 0) {
+ git_buf_free(&buf);
+ return -1;
+ }
+
+ git_buf_free(&buf);
+
+ /* Chunk body */
+ if (len > 0 && gitno_send(socket, buffer, len, 0) < 0)
+ return -1;
+
+ /* Chunk footer */
+ if (gitno_send(socket, "\r\n", 2, 0) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int http_connect(http_subtransport *t)
+{
+ int flags = 0;
+
+ if (t->connected &&
+ http_should_keep_alive(&t->parser) &&
+ http_body_is_final(&t->parser))
+ return 0;
+
+ if (t->socket.socket)
+ gitno_close(&t->socket);
+
+ if (t->connection_data.use_ssl) {
+ int tflags;
+
+ if (t->owner->parent.read_flags(&t->owner->parent, &tflags) < 0)
+ return -1;
+
+ flags |= GITNO_CONNECT_SSL;
+
+ if (GIT_TRANSPORTFLAGS_NO_CHECK_CERT & tflags)
+ flags |= GITNO_CONNECT_SSL_NO_CHECK_CERT;
+ }
+
+ if (gitno_connect(&t->socket, t->connection_data.host, t->connection_data.port, flags) < 0)
+ return -1;
+
+ t->connected = 1;
+ return 0;
+}
+
+static int http_stream_read(
+ git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read)
+{
+ http_stream *s = (http_stream *)stream;
+ http_subtransport *t = OWNING_SUBTRANSPORT(s);
+ parser_context ctx;
+ size_t bytes_parsed;
+
+replay:
+ *bytes_read = 0;
+
+ assert(t->connected);
+
+ if (!s->sent_request) {
+ git_buf request = GIT_BUF_INIT;
+
+ clear_parser_state(t);
+
+ if (gen_request(&request, s, 0) < 0) {
+ giterr_set(GITERR_NET, "Failed to generate request");
+ return -1;
+ }
+
+ if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) {
+ git_buf_free(&request);
+ return -1;
+ }
+
+ git_buf_free(&request);
+
+ s->sent_request = 1;
+ }
+
+ if (!s->received_response) {
+ if (s->chunked) {
+ assert(s->verb == post_verb);
+
+ /* Flush, if necessary */
+ if (s->chunk_buffer_len > 0 &&
+ write_chunk(&t->socket, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+
+ /* Write the final chunk. */
+ if (gitno_send(&t->socket, "0\r\n\r\n", 5, 0) < 0)
+ return -1;
+ }
+
+ s->received_response = 1;
+ }
+
+ while (!*bytes_read && !t->parse_finished) {
+ t->parse_buffer.offset = 0;
+
+ if (gitno_recv(&t->parse_buffer) < 0)
+ return -1;
+
+ /* This call to http_parser_execute will result in invocations of the
+ * on_* family of callbacks. The most interesting of these is
+ * on_body_fill_buffer, which is called when data is ready to be copied
+ * into the target buffer. We need to marshal the buffer, buf_size, and
+ * bytes_read parameters to this callback. */
+ ctx.t = t;
+ ctx.s = s;
+ ctx.buffer = buffer;
+ ctx.buf_size = buf_size;
+ ctx.bytes_read = bytes_read;
+
+ /* Set the context, call the parser, then unset the context. */
+ t->parser.data = &ctx;
+
+ bytes_parsed = http_parser_execute(&t->parser,
+ &t->settings,
+ t->parse_buffer.data,
+ t->parse_buffer.offset);
+
+ t->parser.data = NULL;
+
+ /* If there was a handled authentication failure, then parse_error
+ * will have signaled us that we should replay the request. */
+ if (PARSE_ERROR_REPLAY == t->parse_error) {
+ s->sent_request = 0;
+
+ if (http_connect(t) < 0)
+ return -1;
+
+ goto replay;
+ }
+
+ if (t->parse_error < 0)
+ return -1;
+
+ if (bytes_parsed != t->parse_buffer.offset) {
+ giterr_set(GITERR_NET,
+ "HTTP parser error: %s",
+ http_errno_description((enum http_errno)t->parser.http_errno));
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int http_stream_write_chunked(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ http_stream *s = (http_stream *)stream;
+ http_subtransport *t = OWNING_SUBTRANSPORT(s);
+
+ assert(t->connected);
+
+ /* Send the request, if necessary */
+ if (!s->sent_request) {
+ git_buf request = GIT_BUF_INIT;
+
+ clear_parser_state(t);
+
+ if (gen_request(&request, s, 0) < 0) {
+ giterr_set(GITERR_NET, "Failed to generate request");
+ return -1;
+ }
+
+ if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) {
+ git_buf_free(&request);
+ return -1;
+ }
+
+ git_buf_free(&request);
+
+ s->sent_request = 1;
+ }
+
+ if (len > CHUNK_SIZE) {
+ /* Flush, if necessary */
+ if (s->chunk_buffer_len > 0) {
+ if (write_chunk(&t->socket, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+ }
+
+ /* Write chunk directly */
+ if (write_chunk(&t->socket, buffer, len) < 0)
+ return -1;
+ }
+ else {
+ /* Append as much to the buffer as we can */
+ int count = min(CHUNK_SIZE - s->chunk_buffer_len, len);
+
+ if (!s->chunk_buffer)
+ s->chunk_buffer = git__malloc(CHUNK_SIZE);
+
+ memcpy(s->chunk_buffer + s->chunk_buffer_len, buffer, count);
+ s->chunk_buffer_len += count;
+ buffer += count;
+ len -= count;
+
+ /* Is the buffer full? If so, then flush */
+ if (CHUNK_SIZE == s->chunk_buffer_len) {
+ if (write_chunk(&t->socket, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+
+ if (len > 0) {
+ memcpy(s->chunk_buffer, buffer, len);
+ s->chunk_buffer_len = len;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int http_stream_write_single(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ http_stream *s = (http_stream *)stream;
+ http_subtransport *t = OWNING_SUBTRANSPORT(s);
+ git_buf request = GIT_BUF_INIT;
+
+ assert(t->connected);
+
+ if (s->sent_request) {
+ giterr_set(GITERR_NET, "Subtransport configured for only one write");
+ return -1;
+ }
+
+ clear_parser_state(t);
+
+ if (gen_request(&request, s, len) < 0) {
+ giterr_set(GITERR_NET, "Failed to generate request");
+ return -1;
+ }
+
+ if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0)
+ goto on_error;
+
+ if (len && gitno_send(&t->socket, buffer, len, 0) < 0)
+ goto on_error;
+
+ git_buf_free(&request);
+ s->sent_request = 1;
+
+ return 0;
+
+on_error:
+ git_buf_free(&request);
+ return -1;
+}
+
+static void http_stream_free(git_smart_subtransport_stream *stream)
+{
+ http_stream *s = (http_stream *)stream;
+
+ if (s->chunk_buffer)
+ git__free(s->chunk_buffer);
+
+ if (s->redirect_url)
+ git__free(s->redirect_url);
+
+ git__free(s);
+}
+
+static int http_stream_alloc(http_subtransport *t,
+ git_smart_subtransport_stream **stream)
+{
+ http_stream *s;
+
+ if (!stream)
+ return -1;
+
+ s = git__calloc(sizeof(http_stream), 1);
+ GITERR_CHECK_ALLOC(s);
+
+ s->parent.subtransport = &t->parent;
+ s->parent.read = http_stream_read;
+ s->parent.write = http_stream_write_single;
+ s->parent.free = http_stream_free;
+
+ *stream = (git_smart_subtransport_stream *)s;
+ return 0;
+}
+
+static int http_uploadpack_ls(
+ http_subtransport *t,
+ git_smart_subtransport_stream **stream)
+{
+ http_stream *s;
+
+ if (http_stream_alloc(t, stream) < 0)
+ return -1;
+
+ s = (http_stream *)*stream;
+
+ s->service = upload_pack_service;
+ s->service_url = upload_pack_ls_service_url;
+ s->verb = get_verb;
+
+ return 0;
+}
+
+static int http_uploadpack(
+ http_subtransport *t,
+ git_smart_subtransport_stream **stream)
+{
+ http_stream *s;
+
+ if (http_stream_alloc(t, stream) < 0)
+ return -1;
+
+ s = (http_stream *)*stream;
+
+ s->service = upload_pack_service;
+ s->service_url = upload_pack_service_url;
+ s->verb = post_verb;
+
+ return 0;
+}
+
+static int http_receivepack_ls(
+ http_subtransport *t,
+ git_smart_subtransport_stream **stream)
+{
+ http_stream *s;
+
+ if (http_stream_alloc(t, stream) < 0)
+ return -1;
+
+ s = (http_stream *)*stream;
+
+ s->service = receive_pack_service;
+ s->service_url = receive_pack_ls_service_url;
+ s->verb = get_verb;
+
+ return 0;
+}
+
+static int http_receivepack(
+ http_subtransport *t,
+ git_smart_subtransport_stream **stream)
+{
+ http_stream *s;
+
+ if (http_stream_alloc(t, stream) < 0)
+ return -1;
+
+ s = (http_stream *)*stream;
+
+ /* Use Transfer-Encoding: chunked for this request */
+ s->chunked = 1;
+ s->parent.write = http_stream_write_chunked;
+
+ s->service = receive_pack_service;
+ s->service_url = receive_pack_service_url;
+ s->verb = post_verb;
+
+ return 0;
+}
+
+static int http_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *subtransport,
+ const char *url,
+ git_smart_service_t action)
+{
+ http_subtransport *t = (http_subtransport *)subtransport;
+ int ret;
+
+ if (!stream)
+ return -1;
+
+ if ((!t->connection_data.host || !t->connection_data.port || !t->connection_data.path) &&
+ (ret = gitno_connection_data_from_url(&t->connection_data, url, NULL)) < 0)
+ return ret;
+
+ if (http_connect(t) < 0)
+ return -1;
+
+ switch (action) {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ return http_uploadpack_ls(t, stream);
+
+ case GIT_SERVICE_UPLOADPACK:
+ return http_uploadpack(t, stream);
+
+ case GIT_SERVICE_RECEIVEPACK_LS:
+ return http_receivepack_ls(t, stream);
+
+ case GIT_SERVICE_RECEIVEPACK:
+ return http_receivepack(t, stream);
+ }
+
+ *stream = NULL;
+ return -1;
+}
+
+static int http_close(git_smart_subtransport *subtransport)
+{
+ http_subtransport *t = (http_subtransport *) subtransport;
+
+ clear_parser_state(t);
+
+ if (t->socket.socket) {
+ gitno_close(&t->socket);
+ memset(&t->socket, 0x0, sizeof(gitno_socket));
+ }
+
+ if (t->cred) {
+ t->cred->free(t->cred);
+ t->cred = NULL;
+ }
+
+ if (t->url_cred) {
+ t->url_cred->free(t->url_cred);
+ t->url_cred = NULL;
+ }
+
+ gitno_connection_data_free_ptrs(&t->connection_data);
+
+ return 0;
+}
+
+static void http_free(git_smart_subtransport *subtransport)
+{
+ http_subtransport *t = (http_subtransport *) subtransport;
+
+ http_close(subtransport);
+
+ git__free(t);
+}
+
+int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner)
+{
+ http_subtransport *t;
+
+ if (!out)
+ return -1;
+
+ t = git__calloc(sizeof(http_subtransport), 1);
+ GITERR_CHECK_ALLOC(t);
+
+ t->owner = (transport_smart *)owner;
+ t->parent.action = http_action;
+ t->parent.close = http_close;
+ t->parent.free = http_free;
+
+ t->settings.on_header_field = on_header_field;
+ t->settings.on_header_value = on_header_value;
+ t->settings.on_headers_complete = on_headers_complete;
+ t->settings.on_body = on_body_fill_buffer;
+ t->settings.on_message_complete = on_message_complete;
+
+ *out = (git_smart_subtransport *) t;
+ return 0;
+}
+
+#endif /* !GIT_WINHTTP */
diff --git a/src/transports/local.c b/src/transports/local.c
new file mode 100644
index 000000000..f859f0b70
--- /dev/null
+++ b/src/transports/local.c
@@ -0,0 +1,673 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+#include "git2/types.h"
+#include "git2/net.h"
+#include "git2/repository.h"
+#include "git2/object.h"
+#include "git2/tag.h"
+#include "git2/transport.h"
+#include "git2/revwalk.h"
+#include "git2/odb_backend.h"
+#include "git2/pack.h"
+#include "git2/commit.h"
+#include "git2/revparse.h"
+#include "git2/push.h"
+#include "pack-objects.h"
+#include "refs.h"
+#include "posix.h"
+#include "path.h"
+#include "buffer.h"
+#include "repository.h"
+#include "odb.h"
+#include "push.h"
+#include "remote.h"
+
+typedef struct {
+ git_transport parent;
+ git_remote *owner;
+ char *url;
+ int direction;
+ int flags;
+ git_atomic cancelled;
+ git_repository *repo;
+ git_vector refs;
+ unsigned connected : 1,
+ have_refs : 1;
+} transport_local;
+
+static void free_head(git_remote_head *head)
+{
+ git__free(head->name);
+ git__free(head->symref_target);
+ git__free(head);
+}
+
+static int add_ref(transport_local *t, const char *name)
+{
+ const char peeled[] = "^{}";
+ git_reference *ref, *resolved;
+ git_remote_head *head;
+ git_oid obj_id;
+ git_object *obj = NULL, *target = NULL;
+ git_buf buf = GIT_BUF_INIT;
+ int error;
+
+ if ((error = git_reference_lookup(&ref, t->repo, name)) < 0)
+ return error;
+
+ error = git_reference_resolve(&resolved, ref);
+ if (error < 0) {
+ git_reference_free(ref);
+ if (!strcmp(name, GIT_HEAD_FILE) && error == GIT_ENOTFOUND) {
+ /* This is actually okay. Empty repos often have a HEAD that
+ * points to a nonexistent "refs/heads/master". */
+ giterr_clear();
+ return 0;
+ }
+ return error;
+ }
+
+ git_oid_cpy(&obj_id, git_reference_target(resolved));
+ git_reference_free(resolved);
+
+ head = git__calloc(1, sizeof(git_remote_head));
+ GITERR_CHECK_ALLOC(head);
+
+ head->name = git__strdup(name);
+ GITERR_CHECK_ALLOC(head->name);
+
+ git_oid_cpy(&head->oid, &obj_id);
+
+ if (git_reference_type(ref) == GIT_REF_SYMBOLIC) {
+ head->symref_target = git__strdup(git_reference_symbolic_target(ref));
+ GITERR_CHECK_ALLOC(head->symref_target);
+ }
+ git_reference_free(ref);
+
+ if ((error = git_vector_insert(&t->refs, head)) < 0) {
+ free_head(head);
+ return error;
+ }
+
+ /* If it's not a tag, we don't need to try to peel it */
+ if (git__prefixcmp(name, GIT_REFS_TAGS_DIR))
+ return 0;
+
+ if ((error = git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJ_ANY)) < 0)
+ return error;
+
+ head = NULL;
+
+ /* If it's not an annotated tag, or if we're mocking
+ * git-receive-pack, just get out */
+ if (git_object_type(obj) != GIT_OBJ_TAG ||
+ t->direction != GIT_DIRECTION_FETCH) {
+ git_object_free(obj);
+ return 0;
+ }
+
+ /* And if it's a tag, peel it, and add it to the list */
+ head = git__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);
+
+ if (!(error = git_tag_peel(&target, (git_tag *)obj))) {
+ git_oid_cpy(&head->oid, git_object_id(target));
+
+ if ((error = git_vector_insert(&t->refs, head)) < 0) {
+ free_head(head);
+ }
+ }
+
+ git_object_free(obj);
+ git_object_free(target);
+
+ return error;
+}
+
+static int store_refs(transport_local *t)
+{
+ size_t i;
+ git_remote_head *head;
+ git_strarray ref_names = {0};
+
+ assert(t);
+
+ if (git_reference_list(&ref_names, t->repo) < 0)
+ goto on_error;
+
+ /* Clear all heads we might have fetched in a previous connect */
+ git_vector_foreach(&t->refs, i, head) {
+ git__free(head->name);
+ git__free(head);
+ }
+
+ /* Clear the vector so we can reuse it */
+ git_vector_clear(&t->refs);
+
+ /* Sort the references first */
+ git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb);
+
+ /* Add HEAD iff direction is fetch */
+ if (t->direction == GIT_DIRECTION_FETCH && add_ref(t, GIT_HEAD_FILE) < 0)
+ goto on_error;
+
+ for (i = 0; i < ref_names.count; ++i) {
+ if (add_ref(t, ref_names.strings[i]) < 0)
+ goto on_error;
+ }
+
+ t->have_refs = 1;
+ git_strarray_free(&ref_names);
+ return 0;
+
+on_error:
+ git_vector_free(&t->refs);
+ git_strarray_free(&ref_names);
+ return -1;
+}
+
+/*
+ * Try to open the url as a git directory. The direction doesn't
+ * matter in this case because we're calculating the heads ourselves.
+ */
+static int local_connect(
+ git_transport *transport,
+ const char *url,
+ git_cred_acquire_cb cred_acquire_cb,
+ void *cred_acquire_payload,
+ int direction, int flags)
+{
+ git_repository *repo;
+ int error;
+ transport_local *t = (transport_local *) transport;
+ const char *path;
+ git_buf buf = GIT_BUF_INIT;
+
+ GIT_UNUSED(cred_acquire_cb);
+ GIT_UNUSED(cred_acquire_payload);
+
+ if (t->connected)
+ return 0;
+
+ t->url = git__strdup(url);
+ GITERR_CHECK_ALLOC(t->url);
+ t->direction = direction;
+ t->flags = flags;
+
+ /* 'url' may be a url or path; convert to a path */
+ if ((error = git_path_from_url_or_path(&buf, url)) < 0) {
+ git_buf_free(&buf);
+ return error;
+ }
+ path = git_buf_cstr(&buf);
+
+ error = git_repository_open(&repo, path);
+
+ git_buf_free(&buf);
+
+ if (error < 0)
+ return -1;
+
+ t->repo = repo;
+
+ if (store_refs(t) < 0)
+ return -1;
+
+ t->connected = 1;
+
+ return 0;
+}
+
+static int local_ls(const git_remote_head ***out, size_t *size, git_transport *transport)
+{
+ transport_local *t = (transport_local *)transport;
+
+ if (!t->have_refs) {
+ giterr_set(GITERR_NET, "The transport has not yet loaded the refs");
+ return -1;
+ }
+
+ *out = (const git_remote_head **)t->refs.contents;
+ *size = t->refs.length;
+
+ 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;
+ else
+ giterr_clear();
+ git_object_free(obj);
+ }
+
+ return 0;
+}
+
+static int local_push_copy_object(
+ git_odb *local_odb,
+ git_odb *remote_odb,
+ git_pobject *obj)
+{
+ int error = 0;
+ git_odb_object *odb_obj = NULL;
+ git_odb_stream *odb_stream;
+ size_t odb_obj_size;
+ git_otype odb_obj_type;
+ git_oid remote_odb_obj_oid;
+
+ /* Object already exists in the remote ODB; do nothing and return 0*/
+ if (git_odb_exists(remote_odb, &obj->id))
+ return 0;
+
+ if ((error = git_odb_read(&odb_obj, local_odb, &obj->id)) < 0)
+ return error;
+
+ odb_obj_size = git_odb_object_size(odb_obj);
+ odb_obj_type = git_odb_object_type(odb_obj);
+
+ if ((error = git_odb_open_wstream(&odb_stream, remote_odb,
+ odb_obj_size, odb_obj_type)) < 0)
+ goto on_error;
+
+ if (git_odb_stream_write(odb_stream, (char *)git_odb_object_data(odb_obj),
+ odb_obj_size) < 0 ||
+ git_odb_stream_finalize_write(&remote_odb_obj_oid, odb_stream) < 0) {
+ error = -1;
+ } else if (git_oid__cmp(&obj->id, &remote_odb_obj_oid) != 0) {
+ giterr_set(GITERR_ODB, "Error when writing object to remote odb "
+ "during local push operation. Remote odb object oid does not "
+ "match local oid.");
+ error = -1;
+ }
+
+ git_odb_stream_free(odb_stream);
+
+on_error:
+ git_odb_object_free(odb_obj);
+ return error;
+}
+
+static int local_push_update_remote_ref(
+ git_repository *remote_repo,
+ const char *lref,
+ const char *rref,
+ git_oid *loid,
+ git_oid *roid)
+{
+ int error;
+ git_reference *remote_ref = NULL;
+
+ /* rref will be NULL if it is implicit in the pushspec (e.g. 'b1:') */
+ rref = rref ? rref : lref;
+
+ if (lref) {
+ /* Create or update a ref */
+ if ((error = git_reference_create(NULL, remote_repo, rref, loid,
+ !git_oid_iszero(roid), NULL, NULL)) < 0)
+ return error;
+ } else {
+ /* Delete a ref */
+ if ((error = git_reference_lookup(&remote_ref, remote_repo, rref)) < 0) {
+ if (error == GIT_ENOTFOUND)
+ error = 0;
+ return error;
+ }
+
+ if ((error = git_reference_delete(remote_ref)) < 0)
+ return error;
+
+ git_reference_free(remote_ref);
+ }
+
+ return 0;
+}
+
+static int local_push(
+ git_transport *transport,
+ git_push *push)
+{
+ transport_local *t = (transport_local *)transport;
+ git_odb *remote_odb = NULL;
+ git_odb *local_odb = NULL;
+ git_repository *remote_repo = NULL;
+ push_spec *spec;
+ char *url = NULL;
+ const char *path;
+ git_buf buf = GIT_BUF_INIT;
+ int error;
+ unsigned int i;
+ size_t j;
+
+ /* 'push->remote->url' may be a url or path; convert to a path */
+ if ((error = git_path_from_url_or_path(&buf, push->remote->url)) < 0) {
+ git_buf_free(&buf);
+ return error;
+ }
+ path = git_buf_cstr(&buf);
+
+ error = git_repository_open(&remote_repo, path);
+
+ git_buf_free(&buf);
+
+ if (error < 0)
+ return error;
+
+ /* We don't currently support pushing locally to non-bare repos. Proper
+ non-bare repo push support would require checking configs to see if
+ we should override the default 'don't let this happen' behavior */
+ if (!remote_repo->is_bare) {
+ error = GIT_EBAREREPO;
+ giterr_set(GITERR_INVALID, "Local push doesn't (yet) support pushing to non-bare repos.");
+ goto on_error;
+ }
+
+ if ((error = git_repository_odb__weakptr(&remote_odb, remote_repo)) < 0 ||
+ (error = git_repository_odb__weakptr(&local_odb, push->repo)) < 0)
+ goto on_error;
+
+ for (i = 0; i < push->pb->nr_objects; i++) {
+ if ((error = local_push_copy_object(local_odb, remote_odb,
+ &push->pb->object_list[i])) < 0)
+ goto on_error;
+ }
+
+ push->unpack_ok = 1;
+
+ git_vector_foreach(&push->specs, j, spec) {
+ push_status *status;
+ const git_error *last;
+ char *ref = spec->rref ? spec->rref : spec->lref;
+
+ status = git__calloc(sizeof(push_status), 1);
+ if (!status)
+ goto on_error;
+
+ status->ref = git__strdup(ref);
+ if (!status->ref) {
+ git_push_status_free(status);
+ goto on_error;
+ }
+
+ error = local_push_update_remote_ref(remote_repo, spec->lref, spec->rref,
+ &spec->loid, &spec->roid);
+
+ switch (error) {
+ case GIT_OK:
+ break;
+ case GIT_EINVALIDSPEC:
+ status->msg = git__strdup("funny refname");
+ break;
+ case GIT_ENOTFOUND:
+ status->msg = git__strdup("Remote branch not found to delete");
+ break;
+ default:
+ last = giterr_last();
+
+ if (last && last->message)
+ status->msg = git__strdup(last->message);
+ else
+ status->msg = git__strdup("Unspecified error encountered");
+ break;
+ }
+
+ /* failed to allocate memory for a status message */
+ if (error < 0 && !status->msg) {
+ git_push_status_free(status);
+ goto on_error;
+ }
+
+ /* failed to insert the ref update status */
+ if ((error = git_vector_insert(&push->status, status)) < 0) {
+ git_push_status_free(status);
+ goto on_error;
+ }
+ }
+
+ if (push->specs.length) {
+ int flags = t->flags;
+ url = git__strdup(t->url);
+
+ if (!url || t->parent.close(&t->parent) < 0 ||
+ t->parent.connect(&t->parent, url,
+ push->remote->callbacks.credentials, NULL, GIT_DIRECTION_PUSH, flags))
+ goto on_error;
+ }
+
+ error = 0;
+
+on_error:
+ git_repository_free(remote_repo);
+ git__free(url);
+
+ return error;
+}
+
+typedef struct foreach_data {
+ git_transfer_progress *stats;
+ git_transfer_progress_cb 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->append(data->writepack, buf, len, data->stats);
+}
+
+static int local_download_pack(
+ git_transport *transport,
+ git_repository *repo,
+ git_transfer_progress *stats,
+ git_transfer_progress_cb progress_cb,
+ void *progress_payload)
+{
+ transport_local *t = (transport_local*)transport;
+ git_revwalk *walk = NULL;
+ git_remote_head *rhead;
+ unsigned int i;
+ int error = -1;
+ git_oid oid;
+ git_packbuilder *pack = NULL;
+ git_odb_writepack *writepack = NULL;
+ git_odb *odb = NULL;
+
+ if ((error = git_revwalk_new(&walk, t->repo)) < 0)
+ goto cleanup;
+ git_revwalk_sorting(walk, GIT_SORT_TIME);
+
+ if ((error = git_packbuilder_new(&pack, t->repo)) < 0)
+ goto cleanup;
+
+ stats->total_objects = 0;
+ stats->indexed_objects = 0;
+ stats->received_objects = 0;
+ stats->received_bytes = 0;
+
+ git_vector_foreach(&t->refs, i, rhead) {
+ git_object *obj;
+ if ((error = git_object_lookup(&obj, t->repo, &rhead->oid, GIT_OBJ_ANY)) < 0)
+ goto cleanup;
+
+ if (git_object_type(obj) == GIT_OBJ_COMMIT) {
+ /* Revwalker includes only wanted commits */
+ error = git_revwalk_push(walk, &rhead->oid);
+ if (!git_oid_iszero(&rhead->loid))
+ error = git_revwalk_hide(walk, &rhead->loid);
+ } else {
+ /* Tag or some other wanted object. Add it on its own */
+ error = git_packbuilder_insert(pack, &rhead->oid, rhead->name);
+ }
+ git_object_free(obj);
+ }
+
+ /* Walk the objects, building a packfile */
+ if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
+ goto cleanup;
+
+ while ((error = git_revwalk_next(&oid, walk)) == 0) {
+ git_commit *commit;
+
+ /* Skip commits we already have */
+ if (git_odb_exists(odb, &oid)) continue;
+
+ if (!git_object_lookup((git_object**)&commit, t->repo, &oid, GIT_OBJ_COMMIT)) {
+ const git_oid *tree_oid = git_commit_tree_id(commit);
+
+ /* Add the commit and its tree */
+ if ((error = git_packbuilder_insert(pack, &oid, NULL)) < 0 ||
+ (error = git_packbuilder_insert_tree(pack, tree_oid)) < 0) {
+ git_commit_free(commit);
+ goto cleanup;
+ }
+
+ git_commit_free(commit);
+ }
+ }
+
+ if ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) != 0)
+ goto cleanup;
+
+ /* Write the data to the ODB */
+ {
+ foreach_data data = {0};
+ data.stats = stats;
+ data.progress_cb = progress_cb;
+ data.progress_payload = progress_payload;
+ data.writepack = writepack;
+
+ if ((error = git_packbuilder_foreach(pack, foreach_cb, &data)) != 0)
+ goto cleanup;
+ }
+ error = writepack->commit(writepack, stats);
+
+cleanup:
+ if (writepack) writepack->free(writepack);
+ git_packbuilder_free(pack);
+ git_revwalk_free(walk);
+ return error;
+}
+
+static int local_is_connected(git_transport *transport)
+{
+ transport_local *t = (transport_local *)transport;
+
+ return t->connected;
+}
+
+static int local_read_flags(git_transport *transport, int *flags)
+{
+ transport_local *t = (transport_local *)transport;
+
+ *flags = t->flags;
+
+ return 0;
+}
+
+static void local_cancel(git_transport *transport)
+{
+ transport_local *t = (transport_local *)transport;
+
+ git_atomic_set(&t->cancelled, 1);
+}
+
+static int local_close(git_transport *transport)
+{
+ transport_local *t = (transport_local *)transport;
+
+ t->connected = 0;
+
+ if (t->repo) {
+ git_repository_free(t->repo);
+ t->repo = NULL;
+ }
+
+ if (t->url) {
+ git__free(t->url);
+ t->url = NULL;
+ }
+
+ return 0;
+}
+
+static void local_free(git_transport *transport)
+{
+ transport_local *t = (transport_local *)transport;
+ size_t i;
+ git_remote_head *head;
+
+ git_vector_foreach(&t->refs, i, head)
+ free_head(head);
+
+ git_vector_free(&t->refs);
+
+ /* Close the transport, if it's still open. */
+ local_close(transport);
+
+ /* Free the transport */
+ git__free(t);
+}
+
+/**************
+ * Public API *
+ **************/
+
+int git_transport_local(git_transport **out, git_remote *owner, void *param)
+{
+ transport_local *t;
+
+ GIT_UNUSED(param);
+
+ t = git__calloc(1, sizeof(transport_local));
+ GITERR_CHECK_ALLOC(t);
+
+ t->parent.version = GIT_TRANSPORT_VERSION;
+ t->parent.connect = local_connect;
+ t->parent.negotiate_fetch = local_negotiate_fetch;
+ t->parent.download_pack = local_download_pack;
+ t->parent.push = local_push;
+ t->parent.close = local_close;
+ t->parent.free = local_free;
+ t->parent.ls = local_ls;
+ t->parent.is_connected = local_is_connected;
+ t->parent.read_flags = local_read_flags;
+ t->parent.cancel = local_cancel;
+
+ git_vector_init(&t->refs, 0, NULL);
+ t->owner = owner;
+
+ *out = (git_transport *) t;
+
+ return 0;
+}
diff --git a/src/transports/smart.c b/src/transports/smart.c
new file mode 100644
index 000000000..a5c3e82dc
--- /dev/null
+++ b/src/transports/smart.c
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "git2.h"
+#include "smart.h"
+#include "refs.h"
+#include "refspec.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->cancelled.val) {
+ error = t->packetsize_cb(bytes_read, t->packetsize_payload);
+ if (error) {
+ git_atomic_set(&t->cancelled, 1);
+ return GIT_EUSER;
+ }
+ }
+
+ return (int)(buf->offset - old_len);
+}
+
+GIT_INLINE(int) git_smart__reset_stream(transport_smart *t, bool close_subtransport)
+{
+ if (t->current_stream) {
+ t->current_stream->free(t->current_stream);
+ t->current_stream = NULL;
+ }
+
+ if (close_subtransport &&
+ t->wrapped->close(t->wrapped) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int git_smart__set_callbacks(
+ git_transport *transport,
+ git_transport_message_cb progress_cb,
+ git_transport_message_cb error_cb,
+ void *message_cb_payload)
+{
+ transport_smart *t = (transport_smart *)transport;
+
+ t->progress_cb = progress_cb;
+ t->error_cb = error_cb;
+ t->message_cb_payload = message_cb_payload;
+
+ return 0;
+}
+
+int git_smart__update_heads(transport_smart *t, git_vector *symrefs)
+{
+ size_t i;
+ git_pkt *pkt;
+
+ git_vector_clear(&t->heads);
+ git_vector_foreach(&t->refs, i, pkt) {
+ git_pkt_ref *ref = (git_pkt_ref *) pkt;
+ if (pkt->type != GIT_PKT_REF)
+ continue;
+
+ if (symrefs) {
+ git_refspec *spec;
+ git_buf buf = GIT_BUF_INIT;
+ size_t j;
+ int error = 0;
+
+ git_vector_foreach(symrefs, j, spec) {
+ git_buf_clear(&buf);
+ if (git_refspec_src_matches(spec, ref->head.name) &&
+ !(error = git_refspec_transform(&buf, spec, ref->head.name)))
+ ref->head.symref_target = git_buf_detach(&buf);
+ }
+
+ git_buf_free(&buf);
+
+ if (error < 0)
+ return error;
+ }
+
+ if (git_vector_insert(&t->heads, &ref->head) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+static void free_symrefs(git_vector *symrefs)
+{
+ git_refspec *spec;
+ size_t i;
+
+ git_vector_foreach(symrefs, i, spec) {
+ git_refspec__free(spec);
+ git__free(spec);
+ }
+
+ git_vector_free(symrefs);
+}
+
+static int git_smart__connect(
+ git_transport *transport,
+ const char *url,
+ git_cred_acquire_cb cred_acquire_cb,
+ void *cred_acquire_payload,
+ int direction,
+ int flags)
+{
+ transport_smart *t = (transport_smart *)transport;
+ git_smart_subtransport_stream *stream;
+ int error;
+ git_pkt *pkt;
+ git_pkt_ref *first;
+ git_vector symrefs;
+ git_smart_service_t service;
+
+ if (git_smart__reset_stream(t, true) < 0)
+ return -1;
+
+ t->url = git__strdup(url);
+ GITERR_CHECK_ALLOC(t->url);
+
+ t->direction = direction;
+ t->flags = flags;
+ t->cred_acquire_cb = cred_acquire_cb;
+ t->cred_acquire_payload = cred_acquire_payload;
+
+ if (GIT_DIRECTION_FETCH == t->direction)
+ service = GIT_SERVICE_UPLOADPACK_LS;
+ else if (GIT_DIRECTION_PUSH == t->direction)
+ service = GIT_SERVICE_RECEIVEPACK_LS;
+ else {
+ giterr_set(GITERR_NET, "Invalid direction");
+ return -1;
+ }
+
+ if ((error = t->wrapped->action(&stream, t->wrapped, t->url, service)) < 0)
+ return error;
+
+ /* Save off the current stream (i.e. socket) that we are working with */
+ t->current_stream = stream;
+
+ gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
+
+ /* 2 flushes for RPC; 1 for stateful */
+ if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0)
+ return error;
+
+ /* Strip the comment packet for RPC */
+ if (t->rpc) {
+ pkt = (git_pkt *)git_vector_get(&t->refs, 0);
+
+ if (!pkt || GIT_PKT_COMMENT != pkt->type) {
+ giterr_set(GITERR_NET, "Invalid response");
+ return -1;
+ } else {
+ /* Remove the comment pkt from the list */
+ git_vector_remove(&t->refs, 0);
+ git__free(pkt);
+ }
+ }
+
+ /* We now have loaded the refs. */
+ t->have_refs = 1;
+
+ first = (git_pkt_ref *)git_vector_get(&t->refs, 0);
+
+ if ((error = git_vector_init(&symrefs, 1, NULL)) < 0)
+ return error;
+
+ /* Detect capabilities */
+ if (git_smart__detect_caps(first, &t->caps, &symrefs) < 0)
+ return -1;
+
+ /* If the only ref in the list is capabilities^{} with OID_ZERO, remove it */
+ if (1 == t->refs.length && !strcmp(first->head.name, "capabilities^{}") &&
+ git_oid_iszero(&first->head.oid)) {
+ git_vector_clear(&t->refs);
+ git_pkt_free((git_pkt *)first);
+ }
+
+ /* Keep a list of heads for _ls */
+ git_smart__update_heads(t, &symrefs);
+
+ free_symrefs(&symrefs);
+
+ if (t->rpc && git_smart__reset_stream(t, false) < 0)
+ return -1;
+
+ /* We're now logically connected. */
+ t->connected = 1;
+
+ return 0;
+}
+
+static int git_smart__ls(const git_remote_head ***out, size_t *size, git_transport *transport)
+{
+ transport_smart *t = (transport_smart *)transport;
+
+ if (!t->have_refs) {
+ giterr_set(GITERR_NET, "The transport has not yet loaded the refs");
+ return -1;
+ }
+
+ *out = (const git_remote_head **) t->heads.contents;
+ *size = t->heads.length;
+
+ return 0;
+}
+
+int git_smart__negotiation_step(git_transport *transport, void *data, size_t len)
+{
+ transport_smart *t = (transport_smart *)transport;
+ git_smart_subtransport_stream *stream;
+ int error;
+
+ if (t->rpc && git_smart__reset_stream(t, false) < 0)
+ return -1;
+
+ if (GIT_DIRECTION_FETCH != t->direction) {
+ giterr_set(GITERR_NET, "This operation is only valid for fetch");
+ return -1;
+ }
+
+ if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0)
+ return error;
+
+ /* If this is a stateful implementation, the stream we get back should be the same */
+ assert(t->rpc || t->current_stream == stream);
+
+ /* Save off the current stream (i.e. socket) that we are working with */
+ t->current_stream = stream;
+
+ if ((error = stream->write(stream, (const char *)data, len)) < 0)
+ return error;
+
+ gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
+
+ return 0;
+}
+
+int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **stream)
+{
+ int error;
+
+ if (t->rpc && git_smart__reset_stream(t, false) < 0)
+ return -1;
+
+ if (GIT_DIRECTION_PUSH != t->direction) {
+ giterr_set(GITERR_NET, "This operation is only valid for push");
+ return -1;
+ }
+
+ if ((error = t->wrapped->action(stream, t->wrapped, t->url, GIT_SERVICE_RECEIVEPACK)) < 0)
+ return error;
+
+ /* If this is a stateful implementation, the stream we get back should be the same */
+ assert(t->rpc || t->current_stream == *stream);
+
+ /* Save off the current stream (i.e. socket) that we are working with */
+ t->current_stream = *stream;
+
+ gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
+
+ return 0;
+}
+
+static void git_smart__cancel(git_transport *transport)
+{
+ transport_smart *t = (transport_smart *)transport;
+
+ git_atomic_set(&t->cancelled, 1);
+}
+
+static int git_smart__is_connected(git_transport *transport)
+{
+ transport_smart *t = (transport_smart *)transport;
+
+ return t->connected;
+}
+
+static int git_smart__read_flags(git_transport *transport, int *flags)
+{
+ transport_smart *t = (transport_smart *)transport;
+
+ *flags = t->flags;
+
+ return 0;
+}
+
+static int git_smart__close(git_transport *transport)
+{
+ transport_smart *t = (transport_smart *)transport;
+ git_vector *common = &t->common;
+ unsigned int i;
+ git_pkt *p;
+ int ret;
+ git_smart_subtransport_stream *stream;
+ const char flush[] = "0000";
+
+ /*
+ * If we're still connected at this point and not using RPC,
+ * we should say goodbye by sending a flush, or git-daemon
+ * will complain that we disconnected unexpectedly.
+ */
+ if (t->connected && !t->rpc &&
+ !t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) {
+ t->current_stream->write(t->current_stream, flush, 4);
+ }
+
+ ret = git_smart__reset_stream(t, true);
+
+ git_vector_foreach(common, i, p)
+ git_pkt_free(p);
+
+ git_vector_free(common);
+
+ if (t->url) {
+ git__free(t->url);
+ t->url = NULL;
+ }
+
+ t->connected = 0;
+
+ return ret;
+}
+
+static void git_smart__free(git_transport *transport)
+{
+ transport_smart *t = (transport_smart *)transport;
+ git_vector *refs = &t->refs;
+ unsigned int i;
+ git_pkt *p;
+
+ /* Make sure that the current stream is closed, if we have one. */
+ git_smart__close(transport);
+
+ /* Free the subtransport */
+ t->wrapped->free(t->wrapped);
+
+ git_vector_free(&t->heads);
+ git_vector_foreach(refs, i, p)
+ git_pkt_free(p);
+
+ git_vector_free(refs);
+
+ git__free(t);
+}
+
+static int ref_name_cmp(const void *a, const void *b)
+{
+ const git_pkt_ref *ref_a = a, *ref_b = b;
+
+ return strcmp(ref_a->head.name, ref_b->head.name);
+}
+
+int git_transport_smart(git_transport **out, git_remote *owner, void *param)
+{
+ transport_smart *t;
+ git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param;
+
+ if (!param)
+ return -1;
+
+ t = git__calloc(sizeof(transport_smart), 1);
+ GITERR_CHECK_ALLOC(t);
+
+ t->parent.version = GIT_TRANSPORT_VERSION;
+ t->parent.set_callbacks = git_smart__set_callbacks;
+ t->parent.connect = git_smart__connect;
+ t->parent.close = git_smart__close;
+ t->parent.free = git_smart__free;
+ t->parent.negotiate_fetch = git_smart__negotiate_fetch;
+ t->parent.download_pack = git_smart__download_pack;
+ t->parent.push = git_smart__push;
+ t->parent.ls = git_smart__ls;
+ t->parent.is_connected = git_smart__is_connected;
+ t->parent.read_flags = git_smart__read_flags;
+ t->parent.cancel = git_smart__cancel;
+
+ t->owner = owner;
+ t->rpc = definition->rpc;
+
+ if (git_vector_init(&t->refs, 16, ref_name_cmp) < 0) {
+ git__free(t);
+ return -1;
+ }
+
+ if (git_vector_init(&t->heads, 16, ref_name_cmp) < 0) {
+ git__free(t);
+ return -1;
+ }
+
+ if (definition->callback(&t->wrapped, &t->parent) < 0) {
+ git__free(t);
+ return -1;
+ }
+
+ *out = (git_transport *) t;
+ return 0;
+}
diff --git a/src/transports/smart.h b/src/transports/smart.h
new file mode 100644
index 000000000..f1fc29520
--- /dev/null
+++ b/src/transports/smart.h
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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"
+#include "push.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_MULTI_ACK_DETAILED "multi_ack_detailed"
+#define GIT_CAP_SIDE_BAND "side-band"
+#define GIT_CAP_SIDE_BAND_64K "side-band-64k"
+#define GIT_CAP_INCLUDE_TAG "include-tag"
+#define GIT_CAP_DELETE_REFS "delete-refs"
+#define GIT_CAP_REPORT_STATUS "report-status"
+#define GIT_CAP_THIN_PACK "thin-pack"
+#define GIT_CAP_SYMREF "symref"
+
+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,
+ GIT_PKT_OK,
+ GIT_PKT_NG,
+ GIT_PKT_UNPACK,
+};
+
+/* Used for multi_ack and mutli_ack_detailed */
+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;
+ int len;
+ char error[GIT_FLEX_ARRAY];
+} git_pkt_err;
+
+typedef struct {
+ enum git_pkt_type type;
+ char *ref;
+} git_pkt_ok;
+
+typedef struct {
+ enum git_pkt_type type;
+ char *ref;
+ char *msg;
+} git_pkt_ng;
+
+typedef struct {
+ enum git_pkt_type type;
+ int unpack_ok;
+} git_pkt_unpack;
+
+typedef struct transport_smart_caps {
+ int common:1,
+ ofs_delta:1,
+ multi_ack: 1,
+ multi_ack_detailed: 1,
+ side_band:1,
+ side_band_64k:1,
+ include_tag:1,
+ delete_refs:1,
+ report_status:1,
+ thin_pack:1;
+} transport_smart_caps;
+
+typedef int (*packetsize_cb)(size_t received, void *payload);
+
+typedef struct {
+ git_transport parent;
+ git_remote *owner;
+ char *url;
+ git_cred_acquire_cb cred_acquire_cb;
+ void *cred_acquire_payload;
+ 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 heads;
+ 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, git_vector *symrefs);
+int git_smart__push(git_transport *transport, git_push *push);
+
+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_cb progress_cb,
+ void *progress_payload);
+
+/* smart.c */
+int git_smart__negotiation_step(git_transport *transport, void *data, size_t len);
+int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **out);
+
+int git_smart__update_heads(transport_smart *t, git_vector *symrefs);
+
+/* 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/transports/smart_pkt.c b/src/transports/smart_pkt.c
new file mode 100644
index 000000000..b5f9d6dbe
--- /dev/null
+++ b/src/transports/smart_pkt.c
@@ -0,0 +1,556 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+
+#include "git2/types.h"
+#include "git2/errors.h"
+#include "git2/refs.h"
+#include "git2/revwalk.h"
+
+#include "smart.h"
+#include "util.h"
+#include "netops.h"
+#include "posix.h"
+#include "buffer.h"
+
+#include <ctype.h>
+
+#define PKT_LEN_SIZE 4
+static const char pkt_done_str[] = "0009done\n";
+static const char pkt_flush_str[] = "0000";
+static const char pkt_have_prefix[] = "0032have ";
+static const char pkt_want_prefix[] = "0032want ";
+
+static int flush_pkt(git_pkt **out)
+{
+ git_pkt *pkt;
+
+ pkt = git__malloc(sizeof(git_pkt));
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_FLUSH;
+ *out = pkt;
+
+ return 0;
+}
+
+/* the rest of the line will be useful for multi_ack and multi_ack_detailed */
+static int ack_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_ack *pkt;
+ GIT_UNUSED(line);
+ GIT_UNUSED(len);
+
+ pkt = git__calloc(1, sizeof(git_pkt_ack));
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_ACK;
+ line += 3;
+ len -= 3;
+
+ if (len >= GIT_OID_HEXSZ) {
+ git_oid_fromstr(&pkt->oid, line + 1);
+ line += GIT_OID_HEXSZ + 1;
+ len -= GIT_OID_HEXSZ + 1;
+ }
+
+ if (len >= 7) {
+ if (!git__prefixcmp(line + 1, "continue"))
+ pkt->status = GIT_ACK_CONTINUE;
+ if (!git__prefixcmp(line + 1, "common"))
+ pkt->status = GIT_ACK_COMMON;
+ if (!git__prefixcmp(line + 1, "ready"))
+ pkt->status = GIT_ACK_READY;
+ }
+
+ *out = (git_pkt *) pkt;
+
+ return 0;
+}
+
+static int nak_pkt(git_pkt **out)
+{
+ git_pkt *pkt;
+
+ pkt = git__malloc(sizeof(git_pkt));
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_NAK;
+ *out = pkt;
+
+ return 0;
+}
+
+static int pack_pkt(git_pkt **out)
+{
+ git_pkt *pkt;
+
+ pkt = git__malloc(sizeof(git_pkt));
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_PACK;
+ *out = pkt;
+
+ return 0;
+}
+
+static int comment_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_comment *pkt;
+
+ pkt = git__malloc(sizeof(git_pkt_comment) + len + 1);
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_COMMENT;
+ memcpy(pkt->comment, line, len);
+ pkt->comment[len] = '\0';
+
+ *out = (git_pkt *) pkt;
+
+ return 0;
+}
+
+static int err_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_err *pkt;
+
+ /* Remove "ERR " from the line */
+ line += 4;
+ len -= 4;
+ pkt = git__malloc(sizeof(git_pkt_err) + len + 1);
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_ERR;
+ pkt->len = (int)len;
+ memcpy(pkt->error, line, len);
+ pkt->error[len] = '\0';
+
+ *out = (git_pkt *) pkt;
+
+ return 0;
+}
+
+static int data_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_data *pkt;
+
+ line++;
+ len--;
+ pkt = git__malloc(sizeof(git_pkt_data) + len);
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_DATA;
+ pkt->len = (int) len;
+ memcpy(pkt->data, line, len);
+
+ *out = (git_pkt *) pkt;
+
+ return 0;
+}
+
+static int sideband_progress_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_progress *pkt;
+
+ line++;
+ len--;
+ pkt = git__malloc(sizeof(git_pkt_progress) + len);
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_PROGRESS;
+ pkt->len = (int) len;
+ memcpy(pkt->data, line, len);
+
+ *out = (git_pkt *) pkt;
+
+ return 0;
+}
+
+static int sideband_error_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_err *pkt;
+
+ line++;
+ len--;
+ pkt = git__malloc(sizeof(git_pkt_err) + len + 1);
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_ERR;
+ pkt->len = (int)len;
+ memcpy(pkt->error, line, len);
+ pkt->error[len] = '\0';
+
+ *out = (git_pkt *)pkt;
+
+ return 0;
+}
+
+/*
+ * Parse an other-ref line.
+ */
+static int ref_pkt(git_pkt **out, const char *line, size_t len)
+{
+ int error;
+ git_pkt_ref *pkt;
+
+ pkt = git__malloc(sizeof(git_pkt_ref));
+ GITERR_CHECK_ALLOC(pkt);
+
+ memset(pkt, 0x0, sizeof(git_pkt_ref));
+ pkt->type = GIT_PKT_REF;
+ if ((error = git_oid_fromstr(&pkt->head.oid, line)) < 0)
+ goto error_out;
+
+ /* Check for a bit of consistency */
+ if (line[GIT_OID_HEXSZ] != ' ') {
+ giterr_set(GITERR_NET, "Error parsing pkt-line");
+ error = -1;
+ goto error_out;
+ }
+
+ /* Jump from the name */
+ line += GIT_OID_HEXSZ + 1;
+ len -= (GIT_OID_HEXSZ + 1);
+
+ if (line[len - 1] == '\n')
+ --len;
+
+ pkt->head.name = git__malloc(len + 1);
+ GITERR_CHECK_ALLOC(pkt->head.name);
+
+ memcpy(pkt->head.name, line, len);
+ pkt->head.name[len] = '\0';
+
+ if (strlen(pkt->head.name) < len) {
+ pkt->capabilities = strchr(pkt->head.name, '\0') + 1;
+ }
+
+ *out = (git_pkt *)pkt;
+ return 0;
+
+error_out:
+ git__free(pkt);
+ return error;
+}
+
+static int ok_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_ok *pkt;
+ const char *ptr;
+
+ pkt = git__malloc(sizeof(*pkt));
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_OK;
+
+ line += 3; /* skip "ok " */
+ ptr = strchr(line, '\n');
+ len = ptr - line;
+
+ pkt->ref = git__malloc(len + 1);
+ GITERR_CHECK_ALLOC(pkt->ref);
+
+ memcpy(pkt->ref, line, len);
+ pkt->ref[len] = '\0';
+
+ *out = (git_pkt *)pkt;
+ return 0;
+}
+
+static int ng_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_ng *pkt;
+ const char *ptr;
+
+ pkt = git__malloc(sizeof(*pkt));
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_NG;
+
+ line += 3; /* skip "ng " */
+ ptr = strchr(line, ' ');
+ len = ptr - line;
+
+ pkt->ref = git__malloc(len + 1);
+ GITERR_CHECK_ALLOC(pkt->ref);
+
+ memcpy(pkt->ref, line, len);
+ pkt->ref[len] = '\0';
+
+ line = ptr + 1;
+ ptr = strchr(line, '\n');
+ len = ptr - line;
+
+ pkt->msg = git__malloc(len + 1);
+ GITERR_CHECK_ALLOC(pkt->msg);
+
+ memcpy(pkt->msg, line, len);
+ pkt->msg[len] = '\0';
+
+ *out = (git_pkt *)pkt;
+ return 0;
+}
+
+static int unpack_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_unpack *pkt;
+
+ GIT_UNUSED(len);
+
+ pkt = git__malloc(sizeof(*pkt));
+ GITERR_CHECK_ALLOC(pkt);
+
+ pkt->type = GIT_PKT_UNPACK;
+ if (!git__prefixcmp(line, "unpack ok"))
+ pkt->unpack_ok = 1;
+ else
+ pkt->unpack_ok = 0;
+
+ *out = (git_pkt *)pkt;
+ return 0;
+}
+
+static int32_t parse_len(const char *line)
+{
+ char num[PKT_LEN_SIZE + 1];
+ int i, error;
+ int32_t len;
+ const char *num_end;
+
+ memcpy(num, line, PKT_LEN_SIZE);
+ num[PKT_LEN_SIZE] = '\0';
+
+ for (i = 0; i < PKT_LEN_SIZE; ++i) {
+ if (!isxdigit(num[i])) {
+ giterr_set(GITERR_NET, "Found invalid hex digit in length");
+ return -1;
+ }
+ }
+
+ if ((error = git__strtol32(&len, num, &num_end, 16)) < 0)
+ return error;
+
+ return len;
+}
+
+/*
+ * As per the documentation, the syntax is:
+ *
+ * pkt-line = data-pkt / flush-pkt
+ * data-pkt = pkt-len pkt-payload
+ * pkt-len = 4*(HEXDIG)
+ * pkt-payload = (pkt-len -4)*(OCTET)
+ * flush-pkt = "0000"
+ *
+ * Which means that the first four bytes are the length of the line,
+ * in ASCII hexadecimal (including itself)
+ */
+
+int git_pkt_parse_line(
+ git_pkt **head, const char *line, const char **out, size_t bufflen)
+{
+ int ret;
+ int32_t len;
+
+ /* Not even enough for the length */
+ if (bufflen > 0 && bufflen < PKT_LEN_SIZE)
+ return GIT_EBUFS;
+
+ len = parse_len(line);
+ if (len < 0) {
+ /*
+ * If we fail to parse the length, it might be because the
+ * server is trying to send us the packfile already.
+ */
+ if (bufflen >= 4 && !git__prefixcmp(line, "PACK")) {
+ giterr_clear();
+ *out = line;
+ return pack_pkt(head);
+ }
+
+ return (int)len;
+ }
+
+ /*
+ * If we were given a buffer length, then make sure there is
+ * enough in the buffer to satisfy this line
+ */
+ if (bufflen > 0 && bufflen < (size_t)len)
+ return GIT_EBUFS;
+
+ line += PKT_LEN_SIZE;
+ /*
+ * TODO: How do we deal with empty lines? Try again? with the next
+ * line?
+ */
+ if (len == PKT_LEN_SIZE) {
+ *out = line;
+ return 0;
+ }
+
+ if (len == 0) { /* Flush pkt */
+ *out = line;
+ return flush_pkt(head);
+ }
+
+ len -= PKT_LEN_SIZE; /* the encoded length includes its own size */
+
+ if (*line == GIT_SIDE_BAND_DATA)
+ ret = data_pkt(head, line, len);
+ else if (*line == GIT_SIDE_BAND_PROGRESS)
+ ret = sideband_progress_pkt(head, line, len);
+ else if (*line == GIT_SIDE_BAND_ERROR)
+ ret = sideband_error_pkt(head, line, len);
+ else if (!git__prefixcmp(line, "ACK"))
+ ret = ack_pkt(head, line, len);
+ else if (!git__prefixcmp(line, "NAK"))
+ ret = nak_pkt(head);
+ else if (!git__prefixcmp(line, "ERR "))
+ ret = err_pkt(head, line, len);
+ else if (*line == '#')
+ ret = comment_pkt(head, line, len);
+ else if (!git__prefixcmp(line, "ok"))
+ ret = ok_pkt(head, line, len);
+ else if (!git__prefixcmp(line, "ng"))
+ ret = ng_pkt(head, line, len);
+ else if (!git__prefixcmp(line, "unpack"))
+ ret = unpack_pkt(head, line, len);
+ else
+ ret = ref_pkt(head, line, len);
+
+ *out = line + len;
+
+ return ret;
+}
+
+void git_pkt_free(git_pkt *pkt)
+{
+ if (pkt->type == GIT_PKT_REF) {
+ git_pkt_ref *p = (git_pkt_ref *) pkt;
+ git__free(p->head.name);
+ git__free(p->head.symref_target);
+ }
+
+ if (pkt->type == GIT_PKT_OK) {
+ git_pkt_ok *p = (git_pkt_ok *) pkt;
+ git__free(p->ref);
+ }
+
+ if (pkt->type == GIT_PKT_NG) {
+ git_pkt_ng *p = (git_pkt_ng *) pkt;
+ git__free(p->ref);
+ git__free(p->msg);
+ }
+
+ git__free(pkt);
+}
+
+int git_pkt_buffer_flush(git_buf *buf)
+{
+ return git_buf_put(buf, pkt_flush_str, strlen(pkt_flush_str));
+}
+
+static int buffer_want_with_caps(const git_remote_head *head, transport_smart_caps *caps, git_buf *buf)
+{
+ git_buf str = GIT_BUF_INIT;
+ char oid[GIT_OID_HEXSZ +1] = {0};
+ unsigned int len;
+
+ /* Prefer multi_ack_detailed */
+ if (caps->multi_ack_detailed)
+ git_buf_puts(&str, GIT_CAP_MULTI_ACK_DETAILED " ");
+ else if (caps->multi_ack)
+ git_buf_puts(&str, GIT_CAP_MULTI_ACK " ");
+
+ /* Prefer side-band-64k if the server supports both */
+ if (caps->side_band_64k)
+ git_buf_printf(&str, "%s ", GIT_CAP_SIDE_BAND_64K);
+ else if (caps->side_band)
+ git_buf_printf(&str, "%s ", GIT_CAP_SIDE_BAND);
+
+ if (caps->include_tag)
+ git_buf_puts(&str, GIT_CAP_INCLUDE_TAG " ");
+
+ if (caps->thin_pack)
+ git_buf_puts(&str, GIT_CAP_THIN_PACK " ");
+
+ if (caps->ofs_delta)
+ git_buf_puts(&str, GIT_CAP_OFS_DELTA " ");
+
+ if (git_buf_oom(&str))
+ return -1;
+
+ len = (unsigned int)
+ (strlen("XXXXwant ") + GIT_OID_HEXSZ + 1 /* NUL */ +
+ git_buf_len(&str) + 1 /* LF */);
+ git_buf_grow(buf, git_buf_len(buf) + len);
+ git_oid_fmt(oid, &head->oid);
+ git_buf_printf(buf, "%04xwant %s %s\n", len, oid, git_buf_cstr(&str));
+ git_buf_free(&str);
+
+ return git_buf_oom(buf);
+}
+
+/*
+ * All "want" packets have the same length and format, so what we do
+ * is overwrite the OID each time.
+ */
+
+int git_pkt_buffer_wants(
+ const git_remote_head * const *refs,
+ size_t count,
+ transport_smart_caps *caps,
+ git_buf *buf)
+{
+ size_t i = 0;
+ const git_remote_head *head;
+
+ if (caps->common) {
+ for (; i < count; ++i) {
+ head = refs[i];
+ if (!head->local)
+ break;
+ }
+
+ if (buffer_want_with_caps(refs[i], caps, buf) < 0)
+ return -1;
+
+ i++;
+ }
+
+ for (; i < count; ++i) {
+ char oid[GIT_OID_HEXSZ];
+
+ head = refs[i];
+ if (head->local)
+ continue;
+
+ git_oid_fmt(oid, &head->oid);
+ git_buf_put(buf, pkt_want_prefix, strlen(pkt_want_prefix));
+ git_buf_put(buf, oid, GIT_OID_HEXSZ);
+ git_buf_putc(buf, '\n');
+ if (git_buf_oom(buf))
+ return -1;
+ }
+
+ return git_pkt_buffer_flush(buf);
+}
+
+int git_pkt_buffer_have(git_oid *oid, git_buf *buf)
+{
+ char oidhex[GIT_OID_HEXSZ + 1];
+
+ memset(oidhex, 0x0, sizeof(oidhex));
+ git_oid_fmt(oidhex, oid);
+ return git_buf_printf(buf, "%s%s\n", pkt_have_prefix, oidhex);
+}
+
+int git_pkt_buffer_done(git_buf *buf)
+{
+ return git_buf_puts(buf, pkt_done_str);
+}
diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c
new file mode 100644
index 000000000..a52aacc60
--- /dev/null
+++ b/src/transports/smart_protocol.c
@@ -0,0 +1,1031 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "git2/odb_backend.h"
+
+#include "smart.h"
+#include "refs.h"
+#include "repository.h"
+#include "push.h"
+#include "pack-objects.h"
+#include "remote.h"
+#include "util.h"
+
+#define NETWORK_XFER_THRESHOLD (100*1024)
+/* The minimal interval between progress updates (in seconds). */
+#define MIN_PROGRESS_UPDATE_INTERVAL 0.5
+
+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 = NULL;
+ git_pkt *pkt = NULL;
+ size_t i;
+
+ /* Clear existing refs in case git_remote_connect() is called again
+ * after git_remote_disconnect().
+ */
+ git_vector_foreach(refs, i, pkt) {
+ git_pkt_free(pkt);
+ }
+ git_vector_clear(refs);
+ pkt = NULL;
+
+ 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 error;
+
+ if (error == GIT_EBUFS) {
+ if ((recvd = gitno_recv(buf)) < 0)
+ return recvd;
+
+ 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;
+}
+
+static int append_symref(const char **out, git_vector *symrefs, const char *ptr)
+{
+ int error;
+ const char *end;
+ git_buf buf = GIT_BUF_INIT;
+ git_refspec *mapping;
+
+ ptr += strlen(GIT_CAP_SYMREF);
+ if (*ptr != '=')
+ goto on_invalid;
+
+ ptr++;
+ if (!(end = strchr(ptr, ' ')) &&
+ !(end = strchr(ptr, '\0')))
+ goto on_invalid;
+
+ if ((error = git_buf_put(&buf, ptr, end - ptr)) < 0)
+ return error;
+
+ /* symref mapping has refspec format */
+ mapping = git__malloc(sizeof(git_refspec));
+ GITERR_CHECK_ALLOC(mapping);
+
+ error = git_refspec__parse(mapping, git_buf_cstr(&buf), true);
+ git_buf_free(&buf);
+
+ /* if the error isn't OOM, then it's a parse error; let's use a nicer message */
+ if (error < 0) {
+ if (giterr_last()->klass != GITERR_NOMEMORY)
+ goto on_invalid;
+
+ return error;
+ }
+
+ if ((error = git_vector_insert(symrefs, mapping)) < 0)
+ return error;
+
+ *out = end;
+ return 0;
+
+on_invalid:
+ giterr_set(GITERR_NET, "remote sent invalid symref");
+ return -1;
+}
+
+int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps, git_vector *symrefs)
+{
+ 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;
+ }
+
+ /* Keep multi_ack_detailed before multi_ack */
+ if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK_DETAILED)) {
+ caps->common = caps->multi_ack_detailed = 1;
+ ptr += strlen(GIT_CAP_MULTI_ACK_DETAILED);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) {
+ caps->common = caps->multi_ack = 1;
+ ptr += strlen(GIT_CAP_MULTI_ACK);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) {
+ caps->common = caps->include_tag = 1;
+ ptr += strlen(GIT_CAP_INCLUDE_TAG);
+ continue;
+ }
+
+ /* Keep side-band check after side-band-64k */
+ if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) {
+ caps->common = caps->side_band_64k = 1;
+ ptr += strlen(GIT_CAP_SIDE_BAND_64K);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) {
+ caps->common = caps->side_band = 1;
+ ptr += strlen(GIT_CAP_SIDE_BAND);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, GIT_CAP_DELETE_REFS)) {
+ caps->common = caps->delete_refs = 1;
+ ptr += strlen(GIT_CAP_DELETE_REFS);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, GIT_CAP_THIN_PACK)) {
+ caps->common = caps->thin_pack = 1;
+ ptr += strlen(GIT_CAP_THIN_PACK);
+ continue;
+ }
+
+ if (!git__prefixcmp(ptr, GIT_CAP_SYMREF)) {
+ int error;
+
+ if ((error = append_symref(&ptr, symrefs, ptr)) < 0)
+ return error;
+
+ 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 = NULL;
+ 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 error;
+
+ if ((ret = gitno_recv(buf)) < 0)
+ return ret;
+ } 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;
+ int error;
+
+ do {
+ if ((error = recv_pkt(&pkt, buf)) < 0)
+ return error;
+
+ 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;
+ int error;
+
+ if ((error = git_reference_list(&refs, repo)) < 0)
+ return error;
+
+ if ((error = git_revwalk_new(&walk, repo)) < 0)
+ return error;
+
+ 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 ((error = git_reference_lookup(&ref, repo, refs.strings[i])) < 0)
+ goto on_error;
+
+ if (git_reference_type(ref) == GIT_REF_SYMBOLIC)
+ continue;
+
+ if ((error = git_revwalk_push(walk, git_reference_target(ref))) < 0)
+ goto on_error;
+
+ git_reference_free(ref);
+ }
+
+ git_strarray_free(&refs);
+ *out = walk;
+ return 0;
+
+on_error:
+ git_reference_free(ref);
+ git_strarray_free(&refs);
+ return error;
+}
+
+static int wait_while_ack(gitno_buffer *buf)
+{
+ int error;
+ git_pkt_ack *pkt = NULL;
+
+ while (1) {
+ git__free(pkt);
+
+ if ((error = recv_pkt((git_pkt **)&pkt, buf)) < 0)
+ return error;
+
+ if (pkt->type == GIT_PKT_NAK)
+ break;
+
+ if (pkt->type == GIT_PKT_ACK &&
+ (pkt->status != GIT_ACK_CONTINUE ||
+ pkt->status != GIT_ACK_COMMON)) {
+ git__free(pkt);
+ return 0;
+ }
+ }
+
+ git__free(pkt);
+ return 0;
+}
+
+int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, const git_remote_head * const *wants, 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;
+
+ if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0)
+ return error;
+
+ if ((error = fetch_setup_walk(&walk, repo)) < 0)
+ goto on_error;
+
+ /*
+ * Our support for ACK extensions is simply to parse them. On
+ * the first ACK we will accept that as enough common
+ * objects. We give up if we haven't found an answer in the
+ * first 256 we send.
+ */
+ i = 0;
+ while (i < 256) {
+ error = git_revwalk_next(&oid, walk);
+
+ if (error < 0) {
+ if (GIT_ITEROVER == error)
+ break;
+
+ goto on_error;
+ }
+
+ git_pkt_buffer_have(&oid, &data);
+ i++;
+ if (i % 20 == 0) {
+ if (t->cancelled.val) {
+ giterr_set(GITERR_NET, "The fetch was cancelled by the user");
+ error = GIT_EUSER;
+ goto on_error;
+ }
+
+ git_pkt_buffer_flush(&data);
+ if (git_buf_oom(&data)) {
+ error = -1;
+ goto on_error;
+ }
+
+ if ((error = git_smart__negotiation_step(&t->parent, data.ptr, data.size)) < 0)
+ goto on_error;
+
+ git_buf_clear(&data);
+ if (t->caps.multi_ack || t->caps.multi_ack_detailed) {
+ if ((error = store_common(t)) < 0)
+ goto on_error;
+ } else {
+ pkt_type = recv_pkt(NULL, buf);
+
+ if (pkt_type == GIT_PKT_ACK) {
+ break;
+ } else if (pkt_type == GIT_PKT_NAK) {
+ continue;
+ } else if (pkt_type < 0) {
+ /* recv_pkt returned an error */
+ error = pkt_type;
+ goto on_error;
+ } else {
+ giterr_set(GITERR_NET, "Unexpected pkt type");
+ error = -1;
+ goto on_error;
+ }
+ }
+ }
+
+ if (t->common.length > 0)
+ break;
+
+ if (i % 20 == 0 && t->rpc) {
+ git_pkt_ack *pkt;
+ unsigned int i;
+
+ if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0)
+ goto on_error;
+
+ git_vector_foreach(&t->common, i, pkt) {
+ if ((error = git_pkt_buffer_have(&pkt->oid, &data)) < 0)
+ goto on_error;
+ }
+
+ if (git_buf_oom(&data)) {
+ error = -1;
+ goto on_error;
+ }
+ }
+ }
+
+ /* Tell the other end that we're done negotiating */
+ if (t->rpc && t->common.length > 0) {
+ git_pkt_ack *pkt;
+ unsigned int i;
+
+ if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0)
+ goto on_error;
+
+ git_vector_foreach(&t->common, i, pkt) {
+ if ((error = git_pkt_buffer_have(&pkt->oid, &data)) < 0)
+ goto on_error;
+ }
+
+ if (git_buf_oom(&data)) {
+ error = -1;
+ goto on_error;
+ }
+ }
+
+ if ((error = git_pkt_buffer_done(&data)) < 0)
+ goto on_error;
+
+ if (t->cancelled.val) {
+ giterr_set(GITERR_NET, "The fetch was cancelled by the user");
+ error = GIT_EUSER;
+ goto on_error;
+ }
+ if ((error = git_smart__negotiation_step(&t->parent, data.ptr, data.size)) < 0)
+ goto on_error;
+
+ git_buf_free(&data);
+ git_revwalk_free(walk);
+
+ /* Now let's eat up whatever the server gives us */
+ if (!t->caps.multi_ack && !t->caps.multi_ack_detailed) {
+ pkt_type = recv_pkt(NULL, buf);
+
+ if (pkt_type < 0) {
+ return pkt_type;
+ } else if (pkt_type != GIT_PKT_ACK && pkt_type != GIT_PKT_NAK) {
+ giterr_set(GITERR_NET, "Unexpected pkt type");
+ return -1;
+ }
+ } else {
+ error = wait_while_ack(buf);
+ }
+
+ return error;
+
+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->append(writepack, buf->data, buf->offset, stats) < 0)
+ return -1;
+
+ gitno_consume_n(buf, buf->offset);
+
+ if ((recvd = gitno_recv(buf)) < 0)
+ return recvd;
+ } while(recvd > 0);
+
+ if (writepack->commit(writepack, stats) < 0)
+ return -1;
+
+ return 0;
+}
+
+struct network_packetsize_payload
+{
+ git_transfer_progress_cb callback;
+ void *payload;
+ git_transfer_progress *stats;
+ size_t last_fired_bytes;
+};
+
+static int network_packetsize(size_t received, void *payload)
+{
+ struct network_packetsize_payload *npp = (struct network_packetsize_payload*)payload;
+
+ /* Accumulate bytes */
+ npp->stats->received_bytes += received;
+
+ /* Fire notification if the threshold is reached */
+ if ((npp->stats->received_bytes - npp->last_fired_bytes) > NETWORK_XFER_THRESHOLD) {
+ npp->last_fired_bytes = npp->stats->received_bytes;
+
+ if (npp->callback(npp->stats, npp->payload))
+ return GIT_EUSER;
+ }
+
+ return 0;
+}
+
+int git_smart__download_pack(
+ git_transport *transport,
+ git_repository *repo,
+ git_transfer_progress *stats,
+ git_transfer_progress_cb transfer_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 = 0;
+ struct network_packetsize_payload npp = {0};
+
+ memset(stats, 0, sizeof(git_transfer_progress));
+
+ if (transfer_progress_cb) {
+ npp.callback = transfer_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->cancelled.val)
+ if (t->packetsize_cb(t->buffer.offset, t->packetsize_payload))
+ git_atomic_set(&t->cancelled, 1);
+ }
+
+ if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 ||
+ ((error = git_odb_write_pack(&writepack, odb, transfer_progress_cb, progress_payload)) != 0))
+ goto done;
+
+ /*
+ * 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) {
+ error = no_sideband(t, writepack, buf, stats);
+ goto done;
+ }
+
+ do {
+ git_pkt *pkt = NULL;
+
+ /* Check cancellation before network call */
+ if (t->cancelled.val) {
+ giterr_clear();
+ error = GIT_EUSER;
+ goto done;
+ }
+
+ if ((error = recv_pkt(&pkt, buf)) >= 0) {
+ /* Check cancellation after network call */
+ if (t->cancelled.val) {
+ giterr_clear();
+ error = GIT_EUSER;
+ } else if (pkt->type == GIT_PKT_PROGRESS) {
+ if (t->progress_cb) {
+ git_pkt_progress *p = (git_pkt_progress *) pkt;
+ error = t->progress_cb(p->data, p->len, t->message_cb_payload);
+ }
+ } else if (pkt->type == GIT_PKT_DATA) {
+ git_pkt_data *p = (git_pkt_data *) pkt;
+ error = writepack->append(writepack, p->data, p->len, stats);
+ } else if (pkt->type == GIT_PKT_FLUSH) {
+ /* A flush indicates the end of the packfile */
+ git__free(pkt);
+ break;
+ }
+ }
+
+ git__free(pkt);
+ if (error < 0)
+ goto done;
+
+ } while (1);
+
+ /*
+ * Trailing execution of transfer_progress_cb, if necessary...
+ * Only the callback through the npp datastructure currently
+ * updates the last_fired_bytes value. It is possible that
+ * progress has already been reported with the correct
+ * "received_bytes" value, but until (if?) this is unified
+ * then we will report progress again to be sure that the
+ * correct last received_bytes value is reported.
+ */
+ if (npp.callback && npp.stats->received_bytes > npp.last_fired_bytes) {
+ error = npp.callback(npp.stats, npp.payload);
+ if (error != 0)
+ goto done;
+ }
+
+ error = writepack->commit(writepack, stats);
+
+done:
+ if (writepack)
+ writepack->free(writepack);
+ if (transfer_progress_cb) {
+ t->packetsize_cb = NULL;
+ t->packetsize_payload = NULL;
+ }
+
+ return error;
+}
+
+static int gen_pktline(git_buf *buf, git_push *push)
+{
+ push_spec *spec;
+ size_t i, len;
+ char old_id[41], new_id[41];
+
+ old_id[40] = '\0'; new_id[40] = '\0';
+
+ git_vector_foreach(&push->specs, i, spec) {
+ len = 2*GIT_OID_HEXSZ + 7 + strlen(spec->rref);
+
+ if (i == 0) {
+ ++len; /* '\0' */
+ if (push->report_status)
+ len += strlen(GIT_CAP_REPORT_STATUS) + 1;
+ len += strlen(GIT_CAP_SIDE_BAND_64K) + 1;
+ }
+
+ git_oid_fmt(old_id, &spec->roid);
+ git_oid_fmt(new_id, &spec->loid);
+
+ git_buf_printf(buf, "%04"PRIxZ"%s %s %s", len, old_id, new_id, spec->rref);
+
+ if (i == 0) {
+ git_buf_putc(buf, '\0');
+ /* Core git always starts their capabilities string with a space */
+ if (push->report_status) {
+ git_buf_putc(buf, ' ');
+ git_buf_printf(buf, GIT_CAP_REPORT_STATUS);
+ }
+ git_buf_putc(buf, ' ');
+ git_buf_printf(buf, GIT_CAP_SIDE_BAND_64K);
+ }
+
+ git_buf_putc(buf, '\n');
+ }
+
+ git_buf_puts(buf, "0000");
+ return git_buf_oom(buf) ? -1 : 0;
+}
+
+static int add_push_report_pkt(git_push *push, git_pkt *pkt)
+{
+ push_status *status;
+
+ switch (pkt->type) {
+ case GIT_PKT_OK:
+ status = git__calloc(sizeof(push_status), 1);
+ GITERR_CHECK_ALLOC(status);
+ status->msg = NULL;
+ status->ref = git__strdup(((git_pkt_ok *)pkt)->ref);
+ if (!status->ref ||
+ git_vector_insert(&push->status, status) < 0) {
+ git_push_status_free(status);
+ return -1;
+ }
+ break;
+ case GIT_PKT_NG:
+ status = git__calloc(sizeof(push_status), 1);
+ GITERR_CHECK_ALLOC(status);
+ status->ref = git__strdup(((git_pkt_ng *)pkt)->ref);
+ status->msg = git__strdup(((git_pkt_ng *)pkt)->msg);
+ if (!status->ref || !status->msg ||
+ git_vector_insert(&push->status, status) < 0) {
+ git_push_status_free(status);
+ return -1;
+ }
+ break;
+ case GIT_PKT_UNPACK:
+ push->unpack_ok = ((git_pkt_unpack *)pkt)->unpack_ok;
+ break;
+ case GIT_PKT_FLUSH:
+ return GIT_ITEROVER;
+ default:
+ giterr_set(GITERR_NET, "report-status: protocol error");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int add_push_report_sideband_pkt(git_push *push, git_pkt_data *data_pkt)
+{
+ git_pkt *pkt;
+ const char *line = data_pkt->data, *line_end;
+ size_t line_len = data_pkt->len;
+ int error;
+
+ while (line_len > 0) {
+ error = git_pkt_parse_line(&pkt, line, &line_end, line_len);
+
+ if (error < 0)
+ return error;
+
+ /* Advance in the buffer */
+ line_len -= (line_end - line);
+ line = line_end;
+
+ error = add_push_report_pkt(push, pkt);
+
+ git_pkt_free(pkt);
+
+ if (error < 0 && error != GIT_ITEROVER)
+ return error;
+ }
+
+ return 0;
+}
+
+static int parse_report(transport_smart *transport, git_push *push)
+{
+ git_pkt *pkt = NULL;
+ const char *line_end = NULL;
+ gitno_buffer *buf = &transport->buffer;
+ int error, recvd;
+
+ for (;;) {
+ if (buf->offset > 0)
+ error = git_pkt_parse_line(&pkt, buf->data,
+ &line_end, buf->offset);
+ else
+ error = GIT_EBUFS;
+
+ if (error < 0 && error != GIT_EBUFS)
+ return -1;
+
+ if (error == GIT_EBUFS) {
+ if ((recvd = gitno_recv(buf)) < 0)
+ return recvd;
+
+ if (recvd == 0) {
+ giterr_set(GITERR_NET, "Early EOF");
+ return -1;
+ }
+ continue;
+ }
+
+ gitno_consume(buf, line_end);
+
+ error = 0;
+
+ switch (pkt->type) {
+ case GIT_PKT_DATA:
+ /* This is a sideband packet which contains other packets */
+ error = add_push_report_sideband_pkt(push, (git_pkt_data *)pkt);
+ break;
+ case GIT_PKT_ERR:
+ giterr_set(GITERR_NET, "report-status: Error reported: %s",
+ ((git_pkt_err *)pkt)->error);
+ error = -1;
+ break;
+ case GIT_PKT_PROGRESS:
+ if (transport->progress_cb) {
+ git_pkt_progress *p = (git_pkt_progress *) pkt;
+ error = transport->progress_cb(p->data, p->len, transport->message_cb_payload);
+ }
+ break;
+ default:
+ error = add_push_report_pkt(push, pkt);
+ break;
+ }
+
+ git_pkt_free(pkt);
+
+ /* add_push_report_pkt returns GIT_ITEROVER when it receives a flush */
+ if (error == GIT_ITEROVER)
+ return 0;
+
+ if (error < 0)
+ return error;
+ }
+}
+
+static int add_ref_from_push_spec(git_vector *refs, push_spec *push_spec)
+{
+ git_pkt_ref *added = git__calloc(1, sizeof(git_pkt_ref));
+ GITERR_CHECK_ALLOC(added);
+
+ added->type = GIT_PKT_REF;
+ git_oid_cpy(&added->head.oid, &push_spec->loid);
+ added->head.name = git__strdup(push_spec->rref);
+
+ if (!added->head.name ||
+ git_vector_insert(refs, added) < 0) {
+ git_pkt_free((git_pkt *)added);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int update_refs_from_report(
+ git_vector *refs,
+ git_vector *push_specs,
+ git_vector *push_report)
+{
+ git_pkt_ref *ref;
+ push_spec *push_spec;
+ push_status *push_status;
+ size_t i, j, refs_len;
+ int cmp;
+
+ /* For each push spec we sent to the server, we should have
+ * gotten back a status packet in the push report */
+ if (push_specs->length != push_report->length) {
+ giterr_set(GITERR_NET, "report-status: protocol error");
+ return -1;
+ }
+
+ /* We require that push_specs be sorted with push_spec_rref_cmp,
+ * and that push_report be sorted with push_status_ref_cmp */
+ git_vector_sort(push_specs);
+ git_vector_sort(push_report);
+
+ git_vector_foreach(push_specs, i, push_spec) {
+ push_status = git_vector_get(push_report, i);
+
+ /* For each push spec we sent to the server, we should have
+ * gotten back a status packet in the push report which matches */
+ if (strcmp(push_spec->rref, push_status->ref)) {
+ giterr_set(GITERR_NET, "report-status: protocol error");
+ return -1;
+ }
+ }
+
+ /* We require that refs be sorted with ref_name_cmp */
+ git_vector_sort(refs);
+ i = j = 0;
+ refs_len = refs->length;
+
+ /* Merge join push_specs with refs */
+ while (i < push_specs->length && j < refs_len) {
+ push_spec = git_vector_get(push_specs, i);
+ push_status = git_vector_get(push_report, i);
+ ref = git_vector_get(refs, j);
+
+ cmp = strcmp(push_spec->rref, ref->head.name);
+
+ /* Iterate appropriately */
+ if (cmp <= 0) i++;
+ if (cmp >= 0) j++;
+
+ /* Add case */
+ if (cmp < 0 &&
+ !push_status->msg &&
+ add_ref_from_push_spec(refs, push_spec) < 0)
+ return -1;
+
+ /* Update case, delete case */
+ if (cmp == 0 &&
+ !push_status->msg)
+ git_oid_cpy(&ref->head.oid, &push_spec->loid);
+ }
+
+ for (; i < push_specs->length; i++) {
+ push_spec = git_vector_get(push_specs, i);
+ push_status = git_vector_get(push_report, i);
+
+ /* Add case */
+ if (!push_status->msg &&
+ add_ref_from_push_spec(refs, push_spec) < 0)
+ return -1;
+ }
+
+ /* Remove any refs which we updated to have a zero OID. */
+ git_vector_rforeach(refs, i, ref) {
+ if (git_oid_iszero(&ref->head.oid)) {
+ git_vector_remove(refs, i);
+ git_pkt_free((git_pkt *)ref);
+ }
+ }
+
+ git_vector_sort(refs);
+
+ return 0;
+}
+
+struct push_packbuilder_payload
+{
+ git_smart_subtransport_stream *stream;
+ git_packbuilder *pb;
+ git_push_transfer_progress cb;
+ void *cb_payload;
+ size_t last_bytes;
+ double last_progress_report_time;
+};
+
+static int stream_thunk(void *buf, size_t size, void *data)
+{
+ int error = 0;
+ struct push_packbuilder_payload *payload = data;
+
+ if ((error = payload->stream->write(payload->stream, (const char *)buf, size)) < 0)
+ return error;
+
+ if (payload->cb) {
+ double current_time = git__timer();
+ payload->last_bytes += size;
+
+ if ((current_time - payload->last_progress_report_time) >= MIN_PROGRESS_UPDATE_INTERVAL) {
+ payload->last_progress_report_time = current_time;
+ error = payload->cb(payload->pb->nr_written, payload->pb->nr_objects, payload->last_bytes, payload->cb_payload);
+ }
+ }
+
+ return error;
+}
+
+int git_smart__push(git_transport *transport, git_push *push)
+{
+ transport_smart *t = (transport_smart *)transport;
+ struct push_packbuilder_payload packbuilder_payload = {0};
+ git_buf pktline = GIT_BUF_INIT;
+ int error = 0, need_pack = 0;
+ push_spec *spec;
+ unsigned int i;
+
+ packbuilder_payload.pb = push->pb;
+
+ if (push->transfer_progress_cb) {
+ packbuilder_payload.cb = push->transfer_progress_cb;
+ packbuilder_payload.cb_payload = push->transfer_progress_cb_payload;
+ }
+
+#ifdef PUSH_DEBUG
+{
+ git_remote_head *head;
+ char hex[41]; hex[40] = '\0';
+
+ git_vector_foreach(&push->remote->refs, i, head) {
+ git_oid_fmt(hex, &head->oid);
+ fprintf(stderr, "%s (%s)\n", hex, head->name);
+ }
+
+ git_vector_foreach(&push->specs, i, spec) {
+ git_oid_fmt(hex, &spec->roid);
+ fprintf(stderr, "%s (%s) -> ", hex, spec->lref);
+ git_oid_fmt(hex, &spec->loid);
+ fprintf(stderr, "%s (%s)\n", hex, spec->rref ?
+ spec->rref : spec->lref);
+ }
+}
+#endif
+
+ /*
+ * Figure out if we need to send a packfile; which is in all
+ * cases except when we only send delete commands
+ */
+ git_vector_foreach(&push->specs, i, spec) {
+ if (spec->lref) {
+ need_pack = 1;
+ break;
+ }
+ }
+
+ if ((error = git_smart__get_push_stream(t, &packbuilder_payload.stream)) < 0 ||
+ (error = gen_pktline(&pktline, push)) < 0 ||
+ (error = packbuilder_payload.stream->write(packbuilder_payload.stream, git_buf_cstr(&pktline), git_buf_len(&pktline))) < 0)
+ goto done;
+
+ if (need_pack &&
+ (error = git_packbuilder_foreach(push->pb, &stream_thunk, &packbuilder_payload)) < 0)
+ goto done;
+
+ /* If we sent nothing or the server doesn't support report-status, then
+ * we consider the pack to have been unpacked successfully */
+ if (!push->specs.length || !push->report_status)
+ push->unpack_ok = 1;
+ else if ((error = parse_report(t, push)) < 0)
+ goto done;
+
+ /* If progress is being reported write the final report */
+ if (push->transfer_progress_cb) {
+ error = push->transfer_progress_cb(
+ push->pb->nr_written,
+ push->pb->nr_objects,
+ packbuilder_payload.last_bytes,
+ push->transfer_progress_cb_payload);
+
+ if (error < 0)
+ goto done;
+ }
+
+ if (push->status.length) {
+ error = update_refs_from_report(&t->refs, &push->specs, &push->status);
+ if (error < 0)
+ goto done;
+
+ error = git_smart__update_heads(t, NULL);
+ }
+
+done:
+ git_buf_free(&pktline);
+ return error;
+}
diff --git a/src/transports/ssh.c b/src/transports/ssh.c
new file mode 100644
index 000000000..b403727c9
--- /dev/null
+++ b/src/transports/ssh.c
@@ -0,0 +1,617 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "buffer.h"
+#include "netops.h"
+#include "smart.h"
+
+#ifdef GIT_SSH
+
+#include <libssh2.h>
+
+#define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport)
+
+static const char prefix_ssh[] = "ssh://";
+static const char cmd_uploadpack[] = "git-upload-pack";
+static const char cmd_receivepack[] = "git-receive-pack";
+
+typedef struct {
+ git_smart_subtransport_stream parent;
+ gitno_socket socket;
+ LIBSSH2_SESSION *session;
+ LIBSSH2_CHANNEL *channel;
+ const char *cmd;
+ char *url;
+ unsigned sent_command : 1;
+} ssh_stream;
+
+typedef struct {
+ git_smart_subtransport parent;
+ transport_smart *owner;
+ ssh_stream *current_stream;
+ git_cred *cred;
+} ssh_subtransport;
+
+static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg)
+{
+ char *ssherr;
+ libssh2_session_last_error(session, &ssherr, NULL, 0);
+
+ giterr_set(GITERR_SSH, "%s: %s", errmsg, ssherr);
+}
+
+/*
+ * Create a git protocol request.
+ *
+ * For example: git-upload-pack '/libgit2/libgit2'
+ */
+static int gen_proto(git_buf *request, const char *cmd, const char *url)
+{
+ char *repo;
+ int len;
+
+ if (!git__prefixcmp(url, prefix_ssh)) {
+ url = url + strlen(prefix_ssh);
+ repo = strchr(url, '/');
+ } else {
+ repo = strchr(url, ':');
+ if (repo) repo++;
+ }
+
+ if (!repo) {
+ giterr_set(GITERR_NET, "Malformed git protocol URL");
+ return -1;
+ }
+
+ len = strlen(cmd) + 1 /* Space */ + 1 /* Quote */ + strlen(repo) + 1 /* Quote */ + 1;
+
+ git_buf_grow(request, len);
+ git_buf_printf(request, "%s '%s'", cmd, repo);
+ git_buf_putc(request, '\0');
+
+ if (git_buf_oom(request))
+ return -1;
+
+ return 0;
+}
+
+static int send_command(ssh_stream *s)
+{
+ int error;
+ git_buf request = GIT_BUF_INIT;
+
+ error = gen_proto(&request, s->cmd, s->url);
+ if (error < 0)
+ goto cleanup;
+
+ error = libssh2_channel_exec(s->channel, request.ptr);
+ if (error < LIBSSH2_ERROR_NONE) {
+ ssh_error(s->session, "SSH could not execute request");
+ goto cleanup;
+ }
+
+ s->sent_command = 1;
+
+cleanup:
+ git_buf_free(&request);
+ return error;
+}
+
+static int ssh_stream_read(
+ git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read)
+{
+ int rc;
+ ssh_stream *s = (ssh_stream *)stream;
+
+ *bytes_read = 0;
+
+ if (!s->sent_command && send_command(s) < 0)
+ return -1;
+
+ if ((rc = libssh2_channel_read(s->channel, buffer, buf_size)) < LIBSSH2_ERROR_NONE) {
+ ssh_error(s->session, "SSH could not read data");;
+ return -1;
+ }
+
+ *bytes_read = rc;
+
+ return 0;
+}
+
+static int ssh_stream_write(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ ssh_stream *s = (ssh_stream *)stream;
+
+ if (!s->sent_command && send_command(s) < 0)
+ return -1;
+
+ if (libssh2_channel_write(s->channel, buffer, len) < LIBSSH2_ERROR_NONE) {
+ ssh_error(s->session, "SSH could not write data");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void ssh_stream_free(git_smart_subtransport_stream *stream)
+{
+ ssh_stream *s = (ssh_stream *)stream;
+ ssh_subtransport *t = OWNING_SUBTRANSPORT(s);
+ int ret;
+
+ GIT_UNUSED(ret);
+
+ t->current_stream = NULL;
+
+ if (s->channel) {
+ libssh2_channel_close(s->channel);
+ libssh2_channel_free(s->channel);
+ s->channel = NULL;
+ }
+
+ if (s->session) {
+ libssh2_session_free(s->session);
+ s->session = NULL;
+ }
+
+ if (s->socket.socket) {
+ (void)gitno_close(&s->socket);
+ /* can't do anything here with error return value */
+ }
+
+ git__free(s->url);
+ git__free(s);
+}
+
+static int ssh_stream_alloc(
+ ssh_subtransport *t,
+ const char *url,
+ const char *cmd,
+ git_smart_subtransport_stream **stream)
+{
+ ssh_stream *s;
+
+ assert(stream);
+
+ s = git__calloc(sizeof(ssh_stream), 1);
+ GITERR_CHECK_ALLOC(s);
+
+ s->parent.subtransport = &t->parent;
+ s->parent.read = ssh_stream_read;
+ s->parent.write = ssh_stream_write;
+ s->parent.free = ssh_stream_free;
+
+ s->cmd = cmd;
+
+ s->url = git__strdup(url);
+ if (!s->url) {
+ git__free(s);
+ return -1;
+ }
+
+ *stream = &s->parent;
+ return 0;
+}
+
+static int git_ssh_extract_url_parts(
+ char **host,
+ char **username,
+ const char *url)
+{
+ char *colon, *at;
+ const char *start;
+
+ colon = strchr(url, ':');
+
+
+ at = strchr(url, '@');
+ if (at) {
+ start = at + 1;
+ *username = git__substrdup(url, at - url);
+ GITERR_CHECK_ALLOC(*username);
+ } else {
+ start = url;
+ *username = NULL;
+ }
+
+ if (colon == NULL || (colon < start)) {
+ giterr_set(GITERR_NET, "Malformed URL");
+ return -1;
+ }
+
+ *host = git__substrdup(start, colon - start);
+ GITERR_CHECK_ALLOC(*host);
+
+ return 0;
+}
+
+static int ssh_agent_auth(LIBSSH2_SESSION *session, git_cred_ssh_key *c) {
+ int rc = LIBSSH2_ERROR_NONE;
+
+ struct libssh2_agent_publickey *curr, *prev = NULL;
+
+ LIBSSH2_AGENT *agent = libssh2_agent_init(session);
+
+ if (agent == NULL)
+ return -1;
+
+ rc = libssh2_agent_connect(agent);
+
+ if (rc != LIBSSH2_ERROR_NONE)
+ goto shutdown;
+
+ rc = libssh2_agent_list_identities(agent);
+
+ if (rc != LIBSSH2_ERROR_NONE)
+ goto shutdown;
+
+ while (1) {
+ rc = libssh2_agent_get_identity(agent, &curr, prev);
+
+ if (rc < 0)
+ goto shutdown;
+
+ if (rc == 1)
+ goto shutdown;
+
+ rc = libssh2_agent_userauth(agent, c->username, curr);
+
+ if (rc == 0)
+ break;
+
+ prev = curr;
+ }
+
+shutdown:
+ libssh2_agent_disconnect(agent);
+ libssh2_agent_free(agent);
+
+ return rc;
+}
+
+static int _git_ssh_authenticate_session(
+ LIBSSH2_SESSION* session,
+ git_cred* cred)
+{
+ int rc;
+
+ do {
+ switch (cred->credtype) {
+ case GIT_CREDTYPE_USERPASS_PLAINTEXT: {
+ git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
+ rc = libssh2_userauth_password(session, c->username, c->password);
+ break;
+ }
+ case GIT_CREDTYPE_SSH_KEY: {
+ git_cred_ssh_key *c = (git_cred_ssh_key *)cred;
+
+ if (c->privatekey)
+ rc = libssh2_userauth_publickey_fromfile(
+ session, c->username, c->publickey,
+ c->privatekey, c->passphrase);
+ else
+ rc = ssh_agent_auth(session, c);
+
+ break;
+ }
+ case GIT_CREDTYPE_SSH_CUSTOM: {
+ git_cred_ssh_custom *c = (git_cred_ssh_custom *)cred;
+
+ rc = libssh2_userauth_publickey(
+ session, c->username, (const unsigned char *)c->publickey,
+ c->publickey_len, c->sign_callback, &c->payload);
+ break;
+ }
+ case GIT_CREDTYPE_SSH_INTERACTIVE: {
+ void **abstract = libssh2_session_abstract(session);
+ git_cred_ssh_interactive *c = (git_cred_ssh_interactive *)cred;
+
+ /* ideally, we should be able to set this by calling
+ * libssh2_session_init_ex() instead of libssh2_session_init().
+ * libssh2's API is inconsistent here i.e. libssh2_userauth_publickey()
+ * allows you to pass the `abstract` as part of the call, whereas
+ * libssh2_userauth_keyboard_interactive() does not!
+ *
+ * The only way to set the `abstract` pointer is by calling
+ * libssh2_session_abstract(), which will replace the existing
+ * pointer as is done below. This is safe for now (at time of writing),
+ * but may not be valid in future.
+ */
+ *abstract = c->payload;
+
+ rc = libssh2_userauth_keyboard_interactive(
+ session, c->username, c->prompt_callback);
+ break;
+ }
+ default:
+ rc = LIBSSH2_ERROR_AUTHENTICATION_FAILED;
+ }
+ } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
+
+ if (rc != LIBSSH2_ERROR_NONE) {
+ ssh_error(session, "Failed to authenticate SSH session");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int _git_ssh_session_create(
+ LIBSSH2_SESSION** session,
+ gitno_socket socket)
+{
+ int rc = 0;
+ LIBSSH2_SESSION* s;
+
+ assert(session);
+
+ s = libssh2_session_init();
+ if (!s) {
+ giterr_set(GITERR_NET, "Failed to initialize SSH session");
+ return -1;
+ }
+
+ do {
+ rc = libssh2_session_startup(s, socket.socket);
+ } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc);
+
+ if (rc != LIBSSH2_ERROR_NONE) {
+ ssh_error(s, "Failed to start SSH session");
+ libssh2_session_free(s);
+ return -1;
+ }
+
+ libssh2_session_set_blocking(s, 1);
+
+ *session = s;
+
+ return 0;
+}
+
+static int _git_ssh_setup_conn(
+ ssh_subtransport *t,
+ const char *url,
+ const char *cmd,
+ git_smart_subtransport_stream **stream)
+{
+ char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL;
+ const char *default_port="22";
+ int no_callback = 0;
+ ssh_stream *s;
+ LIBSSH2_SESSION* session=NULL;
+ LIBSSH2_CHANNEL* channel=NULL;
+
+ *stream = NULL;
+ if (ssh_stream_alloc(t, url, cmd, stream) < 0)
+ return -1;
+
+ s = (ssh_stream *)*stream;
+
+ if (!git__prefixcmp(url, prefix_ssh)) {
+ if (gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, default_port) < 0)
+ goto on_error;
+ } else {
+ if (git_ssh_extract_url_parts(&host, &user, url) < 0)
+ goto on_error;
+ port = git__strdup(default_port);
+ GITERR_CHECK_ALLOC(port);
+ }
+
+ if (gitno_connect(&s->socket, host, port, 0) < 0)
+ goto on_error;
+
+ if (user && pass) {
+ if (git_cred_userpass_plaintext_new(&t->cred, user, pass) < 0)
+ goto on_error;
+ } else if (!t->owner->cred_acquire_cb) {
+ no_callback = 1;
+ } else {
+ int error;
+ error = t->owner->cred_acquire_cb(&t->cred, t->owner->url, user,
+ GIT_CREDTYPE_USERPASS_PLAINTEXT |
+ GIT_CREDTYPE_SSH_KEY | GIT_CREDTYPE_SSH_CUSTOM |
+ GIT_CREDTYPE_SSH_INTERACTIVE,
+ t->owner->cred_acquire_payload);
+
+ if (error == GIT_PASSTHROUGH)
+ no_callback = 1;
+ else if (error < 0)
+ goto on_error;
+ else if (!t->cred) {
+ giterr_set(GITERR_SSH, "Callback failed to initialize SSH credentials");
+ goto on_error;
+ }
+ }
+
+ if (no_callback) {
+ giterr_set(GITERR_SSH, "authentication required but no callback set");
+ goto on_error;
+ }
+
+ assert(t->cred);
+
+ if (_git_ssh_session_create(&session, s->socket) < 0)
+ goto on_error;
+
+ if (_git_ssh_authenticate_session(session, t->cred) < 0)
+ goto on_error;
+
+ channel = libssh2_channel_open_session(session);
+ if (!channel) {
+ ssh_error(session, "Failed to open SSH channel");
+ goto on_error;
+ }
+
+ libssh2_channel_set_blocking(channel, 1);
+
+ s->session = session;
+ s->channel = channel;
+
+ t->current_stream = s;
+ git__free(host);
+ git__free(port);
+ git__free(path);
+ git__free(user);
+ git__free(pass);
+
+ return 0;
+
+on_error:
+ s->session = NULL;
+ s->channel = NULL;
+ t->current_stream = NULL;
+
+ if (*stream)
+ ssh_stream_free(*stream);
+
+ git__free(host);
+ git__free(port);
+ git__free(user);
+ git__free(pass);
+
+ if (session)
+ libssh2_session_free(session);
+
+ return -1;
+}
+
+static int ssh_uploadpack_ls(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ if (_git_ssh_setup_conn(t, url, cmd_uploadpack, stream) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int ssh_uploadpack(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ GIT_UNUSED(url);
+
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
+ }
+
+ giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK");
+ return -1;
+}
+
+static int ssh_receivepack_ls(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ if (_git_ssh_setup_conn(t, url, cmd_receivepack, stream) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int ssh_receivepack(
+ ssh_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ GIT_UNUSED(url);
+
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
+ }
+
+ giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK");
+ return -1;
+}
+
+static int _ssh_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *subtransport,
+ const char *url,
+ git_smart_service_t action)
+{
+ ssh_subtransport *t = (ssh_subtransport *) subtransport;
+
+ switch (action) {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ return ssh_uploadpack_ls(t, url, stream);
+
+ case GIT_SERVICE_UPLOADPACK:
+ return ssh_uploadpack(t, url, stream);
+
+ case GIT_SERVICE_RECEIVEPACK_LS:
+ return ssh_receivepack_ls(t, url, stream);
+
+ case GIT_SERVICE_RECEIVEPACK:
+ return ssh_receivepack(t, url, stream);
+ }
+
+ *stream = NULL;
+ return -1;
+}
+
+static int _ssh_close(git_smart_subtransport *subtransport)
+{
+ ssh_subtransport *t = (ssh_subtransport *) subtransport;
+
+ assert(!t->current_stream);
+
+ GIT_UNUSED(t);
+
+ return 0;
+}
+
+static void _ssh_free(git_smart_subtransport *subtransport)
+{
+ ssh_subtransport *t = (ssh_subtransport *) subtransport;
+
+ assert(!t->current_stream);
+
+ git__free(t);
+}
+#endif
+
+int git_smart_subtransport_ssh(
+ git_smart_subtransport **out, git_transport *owner)
+{
+#ifdef GIT_SSH
+ ssh_subtransport *t;
+
+ assert(out);
+
+ t = git__calloc(sizeof(ssh_subtransport), 1);
+ GITERR_CHECK_ALLOC(t);
+
+ t->owner = (transport_smart *)owner;
+ t->parent.action = _ssh_action;
+ t->parent.close = _ssh_close;
+ t->parent.free = _ssh_free;
+
+ *out = (git_smart_subtransport *) t;
+ return 0;
+#else
+ GIT_UNUSED(owner);
+
+ assert(out);
+ *out = NULL;
+
+ giterr_set(GITERR_INVALID, "Cannot create SSH transport. Library was built without SSH support");
+ return -1;
+#endif
+}
diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c
new file mode 100644
index 000000000..bd9509cd4
--- /dev/null
+++ b/src/transports/winhttp.c
@@ -0,0 +1,1219 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifdef GIT_WINHTTP
+
+#include "git2.h"
+#include "git2/transport.h"
+#include "buffer.h"
+#include "posix.h"
+#include "netops.h"
+#include "smart.h"
+#include "remote.h"
+#include "repository.h"
+
+#include <winhttp.h>
+#pragma comment(lib, "winhttp")
+
+#include <strsafe.h>
+
+/* For IInternetSecurityManager zone check */
+#include <objbase.h>
+#include <urlmon.h>
+
+/* For UuidCreate */
+#pragma comment(lib, "rpcrt4")
+
+#define WIDEN2(s) L ## s
+#define WIDEN(s) WIDEN2(s)
+
+#define MAX_CONTENT_TYPE_LEN 100
+#define WINHTTP_OPTION_PEERDIST_EXTENSION_STATE 109
+#define CACHED_POST_BODY_BUF_SIZE 4096
+#define UUID_LENGTH_CCH 32
+
+static const char *prefix_http = "http://";
+static const char *prefix_https = "https://";
+static const char *upload_pack_service = "upload-pack";
+static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack";
+static const char *upload_pack_service_url = "/git-upload-pack";
+static const char *receive_pack_service = "receive-pack";
+static const char *receive_pack_ls_service_url = "/info/refs?service=git-receive-pack";
+static const char *receive_pack_service_url = "/git-receive-pack";
+static const wchar_t *get_verb = L"GET";
+static const wchar_t *post_verb = L"POST";
+static const wchar_t *pragma_nocache = L"Pragma: no-cache";
+static const wchar_t *transfer_encoding = L"Transfer-Encoding: chunked";
+static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
+ SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
+ SECURITY_FLAG_IGNORE_UNKNOWN_CA;
+
+#define OWNING_SUBTRANSPORT(s) ((winhttp_subtransport *)(s)->parent.subtransport)
+
+typedef enum {
+ GIT_WINHTTP_AUTH_BASIC = 1,
+ GIT_WINHTTP_AUTH_NEGOTIATE = 2,
+} winhttp_authmechanism_t;
+
+typedef struct {
+ git_smart_subtransport_stream parent;
+ const char *service;
+ const char *service_url;
+ const wchar_t *verb;
+ HINTERNET request;
+ wchar_t *request_uri;
+ char *chunk_buffer;
+ unsigned chunk_buffer_len;
+ HANDLE post_body;
+ DWORD post_body_len;
+ unsigned sent_request : 1,
+ received_response : 1,
+ chunked : 1;
+} winhttp_stream;
+
+typedef struct {
+ git_smart_subtransport parent;
+ transport_smart *owner;
+ gitno_connection_data connection_data;
+ git_cred *cred;
+ git_cred *url_cred;
+ int auth_mechanism;
+ HINTERNET session;
+ HINTERNET connection;
+} 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;
+
+ if ((wide_len = git__utf8_to_16_alloc(&wide, git_buf_cstr(&buf))) < 0) {
+ 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 apply_default_credentials(HINTERNET request)
+{
+ /* Either the caller explicitly requested that default credentials be passed,
+ * or our fallback credential callback was invoked and checked that the target
+ * URI was in the appropriate Internet Explorer security zone. By setting this
+ * flag, we guarantee that the credentials are delivered by WinHTTP. The default
+ * is "medium" which applies to the intranet and sounds like it would correspond
+ * to Internet Explorer security zones, but in fact does not. */
+ DWORD data = WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW;
+
+ if (!WinHttpSetOption(request, WINHTTP_OPTION_AUTOLOGON_POLICY, &data, sizeof(DWORD)))
+ return -1;
+
+ return 0;
+}
+
+static int fallback_cred_acquire_cb(
+ git_cred **cred,
+ const char *url,
+ const char *username_from_url,
+ unsigned int allowed_types,
+ void *payload)
+{
+ int error = 1;
+
+ /* If the target URI supports integrated Windows authentication
+ * as an authentication mechanism */
+ if (GIT_CREDTYPE_DEFAULT & allowed_types) {
+ wchar_t *wide_url;
+
+ /* Convert URL to wide characters */
+ if (git__utf8_to_16_alloc(&wide_url, url) < 0) {
+ giterr_set(GITERR_OS, "Failed to convert string to wide form");
+ return -1;
+ }
+
+ if (SUCCEEDED(CoInitializeEx(NULL, COINIT_MULTITHREADED))) {
+ IInternetSecurityManager* pISM;
+
+ /* And if the target URI is in the My Computer, Intranet, or Trusted zones */
+ if (SUCCEEDED(CoCreateInstance(&CLSID_InternetSecurityManager, NULL,
+ CLSCTX_ALL, &IID_IInternetSecurityManager, (void **)&pISM))) {
+ DWORD dwZone;
+
+ if (SUCCEEDED(pISM->lpVtbl->MapUrlToZone(pISM, wide_url, &dwZone, 0)) &&
+ (URLZONE_LOCAL_MACHINE == dwZone ||
+ URLZONE_INTRANET == dwZone ||
+ URLZONE_TRUSTED == dwZone)) {
+ git_cred *existing = *cred;
+
+ if (existing)
+ existing->free(existing);
+
+ /* Then use default Windows credentials to authenticate this request */
+ error = git_cred_default_new(cred);
+ }
+
+ pISM->lpVtbl->Release(pISM);
+ }
+
+ CoUninitialize();
+ }
+
+ git__free(wide_url);
+ }
+
+ return error;
+}
+
+static int winhttp_stream_connect(winhttp_stream *s)
+{
+ winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
+ git_buf buf = GIT_BUF_INIT;
+ char *proxy_url = NULL;
+ wchar_t ct[MAX_CONTENT_TYPE_LEN];
+ wchar_t *types[] = { L"*/*", NULL };
+ BOOL peerdist = FALSE;
+ int error = -1;
+ unsigned long disable_redirects = WINHTTP_DISABLE_REDIRECTS;
+
+ /* Prepare URL */
+ git_buf_printf(&buf, "%s%s", t->connection_data.path, s->service_url);
+
+ if (git_buf_oom(&buf))
+ return -1;
+
+ /* Convert URL to wide characters */
+ if (git__utf8_to_16_alloc(&s->request_uri, git_buf_cstr(&buf)) < 0) {
+ giterr_set(GITERR_OS, "Failed to convert string to wide form");
+ goto on_error;
+ }
+
+ /* Establish request */
+ s->request = WinHttpOpenRequest(
+ t->connection,
+ s->verb,
+ s->request_uri,
+ NULL,
+ WINHTTP_NO_REFERER,
+ types,
+ t->connection_data.use_ssl ? WINHTTP_FLAG_SECURE : 0);
+
+ if (!s->request) {
+ giterr_set(GITERR_OS, "Failed to open request");
+ goto on_error;
+ }
+
+ /* Set proxy if necessary */
+ if (git_remote__get_http_proxy(t->owner->owner, !!t->connection_data.use_ssl, &proxy_url) < 0)
+ goto on_error;
+
+ if (proxy_url) {
+ WINHTTP_PROXY_INFO proxy_info;
+ wchar_t *proxy_wide;
+
+ /* Convert URL to wide characters */
+ int proxy_wide_len = git__utf8_to_16_alloc(&proxy_wide, proxy_url);
+
+ if (proxy_wide_len < 0) {
+ giterr_set(GITERR_OS, "Failed to convert string to wide form");
+ goto on_error;
+ }
+
+ /* Strip any trailing forward slash on the proxy URL;
+ * WinHTTP doesn't like it if one is present */
+ if (proxy_wide_len > 1 && L'/' == proxy_wide[proxy_wide_len - 2])
+ proxy_wide[proxy_wide_len - 2] = L'\0';
+
+ proxy_info.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
+ proxy_info.lpszProxy = proxy_wide;
+ proxy_info.lpszProxyBypass = NULL;
+
+ if (!WinHttpSetOption(s->request,
+ WINHTTP_OPTION_PROXY,
+ &proxy_info,
+ sizeof(WINHTTP_PROXY_INFO))) {
+ giterr_set(GITERR_OS, "Failed to set proxy");
+ git__free(proxy_wide);
+ goto on_error;
+ }
+
+ git__free(proxy_wide);
+ }
+
+ /* Disable WinHTTP redirects so we can handle them manually. Why, you ask?
+ * http://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/b2ff8879-ab9f-4218-8f09-16d25dff87ae
+ */
+ if (!WinHttpSetOption(s->request,
+ WINHTTP_OPTION_DISABLE_FEATURE,
+ &disable_redirects,
+ sizeof(disable_redirects))) {
+ giterr_set(GITERR_OS, "Failed to disable redirects");
+ 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;
+ }
+
+ if (post_verb == s->verb) {
+ /* Send Content-Type and Accept headers -- only necessary on a POST */
+ git_buf_clear(&buf);
+ if (git_buf_printf(&buf,
+ "Content-Type: application/x-git-%s-request",
+ s->service) < 0)
+ goto on_error;
+
+ if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)) < 0) {
+ giterr_set(GITERR_OS, "Failed to convert content-type to wide characters");
+ goto on_error;
+ }
+
+ if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L,
+ WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) {
+ giterr_set(GITERR_OS, "Failed to add a header to the request");
+ goto on_error;
+ }
+
+ git_buf_clear(&buf);
+ if (git_buf_printf(&buf,
+ "Accept: application/x-git-%s-result",
+ s->service) < 0)
+ goto on_error;
+
+ if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)) < 0) {
+ giterr_set(GITERR_OS, "Failed to convert accept header to wide characters");
+ goto on_error;
+ }
+
+ if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L,
+ WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) {
+ giterr_set(GITERR_OS, "Failed to add a header to the request");
+ goto on_error;
+ }
+ }
+
+ /* If requested, disable certificate validation */
+ if (t->connection_data.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;
+ else if (t->cred &&
+ t->cred->credtype == GIT_CREDTYPE_DEFAULT &&
+ t->auth_mechanism == GIT_WINHTTP_AUTH_NEGOTIATE &&
+ apply_default_credentials(s->request) < 0)
+ goto on_error;
+
+ /* If no other credentials have been applied and the URL has username and
+ * password, use those */
+ if (!t->cred && t->connection_data.user && t->connection_data.pass) {
+ if (!t->url_cred &&
+ git_cred_userpass_plaintext_new(&t->url_cred, t->connection_data.user, t->connection_data.pass) < 0)
+ goto on_error;
+ if (apply_basic_credential(s->request, t->url_cred) < 0)
+ goto on_error;
+ }
+
+ /* We've done everything up to calling WinHttpSendRequest. */
+
+ error = 0;
+
+on_error:
+ git__free(proxy_url);
+ git_buf_free(&buf);
+ return error;
+}
+
+static int parse_unauthorized_response(
+ HINTERNET request,
+ int *allowed_types,
+ int *auth_mechanism)
+{
+ DWORD supported, first, target;
+
+ *allowed_types = 0;
+ *auth_mechanism = 0;
+
+ /* WinHttpQueryHeaders() must be called before WinHttpQueryAuthSchemes().
+ * We can assume this was already done, since we know we are unauthorized.
+ */
+ if (!WinHttpQueryAuthSchemes(request, &supported, &first, &target)) {
+ giterr_set(GITERR_OS, "Failed to parse supported auth schemes");
+ return -1;
+ }
+
+ if (WINHTTP_AUTH_SCHEME_BASIC & supported) {
+ *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
+ *auth_mechanism = GIT_WINHTTP_AUTH_BASIC;
+ }
+
+ if ((WINHTTP_AUTH_SCHEME_NTLM & supported) ||
+ (WINHTTP_AUTH_SCHEME_NEGOTIATE & supported)) {
+ *allowed_types |= GIT_CREDTYPE_DEFAULT;
+ *auth_mechanism = GIT_WINHTTP_AUTH_NEGOTIATE;
+ }
+
+ return 0;
+}
+
+static int write_chunk(HINTERNET request, const char *buffer, size_t len)
+{
+ DWORD bytes_written;
+ git_buf buf = GIT_BUF_INIT;
+
+ /* Chunk header */
+ git_buf_printf(&buf, "%X\r\n", len);
+
+ if (git_buf_oom(&buf))
+ return -1;
+
+ if (!WinHttpWriteData(request,
+ git_buf_cstr(&buf), (DWORD)git_buf_len(&buf),
+ &bytes_written)) {
+ git_buf_free(&buf);
+ giterr_set(GITERR_OS, "Failed to write chunk header");
+ return -1;
+ }
+
+ git_buf_free(&buf);
+
+ /* Chunk body */
+ if (!WinHttpWriteData(request,
+ buffer, (DWORD)len,
+ &bytes_written)) {
+ giterr_set(GITERR_OS, "Failed to write chunk");
+ return -1;
+ }
+
+ /* Chunk footer */
+ if (!WinHttpWriteData(request,
+ "\r\n", 2,
+ &bytes_written)) {
+ giterr_set(GITERR_OS, "Failed to write chunk footer");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int winhttp_connect(
+ winhttp_subtransport *t,
+ const char *url)
+{
+ wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")";
+ wchar_t *wide_host;
+ int32_t port;
+ const char *default_port = "80";
+ int error = -1;
+
+ /* Prepare port */
+ if (git__strtol32(&port, t->connection_data.port, NULL, 10) < 0)
+ return -1;
+
+ /* Prepare host */
+ if (git__utf8_to_16_alloc(&wide_host, t->connection_data.host) < 0) {
+ giterr_set(GITERR_OS, "Unable to convert host to wide characters");
+ return -1;
+ }
+
+ /* 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");
+ goto on_error;
+ }
+
+ /* Establish connection */
+ t->connection = WinHttpConnect(
+ t->session,
+ wide_host,
+ (INTERNET_PORT) port,
+ 0);
+
+ if (!t->connection) {
+ giterr_set(GITERR_OS, "Failed to connect to host");
+ goto on_error;
+ }
+
+ error = 0;
+
+on_error:
+ git__free(wide_host);
+
+ 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;
+ char replay_count = 0;
+
+replay:
+ /* Enforce a reasonable cap on the number of replays */
+ if (++replay_count >= 7) {
+ giterr_set(GITERR_NET, "Too many redirects or authentication replays");
+ return -1;
+ }
+
+ /* Connect if necessary */
+ if (!s->request && winhttp_stream_connect(s) < 0)
+ return -1;
+
+ if (!s->received_response) {
+ DWORD status_code, status_code_length, content_type_length, bytes_written;
+ char expected_content_type_8[MAX_CONTENT_TYPE_LEN];
+ wchar_t expected_content_type[MAX_CONTENT_TYPE_LEN], content_type[MAX_CONTENT_TYPE_LEN];
+
+ if (!s->sent_request) {
+ if (!WinHttpSendRequest(s->request,
+ WINHTTP_NO_ADDITIONAL_HEADERS, 0,
+ WINHTTP_NO_REQUEST_DATA, 0,
+ s->post_body_len, 0)) {
+ giterr_set(GITERR_OS, "Failed to send request");
+ return -1;
+ }
+
+ s->sent_request = 1;
+ }
+
+ if (s->chunked) {
+ assert(s->verb == post_verb);
+
+ /* Flush, if necessary */
+ if (s->chunk_buffer_len > 0 &&
+ write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+
+ /* Write the final chunk. */
+ if (!WinHttpWriteData(s->request,
+ "0\r\n\r\n", 5,
+ &bytes_written)) {
+ giterr_set(GITERR_OS, "Failed to write final chunk");
+ return -1;
+ }
+ }
+ else if (s->post_body) {
+ char *buffer;
+ DWORD len = s->post_body_len, bytes_read;
+
+ if (INVALID_SET_FILE_POINTER == SetFilePointer(s->post_body,
+ 0, 0, FILE_BEGIN) &&
+ NO_ERROR != GetLastError()) {
+ giterr_set(GITERR_OS, "Failed to reset file pointer");
+ return -1;
+ }
+
+ buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE);
+
+ while (len > 0) {
+ DWORD bytes_written;
+
+ if (!ReadFile(s->post_body, buffer,
+ min(CACHED_POST_BODY_BUF_SIZE, len),
+ &bytes_read, NULL) ||
+ !bytes_read) {
+ git__free(buffer);
+ giterr_set(GITERR_OS, "Failed to read from temp file");
+ return -1;
+ }
+
+ if (!WinHttpWriteData(s->request, buffer,
+ bytes_read, &bytes_written)) {
+ git__free(buffer);
+ giterr_set(GITERR_OS, "Failed to write data");
+ return -1;
+ }
+
+ len -= bytes_read;
+ assert(bytes_read == bytes_written);
+ }
+
+ git__free(buffer);
+
+ /* Eagerly close the temp file */
+ CloseHandle(s->post_body);
+ s->post_body = NULL;
+ }
+
+ if (!WinHttpReceiveResponse(s->request, 0)) {
+ giterr_set(GITERR_OS, "Failed to receive response");
+ return -1;
+ }
+
+ /* Verify that we got a 200 back */
+ status_code_length = sizeof(status_code);
+
+ if (!WinHttpQueryHeaders(s->request,
+ WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
+ WINHTTP_HEADER_NAME_BY_INDEX,
+ &status_code, &status_code_length,
+ WINHTTP_NO_HEADER_INDEX)) {
+ giterr_set(GITERR_OS, "Failed to retrieve status code");
+ return -1;
+ }
+
+ /* The implementation of WinHTTP prior to Windows 7 will not
+ * redirect to an identical URI. Some Git hosters use self-redirects
+ * as part of their DoS mitigation strategy. Check first to see if we
+ * have a redirect status code, and that we haven't already streamed
+ * a post body. (We can't replay a streamed POST.) */
+ if (!s->chunked &&
+ (HTTP_STATUS_MOVED == status_code ||
+ HTTP_STATUS_REDIRECT == status_code ||
+ (HTTP_STATUS_REDIRECT_METHOD == status_code &&
+ get_verb == s->verb) ||
+ HTTP_STATUS_REDIRECT_KEEP_VERB == status_code)) {
+
+ /* Check for Windows 7. This workaround is only necessary on
+ * Windows Vista and earlier. Windows 7 is version 6.1. */
+ wchar_t *location;
+ DWORD location_length;
+ char *location8;
+
+ /* OK, fetch the Location header from the redirect. */
+ if (WinHttpQueryHeaders(s->request,
+ WINHTTP_QUERY_LOCATION,
+ WINHTTP_HEADER_NAME_BY_INDEX,
+ WINHTTP_NO_OUTPUT_BUFFER,
+ &location_length,
+ WINHTTP_NO_HEADER_INDEX) ||
+ GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ giterr_set(GITERR_OS, "Failed to read Location header");
+ return -1;
+ }
+
+ location = git__malloc(location_length);
+ GITERR_CHECK_ALLOC(location);
+
+ if (!WinHttpQueryHeaders(s->request,
+ WINHTTP_QUERY_LOCATION,
+ WINHTTP_HEADER_NAME_BY_INDEX,
+ location,
+ &location_length,
+ WINHTTP_NO_HEADER_INDEX)) {
+ giterr_set(GITERR_OS, "Failed to read Location header");
+ git__free(location);
+ return -1;
+ }
+
+ /* Convert the Location header to UTF-8 */
+ if (git__utf16_to_8_alloc(&location8, location) < 0) {
+ giterr_set(GITERR_OS, "Failed to convert Location header to UTF-8");
+ git__free(location);
+ return -1;
+ }
+
+ git__free(location);
+
+ /* Replay the request */
+ WinHttpCloseHandle(s->request);
+ s->request = NULL;
+ s->sent_request = 0;
+
+ if (!git__prefixcmp_icase(location8, prefix_https)) {
+ /* Upgrade to secure connection; disconnect and start over */
+ if (gitno_connection_data_from_url(&t->connection_data, location8, s->service_url) < 0) {
+ git__free(location8);
+ return -1;
+ }
+
+ winhttp_connect(t, location8);
+ }
+
+ git__free(location8);
+ goto replay;
+ }
+
+ /* Handle authentication failures */
+ if (HTTP_STATUS_DENIED == status_code && get_verb == s->verb) {
+ 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))) {
+ int cred_error = 1;
+
+ /* Start with the user-supplied credential callback, if present */
+ if (t->owner->cred_acquire_cb) {
+ cred_error = t->owner->cred_acquire_cb(&t->cred, t->owner->url,
+ t->connection_data.user, allowed_types, t->owner->cred_acquire_payload);
+
+ if (cred_error < 0)
+ return cred_error;
+ }
+
+ /* Invoke the fallback credentials acquisition callback if necessary */
+ if (cred_error > 0) {
+ cred_error = fallback_cred_acquire_cb(&t->cred, t->owner->url,
+ t->connection_data.user, allowed_types, NULL);
+
+ if (cred_error < 0)
+ return cred_error;
+ }
+
+ if (!cred_error) {
+ 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);
+
+ if (git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8) < 0) {
+ giterr_set(GITERR_OS, "Failed to convert expected content-type to wide characters");
+ return -1;
+ }
+
+ content_type_length = sizeof(content_type);
+
+ if (!WinHttpQueryHeaders(s->request,
+ WINHTTP_QUERY_CONTENT_TYPE,
+ WINHTTP_HEADER_NAME_BY_INDEX,
+ &content_type, &content_type_length,
+ WINHTTP_NO_HEADER_INDEX)) {
+ giterr_set(GITERR_OS, "Failed to retrieve response content-type");
+ return -1;
+ }
+
+ if (wcscmp(expected_content_type, content_type)) {
+ giterr_set(GITERR_NET, "Received unexpected content-type");
+ return -1;
+ }
+
+ s->received_response = 1;
+ }
+
+ if (!WinHttpReadData(s->request,
+ (LPVOID)buffer,
+ (DWORD)buf_size,
+ &dw_bytes_read))
+ {
+ giterr_set(GITERR_OS, "Failed to read data");
+ return -1;
+ }
+
+ *bytes_read = dw_bytes_read;
+
+ return 0;
+}
+
+static int winhttp_stream_write_single(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ winhttp_stream *s = (winhttp_stream *)stream;
+ winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
+ DWORD bytes_written;
+
+ if (!s->request && winhttp_stream_connect(s) < 0)
+ return -1;
+
+ /* This implementation of write permits only a single call. */
+ if (s->sent_request) {
+ giterr_set(GITERR_NET, "Subtransport configured for only one write");
+ return -1;
+ }
+
+ if (!WinHttpSendRequest(s->request,
+ WINHTTP_NO_ADDITIONAL_HEADERS, 0,
+ WINHTTP_NO_REQUEST_DATA, 0,
+ (DWORD)len, 0)) {
+ giterr_set(GITERR_OS, "Failed to send request");
+ return -1;
+ }
+
+ s->sent_request = 1;
+
+ if (!WinHttpWriteData(s->request,
+ (LPCVOID)buffer,
+ (DWORD)len,
+ &bytes_written)) {
+ giterr_set(GITERR_OS, "Failed to write data");
+ return -1;
+ }
+
+ assert((DWORD)len == bytes_written);
+
+ return 0;
+}
+
+static int put_uuid_string(LPWSTR buffer, size_t buffer_len_cch)
+{
+ UUID uuid;
+ RPC_STATUS status = UuidCreate(&uuid);
+ HRESULT result;
+
+ if (RPC_S_OK != status &&
+ RPC_S_UUID_LOCAL_ONLY != status &&
+ RPC_S_UUID_NO_ADDRESS != status) {
+ giterr_set(GITERR_NET, "Unable to generate name for temp file");
+ return -1;
+ }
+
+ if (buffer_len_cch < UUID_LENGTH_CCH + 1) {
+ giterr_set(GITERR_NET, "Buffer too small for name of temp file");
+ return -1;
+ }
+
+ result = StringCbPrintfW(
+ buffer, buffer_len_cch,
+ L"%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x",
+ uuid.Data1, uuid.Data2, uuid.Data3,
+ uuid.Data4[0], uuid.Data4[1], uuid.Data4[2], uuid.Data4[3],
+ uuid.Data4[4], uuid.Data4[5], uuid.Data4[6], uuid.Data4[7]);
+
+ if (FAILED(result)) {
+ giterr_set(GITERR_OS, "Unable to generate name for temp file");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int get_temp_file(LPWSTR buffer, DWORD buffer_len_cch)
+{
+ size_t len;
+
+ if (!GetTempPathW(buffer_len_cch, buffer)) {
+ giterr_set(GITERR_OS, "Failed to get temp path");
+ return -1;
+ }
+
+ len = wcslen(buffer);
+
+ if (buffer[len - 1] != '\\' && len < buffer_len_cch)
+ buffer[len++] = '\\';
+
+ if (put_uuid_string(&buffer[len], (size_t)buffer_len_cch - len) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int winhttp_stream_write_buffered(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ winhttp_stream *s = (winhttp_stream *)stream;
+ winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
+ DWORD bytes_written;
+
+ if (!s->request && winhttp_stream_connect(s) < 0)
+ return -1;
+
+ /* Buffer the payload, using a temporary file so we delegate
+ * memory management of the data to the operating system. */
+ if (!s->post_body) {
+ wchar_t temp_path[MAX_PATH + 1];
+
+ if (get_temp_file(temp_path, MAX_PATH + 1) < 0)
+ return -1;
+
+ s->post_body = CreateFileW(temp_path,
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_DELETE, NULL,
+ CREATE_NEW,
+ FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_SEQUENTIAL_SCAN,
+ NULL);
+
+ if (INVALID_HANDLE_VALUE == s->post_body) {
+ s->post_body = NULL;
+ giterr_set(GITERR_OS, "Failed to create temporary file");
+ return -1;
+ }
+ }
+
+ if (!WriteFile(s->post_body, buffer, (DWORD)len, &bytes_written, NULL)) {
+ giterr_set(GITERR_OS, "Failed to write to temporary file");
+ return -1;
+ }
+
+ assert((DWORD)len == bytes_written);
+
+ s->post_body_len += bytes_written;
+
+ return 0;
+}
+
+static int winhttp_stream_write_chunked(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
+{
+ winhttp_stream *s = (winhttp_stream *)stream;
+ winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
+
+ if (!s->request && winhttp_stream_connect(s) < 0)
+ return -1;
+
+ if (!s->sent_request) {
+ /* Send Transfer-Encoding: chunked header */
+ if (!WinHttpAddRequestHeaders(s->request,
+ transfer_encoding, (ULONG) -1L,
+ WINHTTP_ADDREQ_FLAG_ADD)) {
+ giterr_set(GITERR_OS, "Failed to add a header to the request");
+ return -1;
+ }
+
+ if (!WinHttpSendRequest(s->request,
+ WINHTTP_NO_ADDITIONAL_HEADERS, 0,
+ WINHTTP_NO_REQUEST_DATA, 0,
+ WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0)) {
+ giterr_set(GITERR_OS, "Failed to send request");
+ return -1;
+ }
+
+ s->sent_request = 1;
+ }
+
+ if (len > CACHED_POST_BODY_BUF_SIZE) {
+ /* Flush, if necessary */
+ if (s->chunk_buffer_len > 0) {
+ if (write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+ }
+
+ /* Write chunk directly */
+ if (write_chunk(s->request, buffer, len) < 0)
+ return -1;
+ }
+ else {
+ /* Append as much to the buffer as we can */
+ int count = min(CACHED_POST_BODY_BUF_SIZE - s->chunk_buffer_len, (int)len);
+
+ if (!s->chunk_buffer)
+ s->chunk_buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE);
+
+ memcpy(s->chunk_buffer + s->chunk_buffer_len, buffer, count);
+ s->chunk_buffer_len += count;
+ buffer += count;
+ len -= count;
+
+ /* Is the buffer full? If so, then flush */
+ if (CACHED_POST_BODY_BUF_SIZE == s->chunk_buffer_len) {
+ if (write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
+
+ s->chunk_buffer_len = 0;
+
+ /* Is there any remaining data from the source? */
+ if (len > 0) {
+ memcpy(s->chunk_buffer, buffer, len);
+ s->chunk_buffer_len = (unsigned int)len;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void winhttp_stream_free(git_smart_subtransport_stream *stream)
+{
+ winhttp_stream *s = (winhttp_stream *)stream;
+
+ if (s->chunk_buffer) {
+ git__free(s->chunk_buffer);
+ s->chunk_buffer = NULL;
+ }
+
+ if (s->post_body) {
+ CloseHandle(s->post_body);
+ s->post_body = NULL;
+ }
+
+ if (s->request_uri) {
+ git__free(s->request_uri);
+ s->request_uri = NULL;
+ }
+
+ if (s->request) {
+ WinHttpCloseHandle(s->request);
+ s->request = NULL;
+ }
+
+ git__free(s);
+}
+
+static int winhttp_stream_alloc(winhttp_subtransport *t, winhttp_stream **stream)
+{
+ winhttp_stream *s;
+
+ if (!stream)
+ return -1;
+
+ s = git__calloc(sizeof(winhttp_stream), 1);
+ GITERR_CHECK_ALLOC(s);
+
+ s->parent.subtransport = &t->parent;
+ s->parent.read = winhttp_stream_read;
+ s->parent.write = winhttp_stream_write_single;
+ s->parent.free = winhttp_stream_free;
+
+ *stream = s;
+
+ return 0;
+}
+
+static int winhttp_uploadpack_ls(
+ winhttp_subtransport *t,
+ winhttp_stream *s)
+{
+ s->service = upload_pack_service;
+ s->service_url = upload_pack_ls_service_url;
+ s->verb = get_verb;
+
+ return 0;
+}
+
+static int winhttp_uploadpack(
+ winhttp_subtransport *t,
+ winhttp_stream *s)
+{
+ s->service = upload_pack_service;
+ s->service_url = upload_pack_service_url;
+ s->verb = post_verb;
+
+ return 0;
+}
+
+static int winhttp_receivepack_ls(
+ winhttp_subtransport *t,
+ winhttp_stream *s)
+{
+ s->service = receive_pack_service;
+ s->service_url = receive_pack_ls_service_url;
+ s->verb = get_verb;
+
+ return 0;
+}
+
+static int winhttp_receivepack(
+ winhttp_subtransport *t,
+ winhttp_stream *s)
+{
+ /* WinHTTP only supports Transfer-Encoding: chunked
+ * on Windows Vista (NT 6.0) and higher. */
+ s->chunked = git_has_win32_version(6, 0, 0);
+
+ if (s->chunked)
+ s->parent.write = winhttp_stream_write_chunked;
+ else
+ s->parent.write = winhttp_stream_write_buffered;
+
+ s->service = receive_pack_service;
+ s->service_url = receive_pack_service_url;
+ s->verb = post_verb;
+
+ return 0;
+}
+
+static int winhttp_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *subtransport,
+ const char *url,
+ git_smart_service_t action)
+{
+ winhttp_subtransport *t = (winhttp_subtransport *)subtransport;
+ winhttp_stream *s;
+ int ret = -1;
+
+ if (!t->connection)
+ if (gitno_connection_data_from_url(&t->connection_data, url, NULL) < 0 ||
+ winhttp_connect(t, url) < 0)
+ return -1;
+
+ if (winhttp_stream_alloc(t, &s) < 0)
+ return -1;
+
+ if (!stream)
+ return -1;
+
+ switch (action)
+ {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ ret = winhttp_uploadpack_ls(t, s);
+ break;
+
+ case GIT_SERVICE_UPLOADPACK:
+ ret = winhttp_uploadpack(t, s);
+ break;
+
+ case GIT_SERVICE_RECEIVEPACK_LS:
+ ret = winhttp_receivepack_ls(t, s);
+ break;
+
+ case GIT_SERVICE_RECEIVEPACK:
+ ret = winhttp_receivepack(t, s);
+ break;
+
+ default:
+ assert(0);
+ }
+
+ if (!ret)
+ *stream = &s->parent;
+
+ return ret;
+}
+
+static int winhttp_close(git_smart_subtransport *subtransport)
+{
+ winhttp_subtransport *t = (winhttp_subtransport *)subtransport;
+ int ret = 0;
+
+ gitno_connection_data_free_ptrs(&t->connection_data);
+
+ if (t->cred) {
+ t->cred->free(t->cred);
+ t->cred = NULL;
+ }
+
+ if (t->url_cred) {
+ t->url_cred->free(t->url_cred);
+ t->url_cred = NULL;
+ }
+
+ if (t->connection) {
+ if (!WinHttpCloseHandle(t->connection)) {
+ giterr_set(GITERR_OS, "Unable to close connection");
+ ret = -1;
+ }
+
+ t->connection = NULL;
+ }
+
+ if (t->session) {
+ if (!WinHttpCloseHandle(t->session)) {
+ giterr_set(GITERR_OS, "Unable to close session");
+ ret = -1;
+ }
+
+ t->session = NULL;
+ }
+
+ return ret;
+}
+
+static void winhttp_free(git_smart_subtransport *subtransport)
+{
+ winhttp_subtransport *t = (winhttp_subtransport *)subtransport;
+
+ winhttp_close(subtransport);
+
+ git__free(t);
+}
+
+int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner)
+{
+ winhttp_subtransport *t;
+
+ if (!out)
+ return -1;
+
+ t = git__calloc(sizeof(winhttp_subtransport), 1);
+ GITERR_CHECK_ALLOC(t);
+
+ t->owner = (transport_smart *)owner;
+ t->parent.action = winhttp_action;
+ t->parent.close = winhttp_close;
+ t->parent.free = winhttp_free;
+
+ *out = (git_smart_subtransport *) t;
+ return 0;
+}
+
+#endif /* GIT_WINHTTP */
diff --git a/src/tree-cache.c b/src/tree-cache.c
new file mode 100644
index 000000000..49afd6e49
--- /dev/null
+++ b/src/tree-cache.c
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "tree-cache.h"
+
+static git_tree_cache *find_child(
+ const git_tree_cache *tree, const char *path, const char *end)
+{
+ size_t i, dirlen = end ? (size_t)(end - path) : strlen(path);
+
+ for (i = 0; i < tree->children_count; ++i) {
+ git_tree_cache *child = tree->children[i];
+
+ if (child->namelen == dirlen && !memcmp(path, child->name, dirlen))
+ return child;
+ }
+
+ return NULL;
+}
+
+void git_tree_cache_invalidate_path(git_tree_cache *tree, const char *path)
+{
+ const char *ptr = path, *end;
+
+ if (tree == NULL)
+ return;
+
+ tree->entries = -1;
+
+ while (ptr != NULL) {
+ end = strchr(ptr, '/');
+
+ if (end == NULL) /* End of path */
+ break;
+
+ tree = find_child(tree, ptr, end);
+ if (tree == NULL) /* We don't have that tree */
+ return;
+
+ tree->entries = -1;
+ ptr = end + 1;
+ }
+}
+
+const git_tree_cache *git_tree_cache_get(const git_tree_cache *tree, const char *path)
+{
+ const char *ptr = path, *end;
+
+ if (tree == NULL) {
+ return NULL;
+ }
+
+ while (1) {
+ end = strchr(ptr, '/');
+
+ tree = find_child(tree, ptr, end);
+ if (tree == NULL) /* Can't find it */
+ return NULL;
+
+ if (end == NULL || *end + 1 == '\0')
+ return tree;
+
+ ptr = end + 1;
+ }
+}
+
+static int read_tree_internal(git_tree_cache **out,
+ const char **buffer_in, const char *buffer_end, git_tree_cache *parent)
+{
+ git_tree_cache *tree = NULL;
+ const char *name_start, *buffer;
+ int count;
+ size_t name_len;
+
+ buffer = name_start = *buffer_in;
+
+ if ((buffer = memchr(buffer, '\0', buffer_end - buffer)) == NULL)
+ goto corrupted;
+
+ if (++buffer >= buffer_end)
+ goto corrupted;
+
+ name_len = strlen(name_start);
+ tree = git__malloc(sizeof(git_tree_cache) + name_len + 1);
+ GITERR_CHECK_ALLOC(tree);
+
+ memset(tree, 0x0, sizeof(git_tree_cache));
+ tree->parent = parent;
+
+ /* NUL-terminated tree name */
+ tree->namelen = name_len;
+ memcpy(tree->name, name_start, name_len);
+ tree->name[name_len] = '\0';
+
+ /* Blank-terminated ASCII decimal number of entries in this tree */
+ if (git__strtol32(&count, buffer, &buffer, 10) < 0)
+ goto corrupted;
+
+ tree->entries = count;
+
+ if (*buffer != ' ' || ++buffer >= buffer_end)
+ goto corrupted;
+
+ /* Number of children of the tree, newline-terminated */
+ if (git__strtol32(&count, buffer, &buffer, 10) < 0 || count < 0)
+ goto corrupted;
+
+ tree->children_count = count;
+
+ if (*buffer != '\n' || ++buffer > buffer_end)
+ goto corrupted;
+
+ /* The SHA1 is only there if it's not invalidated */
+ if (tree->entries >= 0) {
+ /* 160-bit SHA-1 for this tree and it's children */
+ if (buffer + GIT_OID_RAWSZ > buffer_end)
+ goto corrupted;
+
+ git_oid_fromraw(&tree->oid, (const unsigned char *)buffer);
+ buffer += GIT_OID_RAWSZ;
+ }
+
+ /* Parse children: */
+ if (tree->children_count > 0) {
+ unsigned int i;
+
+ tree->children = git__malloc(tree->children_count * sizeof(git_tree_cache *));
+ GITERR_CHECK_ALLOC(tree->children);
+
+ memset(tree->children, 0x0, tree->children_count * sizeof(git_tree_cache *));
+
+ for (i = 0; i < tree->children_count; ++i) {
+ if (read_tree_internal(&tree->children[i], &buffer, buffer_end, tree) < 0)
+ goto corrupted;
+ }
+ }
+
+ *buffer_in = buffer;
+ *out = tree;
+ return 0;
+
+ corrupted:
+ git_tree_cache_free(tree);
+ giterr_set(GITERR_INDEX, "Corrupted TREE extension in index");
+ return -1;
+}
+
+int git_tree_cache_read(git_tree_cache **tree, const char *buffer, size_t buffer_size)
+{
+ const char *buffer_end = buffer + buffer_size;
+
+ if (read_tree_internal(tree, &buffer, buffer_end, NULL) < 0)
+ return -1;
+
+ if (buffer < buffer_end) {
+ giterr_set(GITERR_INDEX, "Corrupted TREE extension in index (unexpected trailing data)");
+ return -1;
+ }
+
+ return 0;
+}
+
+void git_tree_cache_free(git_tree_cache *tree)
+{
+ unsigned int i;
+
+ if (tree == NULL)
+ return;
+
+ if (tree->children != NULL) {
+ for (i = 0; i < tree->children_count; ++i)
+ git_tree_cache_free(tree->children[i]);
+
+ git__free(tree->children);
+ }
+
+ git__free(tree);
+}
diff --git a/src/tree-cache.h b/src/tree-cache.h
new file mode 100644
index 000000000..90c82dbbf
--- /dev/null
+++ b/src/tree-cache.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_tree_cache_h__
+#define INCLUDE_tree_cache_h__
+
+#include "common.h"
+#include "git2/oid.h"
+
+struct git_tree_cache {
+ struct git_tree_cache *parent;
+ struct git_tree_cache **children;
+ size_t children_count;
+
+ ssize_t entries;
+ git_oid oid;
+ size_t namelen;
+ char name[GIT_FLEX_ARRAY];
+};
+
+typedef struct git_tree_cache git_tree_cache;
+
+int git_tree_cache_read(git_tree_cache **tree, const char *buffer, size_t buffer_size);
+void git_tree_cache_invalidate_path(git_tree_cache *tree, const char *path);
+const git_tree_cache *git_tree_cache_get(const git_tree_cache *tree, const char *path);
+void git_tree_cache_free(git_tree_cache *tree);
+
+#endif
diff --git a/src/tree.c b/src/tree.c
index d993d549a..b64efe460 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -1,26 +1,8 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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"
@@ -28,13 +10,93 @@
#include "tree.h"
#include "git2/repository.h"
#include "git2/object.h"
+#include "fileops.h"
+#include "tree-cache.h"
+#include "index.h"
#define DEFAULT_TREE_SIZE 16
-#define MAX_FILEMODE 0777777
#define MAX_FILEMODE_BYTES 6
-static int valid_attributes(const int attributes) {
- return attributes >= 0 && attributes <= MAX_FILEMODE;
+static bool valid_filemode(const int filemode)
+{
+ return (filemode == GIT_FILEMODE_TREE
+ || filemode == GIT_FILEMODE_BLOB
+ || filemode == GIT_FILEMODE_BLOB_EXECUTABLE
+ || filemode == GIT_FILEMODE_LINK
+ || filemode == GIT_FILEMODE_COMMIT);
+}
+
+GIT_INLINE(git_filemode_t) normalize_filemode(git_filemode_t filemode)
+{
+ /* Tree bits set, but it's not a commit */
+ if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_TREE)
+ return GIT_FILEMODE_TREE;
+
+ /* If any of the x bits are set */
+ if (GIT_PERMS_IS_EXEC(filemode))
+ return GIT_FILEMODE_BLOB_EXECUTABLE;
+
+ /* 16XXXX means commit */
+ if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_COMMIT)
+ return GIT_FILEMODE_COMMIT;
+
+ /* 12XXXX means commit */
+ if (GIT_MODE_TYPE(filemode) == GIT_FILEMODE_LINK)
+ return GIT_FILEMODE_LINK;
+
+ /* Otherwise, return a blob */
+ return GIT_FILEMODE_BLOB;
+}
+
+static int valid_entry_name(const char *filename)
+{
+ return *filename != '\0' &&
+ strchr(filename, '/') == NULL &&
+ (*filename != '.' ||
+ (strcmp(filename, ".") != 0 &&
+ strcmp(filename, "..") != 0 &&
+ strcmp(filename, DOT_GIT) != 0));
+}
+
+static int entry_sort_cmp(const void *a, const void *b)
+{
+ const git_tree_entry *e1 = (const git_tree_entry *)a;
+ const git_tree_entry *e2 = (const git_tree_entry *)b;
+
+ return git_path_cmp(
+ e1->filename, e1->filename_len, git_tree_entry__is_tree(e1),
+ e2->filename, e2->filename_len, git_tree_entry__is_tree(e2),
+ git__strncmp);
+}
+
+int git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2)
+{
+ return entry_sort_cmp(e1, e2);
+}
+
+int git_tree_entry_icmp(const git_tree_entry *e1, const git_tree_entry *e2)
+{
+ return git_path_cmp(
+ e1->filename, e1->filename_len, git_tree_entry__is_tree(e1),
+ e2->filename, e2->filename_len, git_tree_entry__is_tree(e2),
+ git__strncasecmp);
+}
+
+static git_tree_entry *alloc_entry(const char *filename)
+{
+ git_tree_entry *entry = NULL;
+ size_t filename_len = strlen(filename);
+
+ entry = git__malloc(sizeof(git_tree_entry) + filename_len + 1);
+ if (!entry)
+ return NULL;
+
+ memset(entry, 0x0, sizeof(git_tree_entry));
+ memcpy(entry->filename, filename, filename_len);
+ entry->filename[filename_len] = 0;
+ entry->filename_len = filename_len;
+
+ return entry;
}
struct tree_key_search {
@@ -42,67 +104,143 @@ struct tree_key_search {
size_t filename_len;
};
-int entry_search_cmp(const void *key, const void *array_member)
+static int homing_search_cmp(const void *key, const void *array_member)
{
const struct tree_key_search *ksearch = key;
const git_tree_entry *entry = array_member;
- int result =
- git_futils_cmp_path(
- ksearch->filename, ksearch->filename_len, entry->attr & 040000,
- entry->filename, entry->filename_len, entry->attr & 040000);
+ const size_t len1 = ksearch->filename_len;
+ const size_t len2 = entry->filename_len;
- return result ? result : ((int)ksearch->filename_len - (int)entry->filename_len);
+ return memcmp(
+ ksearch->filename,
+ entry->filename,
+ len1 < len2 ? len1 : len2
+ );
}
-int entry_sort_cmp(const void *a, const void *b)
+/*
+ * Search for an entry in a given tree.
+ *
+ * Note that this search is performed in two steps because
+ * of the way tree entries are sorted internally in git:
+ *
+ * Entries in a tree are not sorted alphabetically; two entries
+ * with the same root prefix will have different positions
+ * depending on whether they are folders (subtrees) or normal files.
+ *
+ * Consequently, it is not possible to find an entry on the tree
+ * with a binary search if you don't know whether the filename
+ * you're looking for is a folder or a normal file.
+ *
+ * To work around this, we first perform a homing binary search
+ * on the tree, using the minimal length root prefix of our filename.
+ * Once the comparisons for this homing search start becoming
+ * ambiguous because of folder vs file sorting, we look linearly
+ * around the area for our target file.
+ */
+static int tree_key_search(
+ size_t *at_pos, git_vector *entries, const char *filename, size_t filename_len)
{
- const git_tree_entry *entry_a = (const git_tree_entry *)(a);
- const git_tree_entry *entry_b = (const git_tree_entry *)(b);
+ struct tree_key_search ksearch;
+ const git_tree_entry *entry;
+ size_t homing, i;
+
+ ksearch.filename = filename;
+ ksearch.filename_len = filename_len;
- return git_futils_cmp_path(
- entry_a->filename, entry_a->filename_len, entry_a->attr & 040000,
- entry_b->filename, entry_b->filename_len, entry_b->attr & 040000);
+ /* Initial homing search; find an entry on the tree with
+ * the same prefix as the filename we're looking for */
+ if (git_vector_bsearch2(&homing, entries, &homing_search_cmp, &ksearch) < 0)
+ return GIT_ENOTFOUND; /* just a signal error; not passed back to user */
+
+ /* We found a common prefix. Look forward as long as
+ * there are entries that share the common prefix */
+ for (i = homing; i < entries->length; ++i) {
+ entry = entries->contents[i];
+
+ if (homing_search_cmp(&ksearch, entry) < 0)
+ break;
+
+ if (entry->filename_len == filename_len &&
+ memcmp(filename, entry->filename, filename_len) == 0) {
+ if (at_pos)
+ *at_pos = i;
+
+ return 0;
+ }
+ }
+
+ /* If we haven't found our filename yet, look backwards
+ * too as long as we have entries with the same prefix */
+ if (homing > 0) {
+ i = homing - 1;
+
+ do {
+ entry = entries->contents[i];
+
+ if (homing_search_cmp(&ksearch, entry) > 0)
+ break;
+
+ if (entry->filename_len == filename_len &&
+ memcmp(filename, entry->filename, filename_len) == 0) {
+ if (at_pos)
+ *at_pos = i;
+
+ return 0;
+ }
+ } while (i-- > 0);
+ }
+
+ /* The filename doesn't exist at all */
+ return GIT_ENOTFOUND;
}
-static int build_ksearch(struct tree_key_search *ksearch, const char *path)
+void git_tree_entry_free(git_tree_entry *entry)
{
- size_t len = strlen(path);
+ if (entry == NULL)
+ return;
- if (len && path[len - 1] == '/')
- len--;
+ git__free(entry);
+}
- if (len == 0 || memchr(path, '/', len) != NULL)
- return GIT_ERROR;
+int git_tree_entry_dup(git_tree_entry **dest, const git_tree_entry *source)
+{
+ size_t total_size;
+ git_tree_entry *copy;
+
+ assert(source);
+
+ total_size = sizeof(git_tree_entry) + source->filename_len + 1;
+
+ copy = git__malloc(total_size);
+ GITERR_CHECK_ALLOC(copy);
- ksearch->filename = path;
- ksearch->filename_len = len;
+ memcpy(copy, source, total_size);
- return GIT_SUCCESS;
+ *dest = copy;
+ return 0;
}
-void git_tree__free(git_tree *tree)
+void git_tree__free(void *_tree)
{
- unsigned int i;
+ git_tree *tree = _tree;
+ size_t i;
+ git_tree_entry *e;
- for (i = 0; i < tree->entries.length; ++i) {
- git_tree_entry *e;
- e = git_vector_get(&tree->entries, i);
-
- free(e->filename);
- free(e);
- }
+ git_vector_foreach(&tree->entries, i, e)
+ git_tree_entry_free(e);
git_vector_free(&tree->entries);
- free(tree);
+ git__free(tree);
}
-const git_oid *git_tree_id(git_tree *c)
+git_filemode_t git_tree_entry_filemode(const git_tree_entry *entry)
{
- return git_object_id((git_object *)c);
+ return normalize_filemode(entry->attr);
}
-unsigned int git_tree_entry_attributes(const git_tree_entry *entry)
+git_filemode_t git_tree_entry_filemode_raw(const git_tree_entry *entry)
{
return entry->attr;
}
@@ -131,75 +269,148 @@ git_otype git_tree_entry_type(const git_tree_entry *entry)
return GIT_OBJ_BLOB;
}
-int git_tree_entry_2object(git_object **object_out, git_repository *repo, const git_tree_entry *entry)
+int git_tree_entry_to_object(
+ git_object **object_out,
+ git_repository *repo,
+ const git_tree_entry *entry)
{
assert(entry && object_out);
return git_object_lookup(object_out, repo, &entry->oid, GIT_OBJ_ANY);
}
-const git_tree_entry *git_tree_entry_byname(git_tree *tree, const char *filename)
+static const git_tree_entry *entry_fromname(
+ const git_tree *tree, const char *name, size_t name_len)
{
- int idx;
- struct tree_key_search ksearch;
+ size_t idx;
- assert(tree && filename);
-
- if (build_ksearch(&ksearch, filename) < GIT_SUCCESS)
- return NULL;
+ /* be safe when we cast away constness - i.e. don't trigger a sort */
+ assert(git_vector_is_sorted(&tree->entries));
- idx = git_vector_bsearch2(&tree->entries, entry_search_cmp, &ksearch);
- if (idx == GIT_ENOTFOUND)
+ if (tree_key_search(&idx, (git_vector *)&tree->entries, name, name_len) < 0)
return NULL;
return git_vector_get(&tree->entries, idx);
}
-const git_tree_entry *git_tree_entry_byindex(git_tree *tree, unsigned int idx)
+const git_tree_entry *git_tree_entry_byname(
+ const git_tree *tree, const char *filename)
+{
+ assert(tree && filename);
+ return entry_fromname(tree, filename, strlen(filename));
+}
+
+const git_tree_entry *git_tree_entry_byindex(
+ const git_tree *tree, size_t idx)
{
assert(tree);
return git_vector_get(&tree->entries, idx);
}
-unsigned int git_tree_entrycount(git_tree *tree)
+const git_tree_entry *git_tree_entry_byid(
+ const git_tree *tree, const git_oid *id)
+{
+ size_t i;
+ const git_tree_entry *e;
+
+ assert(tree);
+
+ git_vector_foreach(&tree->entries, i, e) {
+ if (memcmp(&e->oid.id, &id->id, sizeof(id->id)) == 0)
+ return e;
+ }
+
+ return NULL;
+}
+
+int git_tree__prefix_position(const git_tree *tree, const char *path)
+{
+ const git_vector *entries = &tree->entries;
+ struct tree_key_search ksearch;
+ size_t at_pos;
+
+ if (!path)
+ return 0;
+
+ ksearch.filename = path;
+ ksearch.filename_len = strlen(path);
+
+ /* be safe when we cast away constness - i.e. don't trigger a sort */
+ assert(git_vector_is_sorted(&tree->entries));
+
+ /* Find tree entry with appropriate prefix */
+ git_vector_bsearch2(
+ &at_pos, (git_vector *)entries, &homing_search_cmp, &ksearch);
+
+ for (; at_pos < entries->length; ++at_pos) {
+ const git_tree_entry *entry = entries->contents[at_pos];
+ if (homing_search_cmp(&ksearch, entry) < 0)
+ break;
+ }
+
+ for (; at_pos > 0; --at_pos) {
+ const git_tree_entry *entry = entries->contents[at_pos - 1];
+ if (homing_search_cmp(&ksearch, entry) > 0)
+ break;
+ }
+
+ return (int)at_pos;
+}
+
+size_t git_tree_entrycount(const git_tree *tree)
{
assert(tree);
return tree->entries.length;
}
-static int tree_parse_buffer(git_tree *tree, const char *buffer, const char *buffer_end)
+unsigned int git_treebuilder_entrycount(git_treebuilder *bld)
{
- int error = GIT_SUCCESS;
+ assert(bld);
+ return (unsigned int)bld->entrycount;
+}
- if (git_vector_init(&tree->entries, DEFAULT_TREE_SIZE, entry_sort_cmp) < GIT_SUCCESS)
- return GIT_ENOMEM;
+static int tree_error(const char *str, const char *path)
+{
+ if (path)
+ giterr_set(GITERR_TREE, "%s - %s", str, path);
+ else
+ giterr_set(GITERR_TREE, "%s", str);
+ return -1;
+}
+
+int git_tree__parse(void *_tree, git_odb_object *odb_obj)
+{
+ git_tree *tree = _tree;
+ const char *buffer = git_odb_object_data(odb_obj);
+ const char *buffer_end = buffer + git_odb_object_size(odb_obj);
+
+ if (git_vector_init(&tree->entries, DEFAULT_TREE_SIZE, entry_sort_cmp) < 0)
+ return -1;
while (buffer < buffer_end) {
git_tree_entry *entry;
+ int attr;
- entry = git__calloc(1, sizeof(git_tree_entry));
- if (entry == NULL) {
- error = GIT_ENOMEM;
- break;
- }
+ if (git__strtol32(&attr, buffer, &buffer, 8) < 0 || !buffer)
+ return tree_error("Failed to parse tree. Can't parse filemode", NULL);
- if (git_vector_insert(&tree->entries, entry) < GIT_SUCCESS)
- return GIT_ENOMEM;
+ if (*buffer++ != ' ')
+ return tree_error("Failed to parse tree. Object is corrupted", NULL);
- if (git__strtol32((long *)&entry->attr, buffer, &buffer, 8) < GIT_SUCCESS)
- return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tree. Can't parse attributes");
+ if (memchr(buffer, 0, buffer_end - buffer) == NULL)
+ return tree_error("Failed to parse tree. Object is corrupted", NULL);
- if (*buffer++ != ' ') {
- error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse tree. Object it corrupted");
- break;
- }
+ /** Allocate the entry and store it in the entries vector */
+ {
+ entry = alloc_entry(buffer);
+ GITERR_CHECK_ALLOC(entry);
- if (memchr(buffer, 0, buffer_end - buffer) == NULL) {
- error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse tree. Object it corrupted");
- break;
- }
+ if (git_vector_insert(&tree->entries, entry) < 0) {
+ git__free(entry);
+ return -1;
+ }
- entry->filename = git__strdup(buffer);
- entry->filename_len = strlen(buffer);
+ entry->attr = attr;
+ }
while (buffer < buffer_end && *buffer != 0)
buffer++;
@@ -210,224 +421,281 @@ static int tree_parse_buffer(git_tree *tree, const char *buffer, const char *buf
buffer += GIT_OID_RAWSZ;
}
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse buffer");
-}
+ git_vector_sort(&tree->entries);
-int git_tree__parse(git_tree *tree, git_odb_object *obj)
-{
- assert(tree);
- return tree_parse_buffer(tree, (char *)obj->raw.data, (char *)obj->raw.data + obj->raw.len);
+ return 0;
}
-static int write_index_entry(char *buffer, int mode, const char *path, size_t path_len, const git_oid *oid)
+static size_t find_next_dir(const char *dirname, git_index *index, size_t start)
{
- int written;
- written = sprintf(buffer, "%o %.*s%c", mode, (int)path_len, path, 0);
- memcpy(buffer + written, &oid->id, GIT_OID_RAWSZ);
- return written + GIT_OID_RAWSZ;
+ size_t dirlen, i, entries = git_index_entrycount(index);
+
+ dirlen = strlen(dirname);
+ for (i = start; i < entries; ++i) {
+ const git_index_entry *entry = git_index_get_byindex(index, i);
+ if (strlen(entry->path) < dirlen ||
+ memcmp(entry->path, dirname, dirlen) ||
+ (dirlen > 0 && entry->path[dirlen] != '/')) {
+ break;
+ }
+ }
+
+ return i;
}
-static int write_index(git_oid *oid, git_index *index, const char *base, int baselen, int entry_no, int maxentries)
+static int append_entry(
+ git_treebuilder *bld,
+ const char *filename,
+ const git_oid *id,
+ git_filemode_t filemode)
{
- size_t size, offset;
- char *buffer;
- int nr, error;
-
- /* Guess at some random initial size */
- size = maxentries * 40;
- buffer = git__malloc(size);
- if (buffer == NULL)
- return GIT_ENOMEM;
+ git_tree_entry *entry;
- offset = 0;
+ if (!valid_entry_name(filename))
+ return tree_error("Failed to insert entry. Invalid name for a tree entry", filename);
- for (nr = entry_no; nr < maxentries; ++nr) {
- git_index_entry *entry = git_index_get(index, nr);
+ entry = alloc_entry(filename);
+ GITERR_CHECK_ALLOC(entry);
- const char *pathname = entry->path, *filename, *dirname;
- int pathlen = strlen(pathname), entrylen;
+ git_oid_cpy(&entry->oid, id);
+ entry->attr = (uint16_t)filemode;
- unsigned int write_mode;
- git_oid subtree_oid;
- git_oid *write_oid;
+ if (git_vector_insert_sorted(&bld->entries, entry, NULL) < 0) {
+ git__free(entry);
+ return -1;
+ }
- /* Did we hit the end of the directory? Return how many we wrote */
- if (baselen >= pathlen || memcmp(base, pathname, baselen) != 0)
- break;
+ bld->entrycount++;
+ return 0;
+}
- /* Do we have _further_ subdirectories? */
- filename = pathname + baselen;
- dirname = strchr(filename, '/');
+static int write_tree(
+ git_oid *oid,
+ git_repository *repo,
+ git_index *index,
+ const char *dirname,
+ size_t start)
+{
+ git_treebuilder *bld = NULL;
+ size_t i, entries = git_index_entrycount(index);
+ int error;
+ size_t dirname_len = strlen(dirname);
+ const git_tree_cache *cache;
- write_oid = &entry->oid;
- write_mode = entry->mode;
+ cache = git_tree_cache_get(index->tree, dirname);
+ if (cache != NULL && cache->entries >= 0){
+ git_oid_cpy(oid, &cache->oid);
+ return (int)find_next_dir(dirname, index, start);
+ }
- if (dirname) {
- int subdir_written;
+ if ((error = git_treebuilder_create(&bld, NULL)) < 0 || bld == NULL)
+ return -1;
+
+ /*
+ * This loop is unfortunate, but necessary. The index doesn't have
+ * any directores, so we need to handle that manually, and we
+ * need to keep track of the current position.
+ */
+ for (i = start; i < entries; ++i) {
+ const git_index_entry *entry = git_index_get_byindex(index, i);
+ const char *filename, *next_slash;
+
+ /*
+ * If we've left our (sub)tree, exit the loop and return. The
+ * first check is an early out (and security for the
+ * third). The second check is a simple prefix comparison. The
+ * third check catches situations where there is a directory
+ * win32/sys and a file win32mmap.c. Without it, the following
+ * code believes there is a file win32/mmap.c
+ */
+ if (strlen(entry->path) < dirname_len ||
+ memcmp(entry->path, dirname, dirname_len) ||
+ (dirname_len > 0 && entry->path[dirname_len] != '/')) {
+ break;
+ }
-#if 0
- if (entry->mode != S_IFDIR) {
- free(buffer);
- return GIT_EOBJCORRUPTED;
+ filename = entry->path + dirname_len;
+ if (*filename == '/')
+ filename++;
+ next_slash = strchr(filename, '/');
+ if (next_slash) {
+ git_oid sub_oid;
+ int written;
+ char *subdir, *last_comp;
+
+ subdir = git__strndup(entry->path, next_slash - entry->path);
+ GITERR_CHECK_ALLOC(subdir);
+
+ /* Write out the subtree */
+ written = write_tree(&sub_oid, repo, index, subdir, i);
+ if (written < 0) {
+ git__free(subdir);
+ goto on_error;
+ } else {
+ i = written - 1; /* -1 because of the loop increment */
}
-#endif
- subdir_written = write_index(&subtree_oid, index, pathname, dirname - pathname + 1, nr, maxentries);
- if (subdir_written < GIT_SUCCESS) {
- free(buffer);
- return subdir_written;
+ /*
+ * We need to figure out what we want toinsert
+ * into this tree. If we're traversing
+ * deps/zlib/, then we only want to write
+ * 'zlib' into the tree.
+ */
+ last_comp = strrchr(subdir, '/');
+ if (last_comp) {
+ last_comp++; /* Get rid of the '/' */
+ } else {
+ last_comp = subdir;
}
- nr = subdir_written - 1;
-
- /* Now we need to write out the directory entry into this tree.. */
- pathlen = dirname - pathname;
- write_oid = &subtree_oid;
- write_mode = S_IFDIR;
+ error = append_entry(bld, last_comp, &sub_oid, S_IFDIR);
+ git__free(subdir);
+ if (error < 0)
+ goto on_error;
+ } else {
+ error = append_entry(bld, filename, &entry->id, entry->mode);
+ if (error < 0)
+ goto on_error;
}
+ }
- entrylen = pathlen - baselen;
- if (offset + entrylen + 32 > size) {
- size = alloc_nr(offset + entrylen + 32);
- buffer = git__realloc(buffer, size);
+ if (git_treebuilder_write(oid, repo, bld) < 0)
+ goto on_error;
- if (buffer == NULL)
- return GIT_ENOMEM;
- }
+ git_treebuilder_free(bld);
+ return (int)i;
- offset += write_index_entry(buffer + offset, write_mode, filename, entrylen, write_oid);
+on_error:
+ git_treebuilder_free(bld);
+ return -1;
+}
+
+int git_tree__write_index(
+ git_oid *oid, git_index *index, git_repository *repo)
+{
+ int ret;
+ bool old_ignore_case = false;
+
+ assert(oid && index && repo);
+
+ if (git_index_has_conflicts(index)) {
+ giterr_set(GITERR_INDEX,
+ "Cannot create a tree from a not fully merged index.");
+ return GIT_EUNMERGED;
}
- error = git_odb_write(oid, index->repository->db, buffer, offset, GIT_OBJ_TREE);
- free(buffer);
+ if (index->tree != NULL && index->tree->entries >= 0) {
+ git_oid_cpy(oid, &index->tree->oid);
+ return 0;
+ }
- return (error == GIT_SUCCESS) ? nr : git__rethrow(error, "Failed to write index");
-}
+ /* The tree cache didn't help us; we'll have to write
+ * out a tree. If the index is ignore_case, we must
+ * make it case-sensitive for the duration of the tree-write
+ * operation. */
-int git_tree_create_fromindex(git_oid *oid, git_index *index)
-{
- int error;
+ if (index->ignore_case) {
+ old_ignore_case = true;
+ git_index__set_ignore_case(index, false);
+ }
- if (index->repository == NULL)
- return git__throw(GIT_EBAREINDEX, "Failed to create tree. The index file is not backed up by an existing repository");
+ ret = write_tree(oid, repo, index, "", 0);
- error = write_index(oid, index, "", 0, 0, git_index_entrycount(index));
- return (error < GIT_SUCCESS) ? git__rethrow(error, "Failed to create tree") : GIT_SUCCESS;
-}
+ if (old_ignore_case)
+ git_index__set_ignore_case(index, true);
-static void sort_entries(git_treebuilder *bld)
-{
- git_vector_sort(&bld->entries);
+ return ret < 0 ? ret : 0;
}
int git_treebuilder_create(git_treebuilder **builder_p, const git_tree *source)
{
git_treebuilder *bld;
- unsigned int i, source_entries = DEFAULT_TREE_SIZE;
+ size_t i, source_entries = DEFAULT_TREE_SIZE;
assert(builder_p);
bld = git__calloc(1, sizeof(git_treebuilder));
- if (bld == NULL)
- return GIT_ENOMEM;
+ GITERR_CHECK_ALLOC(bld);
if (source != NULL)
source_entries = source->entries.length;
- if (git_vector_init(&bld->entries, source_entries, entry_sort_cmp) < GIT_SUCCESS) {
- free(bld);
- return GIT_ENOMEM;
- }
+ if (git_vector_init(&bld->entries, source_entries, entry_sort_cmp) < 0)
+ goto on_error;
if (source != NULL) {
- bld->entry_count = source_entries;
- for (i = 0; i < source->entries.length; ++i) {
- git_tree_entry *entry_src = source->entries.contents[i];
- git_tree_entry *entry = git__calloc(1, sizeof(git_tree_entry));
-
- if (entry == NULL) {
- git_treebuilder_free(bld);
- return GIT_ENOMEM;
- }
-
- entry->filename = git__strdup(entry_src->filename);
-
- if (entry->filename == NULL) {
- free(entry);
- git_treebuilder_free(bld);
- return GIT_ENOMEM;
- }
-
- entry->filename_len = entry_src->filename_len;
- git_oid_cpy(&entry->oid, &entry_src->oid);
- entry->attr = entry_src->attr;
-
- git_vector_insert(&bld->entries, entry);
+ git_tree_entry *entry_src;
+
+ git_vector_foreach(&source->entries, i, entry_src) {
+ if (append_entry(
+ bld, entry_src->filename,
+ &entry_src->oid,
+ entry_src->attr) < 0)
+ goto on_error;
}
}
*builder_p = bld;
- return GIT_SUCCESS;
+ return 0;
+
+on_error:
+ git_treebuilder_free(bld);
+ return -1;
}
-int git_treebuilder_insert(git_tree_entry **entry_out, git_treebuilder *bld, const char *filename, const git_oid *id, unsigned int attributes)
+int git_treebuilder_insert(
+ const git_tree_entry **entry_out,
+ git_treebuilder *bld,
+ const char *filename,
+ const git_oid *id,
+ git_filemode_t filemode)
{
git_tree_entry *entry;
- int pos;
- struct tree_key_search ksearch;
+ size_t pos;
assert(bld && id && filename);
- if (!valid_attributes(attributes))
- return git__throw(GIT_ERROR, "Failed to insert entry. Invalid attributes");
+ if (!valid_filemode(filemode))
+ return tree_error("Failed to insert entry. Invalid filemode for file", filename);
- if (build_ksearch(&ksearch, filename) < GIT_SUCCESS)
- return git__throw(GIT_ERROR, "Failed to insert entry. Invalid filename '%s'", filename);
+ if (!valid_entry_name(filename))
+ return tree_error("Failed to insert entry. Invalid name for a tree entry", filename);
- if ((pos = git_vector_bsearch2(&bld->entries, entry_search_cmp, &ksearch)) != GIT_ENOTFOUND) {
+ if (!tree_key_search(&pos, &bld->entries, filename, strlen(filename))) {
entry = git_vector_get(&bld->entries, pos);
if (entry->removed) {
entry->removed = 0;
- bld->entry_count++;
+ bld->entrycount++;
}
} else {
- if ((entry = git__malloc(sizeof(git_tree_entry))) == NULL)
- return GIT_ENOMEM;
+ entry = alloc_entry(filename);
+ GITERR_CHECK_ALLOC(entry);
- memset(entry, 0x0, sizeof(git_tree_entry));
- entry->filename = git__strdup(filename);
- entry->filename_len = strlen(entry->filename);
+ if (git_vector_insert_sorted(&bld->entries, entry, NULL) < 0) {
+ git__free(entry);
+ return -1;
+ }
- bld->entry_count++;
+ bld->entrycount++;
}
git_oid_cpy(&entry->oid, id);
- entry->attr = attributes;
+ entry->attr = filemode;
- if (pos == GIT_ENOTFOUND) {
- if (git_vector_insert(&bld->entries, entry) < 0)
- return GIT_ENOMEM;
- }
-
- if (entry_out != NULL)
+ if (entry_out)
*entry_out = entry;
- return GIT_SUCCESS;
+ return 0;
}
static git_tree_entry *treebuilder_get(git_treebuilder *bld, const char *filename)
{
- int idx;
+ size_t idx;
git_tree_entry *entry;
- struct tree_key_search ksearch;
assert(bld && filename);
- if (build_ksearch(&ksearch, filename) < GIT_SUCCESS)
- return NULL;
-
- idx = git_vector_bsearch2(&bld->entries, entry_search_cmp, &ksearch);
- if (idx == GIT_ENOTFOUND)
+ if (tree_key_search(&idx, &bld->entries, filename, strlen(filename)) < 0)
return NULL;
entry = git_vector_get(&bld->entries, idx);
@@ -447,28 +715,29 @@ int git_treebuilder_remove(git_treebuilder *bld, const char *filename)
git_tree_entry *remove_ptr = treebuilder_get(bld, filename);
if (remove_ptr == NULL || remove_ptr->removed)
- return git__throw(GIT_ENOTFOUND, "Failed to remove entry. File isn't in the tree");
+ return tree_error("Failed to remove entry. File isn't in the tree", filename);
remove_ptr->removed = 1;
- bld->entry_count--;
- return GIT_SUCCESS;
+ bld->entrycount--;
+ return 0;
}
int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *bld)
{
- unsigned int i;
- int error;
+ int error = 0;
+ size_t i;
git_buf tree = GIT_BUF_INIT;
+ git_odb *odb;
assert(bld);
- sort_entries(bld);
+ git_vector_sort(&bld->entries);
/* Grow the buffer beforehand to an estimated size */
- git_buf_grow(&tree, bld->entries.length * 72);
+ error = git_buf_grow(&tree, bld->entries.length * 72);
- for (i = 0; i < bld->entries.length; ++i) {
- git_tree_entry *entry = bld->entries.contents[i];
+ for (i = 0; i < bld->entries.length && !error; ++i) {
+ git_tree_entry *entry = git_vector_get(&bld->entries, i);
if (entry->removed)
continue;
@@ -476,51 +745,212 @@ int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *b
git_buf_printf(&tree, "%o ", entry->attr);
git_buf_put(&tree, entry->filename, entry->filename_len + 1);
git_buf_put(&tree, (char *)entry->oid.id, GIT_OID_RAWSZ);
- }
- if (git_buf_oom(&tree)) {
- git_buf_free(&tree);
- return git__throw(GIT_ENOMEM, "Not enough memory to build the tree data");
+ if (git_buf_oom(&tree))
+ error = -1;
}
- error = git_odb_write(oid, git_repository_database(repo), tree.ptr, tree.size, GIT_OBJ_TREE);
- git_buf_free(&tree);
+ if (!error &&
+ !(error = git_repository_odb__weakptr(&odb, repo)))
+ error = git_odb_write(oid, odb, tree.ptr, tree.size, GIT_OBJ_TREE);
- return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write tree");
+ git_buf_free(&tree);
+ return error;
}
-void git_treebuilder_filter(git_treebuilder *bld, int (*filter)(const git_tree_entry *, void *), void *payload)
+void git_treebuilder_filter(
+ git_treebuilder *bld,
+ git_treebuilder_filter_cb filter,
+ void *payload)
{
- unsigned int i;
+ size_t i;
+ git_tree_entry *entry;
assert(bld && filter);
- for (i = 0; i < bld->entries.length; ++i) {
- git_tree_entry *entry = bld->entries.contents[i];
- if (!entry->removed && filter(entry, payload))
+ git_vector_foreach(&bld->entries, i, entry) {
+ if (!entry->removed && filter(entry, payload)) {
entry->removed = 1;
+ bld->entrycount--;
+ }
}
}
void git_treebuilder_clear(git_treebuilder *bld)
{
- unsigned int i;
+ size_t i;
+ git_tree_entry *e;
+
assert(bld);
- for (i = 0; i < bld->entries.length; ++i) {
- git_tree_entry *e = bld->entries.contents[i];
- free(e->filename);
- free(e);
- }
+ git_vector_foreach(&bld->entries, i, e)
+ git_tree_entry_free(e);
git_vector_clear(&bld->entries);
+ bld->entrycount = 0;
}
void git_treebuilder_free(git_treebuilder *bld)
{
+ if (bld == NULL)
+ return;
+
git_treebuilder_clear(bld);
git_vector_free(&bld->entries);
- free(bld);
+ git__free(bld);
+}
+
+static size_t subpath_len(const char *path)
+{
+ const char *slash_pos = strchr(path, '/');
+ if (slash_pos == NULL)
+ return strlen(path);
+
+ return slash_pos - path;
+}
+
+int git_tree_entry_bypath(
+ git_tree_entry **entry_out,
+ const git_tree *root,
+ const char *path)
+{
+ int error = 0;
+ git_tree *subtree;
+ const git_tree_entry *entry;
+ size_t filename_len;
+
+ /* Find how long is the current path component (i.e.
+ * the filename between two slashes */
+ filename_len = subpath_len(path);
+
+ if (filename_len == 0) {
+ giterr_set(GITERR_TREE, "Invalid tree path given");
+ return GIT_ENOTFOUND;
+ }
+
+ entry = entry_fromname(root, path, filename_len);
+
+ if (entry == NULL) {
+ giterr_set(GITERR_TREE,
+ "The path '%s' does not exist in the given tree", path);
+ return GIT_ENOTFOUND;
+ }
+
+ switch (path[filename_len]) {
+ case '/':
+ /* If there are more components in the path...
+ * then this entry *must* be a tree */
+ if (!git_tree_entry__is_tree(entry)) {
+ giterr_set(GITERR_TREE,
+ "The path '%s' does not exist in the given tree", path);
+ return GIT_ENOTFOUND;
+ }
+
+ /* If there's only a slash left in the path, we
+ * return the current entry; otherwise, we keep
+ * walking down the path */
+ if (path[filename_len + 1] != '\0')
+ break;
+
+ case '\0':
+ /* If there are no more components in the path, return
+ * this entry */
+ return git_tree_entry_dup(entry_out, entry);
+ }
+
+ if (git_tree_lookup(&subtree, root->object.repo, &entry->oid) < 0)
+ return -1;
+
+ error = git_tree_entry_bypath(
+ entry_out,
+ subtree,
+ path + filename_len + 1
+ );
+
+ git_tree_free(subtree);
+ return error;
}
+static int tree_walk(
+ const git_tree *tree,
+ git_treewalk_cb callback,
+ git_buf *path,
+ void *payload,
+ bool preorder)
+{
+ int error = 0;
+ size_t i;
+ const git_tree_entry *entry;
+
+ git_vector_foreach(&tree->entries, i, entry) {
+ if (preorder) {
+ error = callback(path->ptr, entry, payload);
+ if (error < 0) { /* negative value stops iteration */
+ giterr_set_after_callback_function(error, "git_tree_walk");
+ break;
+ }
+ if (error > 0) { /* positive value skips this entry */
+ error = 0;
+ continue;
+ }
+ }
+
+ if (git_tree_entry__is_tree(entry)) {
+ git_tree *subtree;
+ size_t path_len = git_buf_len(path);
+
+ error = git_tree_lookup(&subtree, tree->object.repo, &entry->oid);
+ if (error < 0)
+ break;
+
+ /* append the next entry to the path */
+ git_buf_puts(path, entry->filename);
+ git_buf_putc(path, '/');
+
+ if (git_buf_oom(path))
+ error = -1;
+ else
+ error = tree_walk(subtree, callback, path, payload, preorder);
+
+ git_tree_free(subtree);
+ if (error != 0)
+ break;
+
+ git_buf_truncate(path, path_len);
+ }
+
+ if (!preorder) {
+ error = callback(path->ptr, entry, payload);
+ if (error < 0) { /* negative value stops iteration */
+ giterr_set_after_callback_function(error, "git_tree_walk");
+ break;
+ }
+ error = 0;
+ }
+ }
+
+ return error;
+}
+
+int git_tree_walk(
+ const git_tree *tree,
+ git_treewalk_mode mode,
+ git_treewalk_cb callback,
+ void *payload)
+{
+ int error = 0;
+ git_buf root_path = GIT_BUF_INIT;
+
+ if (mode != GIT_TREEWALK_POST && mode != GIT_TREEWALK_PRE) {
+ giterr_set(GITERR_INVALID, "Invalid walking mode for tree walk");
+ return -1;
+ }
+
+ error = tree_walk(
+ tree, callback, &root_path, payload, (mode == GIT_TREEWALK_PRE));
+
+ git_buf_free(&root_path);
+
+ return error;
+}
diff --git a/src/tree.h b/src/tree.h
index bff3f8edb..f07039a07 100644
--- a/src/tree.h
+++ b/src/tree.h
@@ -1,3 +1,9 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_tree_h__
#define INCLUDE_tree_h__
@@ -7,11 +13,11 @@
#include "vector.h"
struct git_tree_entry {
- unsigned int attr;
- char *filename;
+ uint16_t removed;
+ uint16_t attr;
git_oid oid;
size_t filename_len;
- int removed;
+ char filename[1];
};
struct git_tree {
@@ -21,11 +27,38 @@ struct git_tree {
struct git_treebuilder {
git_vector entries;
- size_t entry_count;
+ size_t entrycount; /* vector may contain "removed" entries */
};
+GIT_INLINE(bool) git_tree_entry__is_tree(const struct git_tree_entry *e)
+{
+ return (S_ISDIR(e->attr) && !S_ISGITLINK(e->attr));
+}
-void git_tree__free(git_tree *tree);
-int git_tree__parse(git_tree *tree, git_odb_object *obj);
+extern int git_tree_entry_icmp(const git_tree_entry *e1, const git_tree_entry *e2);
+
+void git_tree__free(void *tree);
+int git_tree__parse(void *tree, git_odb_object *obj);
+
+/**
+ * Lookup the first position in the tree with a given prefix.
+ *
+ * @param tree a previously loaded tree.
+ * @param prefix the beginning of a path to find in the tree.
+ * @return index of the first item at or after the given prefix.
+ */
+int git_tree__prefix_position(const git_tree *tree, const char *prefix);
+
+
+/**
+ * Write a tree to the given repository
+ */
+int git_tree__write_index(
+ git_oid *oid, git_index *index, git_repository *repo);
+
+/**
+ * Obsolete mode kept for compatibility reasons
+ */
+#define GIT_FILEMODE_BLOB_GROUP_WRITABLE 0100664
#endif
diff --git a/src/tsort.c b/src/tsort.c
index bf8bff9d8..4885e435b 100644
--- a/src/tsort.c
+++ b/src/tsort.c
@@ -1,4 +1,11 @@
-#include <common.h>
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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"
/**
* An array-of-pointers implementation of Python's Timsort
@@ -16,25 +23,26 @@
# define MIN(x,y) (((x) < (y) ? (x) : (y)))
#endif
-typedef int (*cmp_ptr_t)(const void *, const void *);
-
-static int binsearch(void **dst, const void *x, size_t size, cmp_ptr_t cmp)
+static int binsearch(
+ void **dst, const void *x, size_t size, git__sort_r_cmp cmp, void *payload)
{
int l, c, r;
void *lx, *cx;
+ assert(size > 0);
+
l = 0;
- r = size - 1;
+ r = (int)size - 1;
c = r >> 1;
lx = dst[l];
/* check for beginning conditions */
- if (cmp(x, lx) < 0)
+ if (cmp(x, lx, payload) < 0)
return 0;
- else if (cmp(x, lx) == 0) {
+ else if (cmp(x, lx, payload) == 0) {
int i = 1;
- while (cmp(x, dst[i]) == 0)
+ while (cmp(x, dst[i], payload) == 0)
i++;
return i;
}
@@ -42,7 +50,7 @@ static int binsearch(void **dst, const void *x, size_t size, cmp_ptr_t cmp)
/* guaranteed not to be >= rx */
cx = dst[c];
while (1) {
- const int val = cmp(x, cx);
+ const int val = cmp(x, cx, payload);
if (val < 0) {
if (c - l <= 1) return c;
r = c;
@@ -53,7 +61,7 @@ static int binsearch(void **dst, const void *x, size_t size, cmp_ptr_t cmp)
} else {
do {
cx = dst[++c];
- } while (cmp(x, cx) == 0);
+ } while (cmp(x, cx, payload) == 0);
return c;
}
c = l + ((r - l) >> 1);
@@ -61,8 +69,9 @@ static int binsearch(void **dst, const void *x, size_t size, cmp_ptr_t cmp)
}
}
-/* Binary insertion sort, but knowing that the first "start" entries are sorted. Used in timsort. */
-static void bisort(void **dst, size_t start, size_t size, cmp_ptr_t cmp)
+/* Binary insertion sort, but knowing that the first "start" entries are sorted. Used in timsort. */
+static void bisort(
+ void **dst, size_t start, size_t size, git__sort_r_cmp cmp, void *payload)
{
size_t i;
void *x;
@@ -71,13 +80,13 @@ static void bisort(void **dst, size_t start, size_t size, cmp_ptr_t cmp)
for (i = start; i < size; i++) {
int j;
/* If this entry is already correct, just move along */
- if (cmp(dst[i - 1], dst[i]) <= 0)
+ if (cmp(dst[i - 1], dst[i], payload) <= 0)
continue;
/* Else we need to find the right place, shift everything over, and squeeze in */
x = dst[i];
- location = binsearch(dst, x, i, cmp);
- for (j = i - 1; j >= location; j--) {
+ location = binsearch(dst, x, i, cmp, payload);
+ for (j = (int)i - 1; j >= location; j--) {
dst[j + 1] = dst[j];
}
dst[location] = x;
@@ -93,11 +102,12 @@ struct tsort_run {
struct tsort_store {
size_t alloc;
- cmp_ptr_t cmp;
+ git__sort_r_cmp cmp;
+ void *payload;
void **storage;
};
-static void reverse_elements(void **dst, int start, int end)
+static void reverse_elements(void **dst, ssize_t start, ssize_t end)
{
while (start < end) {
void *tmp = dst[start];
@@ -109,7 +119,8 @@ static void reverse_elements(void **dst, int start, int end)
}
}
-static int count_run(void **dst, ssize_t start, ssize_t size, struct tsort_store *store)
+static ssize_t count_run(
+ void **dst, ssize_t start, ssize_t size, struct tsort_store *store)
{
ssize_t curr = start + 2;
@@ -117,7 +128,7 @@ static int count_run(void **dst, ssize_t start, ssize_t size, struct tsort_store
return 1;
if (start >= size - 2) {
- if (store->cmp(dst[size - 2], dst[size - 1]) > 0) {
+ if (store->cmp(dst[size - 2], dst[size - 1], store->payload) > 0) {
void *tmp = dst[size - 1];
dst[size - 1] = dst[size - 2];
dst[size - 2] = tmp;
@@ -126,13 +137,15 @@ static int count_run(void **dst, ssize_t start, ssize_t size, struct tsort_store
return 2;
}
- if (store->cmp(dst[start], dst[start + 1]) <= 0) {
- while (curr < size - 1 && store->cmp(dst[curr - 1], dst[curr]) <= 0)
+ if (store->cmp(dst[start], dst[start + 1], store->payload) <= 0) {
+ while (curr < size - 1 &&
+ store->cmp(dst[curr - 1], dst[curr], store->payload) <= 0)
curr++;
return curr - start;
} else {
- while (curr < size - 1 && store->cmp(dst[curr - 1], dst[curr]) > 0)
+ while (curr < size - 1 &&
+ store->cmp(dst[curr - 1], dst[curr], store->payload) > 0)
curr++;
/* reverse in-place */
@@ -141,7 +154,7 @@ static int count_run(void **dst, ssize_t start, ssize_t size, struct tsort_store
}
}
-static int compute_minrun(size_t n)
+static size_t compute_minrun(size_t n)
{
int r = 0;
while (n >= 64) {
@@ -151,19 +164,19 @@ static int compute_minrun(size_t n)
return n + r;
}
-static int check_invariant(struct tsort_run *stack, int stack_curr)
+static int check_invariant(struct tsort_run *stack, ssize_t stack_curr)
{
if (stack_curr < 2)
return 1;
else if (stack_curr == 2) {
- const int A = stack[stack_curr - 2].length;
- const int B = stack[stack_curr - 1].length;
+ const ssize_t A = stack[stack_curr - 2].length;
+ const ssize_t B = stack[stack_curr - 1].length;
return (A > B);
} else {
- const int A = stack[stack_curr - 3].length;
- const int B = stack[stack_curr - 2].length;
- const int C = stack[stack_curr - 1].length;
+ const ssize_t A = stack[stack_curr - 3].length;
+ const ssize_t B = stack[stack_curr - 2].length;
+ const ssize_t C = stack[stack_curr - 1].length;
return !((A <= B + C) || (B <= C));
}
}
@@ -171,7 +184,7 @@ static int check_invariant(struct tsort_run *stack, int stack_curr)
static int resize(struct tsort_store *store, size_t new_size)
{
if (store->alloc < new_size) {
- void **tempstore = realloc(store->storage, new_size * sizeof(void *));
+ void **tempstore = git__realloc(store->storage, new_size * sizeof(void *));
/**
* Do not propagate on OOM; this will abort the sort and
@@ -188,7 +201,7 @@ static int resize(struct tsort_store *store, size_t new_size)
return 0;
}
-static void merge(void **dst, const struct tsort_run *stack, int stack_curr, struct tsort_store *store)
+static void merge(void **dst, const struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store)
{
const ssize_t A = stack[stack_curr - 2].length;
const ssize_t B = stack[stack_curr - 1].length;
@@ -200,7 +213,7 @@ static void merge(void **dst, const struct tsort_run *stack, int stack_curr, str
if (resize(store, MIN(A, B)) < 0)
return;
- storage = store->storage;
+ storage = store->storage;
/* left merge */
if (A < B) {
@@ -210,7 +223,7 @@ static void merge(void **dst, const struct tsort_run *stack, int stack_curr, str
for (k = curr; k < curr + A + B; k++) {
if ((i < A) && (j < curr + A + B)) {
- if (store->cmp(storage[i], dst[j]) <= 0)
+ if (store->cmp(storage[i], dst[j], store->payload) <= 0)
dst[k] = storage[i++];
else
dst[k] = dst[j++];
@@ -226,7 +239,7 @@ static void merge(void **dst, const struct tsort_run *stack, int stack_curr, str
for (k = curr + A + B - 1; k >= curr; k--) {
if ((i >= 0) && (j >= curr)) {
- if (store->cmp(dst[j], storage[i]) > 0)
+ if (store->cmp(dst[j], storage[i], store->payload) > 0)
dst[k] = dst[j--];
else
dst[k] = storage[i--];
@@ -298,7 +311,7 @@ static ssize_t collapse(void **dst, struct tsort_run *stack, ssize_t stack_curr,
if (run < minrun) run = minrun;\
if (run > (ssize_t)size - curr) run = size - curr;\
if (run > len) {\
- bisort(&dst[curr], len, run, cmp);\
+ bisort(&dst[curr], len, run, cmp, payload);\
len = run;\
}\
run_stack[stack_curr].start = curr;\
@@ -312,7 +325,7 @@ static ssize_t collapse(void **dst, struct tsort_run *stack, ssize_t stack_curr,
stack_curr--; \
} \
if (store->storage != NULL) {\
- free(store->storage);\
+ git__free(store->storage);\
store->storage = NULL;\
}\
return;\
@@ -320,7 +333,8 @@ static ssize_t collapse(void **dst, struct tsort_run *stack, ssize_t stack_curr,
}\
while (0)
-void git__tsort(void **dst, size_t size, cmp_ptr_t cmp)
+void git__tsort_r(
+ void **dst, size_t size, git__sort_r_cmp cmp, void *payload)
{
struct tsort_store _store, *store = &_store;
struct tsort_run run_stack[128];
@@ -331,17 +345,18 @@ void git__tsort(void **dst, size_t size, cmp_ptr_t cmp)
ssize_t minrun;
if (size < 64) {
- bisort(dst, 1, size, cmp);
+ bisort(dst, 1, size, cmp, payload);
return;
}
/* compute the minimum run length */
- minrun = compute_minrun(size);
+ minrun = (ssize_t)compute_minrun(size);
/* temporary storage for merges */
store->alloc = 0;
store->storage = NULL;
store->cmp = cmp;
+ store->payload = payload;
PUSH_NEXT();
PUSH_NEXT();
@@ -356,3 +371,13 @@ void git__tsort(void **dst, size_t size, cmp_ptr_t cmp)
PUSH_NEXT();
}
}
+
+static int tsort_r_cmp(const void *a, const void *b, void *payload)
+{
+ return ((git__tsort_cmp)payload)(a, b);
+}
+
+void git__tsort(void **dst, size_t size, git__tsort_cmp cmp)
+{
+ git__tsort_r(dst, size, tsort_r_cmp, cmp);
+}
diff --git a/src/unix/map.c b/src/unix/map.c
index 5192c8e4c..3d0cbbaf8 100644
--- a/src/unix/map.c
+++ b/src/unix/map.c
@@ -1,22 +1,29 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
#include <git2/common.h>
-#ifndef GIT_WIN32
+#if !defined(GIT_WIN32) && !defined(NO_MMAP)
#include "map.h"
#include <sys/mman.h>
+#include <unistd.h>
#include <errno.h>
+long git__page_size(void)
+{
+ return sysconf(_SC_PAGE_SIZE);
+}
+
int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset)
{
int mprot = 0;
int mflag = 0;
- assert((out != NULL) && (len > 0));
-
- if ((out == NULL) || (len == 0)) {
- errno = EINVAL;
- return git__throw(GIT_ERROR, "Failed to mmap. No map or zero length");
- }
+ GIT_MMAP_VALIDATE(out, len, prot, flags);
out->data = NULL;
out->len = 0;
@@ -25,39 +32,32 @@ int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offs
mprot = PROT_WRITE;
else if (prot & GIT_PROT_READ)
mprot = PROT_READ;
- else {
- errno = EINVAL;
- return git__throw(GIT_ERROR, "Failed to mmap. Invalid protection parameters");
- }
if ((flags & GIT_MAP_TYPE) == GIT_MAP_SHARED)
mflag = MAP_SHARED;
else if ((flags & GIT_MAP_TYPE) == GIT_MAP_PRIVATE)
mflag = MAP_PRIVATE;
+ else
+ mflag = MAP_SHARED;
+
+ out->data = mmap(NULL, len, mprot, mflag, fd, offset);
- if (flags & GIT_MAP_FIXED) {
- errno = EINVAL;
- return git__throw(GIT_ERROR, "Failed to mmap. FIXED not set");
+ if (!out->data || out->data == MAP_FAILED) {
+ giterr_set(GITERR_OS, "Failed to mmap. Could not write data");
+ return -1;
}
- out->data = mmap(NULL, len, mprot, mflag, fd, offset);
- if (!out->data || out->data == MAP_FAILED)
- return git__throw(GIT_EOSERR, "Failed to mmap. Could not write data");
out->len = len;
- return GIT_SUCCESS;
+ return 0;
}
int p_munmap(git_map *map)
{
assert(map != NULL);
-
- if (!map)
- return git__throw(GIT_ERROR, "Failed to munmap. Map does not exist");
-
munmap(map->data, map->len);
- return GIT_SUCCESS;
+ return 0;
}
#endif
diff --git a/src/unix/posix.h b/src/unix/posix.h
index 16daf15bd..1e41bcf18 100644
--- a/src/unix/posix.h
+++ b/src/unix/posix.h
@@ -1,17 +1,36 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_posix__w32_h__
#define INCLUDE_posix__w32_h__
-#include "common.h"
-#include <fnmatch.h>
+#include <stdio.h>
+#include <sys/param.h>
#define p_lstat(p,b) lstat(p,b)
#define p_readlink(a, b, c) readlink(a, b, c)
+#define p_symlink(o,n) symlink(o, n)
#define p_link(o,n) link(o, n)
#define p_unlink(p) unlink(p)
#define p_mkdir(p,m) mkdir(p, m)
#define p_fsync(fd) fsync(fd)
-#define p_realpath(p, po) realpath(p, po)
-#define p_fnmatch(p, s, f) fnmatch(p, s, f)
+
+/* The OpenBSD realpath function behaves differently */
+#if !defined(__OpenBSD__)
+# define p_realpath(p, po) realpath(p, po)
+#else
+char *p_realpath(const char *, char *);
+#endif
+
#define p_vsnprintf(b, c, f, a) vsnprintf(b, c, f, a)
+#define p_snprintf(b, c, f, ...) snprintf(b, c, f, __VA_ARGS__)
+#define p_mkstemp(p) mkstemp(p)
+#define p_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/unix/realpath.c b/src/unix/realpath.c
new file mode 100644
index 000000000..15601bd22
--- /dev/null
+++ b/src/unix/realpath.c
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include <git2/common.h>
+
+#ifdef __OpenBSD__
+
+#include <limits.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+char *p_realpath(const char *pathname, char *resolved)
+{
+ char *ret;
+
+ if ((ret = realpath(pathname, resolved)) == NULL)
+ return NULL;
+
+ /* Figure out if the file exists */
+ if (!access(ret, F_OK))
+ return ret;
+
+ return NULL;
+}
+
+#endif
diff --git a/src/userdiff.h b/src/userdiff.h
new file mode 100644
index 000000000..523f2f8d4
--- /dev/null
+++ b/src/userdiff.h
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_userdiff_h__
+#define INCLUDE_userdiff_h__
+
+/*
+ * This file isolates the built in diff driver function name patterns.
+ * Most of these patterns are taken from Git (with permission from the
+ * original authors for relicensing to libgit2).
+ */
+
+typedef struct {
+ const char *name;
+ const char *fns;
+ const char *words;
+ int flags;
+} git_diff_driver_definition;
+
+#define WORD_DEFAULT "|[^[:space:]]|[\xc0-\xff][\x80-\xbf]+"
+
+/*
+ * These builtin driver definition macros have same signature as in core
+ * git userdiff.c so that the data can be extracted verbatim
+ */
+#define PATTERNS(NAME, FN_PATS, WORD_PAT) \
+ { NAME, FN_PATS, WORD_PAT WORD_DEFAULT, 0 }
+#define IPATTERN(NAME, FN_PATS, WORD_PAT) \
+ { NAME, FN_PATS, WORD_PAT WORD_DEFAULT, REG_ICASE }
+
+/*
+ * The table of diff driver patterns
+ *
+ * Function name patterns are a list of newline separated patterns that
+ * match a function declaration (i.e. the line you want in the hunk header),
+ * or a negative pattern prefixed with a '!' to reject a pattern (such as
+ * rejecting goto labels in C code).
+ *
+ * Word boundary patterns are just a simple pattern that will be OR'ed with
+ * the default value above (i.e. whitespace or non-ASCII characters).
+ */
+static git_diff_driver_definition builtin_defs[] = {
+
+IPATTERN("ada",
+ "!^(.*[ \t])?(is[ \t]+new|renames|is[ \t]+separate)([ \t].*)?$\n"
+ "!^[ \t]*with[ \t].*$\n"
+ "^[ \t]*((procedure|function)[ \t]+.*)$\n"
+ "^[ \t]*((package|protected|task)[ \t]+.*)$",
+ /* -- */
+ "[a-zA-Z][a-zA-Z0-9_]*"
+ "|[-+]?[0-9][0-9#_.aAbBcCdDeEfF]*([eE][+-]?[0-9_]+)?"
+ "|=>|\\.\\.|\\*\\*|:=|/=|>=|<=|<<|>>|<>"),
+
+IPATTERN("fortran",
+ "!^([C*]|[ \t]*!)\n"
+ "!^[ \t]*MODULE[ \t]+PROCEDURE[ \t]\n"
+ "^[ \t]*((END[ \t]+)?(PROGRAM|MODULE|BLOCK[ \t]+DATA"
+ "|([^'\" \t]+[ \t]+)*(SUBROUTINE|FUNCTION))[ \t]+[A-Z].*)$",
+ /* -- */
+ "[a-zA-Z][a-zA-Z0-9_]*"
+ "|\\.([Ee][Qq]|[Nn][Ee]|[Gg][TtEe]|[Ll][TtEe]|[Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee]|[Aa][Nn][Dd]|[Oo][Rr]|[Nn]?[Ee][Qq][Vv]|[Nn][Oo][Tt])\\."
+ /* numbers and format statements like 2E14.4, or ES12.6, 9X.
+ * Don't worry about format statements without leading digits since
+ * they would have been matched above as a variable anyway. */
+ "|[-+]?[0-9.]+([AaIiDdEeFfLlTtXx][Ss]?[-+]?[0-9.]*)?(_[a-zA-Z0-9][a-zA-Z0-9_]*)?"
+ "|//|\\*\\*|::|[/<>=]="),
+
+PATTERNS("html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$",
+ "[^<>= \t]+"),
+
+PATTERNS("java",
+ "!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n"
+ "^[ \t]*(([A-Za-z_][A-Za-z_0-9]*[ \t]+)+[A-Za-z_][A-Za-z_0-9]*[ \t]*\\([^;]*)$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
+ "|[-+*/<>%&^|=!]="
+ "|--|\\+\\+|<<=?|>>>?=?|&&|\\|\\|"),
+
+PATTERNS("matlab",
+ "^[[:space:]]*((classdef|function)[[:space:]].*)$|^%%[[:space:]].*$",
+ "[a-zA-Z_][a-zA-Z0-9_]*|[-+0-9.e]+|[=~<>]=|\\.[*/\\^']|\\|\\||&&"),
+
+PATTERNS("objc",
+ /* Negate C statements that can look like functions */
+ "!^[ \t]*(do|for|if|else|return|switch|while)\n"
+ /* Objective-C methods */
+ "^[ \t]*([-+][ \t]*\\([ \t]*[A-Za-z_][A-Za-z_0-9* \t]*\\)[ \t]*[A-Za-z_].*)$\n"
+ /* C functions */
+ "^[ \t]*(([A-Za-z_][A-Za-z_0-9]*[ \t]+)+[A-Za-z_][A-Za-z_0-9]*[ \t]*\\([^;]*)$\n"
+ /* Objective-C class/protocol definitions */
+ "^(@(implementation|interface|protocol)[ \t].*)$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
+ "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"),
+
+PATTERNS("pascal",
+ "^(((class[ \t]+)?(procedure|function)|constructor|destructor|interface|"
+ "implementation|initialization|finalization)[ \t]*.*)$"
+ "\n"
+ "^(.*=[ \t]*(class|record).*)$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+"
+ "|<>|<=|>=|:=|\\.\\."),
+
+PATTERNS("perl",
+ "^package .*\n"
+ "^sub [[:alnum:]_':]+[ \t]*"
+ "(\\([^)]*\\)[ \t]*)?" /* prototype */
+ /*
+ * Attributes. A regex can't count nested parentheses,
+ * so just slurp up whatever we see, taking care not
+ * to accept lines like "sub foo; # defined elsewhere".
+ *
+ * An attribute could contain a semicolon, but at that
+ * point it seems reasonable enough to give up.
+ */
+ "(:[^;#]*)?"
+ "(\\{[ \t]*)?" /* brace can come here or on the next line */
+ "(#.*)?$\n" /* comment */
+ "^(BEGIN|END|INIT|CHECK|UNITCHECK|AUTOLOAD|DESTROY)[ \t]*"
+ "(\\{[ \t]*)?" /* brace can come here or on the next line */
+ "(#.*)?$\n"
+ "^=head[0-9] .*", /* POD */
+ /* -- */
+ "[[:alpha:]_'][[:alnum:]_']*"
+ "|0[xb]?[0-9a-fA-F_]*"
+ /* taking care not to interpret 3..5 as (3.)(.5) */
+ "|[0-9a-fA-F_]+(\\.[0-9a-fA-F_]+)?([eE][-+]?[0-9_]+)?"
+ "|=>|-[rwxoRWXOezsfdlpSugkbctTBMAC>]|~~|::"
+ "|&&=|\\|\\|=|//=|\\*\\*="
+ "|&&|\\|\\||//|\\+\\+|--|\\*\\*|\\.\\.\\.?"
+ "|[-+*/%.^&<>=!|]="
+ "|=~|!~"
+ "|<<|<>|<=>|>>"),
+
+PATTERNS("python", "^[ \t]*((class|def)[ \t].*)$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+[jJlL]?|0[xX]?[0-9a-fA-F]+[lL]?"
+ "|[-+*/<>%&^|=!]=|//=?|<<=?|>>=?|\\*\\*=?"),
+
+PATTERNS("ruby", "^[ \t]*((class|module|def)[ \t].*)$",
+ /* -- */
+ "(@|@@|\\$)?[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+|\\?(\\\\C-)?(\\\\M-)?."
+ "|//=?|[-+*/<>%&^|=!]=|<<=?|>>=?|===|\\.{1,3}|::|[!=]~"),
+
+PATTERNS("bibtex", "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$",
+ "[={}\"]|[^={}\" \t]+"),
+
+PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$",
+ "\\\\[a-zA-Z@]+|\\\\.|[a-zA-Z0-9\x80-\xff]+"),
+
+PATTERNS("cpp",
+ /* Jump targets or access declarations */
+ "!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:[[:space:]]*($|/[/*])\n"
+ /* functions/methods, variables, and compounds at top level */
+ "^((::[[:space:]]*)?[A-Za-z_].*)$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lLuU]*"
+ "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->\\*?|\\.\\*"),
+
+PATTERNS("csharp",
+ /* Keywords */
+ "!^[ \t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\n"
+ /* Methods and constructors */
+ "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[<>@._[:alnum:]]+[ \t]*\\(.*\\))[ \t]*$\n"
+ /* Properties */
+ "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[@._[:alnum:]]+)[ \t]*$\n"
+ /* Type definitions */
+ "^[ \t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \t]+)*(class|enum|interface|struct)[ \t]+.*)$\n"
+ /* Namespace */
+ "^[ \t]*(namespace[ \t]+.*)$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
+ "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"),
+
+PATTERNS("php",
+ "^[ \t]*(((public|private|protected|static|final)[ \t]+)*((class|function)[ \t].*))$",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+[fFlL]?|0[xX]?[0-9a-fA-F]+[lL]?"
+ "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"),
+
+PATTERNS("javascript",
+ "^[ \t]*(function[ \t][a-zA-Z_][^\\{]*)\n"
+ "^[ \t]*(var[ \t]+[a-zA-Z_][a-zA-Z0-9_]*[ \t]*=[ \t]*function[ \t\\(][^\\{]*)\n"
+ "^[ \t]*([a-zA-Z_][a-zA-Z0-9_]*[ \t]*:[ \t]*function[ \t\\(][^\\{]*)",
+ /* -- */
+ "[a-zA-Z_][a-zA-Z0-9_]*"
+ "|[-+0-9.e]+[fFlL]?|0[xX]?[0-9a-fA-F]+[lL]?"
+ "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"),
+};
+
+#undef IPATTERN
+#undef PATTERNS
+#undef WORD_DEFAULT
+
+#endif
+
diff --git a/src/util.c b/src/util.c
index a51cbe6d8..f9d37e4f4 100644
--- a/src/util.c
+++ b/src/util.c
@@ -1,6 +1,11 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
#include <git2.h>
#include "common.h"
-#include <stdarg.h>
#include <stdio.h>
#include <ctype.h>
#include "posix.h"
@@ -9,41 +14,56 @@
# include <Shlwapi.h>
#endif
-void git_libgit2_version(int *major, int *minor, int *rev)
-{
- *major = LIBGIT2_VER_MAJOR;
- *minor = LIBGIT2_VER_MINOR;
- *rev = LIBGIT2_VER_REVISION;
-}
-
void git_strarray_free(git_strarray *array)
{
size_t i;
+
+ if (array == NULL)
+ return;
+
for (i = 0; i < array->count; ++i)
- free(array->strings[i]);
+ git__free(array->strings[i]);
- free(array->strings);
+ git__free(array->strings);
+
+ memset(array, 0, sizeof(*array));
}
-int git__fnmatch(const char *pattern, const char *name, int flags)
+int git_strarray_copy(git_strarray *tgt, const git_strarray *src)
{
- int ret;
-
- ret = p_fnmatch(pattern, name, flags);
- switch (ret) {
- case 0:
- return GIT_SUCCESS;
- case FNM_NOMATCH:
- return GIT_ENOMATCH;
- default:
- return git__throw(GIT_EOSERR, "Error trying to match path");
+ size_t i;
+
+ assert(tgt && src);
+
+ memset(tgt, 0, sizeof(*tgt));
+
+ if (!src->count)
+ return 0;
+
+ tgt->strings = git__calloc(src->count, sizeof(char *));
+ GITERR_CHECK_ALLOC(tgt->strings);
+
+ for (i = 0; i < src->count; ++i) {
+ if (!src->strings[i])
+ continue;
+
+ tgt->strings[tgt->count] = git__strdup(src->strings[i]);
+ if (!tgt->strings[tgt->count]) {
+ git_strarray_free(tgt);
+ memset(tgt, 0, sizeof(*tgt));
+ return -1;
+ }
+
+ tgt->count++;
}
+
+ return 0;
}
-int git__strtol32(long *result, const char *nptr, const char **endptr, int base)
+int git__strtol64(int64_t *result, const char *nptr, const char **endptr, int base)
{
const char *p;
- long n, nn;
+ int64_t n, nn;
int c, ovfl, v, neg, ndig;
p = nptr;
@@ -55,7 +75,7 @@ int git__strtol32(long *result, const char *nptr, const char **endptr, int base)
/*
* White space
*/
- while (isspace(*p))
+ while (git__isspace(*p))
p++;
/*
@@ -105,35 +125,104 @@ int git__strtol32(long *result, const char *nptr, const char **endptr, int base)
}
Return:
- if (ndig == 0)
- return git__throw(GIT_ENOTNUM, "Failed to convert string to long. Not a number");
+ if (ndig == 0) {
+ giterr_set(GITERR_INVALID, "Failed to convert string to long. Not a number");
+ return -1;
+ }
if (endptr)
*endptr = p;
- if (ovfl)
- return git__throw(GIT_EOVERFLOW, "Failed to convert string to long. Overflow error");
+ if (ovfl) {
+ giterr_set(GITERR_INVALID, "Failed to convert string to long. Overflow error");
+ return -1;
+ }
*result = neg ? -n : n;
- return GIT_SUCCESS;
+ return 0;
}
-int git__fmt(char *buf, size_t buf_sz, const char *fmt, ...)
+int git__strtol32(int32_t *result, const char *nptr, const char **endptr, int base)
{
- va_list va;
- int r;
-
- va_start(va, fmt);
- r = vsnprintf(buf, buf_sz, fmt, va);
- va_end(va);
- if (r < 0 || ((size_t) r) >= buf_sz)
- return git__throw(GIT_ERROR, "Failed to format string");
- return r;
+ int error;
+ int32_t tmp_int;
+ int64_t tmp_long;
+
+ if ((error = git__strtol64(&tmp_long, nptr, endptr, base)) < 0)
+ return error;
+
+ tmp_int = tmp_long & 0xFFFFFFFF;
+ if (tmp_int != tmp_long) {
+ giterr_set(GITERR_INVALID, "Failed to convert. '%s' is too large", nptr);
+ return -1;
+ }
+
+ *result = tmp_int;
+
+ return error;
}
-void git__strntolower(char *str, int len)
+int git__strcmp(const char *a, const char *b)
{
- int i;
+ 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__strcasesort_cmp(const char *a, const char *b)
+{
+ int cmp = 0;
+
+ while (*a && *b) {
+ if (*a != *b) {
+ if (tolower(*a) != tolower(*b))
+ break;
+ /* use case in sort order even if not in equivalence */
+ if (!cmp)
+ cmp = (int)(*(const uint8_t *)a) - (int)(*(const uint8_t *)b);
+ }
+
+ ++a, ++b;
+ }
+
+ if (*a || *b)
+ return tolower(*a) - tolower(*b);
+
+ return cmp;
+}
+
+int git__strncmp(const char *a, const char *b, size_t sz)
+{
+ while (sz && *a && *b && *a == *b)
+ --sz, ++a, ++b;
+ if (!sz)
+ return 0;
+ return (int)(*(const unsigned char *)a) - (int)(*(const unsigned char *)b);
+}
+
+int git__strncasecmp(const char *a, const char *b, size_t sz)
+{
+ int al, bl;
+
+ do {
+ al = (unsigned char)tolower(*a);
+ bl = (unsigned char)tolower(*b);
+ ++a, ++b;
+ } while (--sz && al && al == bl);
+
+ return al - bl;
+}
+
+void git__strntolower(char *str, size_t len)
+{
+ size_t i;
for (i = 0; i < len; ++i) {
str[i] = (char) tolower(str[i]);
@@ -148,7 +237,7 @@ void git__strtolower(char *str)
int git__prefixcmp(const char *str, const char *prefix)
{
for (;;) {
- char p = *(prefix++), s;
+ unsigned char p = *(prefix++), s;
if (!p)
return 0;
if ((s = *(str++)) != p)
@@ -156,6 +245,11 @@ int git__prefixcmp(const char *str, const char *prefix)
}
}
+int git__prefixcmp_icase(const char *str, const char *prefix)
+{
+ return strncasecmp(str, prefix, strlen(prefix));
+}
+
int git__suffixcmp(const char *str, const char *suffix)
{
size_t a = strlen(str);
@@ -190,6 +284,24 @@ char *git__strtok(char **end, const char *sep)
return NULL;
}
+/* Similar to strtok, but does not collapse repeated tokens. */
+char *git__strsep(char **end, const char *sep)
+{
+ char *start = *end, *ptr = *end;
+
+ while (*ptr && !strchr(sep, *ptr))
+ ++ptr;
+
+ if (*ptr) {
+ *end = ptr + 1;
+ *ptr = '\0';
+
+ return start;
+ }
+
+ return NULL;
+}
+
void git__hexdump(const char *buffer, size_t len)
{
static const size_t LINE_WIDTH = 16;
@@ -221,7 +333,7 @@ void git__hexdump(const char *buffer, size_t len)
printf("%02X ", (unsigned char)*line & 0xFF);
for (j = 0; j < (LINE_WIDTH - last_line); ++j)
- printf(" ");
+ printf(" ");
printf("| ");
@@ -262,7 +374,7 @@ uint32_t git__hash(const void *key, int len, unsigned int seed)
case 3: h ^= data[2] << 16;
case 2: h ^= data[1] << 8;
case 1: h ^= data[0];
- h *= m;
+ h *= m;
};
h ^= h >> 13;
@@ -283,13 +395,13 @@ uint32_t git__hash(const void *key, int len, uint32_t seed)
{
#define MURMUR_BLOCK() {\
- k1 *= c1; \
- k1 = git__rotl(k1,11);\
- k1 *= c2;\
- h1 ^= k1;\
- h1 = h1*3 + 0x52dce729;\
- c1 = c1*5 + 0x7b7d159c;\
- c2 = c2*5 + 0x6bce6396;\
+ k1 *= c1; \
+ k1 = git__rotl(k1,11);\
+ k1 *= c2;\
+ h1 ^= k1;\
+ h1 = h1*3 + 0x52dce729;\
+ c1 = c1*5 + 0x7b7d159c;\
+ c2 = c2*5 + 0x6bce6396;\
}
const uint8_t *data = (const uint8_t*)key;
@@ -336,22 +448,203 @@ uint32_t git__hash(const void *key, int len, uint32_t seed)
*
* Copyright (c) 1990 Regents of the University of California.
* All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. [rescinded 22 July 1999]
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+int git__bsearch(
+ void **array,
+ size_t array_len,
+ const void *key,
+ int (*compare)(const void *, const void *),
+ size_t *position)
+{
+ size_t lim;
+ int cmp = -1;
+ void **part, **base = array;
+
+ for (lim = array_len; lim != 0; lim >>= 1) {
+ part = base + (lim >> 1);
+ cmp = (*compare)(key, *part);
+ if (cmp == 0) {
+ base = part;
+ break;
+ }
+ if (cmp > 0) { /* key > p; take right partition */
+ base = part + 1;
+ lim--;
+ } /* else take left partition */
+ }
+
+ if (position)
+ *position = (base - array);
+
+ return (cmp == 0) ? 0 : GIT_ENOTFOUND;
+}
+
+int git__bsearch_r(
+ void **array,
+ size_t array_len,
+ const void *key,
+ int (*compare_r)(const void *, const void *, void *),
+ void *payload,
+ size_t *position)
+{
+ size_t lim;
+ int cmp = -1;
+ void **part, **base = array;
+
+ for (lim = array_len; lim != 0; lim >>= 1) {
+ part = base + (lim >> 1);
+ cmp = (*compare_r)(key, *part, payload);
+ if (cmp == 0) {
+ base = part;
+ break;
+ }
+ if (cmp > 0) { /* key > p; take right partition */
+ base = part + 1;
+ lim--;
+ } /* else take left partition */
+ }
+
+ if (position)
+ *position = (base - array);
+
+ return (cmp == 0) ? 0 : GIT_ENOTFOUND;
+}
+
+/**
+ * A strcmp wrapper
+ *
+ * We don't want direct pointers to the CRT on Windows, we may
+ * get stdcall conflicts.
*/
-void **git__bsearch(const void *key, void **base, size_t nmemb, int (*compar)(const void *, const void *))
+int git__strcmp_cb(const void *a, const void *b)
+{
+ return strcmp((const char *)a, (const char *)b);
+}
+
+int git__strcasecmp_cb(const void *a, const void *b)
+{
+ return strcasecmp((const char *)a, (const char *)b);
+}
+
+int git__parse_bool(int *out, const char *value)
+{
+ /* A missing value means true */
+ if (value == NULL ||
+ !strcasecmp(value, "true") ||
+ !strcasecmp(value, "yes") ||
+ !strcasecmp(value, "on")) {
+ *out = 1;
+ return 0;
+ }
+ if (!strcasecmp(value, "false") ||
+ !strcasecmp(value, "no") ||
+ !strcasecmp(value, "off") ||
+ value[0] == '\0') {
+ *out = 0;
+ return 0;
+ }
+
+ return -1;
+}
+
+size_t git__unescape(char *str)
+{
+ char *scan, *pos = str;
+
+ if (!str)
+ return 0;
+
+ for (scan = str; *scan; pos++, scan++) {
+ if (*scan == '\\' && *(scan + 1) != '\0')
+ scan++; /* skip '\' but include next char */
+ if (pos != scan)
+ *pos = *scan;
+ }
+
+ if (pos != scan) {
+ *pos = '\0';
+ }
+
+ return (pos - str);
+}
+
+#if defined(GIT_WIN32) || defined(BSD)
+typedef struct {
+ git__sort_r_cmp cmp;
+ void *payload;
+} git__qsort_r_glue;
+
+static int GIT_STDLIB_CALL git__qsort_r_glue_cmp(
+ void *payload, const void *a, const void *b)
{
- int lim, cmp;
- void **p;
-
- for (lim = nmemb; lim != 0; lim >>= 1) {
- p = base + (lim >> 1);
- cmp = (*compar)(key, *p);
- if (cmp > 0) { /* key > p: move right */
- base = p + 1;
- lim--;
- } else if (cmp == 0) {
- return (void **)p;
- } /* else move left */
- }
- return NULL;
+ git__qsort_r_glue *glue = payload;
+ return glue->cmp(a, b, glue->payload);
}
+#endif
+void git__qsort_r(
+ void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload)
+{
+#if defined(__MINGW32__) || defined(AMIGA) || \
+ defined(__OpenBSD__) || defined(__NetBSD__) || \
+ defined(__gnu_hurd__) || defined(__ANDROID_API__) || \
+ defined(__sun) || defined(__CYGWIN__) || \
+ (__GLIBC__ == 2 && __GLIBC_MINOR__ < 8)
+ git__insertsort_r(els, nel, elsize, NULL, cmp, payload);
+#elif defined(GIT_WIN32)
+ git__qsort_r_glue glue = { cmp, payload };
+ qsort_s(els, nel, elsize, git__qsort_r_glue_cmp, &glue);
+#elif defined(BSD)
+ git__qsort_r_glue glue = { cmp, payload };
+ qsort_r(els, nel, elsize, &glue, git__qsort_r_glue_cmp);
+#else
+ qsort_r(els, nel, elsize, cmp, payload);
+#endif
+}
+
+void git__insertsort_r(
+ void *els, size_t nel, size_t elsize, void *swapel,
+ git__sort_r_cmp cmp, void *payload)
+{
+ uint8_t *base = els;
+ uint8_t *end = base + nel * elsize;
+ uint8_t *i, *j;
+ bool freeswap = !swapel;
+
+ if (freeswap)
+ swapel = git__malloc(elsize);
+
+ for (i = base + elsize; i < end; i += elsize)
+ for (j = i; j > base && cmp(j, j - elsize, payload) < 0; j -= elsize) {
+ memcpy(swapel, j, elsize);
+ memcpy(j, j - elsize, elsize);
+ memcpy(j - elsize, swapel, elsize);
+ }
+
+ if (freeswap)
+ git__free(swapel);
+}
diff --git a/src/util.h b/src/util.h
index e78b2cd5e..ca676c039 100644
--- a/src/util.h
+++ b/src/util.h
@@ -1,12 +1,35 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_util_h__
#define INCLUDE_util_h__
+#include "common.h"
+#include "strnlen.h"
+
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
-#define bitsizeof(x) (CHAR_BIT * sizeof(x))
+#define bitsizeof(x) (CHAR_BIT * sizeof(x))
#define MSB(x, bits) ((x) & (~0ULL << (bitsizeof(x) - (bits))))
#ifndef min
# define min(a,b) ((a) < (b) ? (a) : (b))
#endif
+#ifndef max
+# define max(a,b) ((a) > (b) ? (a) : (b))
+#endif
+
+#define GIT_DATE_RFC2822_SZ 32
+
+/**
+ * Return the length of a constant string.
+ * We are aware that `strlen` performs the same task and is usually
+ * optimized away by the compiler, whilst being safer because it returns
+ * valid values when passed a pointer instead of a constant string; however
+ * this macro will transparently work with wide-char and single-char strings.
+ */
+#define CONST_STRLEN(x) ((sizeof(x)/sizeof(x[0])) - 1)
/*
* Custom memory allocation wrappers
@@ -16,60 +39,82 @@
GIT_INLINE(void *) git__malloc(size_t len)
{
void *ptr = malloc(len);
- if (!ptr)
- git__throw(GIT_ENOMEM, "Out of memory. Failed to allocate %d bytes.", (int)len);
+ if (!ptr) giterr_set_oom();
return ptr;
}
GIT_INLINE(void *) git__calloc(size_t nelem, size_t elsize)
{
void *ptr = calloc(nelem, elsize);
- if (!ptr)
- git__throw(GIT_ENOMEM, "Out of memory. Failed to allocate %d bytes.", (int)elsize);
+ if (!ptr) giterr_set_oom();
return ptr;
}
GIT_INLINE(char *) git__strdup(const char *str)
{
char *ptr = strdup(str);
- if (!ptr)
- git__throw(GIT_ENOMEM, "Out of memory. Failed to duplicate string");
+ if (!ptr) giterr_set_oom();
return ptr;
}
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;
+ length = p_strnlen(str, n);
+
+ ptr = (char*)git__malloc(length + 1);
- ptr = (char*)malloc(length + 1);
if (!ptr)
- git__throw(GIT_ENOMEM, "Out of memory. Failed to duplicate string");
+ return NULL;
+
+ if (length)
+ memcpy(ptr, str, length);
- memcpy(ptr, str, length);
- ptr[length] = 0;
+ ptr[length] = '\0';
return ptr;
}
+/* NOTE: This doesn't do null or '\0' checking. Watch those boundaries! */
+GIT_INLINE(char *) git__substrdup(const char *start, size_t n)
+{
+ char *ptr = (char*)git__malloc(n+1);
+ memcpy(ptr, start, n);
+ ptr[n] = '\0';
+ return ptr;
+}
+
GIT_INLINE(void *) git__realloc(void *ptr, size_t size)
{
void *new_ptr = realloc(ptr, size);
- if (!new_ptr)
- git__throw(GIT_ENOMEM, "Out of memory. Failed to allocate %d bytes.", (int)size);
+ if (!new_ptr) giterr_set_oom();
return new_ptr;
}
-extern int git__fmt(char *, size_t, const char *, ...)
- GIT_FORMAT_PRINTF(3, 4);
+GIT_INLINE(void) git__free(void *ptr)
+{
+ free(ptr);
+}
+
+#define STRCMP_CASESELECT(IGNORE_CASE, STR1, STR2) \
+ ((IGNORE_CASE) ? strcasecmp((STR1), (STR2)) : strcmp((STR1), (STR2)))
+
+#define CASESELECT(IGNORE_CASE, ICASE, CASE) \
+ ((IGNORE_CASE) ? (ICASE) : (CASE))
+
extern int git__prefixcmp(const char *str, const char *prefix);
+extern int git__prefixcmp_icase(const char *str, const char *prefix);
extern int git__suffixcmp(const char *str, const char *suffix);
-extern int git__strtol32(long *n, const char *buff, const char **end_buf, int base);
+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);
extern void git__hexdump(const char *buffer, size_t n);
extern uint32_t git__hash(const void *key, int len, uint32_t seed);
@@ -81,6 +126,20 @@ GIT_INLINE(int) git__is_sizet(git_off_t p)
return p == (git_off_t)r;
}
+/** @return true if p fits into the range of a uint32_t */
+GIT_INLINE(int) git__is_uint32(size_t p)
+{
+ uint32_t r = (uint32_t)p;
+ return p == (size_t)r;
+}
+
+/** @return true if p fits into the range of an unsigned long */
+GIT_INLINE(int) git__is_ulong(git_off_t p)
+{
+ unsigned long r = (unsigned long)p;
+ return p == (git_off_t)r;
+}
+
/* 32-bit cross-platform rotl */
#ifdef _MSC_VER /* use built-in method in MSVC */
# define git__rotl(v, s) (uint32_t)_rotl(v, s)
@@ -89,35 +148,299 @@ GIT_INLINE(int) git__is_sizet(git_off_t p)
#endif
extern char *git__strtok(char **end, const char *sep);
+extern char *git__strsep(char **end, const char *sep);
-extern void git__strntolower(char *str, int len);
+extern void git__strntolower(char *str, size_t len);
extern void git__strtolower(char *str);
-#define STRLEN(str) (sizeof(str) - 1)
+GIT_INLINE(const char *) git__next_line(const char *s)
+{
+ while (*s && *s != '\n') s++;
+ while (*s == '\n' || *s == '\r') s++;
+ return s;
+}
+
+GIT_INLINE(const void *) git__memrchr(const void *s, int c, size_t n)
+{
+ const unsigned char *cp;
+
+ if (n != 0) {
+ cp = (unsigned char *)s + n;
+ do {
+ if (*(--cp) == (unsigned char)c)
+ return cp;
+ } while (--n != 0);
+ }
+
+ return NULL;
+}
+
+typedef int (*git__tsort_cmp)(const void *a, const void *b);
+
+extern void git__tsort(void **dst, size_t size, git__tsort_cmp cmp);
+
+typedef int (*git__sort_r_cmp)(const void *a, const void *b, void *payload);
+
+extern void git__tsort_r(
+ void **dst, size_t size, git__sort_r_cmp cmp, void *payload);
+
+extern void git__qsort_r(
+ void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload);
+
+extern void git__insertsort_r(
+ void *els, size_t nel, size_t elsize, void *swapel,
+ git__sort_r_cmp cmp, void *payload);
+
+/**
+ * @param position If non-NULL, this will be set to the position where the
+ * element is or would be inserted if not found.
+ * @return 0 if found; GIT_ENOTFOUND if not found
+ */
+extern int git__bsearch(
+ void **array,
+ size_t array_len,
+ const void *key,
+ int (*compare)(const void *key, const void *element),
+ size_t *position);
+
+extern int git__bsearch_r(
+ void **array,
+ size_t array_len,
+ const void *key,
+ int (*compare_r)(const void *key, const void *element, void *payload),
+ void *payload,
+ size_t *position);
+
+extern int git__strcmp_cb(const void *a, const void *b);
+extern int git__strcasecmp_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);
+
+extern int git__strcasesort_cmp(const char *a, const char *b);
-extern int git__fnmatch(const char *pattern, const char *name, int flags);
+#include "thread-utils.h"
+
+typedef struct {
+ git_atomic refcount;
+ void *owner;
+} git_refcount;
+
+typedef void (*git_refcount_freeptr)(void *r);
+
+#define GIT_REFCOUNT_INC(r) { \
+ git_atomic_inc(&((git_refcount *)(r))->refcount); \
+}
+
+#define GIT_REFCOUNT_DEC(_r, do_free) { \
+ git_refcount *r = (git_refcount *)(_r); \
+ int val = git_atomic_dec(&r->refcount); \
+ if (val <= 0 && r->owner == NULL) { do_free(_r); } \
+}
+
+#define GIT_REFCOUNT_OWN(r, o) { \
+ ((git_refcount *)(r))->owner = o; \
+}
+
+#define GIT_REFCOUNT_OWNER(r) (((git_refcount *)(r))->owner)
+
+#define GIT_REFCOUNT_VAL(r) git_atomic_get(&((git_refcount *)(r))->refcount)
+
+
+static signed char from_hex[] = {
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 10 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 20 */
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, /* 30 */
+-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 40 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 50 */
+-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 60 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 70 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 90 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* a0 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* b0 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* c0 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* d0 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* e0 */
+-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* f0 */
+};
+
+GIT_INLINE(int) git__fromhex(char h)
+{
+ return from_hex[(unsigned char) h];
+}
+
+GIT_INLINE(int) git__ishex(const char *str)
+{
+ unsigned i;
+ for (i=0; i<strlen(str); i++)
+ if (git__fromhex(str[i]) < 0)
+ return 0;
+ return 1;
+}
+
+GIT_INLINE(size_t) git__size_t_bitmask(size_t v)
+{
+ v--;
+ v |= v >> 1;
+ v |= v >> 2;
+ v |= v >> 4;
+ v |= v >> 8;
+ v |= v >> 16;
+
+ return v;
+}
+
+GIT_INLINE(size_t) git__size_t_powerof2(size_t v)
+{
+ return git__size_t_bitmask(v) + 1;
+}
+
+GIT_INLINE(bool) git__isupper(int c)
+{
+ return (c >= 'A' && c <= 'Z');
+}
+
+GIT_INLINE(bool) git__isalpha(int c)
+{
+ return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
+}
+
+GIT_INLINE(bool) git__isdigit(int c)
+{
+ return (c >= '0' && c <= '9');
+}
+
+GIT_INLINE(bool) git__isspace(int c)
+{
+ return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v' || c == 0x85 /* Unicode CR+LF */);
+}
+
+GIT_INLINE(bool) git__isspace_nonlf(int c)
+{
+ return (c == ' ' || c == '\t' || c == '\f' || c == '\r' || c == '\v' || c == 0x85 /* Unicode CR+LF */);
+}
+
+GIT_INLINE(bool) git__iswildcard(int c)
+{
+ return (c == '*' || c == '?' || c == '[');
+}
+
+/*
+ * Parse a string value as a boolean, just like Core Git does.
+ *
+ * Valid values for true are: 'true', 'yes', 'on'
+ * Valid values for false are: 'false', 'no', 'off'
+ */
+extern int git__parse_bool(int *out, const char *value);
+
+/*
+ * Parse a string into a value as a git_time_t.
+ *
+ * Sample valid input:
+ * - "yesterday"
+ * - "July 17, 2003"
+ * - "2003-7-17 08:23"
+ */
+extern int git__date_parse(git_time_t *out, const char *date);
/*
- * Realloc the buffer pointed at by variable 'x' so that it can hold
- * at least 'nr' entries; the number of entries currently allocated
- * is 'alloc', using the standard growing factor alloc_nr() macro.
+ * Format a git_time as a RFC2822 string
*
- * DO NOT USE any expression with side-effect for 'x' or 'alloc'.
+ * @param out buffer to store formatted date; a '\\0' terminator will automatically be added.
+ * @param len size of the buffer; should be atleast `GIT_DATE_RFC2822_SZ` in size;
+ * @param date the date to be formatted
+ * @return 0 if successful; -1 on error
*/
-#define alloc_nr(x) (((x)+16)*3/2)
-#define ALLOC_GROW(x, nr, alloc) \
- do { \
- if ((nr) > alloc) { \
- if (alloc_nr(alloc) < (nr)) \
- alloc = (nr); \
- else \
- alloc = alloc_nr(alloc); \
- x = xrealloc((x), alloc * sizeof(*(x))); \
- } \
- } while (0)
-
-extern void git__tsort(void **dst, size_t size, int (*cmp)(const void *, const void *));
-extern void **git__bsearch(const void *key, void **base, size_t nmemb,
- int (*compar)(const void *, const void *));
+extern int git__date_rfc2822_fmt(char *out, size_t len, const git_time *date);
+
+/*
+ * Unescapes a string in-place.
+ *
+ * Edge cases behavior:
+ * - "jackie\" -> "jacky\"
+ * - "chan\\" -> "chan\"
+ */
+extern size_t git__unescape(char *str);
+
+/*
+ * Safely zero-out memory, making sure that the compiler
+ * doesn't optimize away the operation.
+ */
+GIT_INLINE(void) git__memzero(void *data, size_t size)
+{
+#ifdef _MSC_VER
+ SecureZeroMemory((PVOID)data, size);
+#else
+ volatile uint8_t *scan = (volatile uint8_t *)data;
+
+ while (size--)
+ *scan++ = 0x0;
+#endif
+}
+
+#ifdef GIT_WIN32
+
+GIT_INLINE(double) git__timer(void)
+{
+ /* We need the initial tick count to detect if the tick
+ * count has rolled over. */
+ static DWORD initial_tick_count = 0;
+
+ /* GetTickCount returns the number of milliseconds that have
+ * elapsed since the system was started. */
+ DWORD count = GetTickCount();
+
+ if(initial_tick_count == 0) {
+ initial_tick_count = count;
+ } else if (count < initial_tick_count) {
+ /* The tick count has rolled over - adjust for it. */
+ count = (0xFFFFFFFF - initial_tick_count) + count;
+ }
+
+ return (double) count / (double) 1000;
+}
+
+#elif __APPLE__
+
+#include <mach/mach_time.h>
+
+GIT_INLINE(double) git__timer(void)
+{
+ uint64_t time = mach_absolute_time();
+ static double scaling_factor = 0;
+
+ if (scaling_factor == 0) {
+ mach_timebase_info_data_t info;
+ (void)mach_timebase_info(&info);
+ scaling_factor = (double)info.numer / (double)info.denom;
+ }
+
+ return (double)time * scaling_factor / 1.0E-9;
+}
+
+#else
+
+#include <sys/time.h>
+
+GIT_INLINE(double) git__timer(void)
+{
+ struct timespec tp;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) {
+ return (double) tp.tv_sec + (double) tp.tv_nsec / 1E-9;
+ } else {
+ /* Fall back to using gettimeofday */
+ struct timeval tv;
+ struct timezone tz;
+ gettimeofday(&tv, &tz);
+ return (double)tv.tv_sec + (double)tv.tv_usec / 1E-6;
+ }
+}
+
+#endif
#endif /* INCLUDE_util_h__ */
diff --git a/src/vector.c b/src/vector.c
index 0b83b8b0d..c769b696a 100644
--- a/src/vector.c
+++ b/src/vector.c
@@ -1,134 +1,221 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * 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 "vector.h"
-static const double resize_factor = 1.75;
-static const size_t minimum_size = 8;
+/* In elements, not bytes */
+#define MIN_ALLOCSIZE 8
+
+GIT_INLINE(size_t) compute_new_size(git_vector *v)
+{
+ size_t new_size = v->_alloc_size;
+
+ /* Use a resize factor of 1.5, which is quick to compute using integer
+ * instructions and less than the golden ratio (1.618...) */
+ if (new_size < MIN_ALLOCSIZE)
+ new_size = MIN_ALLOCSIZE;
+ else if (new_size <= (SIZE_MAX / 3) * 2)
+ new_size += new_size / 2;
+ else
+ new_size = SIZE_MAX;
+
+ return new_size;
+}
-static int resize_vector(git_vector *v)
+GIT_INLINE(int) resize_vector(git_vector *v, size_t new_size)
{
- v->_alloc_size = ((unsigned int)(v->_alloc_size * resize_factor)) + 1;
- if (v->_alloc_size < minimum_size)
- v->_alloc_size = minimum_size;
+ size_t new_bytes = new_size * sizeof(void *);
+ void *new_contents;
+
+ /* Check for overflow */
+ if (new_bytes / sizeof(void *) != new_size)
+ GITERR_CHECK_ALLOC(NULL);
- v->contents = realloc(v->contents, v->_alloc_size * sizeof(void *));
- if (v->contents == NULL)
- return GIT_ENOMEM;
+ new_contents = git__realloc(v->contents, new_bytes);
+ GITERR_CHECK_ALLOC(new_contents);
- return GIT_SUCCESS;
+ v->_alloc_size = new_size;
+ v->contents = new_contents;
+
+ return 0;
}
+int git_vector_dup(git_vector *v, const git_vector *src, git_vector_cmp cmp)
+{
+ size_t bytes;
+
+ assert(v && src);
+
+ bytes = src->length * sizeof(void *);
+
+ v->_alloc_size = src->length;
+ v->_cmp = cmp ? cmp : src->_cmp;
+ v->length = src->length;
+ v->flags = src->flags;
+ if (cmp != src->_cmp)
+ git_vector_set_sorted(v, 0);
+ v->contents = git__malloc(bytes);
+ GITERR_CHECK_ALLOC(v->contents);
+
+ memcpy(v->contents, src->contents, bytes);
+
+ return 0;
+}
void git_vector_free(git_vector *v)
{
assert(v);
- free(v->contents);
+
+ git__free(v->contents);
+ v->contents = NULL;
+
+ v->length = 0;
+ v->_alloc_size = 0;
}
-int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp)
+void git_vector_free_deep(git_vector *v)
{
+ size_t i;
+
assert(v);
- memset(v, 0x0, sizeof(git_vector));
+ for (i = 0; i < v->length; ++i) {
+ git__free(v->contents[i]);
+ v->contents[i] = NULL;
+ }
+
+ git_vector_free(v);
+}
- if (initial_size == 0)
- initial_size = minimum_size;
+int git_vector_init(git_vector *v, size_t initial_size, git_vector_cmp cmp)
+{
+ assert(v);
- v->_alloc_size = initial_size;
+ v->_alloc_size = 0;
v->_cmp = cmp;
-
v->length = 0;
- v->sorted = 1;
+ v->flags = GIT_VECTOR_SORTED;
+ v->contents = NULL;
- v->contents = git__malloc(v->_alloc_size * sizeof(void *));
- if (v->contents == NULL)
- return GIT_ENOMEM;
+ return resize_vector(v, max(initial_size, MIN_ALLOCSIZE));
+}
- return GIT_SUCCESS;
+void **git_vector_detach(size_t *size, size_t *asize, git_vector *v)
+{
+ void **data = v->contents;
+
+ if (size)
+ *size = v->length;
+ if (asize)
+ *asize = v->_alloc_size;
+
+ v->_alloc_size = 0;
+ v->length = 0;
+ v->contents = NULL;
+
+ return data;
}
int git_vector_insert(git_vector *v, void *element)
{
assert(v);
- if (v->length >= v->_alloc_size) {
- if (resize_vector(v) < 0)
- return GIT_ENOMEM;
- }
+ if (v->length >= v->_alloc_size &&
+ resize_vector(v, compute_new_size(v)) < 0)
+ return -1;
v->contents[v->length++] = element;
- v->sorted = 0;
- return GIT_SUCCESS;
+ git_vector_set_sorted(v, v->length <= 1);
+
+ return 0;
+}
+
+int git_vector_insert_sorted(
+ git_vector *v, void *element, int (*on_dup)(void **old, void *new))
+{
+ int result;
+ size_t pos;
+
+ assert(v && v->_cmp);
+
+ if (!git_vector_is_sorted(v))
+ git_vector_sort(v);
+
+ if (v->length >= v->_alloc_size &&
+ resize_vector(v, compute_new_size(v)) < 0)
+ return -1;
+
+ /* If we find the element and have a duplicate handler callback,
+ * invoke it. If it returns non-zero, then cancel insert, otherwise
+ * proceed with normal insert.
+ */
+ if (!git__bsearch(v->contents, v->length, element, v->_cmp, &pos) &&
+ on_dup && (result = on_dup(&v->contents[pos], element)) < 0)
+ return result;
+
+ /* shift elements to the right */
+ if (pos < v->length)
+ memmove(v->contents + pos + 1, v->contents + pos,
+ (v->length - pos) * sizeof(void *));
+
+ v->contents[pos] = element;
+ v->length++;
+
+ return 0;
}
void git_vector_sort(git_vector *v)
{
assert(v);
- if (v->sorted || v->_cmp == NULL)
+ if (git_vector_is_sorted(v) || !v->_cmp)
return;
- git__tsort(v->contents, v->length, v->_cmp);
- v->sorted = 1;
+ if (v->length > 1)
+ git__tsort(v->contents, v->length, v->_cmp);
+ git_vector_set_sorted(v, 1);
}
-int git_vector_bsearch2(git_vector *v, git_vector_cmp key_lookup, const void *key)
+int git_vector_bsearch2(
+ size_t *at_pos,
+ git_vector *v,
+ git_vector_cmp key_lookup,
+ const void *key)
{
- void **find;
-
assert(v && key && key_lookup);
/* need comparison function to sort the vector */
- if (v->_cmp == NULL)
- return git__throw(GIT_ENOTFOUND, "Can't sort vector. No comparison function set");
+ if (!v->_cmp)
+ return -1;
git_vector_sort(v);
- find = git__bsearch(key, v->contents, v->length, key_lookup);
- if (find != NULL)
- return (int)(find - v->contents);
-
- return git__throw(GIT_ENOTFOUND, "Can't find element");
+ return git__bsearch(v->contents, v->length, key, key_lookup, at_pos);
}
-int git_vector_search2(git_vector *v, git_vector_cmp key_lookup, const void *key)
+int git_vector_search2(
+ size_t *at_pos, const git_vector *v, git_vector_cmp key_lookup, const void *key)
{
- unsigned int i;
+ size_t i;
assert(v && key && key_lookup);
for (i = 0; i < v->length; ++i) {
- if (key_lookup(key, v->contents[i]) == 0)
- return i;
+ if (key_lookup(key, v->contents[i]) == 0) {
+ if (at_pos)
+ *at_pos = i;
+
+ return 0;
+ }
}
- return git__throw(GIT_ENOTFOUND, "Can't find element");
+ return GIT_ENOTFOUND;
}
static int strict_comparison(const void *a, const void *b)
@@ -136,36 +223,40 @@ static int strict_comparison(const void *a, const void *b)
return (a == b) ? 0 : -1;
}
-int git_vector_search(git_vector *v, const void *entry)
-{
- return git_vector_search2(v, v->_cmp ? v->_cmp : strict_comparison, entry);
-}
-
-int git_vector_bsearch(git_vector *v, const void *key)
+int git_vector_search(size_t *at_pos, const git_vector *v, const void *entry)
{
- return git_vector_bsearch2(v, v->_cmp, key);
+ return git_vector_search2(at_pos, v, v->_cmp ? v->_cmp : strict_comparison, entry);
}
-int git_vector_remove(git_vector *v, unsigned int idx)
+int git_vector_remove(git_vector *v, size_t idx)
{
- unsigned int i;
+ size_t shift_count;
assert(v);
- if (idx >= v->length || v->length == 0)
- return git__throw(GIT_ENOTFOUND, "Can't remove element. Index out of bounds");
+ if (idx >= v->length)
+ return GIT_ENOTFOUND;
- for (i = idx; i < v->length - 1; ++i)
- v->contents[i] = v->contents[i + 1];
+ shift_count = v->length - idx - 1;
+
+ if (shift_count)
+ memmove(&v->contents[idx], &v->contents[idx + 1],
+ shift_count * sizeof(void *));
v->length--;
- return GIT_SUCCESS;
+ return 0;
}
-void git_vector_uniq(git_vector *v)
+void git_vector_pop(git_vector *v)
+{
+ if (v->length > 0)
+ v->length--;
+}
+
+void git_vector_uniq(git_vector *v, void (*git_free_cb)(void *))
{
git_vector_cmp cmp;
- unsigned int i, j;
+ size_t i, j;
if (v->length <= 1)
return;
@@ -174,19 +265,95 @@ void git_vector_uniq(git_vector *v)
cmp = v->_cmp ? v->_cmp : strict_comparison;
for (i = 0, j = 1 ; j < v->length; ++j)
- if (!cmp(v->contents[i], v->contents[j]))
+ if (!cmp(v->contents[i], v->contents[j])) {
+ if (git_free_cb)
+ git_free_cb(v->contents[i]);
+
v->contents[i] = v->contents[j];
- else
+ } else
v->contents[++i] = v->contents[j];
v->length -= j - i - 1;
}
+void git_vector_remove_matching(
+ git_vector *v,
+ int (*match)(const git_vector *v, size_t idx, void *payload),
+ void *payload)
+{
+ size_t i, j;
+
+ for (i = 0, j = 0; j < v->length; ++j) {
+ v->contents[i] = v->contents[j];
+
+ if (!match(v, i, payload))
+ i++;
+ }
+
+ v->length = i;
+}
+
void git_vector_clear(git_vector *v)
{
assert(v);
v->length = 0;
- v->sorted = 1;
+ git_vector_set_sorted(v, 1);
+}
+
+void git_vector_swap(git_vector *a, git_vector *b)
+{
+ git_vector t;
+
+ assert(a && b);
+
+ if (a != b) {
+ memcpy(&t, a, sizeof(t));
+ memcpy(a, b, sizeof(t));
+ memcpy(b, &t, sizeof(t));
+ }
}
+int git_vector_resize_to(git_vector *v, size_t new_length)
+{
+ if (new_length > v->_alloc_size &&
+ resize_vector(v, new_length) < 0)
+ return -1;
+
+ if (new_length > v->length)
+ 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 (position + 1 > v->length) {
+ if (git_vector_resize_to(v, position + 1) < 0)
+ return -1;
+ }
+ if (old != NULL)
+ *old = v->contents[position];
+
+ v->contents[position] = value;
+
+ return 0;
+}
+
+int git_vector_verify_sorted(const git_vector *v)
+{
+ size_t i;
+
+ if (!git_vector_is_sorted(v))
+ return -1;
+
+ for (i = 1; i < v->length; ++i) {
+ if (v->_cmp(v->contents[i - 1], v->contents[i]) > 0)
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/src/vector.h b/src/vector.h
index c43a7ce07..aac46c4b3 100644
--- a/src/vector.h
+++ b/src/vector.h
@@ -1,39 +1,117 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_vector_h__
#define INCLUDE_vector_h__
-#include "git2/common.h"
+#include "common.h"
typedef int (*git_vector_cmp)(const void *, const void *);
+enum {
+ GIT_VECTOR_SORTED = (1u << 0),
+ GIT_VECTOR_FLAG_MAX = (1u << 1),
+};
+
typedef struct git_vector {
- unsigned int _alloc_size;
+ size_t _alloc_size;
git_vector_cmp _cmp;
void **contents;
- unsigned int length;
- int sorted;
+ size_t length;
+ uint32_t flags;
} git_vector;
-int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp);
+#define GIT_VECTOR_INIT {0}
+
+int git_vector_init(git_vector *v, size_t initial_size, git_vector_cmp cmp);
void git_vector_free(git_vector *v);
+void git_vector_free_deep(git_vector *v); /* free each entry and self */
void git_vector_clear(git_vector *v);
+int git_vector_dup(git_vector *v, const git_vector *src, git_vector_cmp cmp);
+void git_vector_swap(git_vector *a, git_vector *b);
-int git_vector_search(git_vector *v, const void *entry);
-int git_vector_search2(git_vector *v, git_vector_cmp cmp, const void *key);
-
-int git_vector_bsearch(git_vector *v, const void *entry);
-int git_vector_bsearch2(git_vector *v, git_vector_cmp cmp, const void *key);
+void **git_vector_detach(size_t *size, size_t *asize, git_vector *v);
void git_vector_sort(git_vector *v);
-GIT_INLINE(void *) git_vector_get(git_vector *v, unsigned int position)
+/** Linear search for matching entry using internal comparison function */
+int git_vector_search(size_t *at_pos, const git_vector *v, const void *entry);
+
+/** Linear search for matching entry using explicit comparison function */
+int git_vector_search2(size_t *at_pos, const git_vector *v, git_vector_cmp cmp, const void *key);
+
+/**
+ * Binary search for matching entry using explicit comparison function that
+ * returns position where item would go if not found.
+ */
+int git_vector_bsearch2(
+ size_t *at_pos, git_vector *v, git_vector_cmp cmp, const void *key);
+
+/** Binary search for matching entry using internal comparison function */
+GIT_INLINE(int) git_vector_bsearch(size_t *at_pos, git_vector *v, const void *key)
+{
+ return git_vector_bsearch2(at_pos, v, v->_cmp, key);
+}
+
+GIT_INLINE(void *) git_vector_get(const git_vector *v, size_t position)
{
return (position < v->length) ? v->contents[position] : NULL;
}
+#define GIT_VECTOR_GET(V,I) ((I) < (V)->length ? (V)->contents[(I)] : NULL)
+
+GIT_INLINE(size_t) git_vector_length(const git_vector *v)
+{
+ return v->length;
+}
+
+GIT_INLINE(void *) git_vector_last(const git_vector *v)
+{
+ return (v->length > 0) ? git_vector_get(v, v->length - 1) : NULL;
+}
+
#define git_vector_foreach(v, iter, elem) \
for ((iter) = 0; (iter) < (v)->length && ((elem) = (v)->contents[(iter)], 1); (iter)++ )
+#define git_vector_rforeach(v, iter, elem) \
+ for ((iter) = (v)->length - 1; (iter) < SIZE_MAX && ((elem) = (v)->contents[(iter)], 1); (iter)-- )
+
int git_vector_insert(git_vector *v, void *element);
-int git_vector_remove(git_vector *v, unsigned int idx);
-void git_vector_uniq(git_vector *v);
+int git_vector_insert_sorted(git_vector *v, void *element,
+ int (*on_dup)(void **old, void *new));
+int git_vector_remove(git_vector *v, size_t idx);
+void git_vector_pop(git_vector *v);
+void git_vector_uniq(git_vector *v, void (*git_free_cb)(void *));
+
+void git_vector_remove_matching(
+ git_vector *v,
+ int (*match)(const git_vector *v, size_t idx, void *payload),
+ void *payload);
+
+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);
+
+/** Check if vector is sorted */
+#define git_vector_is_sorted(V) (((V)->flags & GIT_VECTOR_SORTED) != 0)
+
+/** Directly set sorted state of vector */
+#define git_vector_set_sorted(V,S) do { \
+ (V)->flags = (S) ? ((V)->flags | GIT_VECTOR_SORTED) : \
+ ((V)->flags & ~GIT_VECTOR_SORTED); } while (0)
+
+/** Set the comparison function used for sorting the vector */
+GIT_INLINE(void) git_vector_set_cmp(git_vector *v, git_vector_cmp cmp)
+{
+ if (cmp != v->_cmp) {
+ v->_cmp = cmp;
+ git_vector_set_sorted(v, 0);
+ }
+}
+
+/* Just use this in tests, not for realz. returns -1 if not sorted */
+int git_vector_verify_sorted(const git_vector *v);
+
#endif
diff --git a/src/win32/dir.c b/src/win32/dir.c
index 069a41c3a..c7427ea54 100644
--- a/src/win32/dir.c
+++ b/src/win32/dir.c
@@ -1,98 +1,117 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
#define GIT__WIN32_NO_WRAP_DIR
-#include "dir.h"
-
-static int init_filter(char *filter, size_t n, const char *dir)
-{
- size_t len = strlen(dir);
-
- if (len+3 >= n)
- return 0;
-
- strcpy(filter, dir);
- if (len && dir[len-1] != '/')
- strcat(filter, "/");
- strcat(filter, "*");
-
- return 1;
-}
+#include "posix.h"
git__DIR *git__opendir(const char *dir)
{
- char filter[4096];
- git__DIR *new;
+ git_win32_path filter_w;
+ git__DIR *new = NULL;
+ size_t dirlen;
- if (!dir || !init_filter(filter, sizeof(filter), dir))
+ if (!dir || !git_win32__findfirstfile_filter(filter_w, dir))
return NULL;
- new = git__malloc(sizeof(*new));
+ dirlen = strlen(dir);
+
+ new = git__calloc(sizeof(*new) + dirlen + 1, 1);
if (!new)
return NULL;
+ memcpy(new->dir, dir, dirlen);
- new->dir = git__malloc(strlen(dir)+1);
- if (!new->dir) {
- free(new);
- return NULL;
- }
- strcpy(new->dir, dir);
+ new->h = FindFirstFileW(filter_w, &new->f);
- new->h = FindFirstFile(filter, &new->f);
if (new->h == INVALID_HANDLE_VALUE) {
- free(new->dir);
- free(new);
+ giterr_set(GITERR_OS, "Could not open directory '%s'", dir);
+ git__free(new);
return NULL;
}
- new->first = 1;
+ new->first = 1;
return new;
}
-struct git__dirent *git__readdir(git__DIR *d)
+int git__readdir_ext(
+ git__DIR *d,
+ struct git__dirent *entry,
+ struct git__dirent **result,
+ int *is_dir)
{
- if (!d || d->h == INVALID_HANDLE_VALUE)
- return NULL;
+ if (!d || !entry || !result || d->h == INVALID_HANDLE_VALUE)
+ return -1;
+
+ *result = NULL;
if (d->first)
d->first = 0;
- else {
- if (!FindNextFile(d->h, &d->f))
- return NULL;
+ else if (!FindNextFileW(d->h, &d->f)) {
+ if (GetLastError() == ERROR_NO_MORE_FILES)
+ return 0;
+ giterr_set(GITERR_OS, "Could not read from directory '%s'", d->dir);
+ return -1;
}
- if (strlen(d->f.cFileName) >= sizeof(d->entry.d_name))
- return NULL;
+ /* Convert the path to UTF-8 */
+ if (git_win32_path_to_utf8(entry->d_name, d->f.cFileName) < 0)
+ return -1;
- d->entry.d_ino = 0;
- strcpy(d->entry.d_name, d->f.cFileName);
+ entry->d_ino = 0;
- return &d->entry;
+ *result = entry;
+
+ if (is_dir != NULL)
+ *is_dir = ((d->f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0);
+
+ return 0;
+}
+
+struct git__dirent *git__readdir(git__DIR *d)
+{
+ struct git__dirent *result;
+ if (git__readdir_ext(d, &d->entry, &result, NULL) < 0)
+ return NULL;
+ return result;
}
void git__rewinddir(git__DIR *d)
{
- char filter[4096];
+ git_win32_path filter_w;
- if (d) {
- if (d->h != INVALID_HANDLE_VALUE)
- FindClose(d->h);
+ if (!d)
+ return;
+
+ if (d->h != INVALID_HANDLE_VALUE) {
+ FindClose(d->h);
d->h = INVALID_HANDLE_VALUE;
d->first = 0;
- if (init_filter(filter, sizeof(filter), d->dir)) {
- d->h = FindFirstFile(filter, &d->f);
- if (d->h != INVALID_HANDLE_VALUE)
- d->first = 1;
- }
}
+
+ if (!git_win32__findfirstfile_filter(filter_w, d->dir))
+ return;
+
+ d->h = FindFirstFileW(filter_w, &d->f);
+
+ if (d->h == INVALID_HANDLE_VALUE)
+ giterr_set(GITERR_OS, "Could not open directory '%s'", d->dir);
+ else
+ d->first = 1;
}
int git__closedir(git__DIR *d)
{
- if (d) {
- if (d->h != INVALID_HANDLE_VALUE)
- FindClose(d->h);
- if (d->dir)
- free(d->dir);
- free(d);
+ if (!d)
+ return 0;
+
+ if (d->h != INVALID_HANDLE_VALUE) {
+ FindClose(d->h);
+ d->h = INVALID_HANDLE_VALUE;
}
+
+ git__free(d);
return 0;
}
diff --git a/src/win32/dir.h b/src/win32/dir.h
new file mode 100644
index 000000000..bef39d774
--- /dev/null
+++ b/src/win32/dir.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_dir_h__
+#define INCLUDE_dir_h__
+
+#include "common.h"
+#include "w32_util.h"
+
+struct git__dirent {
+ int d_ino;
+ git_win32_utf8_path d_name;
+};
+
+typedef struct {
+ HANDLE h;
+ WIN32_FIND_DATAW f;
+ struct git__dirent entry;
+ int first;
+ char dir[GIT_FLEX_ARRAY];
+} git__DIR;
+
+extern git__DIR *git__opendir(const char *);
+extern struct git__dirent *git__readdir(git__DIR *);
+extern int git__readdir_ext(
+ git__DIR *, struct git__dirent *, struct git__dirent **, int *);
+extern void git__rewinddir(git__DIR *);
+extern int git__closedir(git__DIR *);
+
+# ifndef GIT__WIN32_NO_WRAP_DIR
+# define dirent git__dirent
+# define DIR git__DIR
+# define opendir git__opendir
+# define readdir git__readdir
+# define readdir_r(d,e,r) git__readdir_ext((d),(e),(r),NULL)
+# define rewinddir git__rewinddir
+# define closedir git__closedir
+# endif
+
+#endif /* INCLUDE_dir_h__ */
diff --git a/src/win32/error.c b/src/win32/error.c
new file mode 100644
index 000000000..6b450093f
--- /dev/null
+++ b/src/win32/error.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "error.h"
+#include "utf-conv.h"
+
+#ifdef GIT_WINHTTP
+# include <winhttp.h>
+#endif
+
+char *git_win32_get_error_message(DWORD error_code)
+{
+ LPWSTR lpMsgBuf = NULL;
+ HMODULE hModule = NULL;
+ char *utf8_msg = NULL;
+ DWORD dwFlags =
+ FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS;
+
+ if (!error_code)
+ return NULL;
+
+#ifdef GIT_WINHTTP
+ /* Errors raised by WinHTTP are not in the system resource table */
+ if (error_code >= WINHTTP_ERROR_BASE &&
+ error_code <= WINHTTP_ERROR_LAST)
+ hModule = GetModuleHandleW(L"winhttp");
+#endif
+
+ GIT_UNUSED(hModule);
+
+ if (hModule)
+ dwFlags |= FORMAT_MESSAGE_FROM_HMODULE;
+ else
+ dwFlags |= FORMAT_MESSAGE_FROM_SYSTEM;
+
+ if (FormatMessageW(dwFlags, hModule, error_code,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPWSTR)&lpMsgBuf, 0, NULL)) {
+ /* Convert the message to UTF-8. If this fails, we will
+ * return NULL, which is a condition expected by the caller */
+ if (git__utf16_to_8_alloc(&utf8_msg, lpMsgBuf) < 0)
+ utf8_msg = NULL;
+
+ LocalFree(lpMsgBuf);
+ }
+
+ return utf8_msg;
+}
diff --git a/src/win32/error.h b/src/win32/error.h
new file mode 100644
index 000000000..12947a2e6
--- /dev/null
+++ b/src/win32/error.h
@@ -0,0 +1,13 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_git_win32_error_h__
+#define INCLUDE_git_win32_error_h__
+
+extern char *git_win32_get_error_message(DWORD error_code);
+
+#endif
diff --git a/src/win32/findfile.c b/src/win32/findfile.c
new file mode 100644
index 000000000..86d4ef5bd
--- /dev/null
+++ b/src/win32/findfile.c
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "utf-conv.h"
+#include "path.h"
+#include "findfile.h"
+
+#define REG_MSYSGIT_INSTALL_LOCAL L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
+
+#ifndef _WIN64
+#define REG_MSYSGIT_INSTALL REG_MSYSGIT_INSTALL_LOCAL
+#else
+#define REG_MSYSGIT_INSTALL L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"
+#endif
+
+typedef struct {
+ git_win32_path path;
+ DWORD len;
+} _findfile_path;
+
+static int git_win32__expand_path(_findfile_path *dest, const wchar_t *src)
+{
+ dest->len = ExpandEnvironmentStringsW(src, dest->path, ARRAY_SIZE(dest->path));
+
+ if (!dest->len || dest->len > ARRAY_SIZE(dest->path))
+ return -1;
+
+ return 0;
+}
+
+static int win32_path_to_8(git_buf *dest, const wchar_t *src)
+{
+ git_win32_utf8_path utf8_path;
+
+ if (git_win32_path_to_utf8(utf8_path, src) < 0) {
+ giterr_set(GITERR_OS, "Unable to convert path to UTF-8");
+ return -1;
+ }
+
+ /* Convert backslashes to forward slashes */
+ git_path_mkposix(utf8_path);
+
+ return git_buf_sets(dest, utf8_path);
+}
+
+static wchar_t* win32_walkpath(wchar_t *path, wchar_t *buf, size_t buflen)
+{
+ wchar_t term, *base = path;
+
+ assert(path && buf && buflen);
+
+ term = (*path == L'"') ? *path++ : L';';
+
+ for (buflen--; *path && *path != term && buflen; buflen--)
+ *buf++ = *path++;
+
+ *buf = L'\0'; /* reserved a byte via initial subtract */
+
+ while (*path == term || *path == L';')
+ path++;
+
+ return (path != base) ? path : NULL;
+}
+
+static int win32_find_git_in_path(git_buf *buf, const wchar_t *gitexe, const wchar_t *subdir)
+{
+ wchar_t *env = _wgetenv(L"PATH"), lastch;
+ _findfile_path root;
+ size_t gitexe_len = wcslen(gitexe);
+
+ if (!env)
+ return -1;
+
+ while ((env = win32_walkpath(env, root.path, MAX_PATH-1)) && *root.path) {
+ root.len = (DWORD)wcslen(root.path);
+ lastch = root.path[root.len - 1];
+
+ /* ensure trailing slash (MAX_PATH-1 to walkpath guarantees space) */
+ if (lastch != L'/' && lastch != L'\\') {
+ root.path[root.len++] = L'\\';
+ root.path[root.len] = L'\0';
+ }
+
+ if (root.len + gitexe_len >= MAX_PATH)
+ continue;
+ wcscpy(&root.path[root.len], gitexe);
+
+ if (_waccess(root.path, F_OK) == 0 && root.len > 5) {
+ /* replace "bin\\" or "cmd\\" with subdir */
+ wcscpy(&root.path[root.len - 4], subdir);
+
+ win32_path_to_8(buf, root.path);
+ return 0;
+ }
+ }
+
+ return GIT_ENOTFOUND;
+}
+
+static int win32_find_git_in_registry(
+ git_buf *buf, const HKEY hive, const wchar_t *key, const wchar_t *subdir)
+{
+ HKEY hKey;
+ int error = GIT_ENOTFOUND;
+
+ assert(buf);
+
+ if (!RegOpenKeyExW(hive, key, 0, KEY_READ, &hKey)) {
+ DWORD dwType, cbData;
+ git_win32_path path;
+
+ /* Ensure that the buffer is big enough to have the suffix attached
+ * after we receive the result. */
+ cbData = (DWORD)(sizeof(path) - wcslen(subdir) * sizeof(wchar_t));
+
+ /* InstallLocation points to the root of the git directory */
+ if (!RegQueryValueExW(hKey, L"InstallLocation", NULL, &dwType, (LPBYTE)path, &cbData) &&
+ dwType == REG_SZ) {
+
+ /* Append the suffix */
+ wcscat(path, subdir);
+
+ /* Convert to UTF-8, with forward slashes, and output the path
+ * to the provided buffer */
+ if (!win32_path_to_8(buf, path))
+ error = 0;
+ }
+
+ RegCloseKey(hKey);
+ }
+
+ return error;
+}
+
+static int win32_find_existing_dirs(
+ git_buf *out, const wchar_t *tmpl[])
+{
+ _findfile_path path16;
+ git_buf buf = GIT_BUF_INIT;
+
+ git_buf_clear(out);
+
+ for (; *tmpl != NULL; tmpl++) {
+ if (!git_win32__expand_path(&path16, *tmpl) &&
+ path16.path[0] != L'%' &&
+ !_waccess(path16.path, F_OK))
+ {
+ win32_path_to_8(&buf, path16.path);
+
+ if (buf.size)
+ git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
+ }
+ }
+
+ git_buf_free(&buf);
+
+ return (git_buf_oom(out) ? -1 : 0);
+}
+
+int git_win32__find_system_dirs(git_buf *out, const wchar_t *subdir)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ /* directories where git.exe & git.cmd are found */
+ if (!win32_find_git_in_path(&buf, L"git.exe", subdir) && buf.size)
+ git_buf_set(out, buf.ptr, buf.size);
+ else
+ git_buf_clear(out);
+
+ if (!win32_find_git_in_path(&buf, L"git.cmd", subdir) && buf.size)
+ git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
+
+ /* directories where git is installed according to registry */
+ if (!win32_find_git_in_registry(
+ &buf, HKEY_CURRENT_USER, REG_MSYSGIT_INSTALL_LOCAL, subdir) && buf.size)
+ git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
+
+ if (!win32_find_git_in_registry(
+ &buf, HKEY_LOCAL_MACHINE, REG_MSYSGIT_INSTALL, subdir) && buf.size)
+ git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);
+
+ git_buf_free(&buf);
+
+ return (git_buf_oom(out) ? -1 : 0);
+}
+
+int git_win32__find_global_dirs(git_buf *out)
+{
+ static const wchar_t *global_tmpls[4] = {
+ L"%HOME%\\",
+ L"%HOMEDRIVE%%HOMEPATH%\\",
+ L"%USERPROFILE%\\",
+ NULL,
+ };
+
+ return win32_find_existing_dirs(out, global_tmpls);
+}
+
+int git_win32__find_xdg_dirs(git_buf *out)
+{
+ static const wchar_t *global_tmpls[7] = {
+ L"%XDG_CONFIG_HOME%\\git",
+ L"%APPDATA%\\git",
+ L"%LOCALAPPDATA%\\git",
+ L"%HOME%\\.config\\git",
+ L"%HOMEDRIVE%%HOMEPATH%\\.config\\git",
+ L"%USERPROFILE%\\.config\\git",
+ NULL,
+ };
+
+ return win32_find_existing_dirs(out, global_tmpls);
+}
diff --git a/src/win32/findfile.h b/src/win32/findfile.h
new file mode 100644
index 000000000..a50319b9a
--- /dev/null
+++ b/src/win32/findfile.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_git_findfile_h__
+#define INCLUDE_git_findfile_h__
+
+extern int git_win32__find_system_dirs(git_buf *out, const wchar_t *subpath);
+extern int git_win32__find_global_dirs(git_buf *out);
+extern int git_win32__find_xdg_dirs(git_buf *out);
+
+#endif
+
diff --git a/src/win32/fnmatch.c b/src/win32/fnmatch.c
deleted file mode 100644
index de2f3e74f..000000000
--- a/src/win32/fnmatch.c
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright (c) 1989, 1993, 1994
- * The Regents of the University of California. All rights reserved.
- *
- * This code is derived from software contributed to Berkeley by
- * Guido van Rossum.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the University nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-/*
- * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6.
- * Compares a filename or pathname to a pattern.
- */
-
-#include <ctype.h>
-#include <stdio.h>
-#include <string.h>
-
-#include "fnmatch.h"
-
-#define EOS '\0'
-
-#define RANGE_MATCH 1
-#define RANGE_NOMATCH 0
-#define RANGE_ERROR (-1)
-
-static int rangematch(const char *, char, int, char **);
-
-int
-p_fnmatch(const char *pattern, const char *string, int flags)
-{
- const char *stringstart;
- char *newp;
- char c, test;
-
- for (stringstart = string;;)
- switch (c = *pattern++) {
- case EOS:
- if ((flags & FNM_LEADING_DIR) && *string == '/')
- return (0);
- return (*string == EOS ? 0 : FNM_NOMATCH);
- case '?':
- if (*string == EOS)
- return (FNM_NOMATCH);
- if (*string == '/' && (flags & FNM_PATHNAME))
- return (FNM_NOMATCH);
- if (*string == '.' && (flags & FNM_PERIOD) &&
- (string == stringstart ||
- ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
- return (FNM_NOMATCH);
- ++string;
- break;
- case '*':
- c = *pattern;
- /* Collapse multiple stars. */
- while (c == '*')
- c = *++pattern;
-
- if (*string == '.' && (flags & FNM_PERIOD) &&
- (string == stringstart ||
- ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
- return (FNM_NOMATCH);
-
- /* Optimize for pattern with * at end or before /. */
- if (c == EOS) {
- if (flags & FNM_PATHNAME)
- return ((flags & FNM_LEADING_DIR) ||
- strchr(string, '/') == NULL ?
- 0 : FNM_NOMATCH);
- else
- return (0);
- } else if (c == '/' && (flags & FNM_PATHNAME)) {
- if ((string = strchr(string, '/')) == NULL)
- return (FNM_NOMATCH);
- break;
- }
-
- /* General case, use recursion. */
- while ((test = *string) != EOS) {
- if (!p_fnmatch(pattern, string, flags & ~FNM_PERIOD))
- return (0);
- if (test == '/' && (flags & FNM_PATHNAME))
- break;
- ++string;
- }
- return (FNM_NOMATCH);
- case '[':
- if (*string == EOS)
- return (FNM_NOMATCH);
- if (*string == '/' && (flags & FNM_PATHNAME))
- return (FNM_NOMATCH);
- if (*string == '.' && (flags & FNM_PERIOD) &&
- (string == stringstart ||
- ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
- return (FNM_NOMATCH);
-
- switch (rangematch(pattern, *string, flags, &newp)) {
- case RANGE_ERROR:
- /* not a good range, treat as normal text */
- goto normal;
- case RANGE_MATCH:
- pattern = newp;
- break;
- case RANGE_NOMATCH:
- return (FNM_NOMATCH);
- }
- ++string;
- break;
- case '\\':
- if (!(flags & FNM_NOESCAPE)) {
- if ((c = *pattern++) == EOS) {
- c = '\\';
- --pattern;
- }
- }
- /* FALLTHROUGH */
- default:
- normal:
- if (c != *string && !((flags & FNM_CASEFOLD) &&
- (tolower((unsigned char)c) ==
- tolower((unsigned char)*string))))
- return (FNM_NOMATCH);
- ++string;
- break;
- }
- /* NOTREACHED */
-}
-
-static int
-rangematch(const char *pattern, char test, int flags, char **newp)
-{
- int negate, ok;
- char c, c2;
-
- /*
- * A bracket expression starting with an unquoted circumflex
- * character produces unspecified results (IEEE 1003.2-1992,
- * 3.13.2). This implementation treats it like '!', for
- * consistency with the regular expression syntax.
- * J.T. Conklin (conklin@ngai.kaleida.com)
- */
- if ((negate = (*pattern == '!' || *pattern == '^')) != 0)
- ++pattern;
-
- if (flags & FNM_CASEFOLD)
- test = (char)tolower((unsigned char)test);
-
- /*
- * A right bracket shall lose its special meaning and represent
- * itself in a bracket expression if it occurs first in the list.
- * -- POSIX.2 2.8.3.2
- */
- ok = 0;
- c = *pattern++;
- do {
- if (c == '\\' && !(flags & FNM_NOESCAPE))
- c = *pattern++;
- if (c == EOS)
- return (RANGE_ERROR);
- if (c == '/' && (flags & FNM_PATHNAME))
- return (RANGE_NOMATCH);
- if ((flags & FNM_CASEFOLD))
- c = (char)tolower((unsigned char)c);
- if (*pattern == '-'
- && (c2 = *(pattern+1)) != EOS && c2 != ']') {
- pattern += 2;
- if (c2 == '\\' && !(flags & FNM_NOESCAPE))
- c2 = *pattern++;
- if (c2 == EOS)
- return (RANGE_ERROR);
- if (flags & FNM_CASEFOLD)
- c2 = (char)tolower((unsigned char)c2);
- if (c <= test && test <= c2)
- ok = 1;
- } else if (c == test)
- ok = 1;
- } while ((c = *pattern++) != ']');
-
- *newp = (char *)pattern;
- return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH);
-}
-
diff --git a/src/win32/git2.rc b/src/win32/git2.rc
new file mode 100644
index 000000000..22c63f695
--- /dev/null
+++ b/src/win32/git2.rc
@@ -0,0 +1,40 @@
+#include <winver.h>
+#include "../../include/git2/version.h"
+
+#ifndef LIBGIT2_FILENAME
+# define LIBGIT2_FILENAME "git2"
+#endif
+
+VS_VERSION_INFO VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE
+ FILEVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,0
+ PRODUCTVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,0
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0
+#endif
+ FILEOS VOS_NT_WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904E4"
+ //language ID = U.S. English, char set = Windows, Multilingual
+ BEGIN
+ VALUE "FileDescription", "libgit2 - the Git linkable library\0"
+ VALUE "FileVersion", LIBGIT2_VERSION "\0"
+ VALUE "InternalName", LIBGIT2_FILENAME ".dll\0"
+ VALUE "LegalCopyright", "Copyright (C) the libgit2 contributors. All rights reserved.\0"
+ VALUE "OriginalFilename", LIBGIT2_FILENAME ".dll\0"
+ VALUE "ProductName", "libgit2\0"
+ VALUE "ProductVersion", LIBGIT2_VERSION "\0"
+ VALUE "Comments", "For more information visit http://libgit2.github.com/\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 1252
+ END
+END
diff --git a/src/win32/map.c b/src/win32/map.c
index 76b926490..ef83f882e 100644
--- a/src/win32/map.c
+++ b/src/win32/map.c
@@ -1,7 +1,14 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "map.h"
#include <errno.h>
+#ifndef NO_MMAP
static DWORD get_page_size(void)
{
@@ -16,6 +23,11 @@ static DWORD get_page_size(void)
return page_size;
}
+long git__page_size(void)
+{
+ return (long)get_page_size();
+}
+
int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset)
{
HANDLE fh = (HANDLE)_get_osfhandle(fd);
@@ -23,16 +35,11 @@ int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offs
DWORD fmap_prot = 0;
DWORD view_prot = 0;
DWORD off_low = 0;
- DWORD off_hi = 0;
+ DWORD off_hi = 0;
git_off_t page_start;
git_off_t page_offset;
- assert((out != NULL) && (len > 0));
-
- if ((out == NULL) || (len == 0)) {
- errno = EINVAL;
- return git__throw(GIT_ERROR, "Failed to mmap. No map or zero length");
- }
+ GIT_MMAP_VALIDATE(out, len, prot, flags);
out->data = NULL;
out->len = 0;
@@ -40,86 +47,75 @@ int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offs
if (fh == INVALID_HANDLE_VALUE) {
errno = EBADF;
- return git__throw(GIT_ERROR, "Failed to mmap. Invalid handle value");
+ giterr_set(GITERR_OS, "Failed to mmap. Invalid handle value");
+ return -1;
}
if (prot & GIT_PROT_WRITE)
fmap_prot |= PAGE_READWRITE;
else if (prot & GIT_PROT_READ)
fmap_prot |= PAGE_READONLY;
- else {
- errno = EINVAL;
- return git__throw(GIT_ERROR, "Failed to mmap. Invalid protection parameters");
- }
if (prot & GIT_PROT_WRITE)
view_prot |= FILE_MAP_WRITE;
if (prot & GIT_PROT_READ)
view_prot |= FILE_MAP_READ;
- if (flags & GIT_MAP_FIXED) {
- errno = EINVAL;
- return git__throw(GIT_ERROR, "Failed to mmap. FIXED not set");
- }
-
page_start = (offset / page_size) * page_size;
page_offset = offset - page_start;
- if (page_offset != 0) { /* offset must be multiple of page size */
+ if (page_offset != 0) { /* offset must be multiple of page size */
errno = EINVAL;
- return git__throw(GIT_ERROR, "Failed to mmap. Offset must be multiple of page size");
+ giterr_set(GITERR_OS, "Failed to mmap. Offset must be multiple of page size");
+ return -1;
}
out->fmh = CreateFileMapping(fh, NULL, fmap_prot, 0, 0, NULL);
if (!out->fmh || out->fmh == INVALID_HANDLE_VALUE) {
- /* errno = ? */
+ giterr_set(GITERR_OS, "Failed to mmap. Invalid handle value");
out->fmh = NULL;
- return git__throw(GIT_ERROR, "Failed to mmap. Invalid handle value");
+ return -1;
}
assert(sizeof(git_off_t) == 8);
+
off_low = (DWORD)(page_start);
off_hi = (DWORD)(page_start >> 32);
out->data = MapViewOfFile(out->fmh, view_prot, off_hi, off_low, len);
if (!out->data) {
- /* errno = ? */
+ giterr_set(GITERR_OS, "Failed to mmap. No data written");
CloseHandle(out->fmh);
out->fmh = NULL;
- return git__throw(GIT_ERROR, "Failed to mmap. No data written");
+ return -1;
}
out->len = len;
- return GIT_SUCCESS;
+ return 0;
}
int p_munmap(git_map *map)
{
- assert(map != NULL);
+ int error = 0;
- if (!map)
- return git__throw(GIT_ERROR, "Failed to munmap. Map does not exist");
+ assert(map != NULL);
if (map->data) {
if (!UnmapViewOfFile(map->data)) {
- /* errno = ? */
- CloseHandle(map->fmh);
- map->data = NULL;
- map->fmh = NULL;
- return git__throw(GIT_ERROR, "Failed to munmap. Could not unmap view of file");
+ giterr_set(GITERR_OS, "Failed to munmap. Could not unmap view of file");
+ error = -1;
}
map->data = NULL;
}
if (map->fmh) {
if (!CloseHandle(map->fmh)) {
- /* errno = ? */
- map->fmh = NULL;
- return git__throw(GIT_ERROR, "Failed to munmap. Could not close handle");
+ giterr_set(GITERR_OS, "Failed to munmap. Could not close handle");
+ error = -1;
}
map->fmh = NULL;
}
- return GIT_SUCCESS;
+ return error;
}
-
+#endif
diff --git a/src/win32/mingw-compat.h b/src/win32/mingw-compat.h
index 64d780b16..059e39cbc 100644
--- a/src/win32/mingw-compat.h
+++ b/src/win32/mingw-compat.h
@@ -1,17 +1,26 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_mingw_compat__
#define INCLUDE_mingw_compat__
#if defined(__MINGW32__)
/* use a 64-bit file offset type */
+# undef lseek
# define lseek _lseeki64
+# undef stat
# define stat _stati64
+# undef fstat
# define fstat _fstati64
/* stat: file mode type testing macros */
# define _S_IFLNK 0120000
# define S_IFLNK _S_IFLNK
-# define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK)
+# define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK)
#endif
diff --git a/src/win32/msvc-compat.h b/src/win32/msvc-compat.h
index df3e62d11..50865ed17 100644
--- a/src/win32/msvc-compat.h
+++ b/src/win32/msvc-compat.h
@@ -1,52 +1,51 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
#ifndef INCLUDE_msvc_compat__
#define INCLUDE_msvc_compat__
#if defined(_MSC_VER)
-/* access() mode parameter #defines */
-# define F_OK 0 /* existence check */
-# define W_OK 2 /* write mode check */
-# define R_OK 4 /* read mode check */
+/* access() mode parameter #defines */
+# define F_OK 0 /* existence check */
+# define W_OK 2 /* write mode check */
+# define R_OK 4 /* read mode check */
-# define lseek _lseeki64
-# define stat _stat64
-# define fstat _fstat64
+# define lseek _lseeki64
+# define stat _stat64
+# define fstat _fstat64
/* stat: file mode type testing macros */
-# define _S_IFLNK 0120000
-# define S_IFLNK _S_IFLNK
+# define _S_IFLNK 0120000
+# define S_IFLNK _S_IFLNK
+# define S_IXUSR 00100
-# define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR)
-# define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG)
-# define S_ISFIFO(m) (((m) & _S_IFMT) == _S_IFIFO)
-# define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK)
+# define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR)
+# define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG)
+# define S_ISFIFO(m) (((m) & _S_IFMT) == _S_IFIFO)
+# define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK)
-# define mode_t unsigned short
+# define mode_t unsigned short
/* case-insensitive string comparison */
-# define strcasecmp _stricmp
-# define strncasecmp _strnicmp
+# define strcasecmp _stricmp
+# define strncasecmp _strnicmp
-#if (_MSC_VER >= 1600)
-# include <stdint.h>
-#else
-/* add some missing <stdint.h> typedef's */
-typedef signed char int8_t;
-typedef unsigned char uint8_t;
-
-typedef short int16_t;
-typedef unsigned short uint16_t;
-
-typedef long int32_t;
-typedef unsigned long uint32_t;
+/* MSVC doesn't define ssize_t at all */
+typedef SSIZE_T ssize_t;
-typedef long long int64_t;
-typedef unsigned long long uint64_t;
-
-typedef long long intmax_t;
-typedef unsigned long long uintmax_t;
+/* define snprintf using variadic macro support if available */
+#if _MSC_VER >= 1400
+# define snprintf(BUF, SZ, FMT, ...) _snprintf_s(BUF, SZ, _TRUNCATE, FMT, __VA_ARGS__)
+#else
+# define snprintf _snprintf
#endif
#endif
+#define GIT_STDLIB_CALL __cdecl
+
#endif /* INCLUDE_msvc_compat__ */
diff --git a/src/win32/posix.c b/src/win32/posix.c
deleted file mode 100644
index 1ce3f050c..000000000
--- a/src/win32/posix.c
+++ /dev/null
@@ -1,220 +0,0 @@
-#include "posix.h"
-#include "path.h"
-#include <errno.h>
-
-int p_unlink(const char *path)
-{
- chmod(path, 0666);
- return unlink(path);
-}
-
-int p_fsync(int fd)
-{
- HANDLE fh = (HANDLE)_get_osfhandle(fd);
-
- if (fh == INVALID_HANDLE_VALUE) {
- errno = EBADF;
- return -1;
- }
-
- if (!FlushFileBuffers(fh)) {
- DWORD code = GetLastError();
-
- if (code == ERROR_INVALID_HANDLE)
- errno = EINVAL;
- else
- errno = EIO;
-
- return -1;
- }
-
- return 0;
-}
-
-GIT_INLINE(time_t) filetime_to_time_t(const FILETIME *ft)
-{
- long long winTime = ((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime;
- winTime -= 116444736000000000LL; /* Windows to Unix Epoch conversion */
- winTime /= 10000000; /* Nano to seconds resolution */
- return (time_t)winTime;
-}
-
-static int do_lstat(const char *file_name, struct stat *buf)
-{
- WIN32_FILE_ATTRIBUTE_DATA fdata;
-
- if (GetFileAttributesExA(file_name, GetFileExInfoStandard, &fdata)) {
- int fMode = S_IREAD;
-
- if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
- fMode |= S_IFDIR;
- else
- fMode |= S_IFREG;
-
- if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
- fMode |= S_IWRITE;
-
- if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
- fMode |= S_IFLNK;
-
- buf->st_ino = 0;
- buf->st_gid = 0;
- buf->st_uid = 0;
- buf->st_nlink = 1;
- buf->st_mode = (mode_t)fMode;
- buf->st_size = fdata.nFileSizeLow; /* Can't use nFileSizeHigh, since it's not a stat64 */
- buf->st_dev = buf->st_rdev = (_getdrive() - 1);
- 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 GIT_SUCCESS;
- }
-
- switch (GetLastError()) {
- case ERROR_ACCESS_DENIED:
- case ERROR_SHARING_VIOLATION:
- case ERROR_LOCK_VIOLATION:
- case ERROR_SHARING_BUFFER_EXCEEDED:
- return GIT_EOSERR;
-
- case ERROR_BUFFER_OVERFLOW:
- case ERROR_NOT_ENOUGH_MEMORY:
- return GIT_ENOMEM;
-
- default:
- return GIT_EINVALIDPATH;
- }
-}
-
-int p_lstat(const char *file_name, struct stat *buf)
-{
- int namelen, error;
- char alt_name[GIT_PATH_MAX];
-
- if ((error = do_lstat(file_name, buf)) == GIT_SUCCESS)
- return GIT_SUCCESS;
-
- /* if file_name ended in a '/', Windows returned ENOENT;
- * try again without trailing slashes
- */
- if (error != GIT_EINVALIDPATH)
- return git__throw(GIT_EOSERR, "Failed to lstat file");
-
- namelen = strlen(file_name);
- if (namelen && file_name[namelen-1] != '/')
- return git__throw(GIT_EOSERR, "Failed to lstat file");
-
- while (namelen && file_name[namelen-1] == '/')
- --namelen;
-
- if (!namelen || namelen >= GIT_PATH_MAX)
- return git__throw(GIT_ENOMEM, "Failed to lstat file");
-
- memcpy(alt_name, file_name, namelen);
- alt_name[namelen] = 0;
- return do_lstat(alt_name, buf);
-}
-
-int p_readlink(const char *link, char *target, size_t target_len)
-{
- typedef DWORD (WINAPI *fpath_func)(HANDLE, LPTSTR, DWORD, DWORD);
- static fpath_func pGetFinalPath = NULL;
- HANDLE hFile;
- DWORD dwRet;
-
- /*
- * Try to load the pointer to pGetFinalPath dynamically, because
- * it is not available in platforms older than Vista
- */
- if (pGetFinalPath == NULL) {
- HINSTANCE library = LoadLibrary("kernel32");
-
- if (library != NULL)
- pGetFinalPath = (fpath_func)GetProcAddress(library, "GetFinalPathNameByHandleA");
-
- if (pGetFinalPath == NULL)
- return git__throw(GIT_EOSERR,
- "'GetFinalPathNameByHandleA' is not available in this platform");
- }
-
- hFile = CreateFile(link, // file to open
- GENERIC_READ, // open for reading
- FILE_SHARE_READ, // share for reading
- NULL, // default security
- OPEN_EXISTING, // existing file only
- FILE_FLAG_BACKUP_SEMANTICS, // normal file
- NULL); // no attr. template
-
- if (hFile == INVALID_HANDLE_VALUE)
- return GIT_EOSERR;
-
- dwRet = pGetFinalPath(hFile, target, target_len, 0x0);
- if (dwRet >= target_len)
- return GIT_ENOMEM;
-
- CloseHandle(hFile);
-
- if (dwRet > 4) {
- /* Skip first 4 characters if they are "\\?\" */
- if (target[0] == '\\' && target[1] == '\\' && target[2] == '?' && target[3] == '\\') {
- char tmp[GIT_PATH_MAX];
- unsigned int offset = 4;
- dwRet -= 4;
-
- /* \??\UNC\ */
- if (dwRet > 7 && target[4] == 'U' && target[5] == 'N' && target[6] == 'C') {
- offset += 2;
- dwRet -= 2;
- target[offset] = '\\';
- }
-
- memcpy(tmp, target + offset, dwRet);
- memcpy(target, tmp, dwRet);
- }
- }
-
- target[dwRet] = '\0';
- return dwRet;
-}
-
-int p_hide_directory__w32(const char *path)
-{
- int error;
-
- error = SetFileAttributes(path, FILE_ATTRIBUTE_HIDDEN) != 0 ?
- GIT_SUCCESS : GIT_ERROR; /* MSDN states a "non zero" value indicates a success */
-
- if (error < GIT_SUCCESS)
- error = git__throw(GIT_EOSERR, "Failed to hide directory '%s'", path);
-
- return error;
-}
-
-char *p_realpath(const char *orig_path, char *buffer)
-{
- int ret, alloc = 0;
-
- if (buffer == NULL) {
- buffer = (char *)git__malloc(GIT_PATH_MAX);
- alloc = 1;
- }
-
- ret = GetFullPathName(orig_path, GIT_PATH_MAX, buffer, NULL);
- if (!ret || ret > GIT_PATH_MAX) {
- if (alloc) free(buffer);
- return NULL;
- }
-
- git_path_mkposix(buffer);
- return buffer;
-}
-
-int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr)
-{
-#ifdef _MSC_VER
- int len = _vsnprintf(buffer, count, format, argptr);
- return (len < 0) ? _vscprintf(format, argptr) : len;
-#else /* MinGW */
- return vsnprintf(buffer, count, format, argptr);
-#endif
-}
diff --git a/src/win32/posix.h b/src/win32/posix.h
index efac68e74..2cbea1807 100644
--- a/src/win32/posix.h
+++ b/src/win32/posix.h
@@ -1,28 +1,67 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_posix__w32_h__
#define INCLUDE_posix__w32_h__
#include "common.h"
-#include "fnmatch.h"
+#include "../posix.h"
+#include "utf-conv.h"
+#include "dir.h"
-GIT_INLINE(int) p_link(const char *GIT_UNUSED(old), const char *GIT_UNUSED(new))
+/* define some standard errnos that the runtime may be missing. for example,
+ * mingw lacks EAFNOSUPPORT. */
+
+#ifndef EAFNOSUPPORT
+# define EAFNOSUPPORT (INT_MAX-1)
+#endif
+
+#ifdef _MSC_VER
+# define p_ftruncate(fd, sz) _chsize_s(fd, sz)
+#else /* MinGW */
+# define p_ftruncate(fd, sz) _chsize(fd, sz)
+#endif
+
+GIT_INLINE(int) p_link(const char *old, const char *new)
{
- GIT_UNUSED_ARG(old)
- GIT_UNUSED_ARG(new)
+ GIT_UNUSED(old);
+ GIT_UNUSED(new);
errno = ENOSYS;
return -1;
}
-GIT_INLINE(int) p_mkdir(const char *path, int GIT_UNUSED(mode))
-{
- GIT_UNUSED_ARG(mode)
- return mkdir(path);
-}
-
+extern int p_mkdir(const char *path, mode_t mode);
extern int p_unlink(const char *path);
extern int p_lstat(const char *file_name, struct stat *buf);
-extern int p_readlink(const char *link, char *target, size_t target_len);
-extern int p_hide_directory__w32(const char *path);
+extern int p_readlink(const char *path, char *buf, size_t bufsiz);
+extern int p_symlink(const char *old, const char *new);
extern char *p_realpath(const char *orig_path, char *buffer);
extern int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr);
+extern int p_snprintf(char *buffer, size_t count, const char *format, ...) GIT_FORMAT_PRINTF(3, 4);
+extern int p_mkstemp(char *tmp_path);
+extern int p_stat(const char* path, struct stat* buf);
+extern int p_chdir(const char* path);
+extern int p_chmod(const char* path, mode_t mode);
+extern int p_rmdir(const char* path);
+extern int p_access(const char* path, mode_t mode);
+extern int p_fsync(int fd);
+extern int p_open(const char *path, int flags, ...);
+extern int p_creat(const char *path, mode_t mode);
+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
new file mode 100644
index 000000000..34938431a
--- /dev/null
+++ b/src/win32/posix_w32.c
@@ -0,0 +1,707 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "../posix.h"
+#include "../fileops.h"
+#include "path.h"
+#include "utf-conv.h"
+#include "repository.h"
+#include "reparse.h"
+#include <errno.h>
+#include <io.h>
+#include <fcntl.h>
+#include <ws2tcpip.h>
+
+#ifndef FILE_NAME_NORMALIZED
+# define FILE_NAME_NORMALIZED 0
+#endif
+
+/* Options which we always provide to _wopen.
+ *
+ * _O_BINARY - Raw access; no translation of CR or LF characters
+ * _O_NOINHERIT - Do not mark the created handle as inheritable by child processes.
+ * The Windows default is 'not inheritable', but the CRT's default (following
+ * POSIX convention) is 'inheritable'. We have no desire for our handles to be
+ * inheritable on Windows, so specify the flag to get default behavior back. */
+#define STANDARD_OPEN_FLAGS (_O_BINARY | _O_NOINHERIT)
+
+/* GetFinalPathNameByHandleW signature */
+typedef DWORD(WINAPI *PFGetFinalPathNameByHandleW)(HANDLE, LPWSTR, DWORD, DWORD);
+
+/* Helper function which converts UTF-8 paths to UTF-16.
+ * On failure, errno is set. */
+static int utf8_to_16_with_errno(git_win32_path dest, const char *src)
+{
+ int len = git_win32_path_from_utf8(dest, src);
+
+ if (len < 0) {
+ if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+ errno = ENAMETOOLONG;
+ else
+ errno = EINVAL; /* Bad code point, presumably */
+ }
+
+ return len;
+}
+
+int p_mkdir(const char *path, mode_t mode)
+{
+ git_win32_path buf;
+
+ GIT_UNUSED(mode);
+
+ if (utf8_to_16_with_errno(buf, path) < 0)
+ return -1;
+
+ return _wmkdir(buf);
+}
+
+int p_unlink(const char *path)
+{
+ git_win32_path buf;
+ int error;
+
+ if (utf8_to_16_with_errno(buf, path) < 0)
+ return -1;
+
+ error = _wunlink(buf);
+
+ /* If the file could not be deleted because it was
+ * read-only, clear the bit and try again */
+ if (error == -1 && errno == EACCES) {
+ _wchmod(buf, 0666);
+ error = _wunlink(buf);
+ }
+
+ return error;
+}
+
+int p_fsync(int fd)
+{
+ HANDLE fh = (HANDLE)_get_osfhandle(fd);
+
+ if (fh == INVALID_HANDLE_VALUE) {
+ errno = EBADF;
+ return -1;
+ }
+
+ if (!FlushFileBuffers(fh)) {
+ DWORD code = GetLastError();
+
+ if (code == ERROR_INVALID_HANDLE)
+ errno = EINVAL;
+ else
+ errno = EIO;
+
+ return -1;
+ }
+
+ return 0;
+}
+
+GIT_INLINE(time_t) filetime_to_time_t(const FILETIME *ft)
+{
+ long long winTime = ((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime;
+ winTime -= 116444736000000000LL; /* Windows to Unix Epoch conversion */
+ winTime /= 10000000; /* Nano to seconds resolution */
+ return (time_t)winTime;
+}
+
+/* On success, returns the length, in characters, of the path stored in dest.
+ * On failure, returns a negative value. */
+static int readlink_w(
+ git_win32_path dest,
+ const git_win32_path path)
+{
+ BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
+ GIT_REPARSE_DATA_BUFFER *reparse_buf = (GIT_REPARSE_DATA_BUFFER *)buf;
+ HANDLE handle = NULL;
+ DWORD ioctl_ret;
+ wchar_t *target;
+ size_t target_len;
+
+ int error = -1;
+
+ handle = CreateFileW(path, GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
+ FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
+
+ if (handle == INVALID_HANDLE_VALUE) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0,
+ reparse_buf, sizeof(buf), &ioctl_ret, NULL)) {
+ errno = EINVAL;
+ goto on_error;
+ }
+
+ switch (reparse_buf->ReparseTag) {
+ case IO_REPARSE_TAG_SYMLINK:
+ target = reparse_buf->SymbolicLinkReparseBuffer.PathBuffer +
+ (reparse_buf->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR));
+ target_len = reparse_buf->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
+ break;
+ case IO_REPARSE_TAG_MOUNT_POINT:
+ target = reparse_buf->MountPointReparseBuffer.PathBuffer +
+ (reparse_buf->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR));
+ target_len = reparse_buf->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
+ break;
+ default:
+ errno = EINVAL;
+ goto on_error;
+ }
+
+ if (target_len) {
+ /* The path may need to have a prefix removed. */
+ target_len = git_win32__canonicalize_path(target, target_len);
+
+ /* Need one additional character in the target buffer
+ * for the terminating NULL. */
+ if (GIT_WIN_PATH_UTF16 > target_len) {
+ wcscpy(dest, target);
+ error = (int)target_len;
+ }
+ }
+
+on_error:
+ CloseHandle(handle);
+ return error;
+}
+
+#define WIN32_IS_WSEP(CH) ((CH) == L'/' || (CH) == L'\\')
+
+static int lstat_w(
+ wchar_t *path,
+ struct stat *buf,
+ bool posix_enotdir)
+{
+ WIN32_FILE_ATTRIBUTE_DATA fdata;
+
+ if (GetFileAttributesExW(path, GetFileExInfoStandard, &fdata)) {
+ int fMode = S_IREAD;
+
+ if (!buf)
+ return 0;
+
+ if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ fMode |= S_IFDIR;
+ else
+ fMode |= S_IFREG;
+
+ if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
+ fMode |= S_IWRITE;
+
+ buf->st_ino = 0;
+ buf->st_gid = 0;
+ buf->st_uid = 0;
+ buf->st_nlink = 1;
+ buf->st_mode = (mode_t)fMode;
+ buf->st_size = ((git_off_t)fdata.nFileSizeHigh << 32) + fdata.nFileSizeLow;
+ buf->st_dev = buf->st_rdev = (_getdrive() - 1);
+ 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));
+
+ if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
+ git_win32_path target;
+
+ if (readlink_w(target, path) >= 0) {
+ buf->st_mode = (buf->st_mode & ~S_IFMT) | S_IFLNK;
+
+ /* st_size gets the UTF-8 length of the target name, in bytes,
+ * not counting the NULL terminator */
+ if ((buf->st_size = git__utf16_to_8(NULL, 0, target)) < 0)
+ return -1;
+ }
+ }
+
+ return 0;
+ }
+
+ errno = ENOENT;
+
+ /* To match POSIX behavior, set ENOTDIR when any of the folders in the
+ * file path is a regular file, otherwise set ENOENT.
+ */
+ if (posix_enotdir) {
+ size_t path_len = wcslen(path);
+
+ /* scan up path until we find an existing item */
+ while (1) {
+ DWORD attrs;
+
+ /* remove last directory component */
+ for (path_len--; path_len > 0 && !WIN32_IS_WSEP(path[path_len]); path_len--);
+
+ if (path_len <= 0)
+ break;
+
+ path[path_len] = L'\0';
+ attrs = GetFileAttributesW(path);
+
+ if (attrs != INVALID_FILE_ATTRIBUTES) {
+ if (!(attrs & FILE_ATTRIBUTE_DIRECTORY))
+ errno = ENOTDIR;
+ break;
+ }
+ }
+ }
+
+ return -1;
+}
+
+static int do_lstat(const char *path, struct stat *buf, bool posixly_correct)
+{
+ git_win32_path path_w;
+ int len;
+
+ if ((len = utf8_to_16_with_errno(path_w, path)) < 0)
+ return -1;
+
+ git_win32__path_trim_end(path_w, len);
+
+ return lstat_w(path_w, buf, posixly_correct);
+}
+
+int p_lstat(const char *filename, struct stat *buf)
+{
+ return do_lstat(filename, buf, false);
+}
+
+int p_lstat_posixly(const char *filename, struct stat *buf)
+{
+ return do_lstat(filename, buf, true);
+}
+
+int p_readlink(const char *path, char *buf, size_t bufsiz)
+{
+ git_win32_path path_w, target_w;
+ git_win32_utf8_path target;
+ int len;
+
+ /* readlink(2) does not NULL-terminate the string written
+ * to the target buffer. Furthermore, the target buffer need
+ * not be large enough to hold the entire result. A truncated
+ * result should be written in this case. Since this truncation
+ * could occur in the middle of the encoding of a code point,
+ * we need to buffer the result on the stack. */
+
+ if (utf8_to_16_with_errno(path_w, path) < 0 ||
+ readlink_w(target_w, path_w) < 0 ||
+ (len = git_win32_path_to_utf8(target, target_w)) < 0)
+ return -1;
+
+ bufsiz = min((size_t)len, bufsiz);
+ memcpy(buf, target, bufsiz);
+
+ return (int)bufsiz;
+}
+
+int p_symlink(const char *old, const char *new)
+{
+ /* Real symlinks on NTFS require admin privileges. Until this changes,
+ * libgit2 just creates a text file with the link target in the contents.
+ */
+ return git_futils_fake_symlink(old, new);
+}
+
+int p_open(const char *path, int flags, ...)
+{
+ git_win32_path buf;
+ mode_t mode = 0;
+
+ if (utf8_to_16_with_errno(buf, path) < 0)
+ return -1;
+
+ if (flags & O_CREAT) {
+ va_list arg_list;
+
+ va_start(arg_list, flags);
+ mode = (mode_t)va_arg(arg_list, int);
+ va_end(arg_list);
+ }
+
+ return _wopen(buf, flags | STANDARD_OPEN_FLAGS, mode);
+}
+
+int p_creat(const char *path, mode_t mode)
+{
+ git_win32_path buf;
+
+ if (utf8_to_16_with_errno(buf, path) < 0)
+ return -1;
+
+ return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | STANDARD_OPEN_FLAGS, mode);
+}
+
+int p_getcwd(char *buffer_out, size_t size)
+{
+ git_win32_path buf;
+ wchar_t *cwd = _wgetcwd(buf, GIT_WIN_PATH_UTF16);
+
+ if (!cwd)
+ return -1;
+
+ /* Convert the working directory back to UTF-8 */
+ if (git__utf16_to_8(buffer_out, size, cwd) < 0) {
+ DWORD code = GetLastError();
+
+ if (code == ERROR_INSUFFICIENT_BUFFER)
+ errno = ERANGE;
+ else
+ errno = EINVAL;
+
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Returns the address of the GetFinalPathNameByHandleW function.
+ * This function is available on Windows Vista and higher.
+ */
+static PFGetFinalPathNameByHandleW get_fpnbyhandle(void)
+{
+ static PFGetFinalPathNameByHandleW pFunc = NULL;
+ PFGetFinalPathNameByHandleW toReturn = pFunc;
+
+ if (!toReturn) {
+ HMODULE hModule = GetModuleHandleW(L"kernel32");
+
+ if (hModule)
+ toReturn = (PFGetFinalPathNameByHandleW)GetProcAddress(hModule, "GetFinalPathNameByHandleW");
+
+ pFunc = toReturn;
+ }
+
+ assert(toReturn);
+
+ return toReturn;
+}
+
+static int getfinalpath_w(
+ git_win32_path dest,
+ const wchar_t *path)
+{
+ PFGetFinalPathNameByHandleW pgfp = get_fpnbyhandle();
+ HANDLE hFile;
+ DWORD dwChars;
+
+ if (!pgfp)
+ return -1;
+
+ /* Use FILE_FLAG_BACKUP_SEMANTICS so we can open a directory. Do not
+ * specify FILE_FLAG_OPEN_REPARSE_POINT; we want to open a handle to the
+ * target of the link. */
+ hFile = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE,
+ NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+
+ if (INVALID_HANDLE_VALUE == hFile)
+ return -1;
+
+ /* Call GetFinalPathNameByHandle */
+ dwChars = pgfp(hFile, dest, GIT_WIN_PATH_UTF16, FILE_NAME_NORMALIZED);
+ CloseHandle(hFile);
+
+ if (!dwChars || dwChars >= GIT_WIN_PATH_UTF16)
+ return -1;
+
+ /* The path may be delivered to us with a prefix; canonicalize */
+ return (int)git_win32__canonicalize_path(dest, dwChars);
+}
+
+static int follow_and_lstat_link(git_win32_path path, struct stat* buf)
+{
+ git_win32_path target_w;
+
+ if (getfinalpath_w(target_w, path) < 0)
+ return -1;
+
+ return lstat_w(target_w, buf, false);
+}
+
+int p_stat(const char* path, struct stat* buf)
+{
+ git_win32_path path_w;
+ int len;
+
+ if ((len = utf8_to_16_with_errno(path_w, path)) < 0)
+ return -1;
+
+ git_win32__path_trim_end(path_w, len);
+
+ if (lstat_w(path_w, buf, false) < 0)
+ return -1;
+
+ /* The item is a symbolic link or mount point. No need to iterate
+ * to follow multiple links; use GetFinalPathNameFromHandle. */
+ if (S_ISLNK(buf->st_mode))
+ return follow_and_lstat_link(path_w, buf);
+
+ return 0;
+}
+
+int p_chdir(const char* path)
+{
+ git_win32_path buf;
+
+ if (utf8_to_16_with_errno(buf, path) < 0)
+ return -1;
+
+ return _wchdir(buf);
+}
+
+int p_chmod(const char* path, mode_t mode)
+{
+ git_win32_path buf;
+
+ if (utf8_to_16_with_errno(buf, path) < 0)
+ return -1;
+
+ return _wchmod(buf, mode);
+}
+
+int p_rmdir(const char* path)
+{
+ git_win32_path buf;
+ int error;
+
+ if (utf8_to_16_with_errno(buf, path) < 0)
+ return -1;
+
+ error = _wrmdir(buf);
+
+ if (error == -1) {
+ switch (GetLastError()) {
+ /* _wrmdir() is documented to return EACCES if "A program has an open
+ * handle to the directory." This sounds like what everybody else calls
+ * EBUSY. Let's convert appropriate error codes.
+ */
+ case ERROR_SHARING_VIOLATION:
+ errno = EBUSY;
+ break;
+
+ /* This error can be returned when trying to rmdir an extant file. */
+ case ERROR_DIRECTORY:
+ errno = ENOTDIR;
+ break;
+ }
+ }
+
+ return error;
+}
+
+char *p_realpath(const char *orig_path, char *buffer)
+{
+ git_win32_path orig_path_w, buffer_w;
+
+ if (utf8_to_16_with_errno(orig_path_w, orig_path) < 0)
+ return NULL;
+
+ /* Note that if the path provided is a relative path, then the current directory
+ * is used to resolve the path -- which is a concurrency issue because the current
+ * directory is a process-wide variable. */
+ if (!GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL)) {
+ if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+ errno = ENAMETOOLONG;
+ else
+ errno = EINVAL;
+
+ return NULL;
+ }
+
+ /* The path must exist. */
+ if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) {
+ errno = ENOENT;
+ return NULL;
+ }
+
+ /* Convert the path to UTF-8. */
+ if (buffer) {
+ /* If the caller provided a buffer, then it is assumed to be GIT_WIN_PATH_UTF8
+ * characters in size. If it isn't, then we may overflow. */
+ if (git__utf16_to_8(buffer, GIT_WIN_PATH_UTF8, buffer_w) < 0)
+ return NULL;
+ } else {
+ /* If the caller did not provide a buffer, then we allocate one for the caller
+ * from the heap. */
+ if (git__utf16_to_8_alloc(&buffer, buffer_w) < 0)
+ return NULL;
+ }
+
+ /* Convert backslashes to forward slashes */
+ git_path_mkposix(buffer);
+
+ return buffer;
+}
+
+int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr)
+{
+#ifdef _MSC_VER
+ int len;
+
+ if (count == 0 ||
+ (len = _vsnprintf_s(buffer, count, _TRUNCATE, format, argptr)) < 0)
+ return _vscprintf(format, argptr);
+
+ return len;
+#else /* MinGW */
+ return vsnprintf(buffer, count, format, argptr);
+#endif
+}
+
+int p_snprintf(char *buffer, size_t count, const char *format, ...)
+{
+ va_list va;
+ int r;
+
+ va_start(va, format);
+ r = p_vsnprintf(buffer, count, format, va);
+ va_end(va);
+
+ return r;
+}
+
+int p_mkstemp(char *tmp_path)
+{
+#if defined(_MSC_VER)
+ if (_mktemp_s(tmp_path, strlen(tmp_path) + 1) != 0)
+ return -1;
+#else
+ if (_mktemp(tmp_path) == NULL)
+ return -1;
+#endif
+
+ return p_open(tmp_path, O_RDWR | O_CREAT | O_EXCL, 0744); //-V536
+}
+
+int p_access(const char* path, mode_t mode)
+{
+ git_win32_path buf;
+
+ if (utf8_to_16_with_errno(buf, path) < 0)
+ return -1;
+
+ return _waccess(buf, mode);
+}
+
+int p_rename(const char *from, const char *to)
+{
+ git_win32_path wfrom;
+ git_win32_path wto;
+ int rename_tries;
+ int rename_succeeded;
+ int error;
+
+ if (utf8_to_16_with_errno(wfrom, from) < 0 ||
+ utf8_to_16_with_errno(wto, to) < 0)
+ return -1;
+
+ /* wait up to 50ms if file is locked by another thread or process */
+ rename_tries = 0;
+ rename_succeeded = 0;
+ while (rename_tries < 10) {
+ if (MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) != 0) {
+ rename_succeeded = 1;
+ break;
+ }
+
+ error = GetLastError();
+ if (error == ERROR_SHARING_VIOLATION || error == ERROR_ACCESS_DENIED) {
+ Sleep(5);
+ rename_tries++;
+ } else
+ break;
+ }
+
+ return rename_succeeded ? 0 : -1;
+}
+
+int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags)
+{
+ if ((size_t)((int)length) != length)
+ return -1; /* giterr_set will be done by caller */
+
+ return recv(socket, buffer, (int)length, flags);
+}
+
+int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags)
+{
+ if ((size_t)((int)length) != length)
+ return -1; /* giterr_set will be done by caller */
+
+ return send(socket, buffer, (int)length, flags);
+}
+
+/**
+ * Borrowed from http://old.nabble.com/Porting-localtime_r-and-gmtime_r-td15282276.html
+ * On Win32, `gmtime_r` doesn't exist but `gmtime` is threadsafe, so we can use that
+ */
+struct tm *
+p_localtime_r (const time_t *timer, struct tm *result)
+{
+ struct tm *local_result;
+ local_result = localtime (timer);
+
+ if (local_result == NULL || result == NULL)
+ return NULL;
+
+ memcpy (result, local_result, sizeof (struct tm));
+ return result;
+}
+struct tm *
+p_gmtime_r (const time_t *timer, struct tm *result)
+{
+ struct tm *local_result;
+ local_result = gmtime (timer);
+
+ if (local_result == NULL || result == NULL)
+ return NULL;
+
+ memcpy (result, local_result, sizeof (struct tm));
+ return result;
+}
+
+int p_inet_pton(int af, const char *src, void *dst)
+{
+ struct sockaddr_storage sin;
+ void *addr;
+ int sin_len = sizeof(struct sockaddr_storage), addr_len;
+ int error = 0;
+
+ if (af == AF_INET) {
+ addr = &((struct sockaddr_in *)&sin)->sin_addr;
+ addr_len = sizeof(struct in_addr);
+ } else if (af == AF_INET6) {
+ addr = &((struct sockaddr_in6 *)&sin)->sin6_addr;
+ addr_len = sizeof(struct in6_addr);
+ } else {
+ errno = EAFNOSUPPORT;
+ return -1;
+ }
+
+ if ((error = WSAStringToAddressA((LPSTR)src, af, NULL, (LPSOCKADDR)&sin, &sin_len)) == 0) {
+ memcpy(dst, addr, addr_len);
+ return 1;
+ }
+
+ switch(WSAGetLastError()) {
+ case WSAEINVAL:
+ return 0;
+ case WSAEFAULT:
+ errno = ENOSPC;
+ return -1;
+ case WSA_NOT_ENOUGH_MEMORY:
+ errno = ENOMEM;
+ return -1;
+ }
+
+ errno = EINVAL;
+ return -1;
+}
diff --git a/src/win32/precompiled.c b/src/win32/precompiled.c
new file mode 100644
index 000000000..5f656a45d
--- /dev/null
+++ b/src/win32/precompiled.c
@@ -0,0 +1 @@
+#include "precompiled.h"
diff --git a/src/win32/precompiled.h b/src/win32/precompiled.h
new file mode 100644
index 000000000..33ce106d3
--- /dev/null
+++ b/src/win32/precompiled.h
@@ -0,0 +1,23 @@
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <time.h>
+#include <stdarg.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <regex.h>
+
+#include <io.h>
+#include <direct.h>
+#ifdef GIT_THREADS
+ #include "win32/pthread.h"
+#endif
+
+#include "git2.h"
+#include "common.h"
diff --git a/src/win32/pthread.c b/src/win32/pthread.c
index 41cf5b35b..ec45ecbe5 100644
--- a/src/win32/pthread.c
+++ b/src/win32/pthread.c
@@ -1,85 +1,267 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- *
- * Original code by Ramiro Polla (Public Domain)
+ * 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 "pthread.h"
+#include "../global.h"
+
+#define CLEAN_THREAD_EXIT 0x6F012842
+
+/* The thread procedure stub used to invoke the caller's procedure
+ * and capture the return value for later collection. Windows will
+ * only hold a DWORD, but we need to be able to store an entire
+ * void pointer. This requires the indirection. */
+static DWORD WINAPI git_win32__threadproc(LPVOID lpParameter)
+{
+ git_win32_thread *thread = lpParameter;
+
+ thread->result = thread->proc(thread->param);
+
+ return CLEAN_THREAD_EXIT;
+}
-int pthread_create(pthread_t *GIT_RESTRICT thread,
- const pthread_attr_t *GIT_RESTRICT GIT_UNUSED(attr),
- void *(*start_routine)(void*), void *GIT_RESTRICT arg)
+int git_win32__thread_create(
+ git_win32_thread *GIT_RESTRICT thread,
+ const pthread_attr_t *GIT_RESTRICT attr,
+ void *(*start_routine)(void*),
+ void *GIT_RESTRICT arg)
{
- GIT_UNUSED_ARG(attr);
- *thread = (pthread_t) CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)start_routine, arg, 0, NULL);
- return *thread ? GIT_SUCCESS : git__throw(GIT_EOSERR, "Failed to create pthread");
+ GIT_UNUSED(attr);
+
+ thread->result = NULL;
+ thread->param = arg;
+ thread->proc = start_routine;
+ thread->thread = CreateThread(
+ NULL, 0, git_win32__threadproc, thread, 0, NULL);
+
+ return thread->thread ? 0 : -1;
}
-int pthread_join(pthread_t thread, void **value_ptr)
+int git_win32__thread_join(
+ git_win32_thread *thread,
+ void **value_ptr)
{
- int ret;
- ret = WaitForSingleObject(thread, INFINITE);
- if (ret && value_ptr)
- GetExitCodeThread(thread, (void*) value_ptr);
- return -(!!ret);
+ DWORD exit;
+
+ if (WaitForSingleObject(thread->thread, INFINITE) != WAIT_OBJECT_0)
+ return -1;
+
+ if (!GetExitCodeThread(thread->thread, &exit)) {
+ CloseHandle(thread->thread);
+ return -1;
+ }
+
+ /* Check for the thread having exited uncleanly. If exit was unclean,
+ * then we don't have a return value to give back to the caller. */
+ if (exit != CLEAN_THREAD_EXIT) {
+ assert(false);
+ thread->result = NULL;
+ }
+
+ if (value_ptr)
+ *value_ptr = thread->result;
+
+ CloseHandle(thread->thread);
+ return 0;
}
-int pthread_mutex_init(pthread_mutex_t *GIT_RESTRICT mutex,
- const pthread_mutexattr_t *GIT_RESTRICT GIT_UNUSED(mutexattr))
+int pthread_mutex_init(
+ pthread_mutex_t *GIT_RESTRICT mutex,
+ const pthread_mutexattr_t *GIT_RESTRICT mutexattr)
{
- GIT_UNUSED_ARG(mutexattr);
- InitializeCriticalSection(mutex);
- return 0;
+ GIT_UNUSED(mutexattr);
+ InitializeCriticalSection(mutex);
+ return 0;
}
int pthread_mutex_destroy(pthread_mutex_t *mutex)
{
- DeleteCriticalSection(mutex);
- return 0;
+ DeleteCriticalSection(mutex);
+ return 0;
}
int pthread_mutex_lock(pthread_mutex_t *mutex)
{
- EnterCriticalSection(mutex);
- return 0;
+ EnterCriticalSection(mutex);
+ return 0;
}
int pthread_mutex_unlock(pthread_mutex_t *mutex)
{
- LeaveCriticalSection(mutex);
- return 0;
+ LeaveCriticalSection(mutex);
+ return 0;
+}
+
+int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr)
+{
+ /* We don't support non-default attributes. */
+ if (attr)
+ return EINVAL;
+
+ /* This is an auto-reset event. */
+ *cond = CreateEventW(NULL, FALSE, FALSE, NULL);
+ assert(*cond);
+
+ /* If we can't create the event, claim that the reason was out-of-memory.
+ * The actual reason can be fetched with GetLastError(). */
+ return *cond ? 0 : ENOMEM;
+}
+
+int pthread_cond_destroy(pthread_cond_t *cond)
+{
+ BOOL closed;
+
+ if (!cond)
+ return EINVAL;
+
+ closed = CloseHandle(*cond);
+ assert(closed);
+ GIT_UNUSED(closed);
+
+ *cond = NULL;
+ return 0;
+}
+
+int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
+{
+ int error;
+ DWORD wait_result;
+
+ if (!cond || !mutex)
+ return EINVAL;
+
+ /* The caller must be holding the mutex. */
+ error = pthread_mutex_unlock(mutex);
+
+ if (error)
+ return error;
+
+ wait_result = WaitForSingleObject(*cond, INFINITE);
+ assert(WAIT_OBJECT_0 == wait_result);
+ GIT_UNUSED(wait_result);
+
+ return pthread_mutex_lock(mutex);
+}
+
+int pthread_cond_signal(pthread_cond_t *cond)
+{
+ BOOL signaled;
+
+ if (!cond)
+ return EINVAL;
+
+ signaled = SetEvent(*cond);
+ assert(signaled);
+ GIT_UNUSED(signaled);
+
+ return 0;
}
+/* pthread_cond_broadcast is not implemented because doing so with just
+ * Win32 events is quite complicated, and no caller in libgit2 uses it
+ * yet.
+ */
int pthread_num_processors_np(void)
{
- DWORD_PTR p, s;
- int n = 0;
+ DWORD_PTR p, s;
+ int n = 0;
+
+ if (GetProcessAffinityMask(GetCurrentProcess(), &p, &s))
+ for (; p; p >>= 1)
+ n += p&1;
+
+ return n ? n : 1;
+}
+
+typedef void (WINAPI *win32_srwlock_fn)(GIT_SRWLOCK *);
+
+static win32_srwlock_fn win32_srwlock_initialize;
+static win32_srwlock_fn win32_srwlock_acquire_shared;
+static win32_srwlock_fn win32_srwlock_release_shared;
+static win32_srwlock_fn win32_srwlock_acquire_exclusive;
+static win32_srwlock_fn win32_srwlock_release_exclusive;
+
+int pthread_rwlock_init(
+ pthread_rwlock_t *GIT_RESTRICT lock,
+ const pthread_rwlockattr_t *GIT_RESTRICT attr)
+{
+ GIT_UNUSED(attr);
+
+ if (win32_srwlock_initialize)
+ win32_srwlock_initialize(&lock->native.srwl);
+ else
+ InitializeCriticalSection(&lock->native.csec);
+
+ return 0;
+}
+
+int pthread_rwlock_rdlock(pthread_rwlock_t *lock)
+{
+ if (win32_srwlock_acquire_shared)
+ win32_srwlock_acquire_shared(&lock->native.srwl);
+ else
+ EnterCriticalSection(&lock->native.csec);
- if (GetProcessAffinityMask(GetCurrentProcess(), &p, &s))
- for (; p; p >>= 1)
- n += p&1;
+ return 0;
+}
+
+int pthread_rwlock_rdunlock(pthread_rwlock_t *lock)
+{
+ if (win32_srwlock_release_shared)
+ win32_srwlock_release_shared(&lock->native.srwl);
+ else
+ LeaveCriticalSection(&lock->native.csec);
+
+ return 0;
+}
+
+int pthread_rwlock_wrlock(pthread_rwlock_t *lock)
+{
+ if (win32_srwlock_acquire_exclusive)
+ win32_srwlock_acquire_exclusive(&lock->native.srwl);
+ else
+ EnterCriticalSection(&lock->native.csec);
+
+ return 0;
+}
- return n ? n : 1;
+int pthread_rwlock_wrunlock(pthread_rwlock_t *lock)
+{
+ if (win32_srwlock_release_exclusive)
+ win32_srwlock_release_exclusive(&lock->native.srwl);
+ else
+ LeaveCriticalSection(&lock->native.csec);
+
+ return 0;
+}
+
+int pthread_rwlock_destroy(pthread_rwlock_t *lock)
+{
+ if (!win32_srwlock_initialize)
+ DeleteCriticalSection(&lock->native.csec);
+ git__memzero(lock, sizeof(*lock));
+ return 0;
}
+int win32_pthread_initialize(void)
+{
+ HMODULE hModule = GetModuleHandleW(L"kernel32");
+
+ if (hModule) {
+ win32_srwlock_initialize = (win32_srwlock_fn)
+ GetProcAddress(hModule, "InitializeSRWLock");
+ win32_srwlock_acquire_shared = (win32_srwlock_fn)
+ GetProcAddress(hModule, "AcquireSRWLockShared");
+ win32_srwlock_release_shared = (win32_srwlock_fn)
+ GetProcAddress(hModule, "ReleaseSRWLockShared");
+ win32_srwlock_acquire_exclusive = (win32_srwlock_fn)
+ GetProcAddress(hModule, "AcquireSRWLockExclusive");
+ win32_srwlock_release_exclusive = (win32_srwlock_fn)
+ GetProcAddress(hModule, "ReleaseSRWLockExclusive");
+ }
+
+ return 0;
+}
diff --git a/src/win32/pthread.h b/src/win32/pthread.h
index 10949f1eb..e4826ca7f 100644
--- a/src/win32/pthread.h
+++ b/src/win32/pthread.h
@@ -1,28 +1,8 @@
/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
+ * Copyright (C) the libgit2 contributors. All rights reserved.
*
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- *
- * Original code by Ramiro Polla (Public Domain)
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef GIT_PTHREAD_H
@@ -36,25 +16,77 @@
# define GIT_RESTRICT __restrict__
#endif
+typedef struct {
+ HANDLE thread;
+ void *(*proc)(void *);
+ void *param;
+ void *result;
+} git_win32_thread;
+
typedef int pthread_mutexattr_t;
typedef int pthread_condattr_t;
typedef int pthread_attr_t;
+typedef int pthread_rwlockattr_t;
+
typedef CRITICAL_SECTION pthread_mutex_t;
-typedef HANDLE pthread_t;
+typedef HANDLE pthread_cond_t;
+
+typedef struct { void *Ptr; } GIT_SRWLOCK;
+
+typedef struct {
+ union {
+ GIT_SRWLOCK srwl;
+ CRITICAL_SECTION csec;
+ } native;
+} pthread_rwlock_t;
+
+#define PTHREAD_MUTEX_INITIALIZER {(void*)-1}
-#define PTHREAD_MUTEX_INITIALIZER {(void*)-1};
+int git_win32__thread_create(
+ git_win32_thread *GIT_RESTRICT,
+ const pthread_attr_t *GIT_RESTRICT,
+ void *(*) (void *),
+ void *GIT_RESTRICT);
-int pthread_create(pthread_t *GIT_RESTRICT,
- const pthread_attr_t *GIT_RESTRICT,
- void *(*start_routine)(void*), void *__restrict);
+int git_win32__thread_join(
+ git_win32_thread *,
+ void **);
-int pthread_join(pthread_t, void **);
+#ifdef GIT_THREADS
-int pthread_mutex_init(pthread_mutex_t *GIT_RESTRICT, const pthread_mutexattr_t *GIT_RESTRICT);
+typedef git_win32_thread git_thread;
+
+#define git_thread_create(git_thread_ptr, attr, start_routine, arg) \
+ git_win32__thread_create(git_thread_ptr, attr, start_routine, arg)
+#define git_thread_join(git_thread_ptr, status) \
+ git_win32__thread_join(git_thread_ptr, status)
+
+#endif
+
+int pthread_mutex_init(
+ pthread_mutex_t *GIT_RESTRICT mutex,
+ const pthread_mutexattr_t *GIT_RESTRICT mutexattr);
int pthread_mutex_destroy(pthread_mutex_t *);
int pthread_mutex_lock(pthread_mutex_t *);
int pthread_mutex_unlock(pthread_mutex_t *);
+int pthread_cond_init(pthread_cond_t *, const pthread_condattr_t *);
+int pthread_cond_destroy(pthread_cond_t *);
+int pthread_cond_wait(pthread_cond_t *, pthread_mutex_t *);
+int pthread_cond_signal(pthread_cond_t *);
+/* pthread_cond_broadcast is not supported on Win32 yet. */
+
int pthread_num_processors_np(void);
+int pthread_rwlock_init(
+ pthread_rwlock_t *GIT_RESTRICT lock,
+ const pthread_rwlockattr_t *GIT_RESTRICT attr);
+int pthread_rwlock_rdlock(pthread_rwlock_t *);
+int pthread_rwlock_rdunlock(pthread_rwlock_t *);
+int pthread_rwlock_wrlock(pthread_rwlock_t *);
+int pthread_rwlock_wrunlock(pthread_rwlock_t *);
+int pthread_rwlock_destroy(pthread_rwlock_t *);
+
+extern int win32_pthread_initialize(void);
+
#endif
diff --git a/src/win32/reparse.h b/src/win32/reparse.h
new file mode 100644
index 000000000..70f9fd652
--- /dev/null
+++ b/src/win32/reparse.h
@@ -0,0 +1,57 @@
+/*
+* Copyright (C) the libgit2 contributors. All rights reserved.
+*
+* This file is part of libgit2, distributed under the GNU GPL v2 with
+* a Linking Exception. For full terms see the included COPYING file.
+*/
+
+#ifndef INCLUDE_git_win32_reparse_h__
+#define INCLUDE_git_win32_reparse_h__
+
+/* This structure is defined on MSDN at
+* http://msdn.microsoft.com/en-us/library/windows/hardware/ff552012(v=vs.85).aspx
+*
+* It was formerly included in the Windows 2000 SDK and remains defined in
+* MinGW, so we must define it with a silly name to avoid conflicting.
+*/
+typedef struct _GIT_REPARSE_DATA_BUFFER {
+ ULONG ReparseTag;
+ USHORT ReparseDataLength;
+ USHORT Reserved;
+ union {
+ struct {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ ULONG Flags;
+ WCHAR PathBuffer[1];
+ } SymbolicLinkReparseBuffer;
+ struct {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ WCHAR PathBuffer[1];
+ } MountPointReparseBuffer;
+ struct {
+ UCHAR DataBuffer[1];
+ } GenericReparseBuffer;
+ };
+} GIT_REPARSE_DATA_BUFFER;
+
+#define REPARSE_DATA_HEADER_SIZE 8
+#define REPARSE_DATA_MOUNTPOINT_HEADER_SIZE 8
+#define REPARSE_DATA_UNION_SIZE 12
+
+/* Missing in MinGW */
+#ifndef FSCTL_GET_REPARSE_POINT
+# define FSCTL_GET_REPARSE_POINT 0x000900a8
+#endif
+
+/* Missing in MinGW */
+#ifndef FSCTL_SET_REPARSE_POINT
+# define FSCTL_SET_REPARSE_POINT 0x000900a4
+#endif
+
+#endif
diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c
new file mode 100644
index 000000000..b9ccfb5e5
--- /dev/null
+++ b/src/win32/utf-conv.c
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "utf-conv.h"
+
+#ifndef WC_ERR_INVALID_CHARS
+# define WC_ERR_INVALID_CHARS 0x80
+#endif
+
+GIT_INLINE(DWORD) get_wc_flags(void)
+{
+ static char inited = 0;
+ static DWORD flags;
+
+ /* Invalid code point check supported on Vista+ only */
+ if (!inited) {
+ flags = git_has_win32_version(6, 0, 0) ? WC_ERR_INVALID_CHARS : 0;
+ inited = 1;
+ }
+
+ return flags;
+}
+
+/**
+ * Converts a UTF-8 string to wide characters.
+ *
+ * @param dest The buffer to receive the wide string.
+ * @param dest_size The size of the buffer, in characters.
+ * @param src The UTF-8 string to convert.
+ * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
+ */
+int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src)
+{
+ /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to
+ * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's
+ * length. MultiByteToWideChar never returns int's minvalue, so underflow is not possible */
+ return MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size) - 1;
+}
+
+/**
+ * Converts a wide string to UTF-8.
+ *
+ * @param dest The buffer to receive the UTF-8 string.
+ * @param dest_size The size of the buffer, in bytes.
+ * @param src The wide string to convert.
+ * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
+ */
+int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src)
+{
+ /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to
+ * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's
+ * length. WideCharToMultiByte never returns int's minvalue, so underflow is not possible */
+ return WideCharToMultiByte(CP_UTF8, get_wc_flags(), src, -1, dest, (int)dest_size, NULL, NULL) - 1;
+}
+
+/**
+ * Converts a UTF-8 string to wide characters.
+ * Memory is allocated to hold the converted string.
+ * The caller is responsible for freeing the string with git__free.
+ *
+ * @param dest Receives a pointer to the wide string.
+ * @param src The UTF-8 string to convert.
+ * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
+ */
+int git__utf8_to_16_alloc(wchar_t **dest, const char *src)
+{
+ int utf16_size;
+
+ *dest = NULL;
+
+ /* Length of -1 indicates NULL termination of the input string */
+ utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, NULL, 0);
+
+ if (!utf16_size)
+ return -1;
+
+ *dest = git__malloc(utf16_size * sizeof(wchar_t));
+
+ if (!*dest)
+ return -1;
+
+ utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, *dest, utf16_size);
+
+ if (!utf16_size) {
+ git__free(*dest);
+ *dest = NULL;
+ }
+
+ /* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL
+ * terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue,
+ * so underflow is not possible */
+ return utf16_size - 1;
+}
+
+/**
+ * Converts a wide string to UTF-8.
+ * Memory is allocated to hold the converted string.
+ * The caller is responsible for freeing the string with git__free.
+ *
+ * @param dest Receives a pointer to the UTF-8 string.
+ * @param src The wide string to convert.
+ * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
+ */
+int git__utf16_to_8_alloc(char **dest, const wchar_t *src)
+{
+ int utf8_size;
+ DWORD dwFlags = get_wc_flags();
+
+ *dest = NULL;
+
+ /* Length of -1 indicates NULL termination of the input string */
+ utf8_size = WideCharToMultiByte(CP_UTF8, dwFlags, src, -1, NULL, 0, NULL, NULL);
+
+ if (!utf8_size)
+ return -1;
+
+ *dest = git__malloc(utf8_size);
+
+ if (!*dest)
+ return -1;
+
+ utf8_size = WideCharToMultiByte(CP_UTF8, dwFlags, src, -1, *dest, utf8_size, NULL, NULL);
+
+ if (!utf8_size) {
+ git__free(*dest);
+ *dest = NULL;
+ }
+
+ /* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL
+ * terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue,
+ * so underflow is not possible */
+ return utf8_size - 1;
+}
diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h
new file mode 100644
index 000000000..a480cd93e
--- /dev/null
+++ b/src/win32/utf-conv.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_utfconv_h__
+#define INCLUDE_git_utfconv_h__
+
+#include <wchar.h>
+#include "common.h"
+
+/* Equal to the Win32 MAX_PATH constant. The maximum path length is 259
+ * characters plus a NULL terminator. */
+#define GIT_WIN_PATH_UTF16 260
+
+/* Maximum size of a UTF-8 Win32 path. UTF-8 does have 4-byte sequences,
+ * but they are encoded in UTF-16 using surrogate pairs, which takes up
+ * the space of two characters. Two characters in the range U+0800 ->
+ * U+FFFF take up more space in UTF-8 (6 bytes) than one surrogate pair
+ * (4 bytes). */
+#define GIT_WIN_PATH_UTF8 (259 * 3 + 1)
+
+/* Win32 path types */
+typedef wchar_t git_win32_path[GIT_WIN_PATH_UTF16];
+typedef char git_win32_utf8_path[GIT_WIN_PATH_UTF8];
+
+/**
+ * Converts a UTF-8 string to wide characters.
+ *
+ * @param dest The buffer to receive the wide string.
+ * @param dest_size The size of the buffer, in characters.
+ * @param src The UTF-8 string to convert.
+ * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
+ */
+int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src);
+
+/**
+ * Converts a wide string to UTF-8.
+ *
+ * @param dest The buffer to receive the UTF-8 string.
+ * @param dest_size The size of the buffer, in bytes.
+ * @param src The wide string to convert.
+ * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
+ */
+int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src);
+
+/**
+ * Converts a UTF-8 string to wide characters.
+ * Memory is allocated to hold the converted string.
+ * The caller is responsible for freeing the string with git__free.
+ *
+ * @param dest Receives a pointer to the wide string.
+ * @param src The UTF-8 string to convert.
+ * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
+ */
+int git__utf8_to_16_alloc(wchar_t **dest, const char *src);
+
+/**
+ * Converts a wide string to UTF-8.
+ * Memory is allocated to hold the converted string.
+ * The caller is responsible for freeing the string with git__free.
+ *
+ * @param dest Receives a pointer to the UTF-8 string.
+ * @param src The wide string to convert.
+ * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
+ */
+int git__utf16_to_8_alloc(char **dest, const wchar_t *src);
+
+/**
+ * Converts a UTF-8 Win32 path to wide characters.
+ *
+ * @param dest The buffer to receive the wide string.
+ * @param src The UTF-8 string to convert.
+ * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure
+ */
+GIT_INLINE(int) git_win32_path_from_utf8(git_win32_path dest, const char *src)
+{
+ return git__utf8_to_16(dest, GIT_WIN_PATH_UTF16, src);
+}
+
+/**
+ * Converts a wide Win32 path to UTF-8.
+ *
+ * @param dest The buffer to receive the UTF-8 string.
+ * @param src The wide string to convert.
+ * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure
+ */
+GIT_INLINE(int) git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src)
+{
+ return git__utf16_to_8(dest, GIT_WIN_PATH_UTF8, src);
+}
+
+#endif
diff --git a/src/win32/version.h b/src/win32/version.h
new file mode 100644
index 000000000..79667697f
--- /dev/null
+++ b/src/win32/version.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_win32_version_h__
+#define INCLUDE_win32_version_h__
+
+#include <windows.h>
+
+GIT_INLINE(int) git_has_win32_version(int major, int minor, int service_pack)
+{
+ OSVERSIONINFOEX version_test = {0};
+ DWORD version_test_mask;
+ DWORDLONG version_condition_mask = 0;
+
+ version_test.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+ version_test.dwMajorVersion = major;
+ version_test.dwMinorVersion = minor;
+ version_test.wServicePackMajor = (WORD)service_pack;
+ 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 0;
+
+ return 1;
+}
+
+#endif
diff --git a/src/win32/w32_util.c b/src/win32/w32_util.c
new file mode 100644
index 000000000..2e52525d5
--- /dev/null
+++ b/src/win32/w32_util.c
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 "w32_util.h"
+
+/**
+ * Creates a FindFirstFile(Ex) filter string from a UTF-8 path.
+ * The filter string enumerates all items in the directory.
+ *
+ * @param dest The buffer to receive the filter string.
+ * @param src The UTF-8 path of the directory to enumerate.
+ * @return True if the filter string was created successfully; false otherwise
+ */
+bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src)
+{
+ static const wchar_t suffix[] = L"\\*";
+ int len = git_win32_path_from_utf8(dest, src);
+
+ /* Ensure the path was converted */
+ if (len < 0)
+ return false;
+
+ /* Ensure that the path does not end with a trailing slash,
+ * because we're about to add one. Don't rely our trim_end
+ * helper, because we want to remove the backslash even for
+ * drive letter paths, in this case. */
+ if (len > 0 &&
+ (dest[len - 1] == L'/' || dest[len - 1] == L'\\')) {
+ dest[len - 1] = L'\0';
+ len--;
+ }
+
+ /* Ensure we have enough room to add the suffix */
+ if ((size_t)len >= GIT_WIN_PATH_UTF16 - CONST_STRLEN(suffix))
+ return false;
+
+ wcscat(dest, suffix);
+ return true;
+}
+
+/**
+ * Ensures the given path (file or folder) has the +H (hidden) attribute set.
+ *
+ * @param path The path which should receive the +H bit.
+ * @return 0 on success; -1 on failure
+ */
+int git_win32__sethidden(const char *path)
+{
+ git_win32_path buf;
+ DWORD attrs;
+
+ if (git_win32_path_from_utf8(buf, path) < 0)
+ return -1;
+
+ attrs = GetFileAttributesW(buf);
+
+ /* Ensure the path exists */
+ if (attrs == INVALID_FILE_ATTRIBUTES)
+ return -1;
+
+ /* If the item isn't already +H, add the bit */
+ if ((attrs & FILE_ATTRIBUTE_HIDDEN) == 0 &&
+ !SetFileAttributesW(buf, attrs | FILE_ATTRIBUTE_HIDDEN))
+ return -1;
+
+ return 0;
+}
+
+/**
+ * Removes any trailing backslashes from a path, except in the case of a drive
+ * letter path (C:\, D:\, etc.). This function cannot fail.
+ *
+ * @param path The path which should be trimmed.
+ * @return The length of the modified string (<= the input length)
+ */
+size_t git_win32__path_trim_end(wchar_t *str, size_t len)
+{
+ while (1) {
+ if (!len || str[len - 1] != L'\\')
+ break;
+
+ /* Don't trim backslashes from drive letter paths, which
+ * are 3 characters long and of the form C:\, D:\, etc. */
+ if (len == 3 && git_win32__isalpha(str[0]) && str[1] == ':')
+ break;
+
+ len--;
+ }
+
+ str[len] = L'\0';
+
+ return len;
+}
+
+/**
+ * Removes any of the following namespace prefixes from a path,
+ * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail.
+ *
+ * @param path The path which should be converted.
+ * @return The length of the modified string (<= the input length)
+ */
+size_t git_win32__canonicalize_path(wchar_t *str, size_t len)
+{
+ static const wchar_t dosdevices_prefix[] = L"\\\?\?\\";
+ static const wchar_t nt_prefix[] = L"\\\\?\\";
+ static const wchar_t unc_prefix[] = L"UNC\\";
+ size_t to_advance = 0;
+
+ /* "\??\" -- DOS Devices prefix */
+ if (len >= CONST_STRLEN(dosdevices_prefix) &&
+ !wcsncmp(str, dosdevices_prefix, CONST_STRLEN(dosdevices_prefix))) {
+ to_advance += CONST_STRLEN(dosdevices_prefix);
+ len -= CONST_STRLEN(dosdevices_prefix);
+ }
+ /* "\\?\" -- NT namespace prefix */
+ else if (len >= CONST_STRLEN(nt_prefix) &&
+ !wcsncmp(str, nt_prefix, CONST_STRLEN(nt_prefix))) {
+ to_advance += CONST_STRLEN(nt_prefix);
+ len -= CONST_STRLEN(nt_prefix);
+ }
+
+ /* "\??\UNC\", "\\?\UNC\" -- UNC prefix */
+ if (to_advance && len >= CONST_STRLEN(unc_prefix) &&
+ !wcsncmp(str + to_advance, unc_prefix, CONST_STRLEN(unc_prefix))) {
+ to_advance += CONST_STRLEN(unc_prefix);
+ len -= CONST_STRLEN(unc_prefix);
+ }
+
+ if (to_advance) {
+ memmove(str, str + to_advance, len * sizeof(wchar_t));
+ str[len] = L'\0';
+ }
+
+ return git_win32__path_trim_end(str, len);
+}
diff --git a/src/win32/w32_util.h b/src/win32/w32_util.h
new file mode 100644
index 000000000..a1d388af5
--- /dev/null
+++ b/src/win32/w32_util.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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_w32_util_h__
+#define INCLUDE_w32_util_h__
+
+#include "utf-conv.h"
+
+GIT_INLINE(bool) git_win32__isalpha(wchar_t c)
+{
+ return ((c >= L'A' && c <= L'Z') || (c >= L'a' && c <= L'z'));
+}
+
+/**
+ * Creates a FindFirstFile(Ex) filter string from a UTF-8 path.
+ * The filter string enumerates all items in the directory.
+ *
+ * @param dest The buffer to receive the filter string.
+ * @param src The UTF-8 path of the directory to enumerate.
+ * @return True if the filter string was created successfully; false otherwise
+ */
+bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src);
+
+/**
+ * Ensures the given path (file or folder) has the +H (hidden) attribute set.
+ *
+ * @param path The path which should receive the +H bit.
+ * @return 0 on success; -1 on failure
+ */
+int git_win32__sethidden(const char *path);
+
+/**
+ * Removes any trailing backslashes from a path, except in the case of a drive
+ * letter path (C:\, D:\, etc.). This function cannot fail.
+ *
+ * @param path The path which should be trimmed.
+ * @return The length of the modified string (<= the input length)
+ */
+size_t git_win32__path_trim_end(wchar_t *str, size_t len);
+
+/**
+ * Removes any of the following namespace prefixes from a path,
+ * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail.
+ *
+ * @param path The path which should be converted.
+ * @return The length of the modified string (<= the input length)
+ */
+size_t git_win32__canonicalize_path(wchar_t *str, size_t len);
+
+#endif
diff --git a/src/xdiff/xdiff.h b/src/xdiff/xdiff.h
new file mode 100644
index 000000000..cb8b235b5
--- /dev/null
+++ b/src/xdiff/xdiff.h
@@ -0,0 +1,135 @@
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#if !defined(XDIFF_H)
+#define XDIFF_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* #ifdef __cplusplus */
+
+
+#define XDF_NEED_MINIMAL (1 << 1)
+#define XDF_IGNORE_WHITESPACE (1 << 2)
+#define XDF_IGNORE_WHITESPACE_CHANGE (1 << 3)
+#define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 4)
+#define XDF_PATIENCE_DIFF (1 << 5)
+#define XDF_HISTOGRAM_DIFF (1 << 6)
+#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE | XDF_IGNORE_WHITESPACE_AT_EOL)
+
+#define XDL_PATCH_NORMAL '-'
+#define XDL_PATCH_REVERSE '+'
+#define XDL_PATCH_MODEMASK ((1 << 8) - 1)
+#define XDL_PATCH_IGNOREBSPACE (1 << 8)
+
+#define XDL_EMIT_FUNCNAMES (1 << 0)
+#define XDL_EMIT_COMMON (1 << 1)
+#define XDL_EMIT_FUNCCONTEXT (1 << 2)
+
+#define XDL_MMB_READONLY (1 << 0)
+
+#define XDL_MMF_ATOMIC (1 << 0)
+
+#define XDL_BDOP_INS 1
+#define XDL_BDOP_CPY 2
+#define XDL_BDOP_INSB 3
+
+/* merge simplification levels */
+#define XDL_MERGE_MINIMAL 0
+#define XDL_MERGE_EAGER 1
+#define XDL_MERGE_ZEALOUS 2
+#define XDL_MERGE_ZEALOUS_ALNUM 3
+
+/* merge favor modes */
+#define XDL_MERGE_FAVOR_OURS 1
+#define XDL_MERGE_FAVOR_THEIRS 2
+#define XDL_MERGE_FAVOR_UNION 3
+
+/* merge output styles */
+#define XDL_MERGE_DIFF3 1
+
+typedef struct s_mmfile {
+ char *ptr;
+ size_t size;
+} mmfile_t;
+
+typedef struct s_mmbuffer {
+ char *ptr;
+ size_t size;
+} mmbuffer_t;
+
+typedef struct s_xpparam {
+ unsigned long flags;
+} xpparam_t;
+
+typedef struct s_xdemitcb {
+ void *priv;
+ int (*outf)(void *, mmbuffer_t *, int);
+} xdemitcb_t;
+
+typedef long (*find_func_t)(const char *line, long line_len, char *buffer, long buffer_size, void *priv);
+
+typedef struct s_xdemitconf {
+ long ctxlen;
+ long interhunkctxlen;
+ unsigned long flags;
+ find_func_t find_func;
+ void *find_func_priv;
+ void (*emit_func)(void);
+} xdemitconf_t;
+
+typedef struct s_bdiffparam {
+ long bsize;
+} bdiffparam_t;
+
+
+#define xdl_malloc(x) malloc(x)
+#define xdl_free(ptr) free(ptr)
+#define xdl_realloc(ptr,x) realloc(ptr,x)
+
+void *xdl_mmfile_first(mmfile_t *mmf, long *size);
+long xdl_mmfile_size(mmfile_t *mmf);
+
+int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+ xdemitconf_t const *xecfg, xdemitcb_t *ecb);
+
+typedef struct s_xmparam {
+ xpparam_t xpp;
+ int marker_size;
+ int level;
+ int favor;
+ int style;
+ const char *ancestor; /* label for orig */
+ const char *file1; /* label for mf1 */
+ const char *file2; /* label for mf2 */
+} xmparam_t;
+
+#define DEFAULT_CONFLICT_MARKER_SIZE 7
+
+int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2,
+ xmparam_t const *xmp, mmbuffer_t *result);
+
+#ifdef __cplusplus
+}
+#endif /* #ifdef __cplusplus */
+
+#endif /* #if !defined(XDIFF_H) */
diff --git a/src/xdiff/xdiffi.c b/src/xdiff/xdiffi.c
new file mode 100644
index 000000000..75a392275
--- /dev/null
+++ b/src/xdiff/xdiffi.c
@@ -0,0 +1,572 @@
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#include "xinclude.h"
+
+
+
+#define XDL_MAX_COST_MIN 256
+#define XDL_HEUR_MIN_COST 256
+#define XDL_LINE_MAX (long)((1UL << (CHAR_BIT * sizeof(long) - 1)) - 1)
+#define XDL_SNAKE_CNT 20
+#define XDL_K_HEUR 4
+
+
+
+typedef struct s_xdpsplit {
+ long i1, i2;
+ int min_lo, min_hi;
+} xdpsplit_t;
+
+
+
+
+static long xdl_split(unsigned long const *ha1, long off1, long lim1,
+ unsigned long const *ha2, long off2, long lim2,
+ long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl,
+ xdalgoenv_t *xenv);
+static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2);
+
+
+
+
+
+/*
+ * See "An O(ND) Difference Algorithm and its Variations", by Eugene Myers.
+ * Basically considers a "box" (off1, off2, lim1, lim2) and scan from both
+ * the forward diagonal starting from (off1, off2) and the backward diagonal
+ * starting from (lim1, lim2). If the K values on the same diagonal crosses
+ * returns the furthest point of reach. We might end up having to expensive
+ * cases using this algorithm is full, so a little bit of heuristic is needed
+ * to cut the search and to return a suboptimal point.
+ */
+static long xdl_split(unsigned long const *ha1, long off1, long lim1,
+ unsigned long const *ha2, long off2, long lim2,
+ long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl,
+ xdalgoenv_t *xenv) {
+ long dmin = off1 - lim2, dmax = lim1 - off2;
+ long fmid = off1 - off2, bmid = lim1 - lim2;
+ long odd = (fmid - bmid) & 1;
+ long fmin = fmid, fmax = fmid;
+ long bmin = bmid, bmax = bmid;
+ long ec, d, i1, i2, prev1, best, dd, v, k;
+
+ /*
+ * Set initial diagonal values for both forward and backward path.
+ */
+ kvdf[fmid] = off1;
+ kvdb[bmid] = lim1;
+
+ for (ec = 1;; ec++) {
+ int got_snake = 0;
+
+ /*
+ * We need to extent the diagonal "domain" by one. If the next
+ * values exits the box boundaries we need to change it in the
+ * opposite direction because (max - min) must be a power of two.
+ * Also we initialize the external K value to -1 so that we can
+ * avoid extra conditions check inside the core loop.
+ */
+ if (fmin > dmin)
+ kvdf[--fmin - 1] = -1;
+ else
+ ++fmin;
+ if (fmax < dmax)
+ kvdf[++fmax + 1] = -1;
+ else
+ --fmax;
+
+ for (d = fmax; d >= fmin; d -= 2) {
+ if (kvdf[d - 1] >= kvdf[d + 1])
+ i1 = kvdf[d - 1] + 1;
+ else
+ i1 = kvdf[d + 1];
+ prev1 = i1;
+ i2 = i1 - d;
+ for (; i1 < lim1 && i2 < lim2 && ha1[i1] == ha2[i2]; i1++, i2++);
+ if (i1 - prev1 > xenv->snake_cnt)
+ got_snake = 1;
+ kvdf[d] = i1;
+ if (odd && bmin <= d && d <= bmax && kvdb[d] <= i1) {
+ spl->i1 = i1;
+ spl->i2 = i2;
+ spl->min_lo = spl->min_hi = 1;
+ return ec;
+ }
+ }
+
+ /*
+ * We need to extent the diagonal "domain" by one. If the next
+ * values exits the box boundaries we need to change it in the
+ * opposite direction because (max - min) must be a power of two.
+ * Also we initialize the external K value to -1 so that we can
+ * avoid extra conditions check inside the core loop.
+ */
+ if (bmin > dmin)
+ kvdb[--bmin - 1] = XDL_LINE_MAX;
+ else
+ ++bmin;
+ if (bmax < dmax)
+ kvdb[++bmax + 1] = XDL_LINE_MAX;
+ else
+ --bmax;
+
+ for (d = bmax; d >= bmin; d -= 2) {
+ if (kvdb[d - 1] < kvdb[d + 1])
+ i1 = kvdb[d - 1];
+ else
+ i1 = kvdb[d + 1] - 1;
+ prev1 = i1;
+ i2 = i1 - d;
+ for (; i1 > off1 && i2 > off2 && ha1[i1 - 1] == ha2[i2 - 1]; i1--, i2--);
+ if (prev1 - i1 > xenv->snake_cnt)
+ got_snake = 1;
+ kvdb[d] = i1;
+ if (!odd && fmin <= d && d <= fmax && i1 <= kvdf[d]) {
+ spl->i1 = i1;
+ spl->i2 = i2;
+ spl->min_lo = spl->min_hi = 1;
+ return ec;
+ }
+ }
+
+ if (need_min)
+ continue;
+
+ /*
+ * If the edit cost is above the heuristic trigger and if
+ * we got a good snake, we sample current diagonals to see
+ * if some of the, have reached an "interesting" path. Our
+ * measure is a function of the distance from the diagonal
+ * corner (i1 + i2) penalized with the distance from the
+ * mid diagonal itself. If this value is above the current
+ * edit cost times a magic factor (XDL_K_HEUR) we consider
+ * it interesting.
+ */
+ if (got_snake && ec > xenv->heur_min) {
+ for (best = 0, d = fmax; d >= fmin; d -= 2) {
+ dd = d > fmid ? d - fmid: fmid - d;
+ i1 = kvdf[d];
+ i2 = i1 - d;
+ v = (i1 - off1) + (i2 - off2) - dd;
+
+ if (v > XDL_K_HEUR * ec && v > best &&
+ off1 + xenv->snake_cnt <= i1 && i1 < lim1 &&
+ off2 + xenv->snake_cnt <= i2 && i2 < lim2) {
+ for (k = 1; ha1[i1 - k] == ha2[i2 - k]; k++)
+ if (k == xenv->snake_cnt) {
+ best = v;
+ spl->i1 = i1;
+ spl->i2 = i2;
+ break;
+ }
+ }
+ }
+ if (best > 0) {
+ spl->min_lo = 1;
+ spl->min_hi = 0;
+ return ec;
+ }
+
+ for (best = 0, d = bmax; d >= bmin; d -= 2) {
+ dd = d > bmid ? d - bmid: bmid - d;
+ i1 = kvdb[d];
+ i2 = i1 - d;
+ v = (lim1 - i1) + (lim2 - i2) - dd;
+
+ if (v > XDL_K_HEUR * ec && v > best &&
+ off1 < i1 && i1 <= lim1 - xenv->snake_cnt &&
+ off2 < i2 && i2 <= lim2 - xenv->snake_cnt) {
+ for (k = 0; ha1[i1 + k] == ha2[i2 + k]; k++)
+ if (k == xenv->snake_cnt - 1) {
+ best = v;
+ spl->i1 = i1;
+ spl->i2 = i2;
+ break;
+ }
+ }
+ }
+ if (best > 0) {
+ spl->min_lo = 0;
+ spl->min_hi = 1;
+ return ec;
+ }
+ }
+
+ /*
+ * Enough is enough. We spent too much time here and now we collect
+ * the furthest reaching path using the (i1 + i2) measure.
+ */
+ if (ec >= xenv->mxcost) {
+ long fbest, fbest1, bbest, bbest1;
+
+ fbest = fbest1 = -1;
+ for (d = fmax; d >= fmin; d -= 2) {
+ i1 = XDL_MIN(kvdf[d], lim1);
+ i2 = i1 - d;
+ if (lim2 < i2)
+ i1 = lim2 + d, i2 = lim2;
+ if (fbest < i1 + i2) {
+ fbest = i1 + i2;
+ fbest1 = i1;
+ }
+ }
+
+ bbest = bbest1 = XDL_LINE_MAX;
+ for (d = bmax; d >= bmin; d -= 2) {
+ i1 = XDL_MAX(off1, kvdb[d]);
+ i2 = i1 - d;
+ if (i2 < off2)
+ i1 = off2 + d, i2 = off2;
+ if (i1 + i2 < bbest) {
+ bbest = i1 + i2;
+ bbest1 = i1;
+ }
+ }
+
+ if ((lim1 + lim2) - bbest < fbest - (off1 + off2)) {
+ spl->i1 = fbest1;
+ spl->i2 = fbest - fbest1;
+ spl->min_lo = 1;
+ spl->min_hi = 0;
+ } else {
+ spl->i1 = bbest1;
+ spl->i2 = bbest - bbest1;
+ spl->min_lo = 0;
+ spl->min_hi = 1;
+ }
+ return ec;
+ }
+ }
+}
+
+
+/*
+ * Rule: "Divide et Impera". Recursively split the box in sub-boxes by calling
+ * the box splitting function. Note that the real job (marking changed lines)
+ * is done in the two boundary reaching checks.
+ */
+int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1,
+ diffdata_t *dd2, long off2, long lim2,
+ long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv) {
+ unsigned long const *ha1 = dd1->ha, *ha2 = dd2->ha;
+
+ /*
+ * Shrink the box by walking through each diagonal snake (SW and NE).
+ */
+ for (; off1 < lim1 && off2 < lim2 && ha1[off1] == ha2[off2]; off1++, off2++);
+ for (; off1 < lim1 && off2 < lim2 && ha1[lim1 - 1] == ha2[lim2 - 1]; lim1--, lim2--);
+
+ /*
+ * If one dimension is empty, then all records on the other one must
+ * be obviously changed.
+ */
+ if (off1 == lim1) {
+ char *rchg2 = dd2->rchg;
+ long *rindex2 = dd2->rindex;
+
+ for (; off2 < lim2; off2++)
+ rchg2[rindex2[off2]] = 1;
+ } else if (off2 == lim2) {
+ char *rchg1 = dd1->rchg;
+ long *rindex1 = dd1->rindex;
+
+ for (; off1 < lim1; off1++)
+ rchg1[rindex1[off1]] = 1;
+ } else {
+ xdpsplit_t spl;
+ spl.i1 = spl.i2 = 0;
+
+ /*
+ * Divide ...
+ */
+ if (xdl_split(ha1, off1, lim1, ha2, off2, lim2, kvdf, kvdb,
+ need_min, &spl, xenv) < 0) {
+
+ return -1;
+ }
+
+ /*
+ * ... et Impera.
+ */
+ if (xdl_recs_cmp(dd1, off1, spl.i1, dd2, off2, spl.i2,
+ kvdf, kvdb, spl.min_lo, xenv) < 0 ||
+ xdl_recs_cmp(dd1, spl.i1, lim1, dd2, spl.i2, lim2,
+ kvdf, kvdb, spl.min_hi, xenv) < 0) {
+
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+
+int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+ xdfenv_t *xe) {
+ long ndiags;
+ long *kvd, *kvdf, *kvdb;
+ xdalgoenv_t xenv;
+ diffdata_t dd1, dd2;
+
+ if (xpp->flags & XDF_PATIENCE_DIFF)
+ return xdl_do_patience_diff(mf1, mf2, xpp, xe);
+
+ if (xpp->flags & XDF_HISTOGRAM_DIFF)
+ return xdl_do_histogram_diff(mf1, mf2, xpp, xe);
+
+ if (xdl_prepare_env(mf1, mf2, xpp, xe) < 0) {
+
+ return -1;
+ }
+
+ /*
+ * Allocate and setup K vectors to be used by the differential algorithm.
+ * One is to store the forward path and one to store the backward path.
+ */
+ ndiags = xe->xdf1.nreff + xe->xdf2.nreff + 3;
+ if (!(kvd = (long *) xdl_malloc((2 * ndiags + 2) * sizeof(long)))) {
+
+ xdl_free_env(xe);
+ return -1;
+ }
+ kvdf = kvd;
+ kvdb = kvdf + ndiags;
+ kvdf += xe->xdf2.nreff + 1;
+ kvdb += xe->xdf2.nreff + 1;
+
+ xenv.mxcost = xdl_bogosqrt(ndiags);
+ if (xenv.mxcost < XDL_MAX_COST_MIN)
+ xenv.mxcost = XDL_MAX_COST_MIN;
+ xenv.snake_cnt = XDL_SNAKE_CNT;
+ xenv.heur_min = XDL_HEUR_MIN_COST;
+
+ dd1.nrec = xe->xdf1.nreff;
+ dd1.ha = xe->xdf1.ha;
+ dd1.rchg = xe->xdf1.rchg;
+ dd1.rindex = xe->xdf1.rindex;
+ dd2.nrec = xe->xdf2.nreff;
+ dd2.ha = xe->xdf2.ha;
+ dd2.rchg = xe->xdf2.rchg;
+ dd2.rindex = xe->xdf2.rindex;
+
+ if (xdl_recs_cmp(&dd1, 0, dd1.nrec, &dd2, 0, dd2.nrec,
+ kvdf, kvdb, (xpp->flags & XDF_NEED_MINIMAL) != 0, &xenv) < 0) {
+
+ xdl_free(kvd);
+ xdl_free_env(xe);
+ return -1;
+ }
+
+ xdl_free(kvd);
+
+ return 0;
+}
+
+
+static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2) {
+ xdchange_t *xch;
+
+ if (!(xch = (xdchange_t *) xdl_malloc(sizeof(xdchange_t))))
+ return NULL;
+
+ xch->next = xscr;
+ xch->i1 = i1;
+ xch->i2 = i2;
+ xch->chg1 = chg1;
+ xch->chg2 = chg2;
+
+ return xch;
+}
+
+
+int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
+ long ix, ixo, ixs, ixref, grpsiz, nrec = xdf->nrec;
+ char *rchg = xdf->rchg, *rchgo = xdfo->rchg;
+ xrecord_t **recs = xdf->recs;
+
+ /*
+ * This is the same of what GNU diff does. Move back and forward
+ * change groups for a consistent and pretty diff output. This also
+ * helps in finding joinable change groups and reduce the diff size.
+ */
+ for (ix = ixo = 0;;) {
+ /*
+ * Find the first changed line in the to-be-compacted file.
+ * We need to keep track of both indexes, so if we find a
+ * changed lines group on the other file, while scanning the
+ * to-be-compacted file, we need to skip it properly. Note
+ * that loops that are testing for changed lines on rchg* do
+ * not need index bounding since the array is prepared with
+ * a zero at position -1 and N.
+ */
+ for (; ix < nrec && !rchg[ix]; ix++)
+ while (rchgo[ixo++]);
+ if (ix == nrec)
+ break;
+
+ /*
+ * Record the start of a changed-group in the to-be-compacted file
+ * and find the end of it, on both to-be-compacted and other file
+ * indexes (ix and ixo).
+ */
+ ixs = ix;
+ for (ix++; rchg[ix]; ix++);
+ for (; rchgo[ixo]; ixo++);
+
+ do {
+ grpsiz = ix - ixs;
+
+ /*
+ * If the line before the current change group, is equal to
+ * the last line of the current change group, shift backward
+ * the group.
+ */
+ while (ixs > 0 && recs[ixs - 1]->ha == recs[ix - 1]->ha &&
+ xdl_recmatch(recs[ixs - 1]->ptr, recs[ixs - 1]->size, recs[ix - 1]->ptr, recs[ix - 1]->size, flags)) {
+ rchg[--ixs] = 1;
+ rchg[--ix] = 0;
+
+ /*
+ * This change might have joined two change groups,
+ * so we try to take this scenario in account by moving
+ * the start index accordingly (and so the other-file
+ * end-of-group index).
+ */
+ for (; rchg[ixs - 1]; ixs--);
+ while (rchgo[--ixo]);
+ }
+
+ /*
+ * Record the end-of-group position in case we are matched
+ * with a group of changes in the other file (that is, the
+ * change record before the end-of-group index in the other
+ * file is set).
+ */
+ ixref = rchgo[ixo - 1] ? ix: nrec;
+
+ /*
+ * If the first line of the current change group, is equal to
+ * the line next of the current change group, shift forward
+ * the group.
+ */
+ while (ix < nrec && recs[ixs]->ha == recs[ix]->ha &&
+ xdl_recmatch(recs[ixs]->ptr, recs[ixs]->size, recs[ix]->ptr, recs[ix]->size, flags)) {
+ rchg[ixs++] = 0;
+ rchg[ix++] = 1;
+
+ /*
+ * This change might have joined two change groups,
+ * so we try to take this scenario in account by moving
+ * the start index accordingly (and so the other-file
+ * end-of-group index). Keep tracking the reference
+ * index in case we are shifting together with a
+ * corresponding group of changes in the other file.
+ */
+ for (; rchg[ix]; ix++);
+ while (rchgo[++ixo])
+ ixref = ix;
+ }
+ } while (grpsiz != ix - ixs);
+
+ /*
+ * Try to move back the possibly merged group of changes, to match
+ * the recorded postion in the other file.
+ */
+ while (ixref < ix) {
+ rchg[--ixs] = 1;
+ rchg[--ix] = 0;
+ while (rchgo[--ixo]);
+ }
+ }
+
+ return 0;
+}
+
+
+int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr) {
+ xdchange_t *cscr = NULL, *xch;
+ char *rchg1 = xe->xdf1.rchg, *rchg2 = xe->xdf2.rchg;
+ long i1, i2, l1, l2;
+
+ /*
+ * Trivial. Collects "groups" of changes and creates an edit script.
+ */
+ for (i1 = xe->xdf1.nrec, i2 = xe->xdf2.nrec; i1 >= 0 || i2 >= 0; i1--, i2--)
+ if (rchg1[i1 - 1] || rchg2[i2 - 1]) {
+ for (l1 = i1; rchg1[i1 - 1]; i1--);
+ for (l2 = i2; rchg2[i2 - 1]; i2--);
+
+ if (!(xch = xdl_add_change(cscr, i1, i2, l1 - i1, l2 - i2))) {
+ xdl_free_script(cscr);
+ return -1;
+ }
+ cscr = xch;
+ }
+
+ *xscr = cscr;
+
+ return 0;
+}
+
+
+void xdl_free_script(xdchange_t *xscr) {
+ xdchange_t *xch;
+
+ while ((xch = xscr) != NULL) {
+ xscr = xscr->next;
+ xdl_free(xch);
+ }
+}
+
+
+int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+ xdemitconf_t const *xecfg, xdemitcb_t *ecb) {
+ xdchange_t *xscr;
+ xdfenv_t xe;
+ emit_func_t ef = xecfg->emit_func ?
+ (emit_func_t)xecfg->emit_func : xdl_emit_diff;
+
+ if (xdl_do_diff(mf1, mf2, xpp, &xe) < 0) {
+
+ return -1;
+ }
+ if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 ||
+ xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 ||
+ xdl_build_script(&xe, &xscr) < 0) {
+
+ xdl_free_env(&xe);
+ return -1;
+ }
+ if (xscr) {
+ if (ef(&xe, xscr, ecb, xecfg) < 0) {
+
+ xdl_free_script(xscr);
+ xdl_free_env(&xe);
+ return -1;
+ }
+ xdl_free_script(xscr);
+ }
+ xdl_free_env(&xe);
+
+ return 0;
+}
diff --git a/src/xdiff/xdiffi.h b/src/xdiff/xdiffi.h
new file mode 100644
index 000000000..7a92ea9c4
--- /dev/null
+++ b/src/xdiff/xdiffi.h
@@ -0,0 +1,63 @@
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#if !defined(XDIFFI_H)
+#define XDIFFI_H
+
+
+typedef struct s_diffdata {
+ long nrec;
+ unsigned long const *ha;
+ long *rindex;
+ char *rchg;
+} diffdata_t;
+
+typedef struct s_xdalgoenv {
+ long mxcost;
+ long snake_cnt;
+ long heur_min;
+} xdalgoenv_t;
+
+typedef struct s_xdchange {
+ struct s_xdchange *next;
+ long i1, i2;
+ long chg1, chg2;
+} xdchange_t;
+
+
+
+int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1,
+ diffdata_t *dd2, long off2, long lim2,
+ long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv);
+int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+ xdfenv_t *xe);
+int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags);
+int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr);
+void xdl_free_script(xdchange_t *xscr);
+int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+ xdemitconf_t const *xecfg);
+int xdl_do_patience_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+ xdfenv_t *env);
+int xdl_do_histogram_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+ xdfenv_t *env);
+
+#endif /* #if !defined(XDIFFI_H) */
diff --git a/src/xdiff/xemit.c b/src/xdiff/xemit.c
new file mode 100644
index 000000000..e3e63d902
--- /dev/null
+++ b/src/xdiff/xemit.c
@@ -0,0 +1,253 @@
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#include "xinclude.h"
+
+
+
+
+static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec);
+static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *ecb);
+
+
+
+
+static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) {
+
+ *rec = xdf->recs[ri]->ptr;
+
+ return xdf->recs[ri]->size;
+}
+
+
+static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *ecb) {
+ long size, psize = (long)strlen(pre);
+ char const *rec;
+
+ size = xdl_get_rec(xdf, ri, &rec);
+ if (xdl_emit_diffrec(rec, size, pre, psize, ecb) < 0) {
+
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Starting at the passed change atom, find the latest change atom to be included
+ * inside the differential hunk according to the specified configuration.
+ */
+xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg) {
+ xdchange_t *xch, *xchp;
+ long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen;
+
+ for (xchp = xscr, xch = xscr->next; xch; xchp = xch, xch = xch->next)
+ if (xch->i1 - (xchp->i1 + xchp->chg1) > max_common)
+ break;
+
+ return xchp;
+}
+
+
+static long def_ff(const char *rec, long len, char *buf, long sz, void *priv)
+{
+ (void)priv;
+
+ if (len > 0 &&
+ (isalpha((unsigned char)*rec) || /* identifier? */
+ *rec == '_' || /* also identifier? */
+ *rec == '$')) { /* identifiers from VMS and other esoterico */
+ if (len > sz)
+ len = sz;
+ while (0 < len && isspace((unsigned char)rec[len - 1]))
+ len--;
+ memcpy(buf, rec, len);
+ return len;
+ }
+ return -1;
+}
+
+static int xdl_emit_common(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+ xdemitconf_t const *xecfg) {
+ xdfile_t *xdf = &xe->xdf2;
+ const char *rchg = xdf->rchg;
+ long ix;
+
+ (void)xscr;
+ (void)xecfg;
+
+ for (ix = 0; ix < xdf->nrec; ix++) {
+ if (rchg[ix])
+ continue;
+ if (xdl_emit_record(xdf, ix, "", ecb))
+ return -1;
+ }
+ return 0;
+}
+
+struct func_line {
+ long len;
+ char buf[80];
+};
+
+static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg,
+ struct func_line *func_line, long start, long limit)
+{
+ find_func_t ff = xecfg->find_func ? xecfg->find_func : def_ff;
+ long l, size, step = (start > limit) ? -1 : 1;
+ char *buf, dummy[1];
+
+ buf = func_line ? func_line->buf : dummy;
+ size = func_line ? sizeof(func_line->buf) : sizeof(dummy);
+
+ for (l = start; l != limit && 0 <= l && l < xe->xdf1.nrec; l += step) {
+ const char *rec;
+ long reclen = xdl_get_rec(&xe->xdf1, l, &rec);
+ long len = ff(rec, reclen, buf, size, xecfg->find_func_priv);
+ if (len >= 0) {
+ if (func_line)
+ func_line->len = len;
+ return l;
+ }
+ }
+ return -1;
+}
+
+int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+ xdemitconf_t const *xecfg) {
+ long s1, s2, e1, e2, lctx;
+ xdchange_t *xch, *xche;
+ long funclineprev = -1;
+ struct func_line func_line = { 0 };
+
+ if (xecfg->flags & XDL_EMIT_COMMON)
+ return xdl_emit_common(xe, xscr, ecb, xecfg);
+
+ for (xch = xscr; xch; xch = xche->next) {
+ xche = xdl_get_hunk(xch, xecfg);
+
+ s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0);
+ s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0);
+
+ if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) {
+ long fs1 = get_func_line(xe, xecfg, NULL, xch->i1, -1);
+ if (fs1 < 0)
+ fs1 = 0;
+ if (fs1 < s1) {
+ s2 -= s1 - fs1;
+ s1 = fs1;
+ }
+ }
+
+ again:
+ lctx = xecfg->ctxlen;
+ lctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1));
+ lctx = XDL_MIN(lctx, xe->xdf2.nrec - (xche->i2 + xche->chg2));
+
+ e1 = xche->i1 + xche->chg1 + lctx;
+ e2 = xche->i2 + xche->chg2 + lctx;
+
+ if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) {
+ long fe1 = get_func_line(xe, xecfg, NULL,
+ xche->i1 + xche->chg1,
+ xe->xdf1.nrec);
+ if (fe1 < 0)
+ fe1 = xe->xdf1.nrec;
+ if (fe1 > e1) {
+ e2 += fe1 - e1;
+ e1 = fe1;
+ }
+
+ /*
+ * Overlap with next change? Then include it
+ * in the current hunk and start over to find
+ * its new end.
+ */
+ if (xche->next) {
+ long l = xche->next->i1;
+ if (l <= e1 ||
+ get_func_line(xe, xecfg, NULL, l, e1) < 0) {
+ xche = xche->next;
+ goto again;
+ }
+ }
+ }
+
+ /*
+ * Emit current hunk header.
+ */
+
+ if (xecfg->flags & XDL_EMIT_FUNCNAMES) {
+ get_func_line(xe, xecfg, &func_line,
+ s1 - 1, funclineprev);
+ funclineprev = s1 - 1;
+ }
+ if (xdl_emit_hunk_hdr(s1 + 1, e1 - s1, s2 + 1, e2 - s2,
+ func_line.buf, func_line.len, ecb) < 0)
+ return -1;
+
+ /*
+ * Emit pre-context.
+ */
+ for (; s2 < xch->i2; s2++)
+ if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0)
+ return -1;
+
+ for (s1 = xch->i1, s2 = xch->i2;; xch = xch->next) {
+ /*
+ * Merge previous with current change atom.
+ */
+ for (; s1 < xch->i1 && s2 < xch->i2; s1++, s2++)
+ if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0)
+ return -1;
+
+ /*
+ * Removes lines from the first file.
+ */
+ for (s1 = xch->i1; s1 < xch->i1 + xch->chg1; s1++)
+ if (xdl_emit_record(&xe->xdf1, s1, "-", ecb) < 0)
+ return -1;
+
+ /*
+ * Adds lines from the second file.
+ */
+ for (s2 = xch->i2; s2 < xch->i2 + xch->chg2; s2++)
+ if (xdl_emit_record(&xe->xdf2, s2, "+", ecb) < 0)
+ return -1;
+
+ if (xch == xche)
+ break;
+ s1 = xch->i1 + xch->chg1;
+ s2 = xch->i2 + xch->chg2;
+ }
+
+ /*
+ * Emit post-context.
+ */
+ for (s2 = xche->i2 + xche->chg2; s2 < e2; s2++)
+ if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0)
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/src/xdiff/xemit.h b/src/xdiff/xemit.h
new file mode 100644
index 000000000..c2e2e8302
--- /dev/null
+++ b/src/xdiff/xemit.h
@@ -0,0 +1,36 @@
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#if !defined(XEMIT_H)
+#define XEMIT_H
+
+
+typedef int (*emit_func_t)(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+ xdemitconf_t const *xecfg);
+
+xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg);
+int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+ xdemitconf_t const *xecfg);
+
+
+
+#endif /* #if !defined(XEMIT_H) */
diff --git a/src/xdiff/xhistogram.c b/src/xdiff/xhistogram.c
new file mode 100644
index 000000000..5d101754d
--- /dev/null
+++ b/src/xdiff/xhistogram.c
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in JGit's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "xinclude.h"
+#include "xtypes.h"
+#include "xdiff.h"
+
+#define MAX_PTR UINT_MAX
+#define MAX_CNT UINT_MAX
+
+#define LINE_END(n) (line##n + count##n - 1)
+#define LINE_END_PTR(n) (*line##n + *count##n - 1)
+
+struct histindex {
+ struct record {
+ unsigned int ptr, cnt;
+ struct record *next;
+ } **records, /* an ocurrence */
+ **line_map; /* map of line to record chain */
+ chastore_t rcha;
+ unsigned int *next_ptrs;
+ unsigned int table_bits,
+ records_size,
+ line_map_size;
+
+ unsigned int max_chain_length,
+ key_shift,
+ ptr_shift;
+
+ unsigned int cnt,
+ has_common;
+
+ xdfenv_t *env;
+ xpparam_t const *xpp;
+};
+
+struct region {
+ unsigned int begin1, end1;
+ unsigned int begin2, end2;
+};
+
+#define LINE_MAP(i, a) (i->line_map[(a) - i->ptr_shift])
+
+#define NEXT_PTR(index, ptr) \
+ (index->next_ptrs[(ptr) - index->ptr_shift])
+
+#define CNT(index, ptr) \
+ ((LINE_MAP(index, ptr))->cnt)
+
+#define REC(env, s, l) \
+ (env->xdf##s.recs[l - 1])
+
+static int cmp_recs(xpparam_t const *xpp,
+ xrecord_t *r1, xrecord_t *r2)
+{
+ return r1->ha == r2->ha &&
+ xdl_recmatch(r1->ptr, r1->size, r2->ptr, r2->size,
+ xpp->flags);
+}
+
+#define CMP_ENV(xpp, env, s1, l1, s2, l2) \
+ (cmp_recs(xpp, REC(env, s1, l1), REC(env, s2, l2)))
+
+#define CMP(i, s1, l1, s2, l2) \
+ (cmp_recs(i->xpp, REC(i->env, s1, l1), REC(i->env, s2, l2)))
+
+#define TABLE_HASH(index, side, line) \
+ XDL_HASHLONG((REC(index->env, side, line))->ha, index->table_bits)
+
+static int scanA(struct histindex *index, unsigned int line1, unsigned int count1)
+{
+ unsigned int ptr;
+ unsigned int tbl_idx;
+ unsigned int chain_len;
+ struct record **rec_chain, *rec;
+
+ for (ptr = LINE_END(1); line1 <= ptr; ptr--) {
+ tbl_idx = TABLE_HASH(index, 1, ptr);
+ rec_chain = index->records + tbl_idx;
+ rec = *rec_chain;
+
+ chain_len = 0;
+ while (rec) {
+ if (CMP(index, 1, rec->ptr, 1, ptr)) {
+ /*
+ * ptr is identical to another element. Insert
+ * it onto the front of the existing element
+ * chain.
+ */
+ NEXT_PTR(index, ptr) = rec->ptr;
+ rec->ptr = ptr;
+ /* cap rec->cnt at MAX_CNT */
+ rec->cnt = XDL_MIN(MAX_CNT, rec->cnt + 1);
+ LINE_MAP(index, ptr) = rec;
+ goto continue_scan;
+ }
+
+ rec = rec->next;
+ chain_len++;
+ }
+
+ if (chain_len == index->max_chain_length)
+ return -1;
+
+ /*
+ * This is the first time we have ever seen this particular
+ * element in the sequence. Construct a new chain for it.
+ */
+ if (!(rec = xdl_cha_alloc(&index->rcha)))
+ return -1;
+ rec->ptr = ptr;
+ rec->cnt = 1;
+ rec->next = *rec_chain;
+ *rec_chain = rec;
+ LINE_MAP(index, ptr) = rec;
+
+continue_scan:
+ ; /* no op */
+ }
+
+ return 0;
+}
+
+static int try_lcs(
+ struct histindex *index, struct region *lcs, unsigned int b_ptr,
+ unsigned int line1, unsigned int count1,
+ unsigned int line2, unsigned int count2)
+{
+ unsigned int b_next = b_ptr + 1;
+ struct record *rec = index->records[TABLE_HASH(index, 2, b_ptr)];
+ unsigned int as, ae, bs, be, np, rc;
+ int should_break;
+
+ for (; rec; rec = rec->next) {
+ if (rec->cnt > index->cnt) {
+ if (!index->has_common)
+ index->has_common = CMP(index, 1, rec->ptr, 2, b_ptr);
+ continue;
+ }
+
+ as = rec->ptr;
+ if (!CMP(index, 1, as, 2, b_ptr))
+ continue;
+
+ index->has_common = 1;
+ for (;;) {
+ should_break = 0;
+ np = NEXT_PTR(index, as);
+ bs = b_ptr;
+ ae = as;
+ be = bs;
+ rc = rec->cnt;
+
+ while (line1 < as && line2 < bs
+ && CMP(index, 1, as - 1, 2, bs - 1)) {
+ as--;
+ bs--;
+ if (1 < rc)
+ rc = XDL_MIN(rc, CNT(index, as));
+ }
+ while (ae < LINE_END(1) && be < LINE_END(2)
+ && CMP(index, 1, ae + 1, 2, be + 1)) {
+ ae++;
+ be++;
+ if (1 < rc)
+ rc = XDL_MIN(rc, CNT(index, ae));
+ }
+
+ if (b_next <= be)
+ b_next = be + 1;
+ if (lcs->end1 - lcs->begin1 < ae - as || rc < index->cnt) {
+ lcs->begin1 = as;
+ lcs->begin2 = bs;
+ lcs->end1 = ae;
+ lcs->end2 = be;
+ index->cnt = rc;
+ }
+
+ if (np == 0)
+ break;
+
+ while (np <= ae) {
+ np = NEXT_PTR(index, np);
+ if (np == 0) {
+ should_break = 1;
+ break;
+ }
+ }
+
+ if (should_break)
+ break;
+
+ as = np;
+ }
+ }
+ return b_next;
+}
+
+static int find_lcs(
+ struct histindex *index, struct region *lcs,
+ unsigned int line1, unsigned int count1,
+ unsigned int line2, unsigned int count2)
+{
+ unsigned int b_ptr;
+
+ if (scanA(index, line1, count1))
+ return -1;
+
+ index->cnt = index->max_chain_length + 1;
+
+ for (b_ptr = line2; b_ptr <= LINE_END(2); )
+ b_ptr = try_lcs(index, lcs, b_ptr, line1, count1, line2, count2);
+
+ return index->has_common && index->max_chain_length < index->cnt;
+}
+
+static int fall_back_to_classic_diff(struct histindex *index,
+ int line1, int count1, int line2, int count2)
+{
+ xpparam_t xpp;
+ xpp.flags = index->xpp->flags & ~XDF_HISTOGRAM_DIFF;
+
+ return xdl_fall_back_diff(index->env, &xpp,
+ line1, count1, line2, count2);
+}
+
+static int histogram_diff(
+ xpparam_t const *xpp, xdfenv_t *env,
+ unsigned int line1, unsigned int count1,
+ unsigned int line2, unsigned int count2)
+{
+ struct histindex index;
+ struct region lcs;
+ unsigned int sz;
+ int result = -1;
+
+ if (count1 <= 0 && count2 <= 0)
+ return 0;
+
+ if (LINE_END(1) >= MAX_PTR)
+ return -1;
+
+ if (!count1) {
+ while(count2--)
+ env->xdf2.rchg[line2++ - 1] = 1;
+ return 0;
+ } else if (!count2) {
+ while(count1--)
+ env->xdf1.rchg[line1++ - 1] = 1;
+ return 0;
+ }
+
+ memset(&index, 0, sizeof(index));
+
+ index.env = env;
+ index.xpp = xpp;
+
+ index.records = NULL;
+ index.line_map = NULL;
+ /* in case of early xdl_cha_free() */
+ index.rcha.head = NULL;
+
+ index.table_bits = xdl_hashbits(count1);
+ sz = index.records_size = 1 << index.table_bits;
+ sz *= sizeof(struct record *);
+ if (!(index.records = (struct record **) xdl_malloc(sz)))
+ goto cleanup;
+ memset(index.records, 0, sz);
+
+ sz = index.line_map_size = count1;
+ sz *= sizeof(struct record *);
+ if (!(index.line_map = (struct record **) xdl_malloc(sz)))
+ goto cleanup;
+ memset(index.line_map, 0, sz);
+
+ sz = index.line_map_size;
+ sz *= sizeof(unsigned int);
+ if (!(index.next_ptrs = (unsigned int *) xdl_malloc(sz)))
+ goto cleanup;
+ memset(index.next_ptrs, 0, sz);
+
+ /* lines / 4 + 1 comes from xprepare.c:xdl_prepare_ctx() */
+ if (xdl_cha_init(&index.rcha, sizeof(struct record), count1 / 4 + 1) < 0)
+ goto cleanup;
+
+ index.ptr_shift = line1;
+ index.max_chain_length = 64;
+
+ memset(&lcs, 0, sizeof(lcs));
+ if (find_lcs(&index, &lcs, line1, count1, line2, count2))
+ result = fall_back_to_classic_diff(&index, line1, count1, line2, count2);
+ else {
+ if (lcs.begin1 == 0 && lcs.begin2 == 0) {
+ while (count1--)
+ env->xdf1.rchg[line1++ - 1] = 1;
+ while (count2--)
+ env->xdf2.rchg[line2++ - 1] = 1;
+ result = 0;
+ } else {
+ result = histogram_diff(xpp, env,
+ line1, lcs.begin1 - line1,
+ line2, lcs.begin2 - line2);
+ if (result)
+ goto cleanup;
+ result = histogram_diff(xpp, env,
+ lcs.end1 + 1, LINE_END(1) - lcs.end1,
+ lcs.end2 + 1, LINE_END(2) - lcs.end2);
+ if (result)
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ xdl_free(index.records);
+ xdl_free(index.line_map);
+ xdl_free(index.next_ptrs);
+ xdl_cha_free(&index.rcha);
+
+ return result;
+}
+
+int xdl_do_histogram_diff(mmfile_t *file1, mmfile_t *file2,
+ xpparam_t const *xpp, xdfenv_t *env)
+{
+ if (xdl_prepare_env(file1, file2, xpp, env) < 0)
+ return -1;
+
+ return histogram_diff(xpp, env,
+ env->xdf1.dstart + 1, env->xdf1.dend - env->xdf1.dstart + 1,
+ env->xdf2.dstart + 1, env->xdf2.dend - env->xdf2.dstart + 1);
+}
diff --git a/src/xdiff/xinclude.h b/src/xdiff/xinclude.h
new file mode 100644
index 000000000..4a1cde909
--- /dev/null
+++ b/src/xdiff/xinclude.h
@@ -0,0 +1,46 @@
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#if !defined(XINCLUDE_H)
+#define XINCLUDE_H
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#ifdef _WIN32
+#else
+#include <unistd.h>
+#endif
+
+#include "xmacros.h"
+#include "xdiff.h"
+#include "xtypes.h"
+#include "xutils.h"
+#include "xprepare.h"
+#include "xdiffi.h"
+#include "xemit.h"
+
+
+#endif /* #if !defined(XINCLUDE_H) */
diff --git a/src/xdiff/xmacros.h b/src/xdiff/xmacros.h
new file mode 100644
index 000000000..165a895a9
--- /dev/null
+++ b/src/xdiff/xmacros.h
@@ -0,0 +1,54 @@
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#if !defined(XMACROS_H)
+#define XMACROS_H
+
+
+
+
+#define XDL_MIN(a, b) ((a) < (b) ? (a): (b))
+#define XDL_MAX(a, b) ((a) > (b) ? (a): (b))
+#define XDL_ABS(v) ((v) >= 0 ? (v): -(v))
+#define XDL_ISDIGIT(c) ((c) >= '0' && (c) <= '9')
+#define XDL_ISSPACE(c) (isspace((unsigned char)(c)))
+#define XDL_ADDBITS(v,b) ((v) + ((v) >> (b)))
+#define XDL_MASKBITS(b) ((1UL << (b)) - 1)
+#define XDL_HASHLONG(v,b) (XDL_ADDBITS((unsigned long)(v), b) & XDL_MASKBITS(b))
+#define XDL_PTRFREE(p) do { if (p) { xdl_free(p); (p) = NULL; } } while (0)
+#define XDL_LE32_PUT(p, v) \
+do { \
+ unsigned char *__p = (unsigned char *) (p); \
+ *__p++ = (unsigned char) (v); \
+ *__p++ = (unsigned char) ((v) >> 8); \
+ *__p++ = (unsigned char) ((v) >> 16); \
+ *__p = (unsigned char) ((v) >> 24); \
+} while (0)
+#define XDL_LE32_GET(p, v) \
+do { \
+ unsigned char const *__p = (unsigned char const *) (p); \
+ (v) = (unsigned long) __p[0] | ((unsigned long) __p[1]) << 8 | \
+ ((unsigned long) __p[2]) << 16 | ((unsigned long) __p[3]) << 24; \
+} while (0)
+
+
+#endif /* #if !defined(XMACROS_H) */
diff --git a/src/xdiff/xmerge.c b/src/xdiff/xmerge.c
new file mode 100644
index 000000000..84e424672
--- /dev/null
+++ b/src/xdiff/xmerge.c
@@ -0,0 +1,619 @@
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003-2006 Davide Libenzi, Johannes E. Schindelin
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#include "xinclude.h"
+
+typedef struct s_xdmerge {
+ struct s_xdmerge *next;
+ /*
+ * 0 = conflict,
+ * 1 = no conflict, take first,
+ * 2 = no conflict, take second.
+ * 3 = no conflict, take both.
+ */
+ int mode;
+ /*
+ * These point at the respective postimages. E.g. <i1,chg1> is
+ * how side #1 wants to change the common ancestor; if there is no
+ * overlap, lines before i1 in the postimage of side #1 appear
+ * in the merge result as a region touched by neither side.
+ */
+ long i1, i2;
+ long chg1, chg2;
+ /*
+ * These point at the preimage; of course there is just one
+ * preimage, that is from the shared common ancestor.
+ */
+ long i0;
+ long chg0;
+} xdmerge_t;
+
+static int xdl_append_merge(xdmerge_t **merge, int mode,
+ long i0, long chg0,
+ long i1, long chg1,
+ long i2, long chg2)
+{
+ xdmerge_t *m = *merge;
+ if (m && (i1 <= m->i1 + m->chg1 || i2 <= m->i2 + m->chg2)) {
+ if (mode != m->mode)
+ m->mode = 0;
+ m->chg0 = i0 + chg0 - m->i0;
+ m->chg1 = i1 + chg1 - m->i1;
+ m->chg2 = i2 + chg2 - m->i2;
+ } else {
+ m = xdl_malloc(sizeof(xdmerge_t));
+ if (!m)
+ return -1;
+ m->next = NULL;
+ m->mode = mode;
+ m->i0 = i0;
+ m->chg0 = chg0;
+ m->i1 = i1;
+ m->chg1 = chg1;
+ m->i2 = i2;
+ m->chg2 = chg2;
+ if (*merge)
+ (*merge)->next = m;
+ *merge = m;
+ }
+ return 0;
+}
+
+static int xdl_cleanup_merge(xdmerge_t *c)
+{
+ int count = 0;
+ xdmerge_t *next_c;
+
+ /* were there conflicts? */
+ for (; c; c = next_c) {
+ if (c->mode == 0)
+ count++;
+ next_c = c->next;
+ free(c);
+ }
+ return count;
+}
+
+static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2,
+ int line_count, long flags)
+{
+ int i;
+ xrecord_t **rec1 = xe1->xdf2.recs + i1;
+ xrecord_t **rec2 = xe2->xdf2.recs + i2;
+
+ for (i = 0; i < line_count; i++) {
+ int result = xdl_recmatch(rec1[i]->ptr, rec1[i]->size,
+ rec2[i]->ptr, rec2[i]->size, flags);
+ if (!result)
+ return -1;
+ }
+ return 0;
+}
+
+static int xdl_recs_copy_0(int use_orig, xdfenv_t *xe, int i, int count, int add_nl, char *dest)
+{
+ xrecord_t **recs;
+ int size = 0;
+
+ recs = (use_orig ? xe->xdf1.recs : xe->xdf2.recs) + i;
+
+ if (count < 1)
+ return 0;
+
+ for (i = 0; i < count; size += recs[i++]->size)
+ if (dest)
+ memcpy(dest + size, recs[i]->ptr, recs[i]->size);
+ if (add_nl) {
+ i = recs[count - 1]->size;
+ if (i == 0 || recs[count - 1]->ptr[i - 1] != '\n') {
+ if (dest)
+ dest[size] = '\n';
+ size++;
+ }
+ }
+ return size;
+}
+
+static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest)
+{
+ return xdl_recs_copy_0(0, xe, i, count, add_nl, dest);
+}
+
+static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest)
+{
+ return xdl_recs_copy_0(1, xe, i, count, add_nl, dest);
+}
+
+static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1,
+ xdfenv_t *xe2, const char *name2,
+ const char *name3,
+ int size, int i, int style,
+ xdmerge_t *m, char *dest, int marker_size)
+{
+ int marker1_size = (name1 ? (int)strlen(name1) + 1 : 0);
+ int marker2_size = (name2 ? (int)strlen(name2) + 1 : 0);
+ int marker3_size = (name3 ? (int)strlen(name3) + 1 : 0);
+
+ if (marker_size <= 0)
+ marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
+
+ /* Before conflicting part */
+ size += xdl_recs_copy(xe1, i, m->i1 - i, 0,
+ dest ? dest + size : NULL);
+
+ if (!dest) {
+ size += marker_size + 1 + marker1_size;
+ } else {
+ memset(dest + size, '<', marker_size);
+ size += marker_size;
+ if (marker1_size) {
+ dest[size] = ' ';
+ memcpy(dest + size + 1, name1, marker1_size - 1);
+ size += marker1_size;
+ }
+ dest[size++] = '\n';
+ }
+
+ /* Postimage from side #1 */
+ size += xdl_recs_copy(xe1, m->i1, m->chg1, 1,
+ dest ? dest + size : NULL);
+
+ if (style == XDL_MERGE_DIFF3) {
+ /* Shared preimage */
+ if (!dest) {
+ size += marker_size + 1 + marker3_size;
+ } else {
+ memset(dest + size, '|', marker_size);
+ size += marker_size;
+ if (marker3_size) {
+ dest[size] = ' ';
+ memcpy(dest + size + 1, name3, marker3_size - 1);
+ size += marker3_size;
+ }
+ dest[size++] = '\n';
+ }
+ size += xdl_orig_copy(xe1, m->i0, m->chg0, 1,
+ dest ? dest + size : NULL);
+ }
+
+ if (!dest) {
+ size += marker_size + 1;
+ } else {
+ memset(dest + size, '=', marker_size);
+ size += marker_size;
+ dest[size++] = '\n';
+ }
+
+ /* Postimage from side #2 */
+ size += xdl_recs_copy(xe2, m->i2, m->chg2, 1,
+ dest ? dest + size : NULL);
+ if (!dest) {
+ size += marker_size + 1 + marker2_size;
+ } else {
+ memset(dest + size, '>', marker_size);
+ size += marker_size;
+ if (marker2_size) {
+ dest[size] = ' ';
+ memcpy(dest + size + 1, name2, marker2_size - 1);
+ size += marker2_size;
+ }
+ dest[size++] = '\n';
+ }
+ return size;
+}
+
+static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1,
+ xdfenv_t *xe2, const char *name2,
+ const char *ancestor_name,
+ int favor,
+ xdmerge_t *m, char *dest, int style,
+ int marker_size)
+{
+ int size, i;
+
+ for (size = i = 0; m; m = m->next) {
+ if (favor && !m->mode)
+ m->mode = favor;
+
+ if (m->mode == 0)
+ size = fill_conflict_hunk(xe1, name1, xe2, name2,
+ ancestor_name,
+ size, i, style, m, dest,
+ marker_size);
+ else if (m->mode & 3) {
+ /* Before conflicting part */
+ size += xdl_recs_copy(xe1, i, m->i1 - i, 0,
+ dest ? dest + size : NULL);
+ /* Postimage from side #1 */
+ if (m->mode & 1)
+ size += xdl_recs_copy(xe1, m->i1, m->chg1, 1,
+ dest ? dest + size : NULL);
+ /* Postimage from side #2 */
+ if (m->mode & 2)
+ size += xdl_recs_copy(xe2, m->i2, m->chg2, 1,
+ dest ? dest + size : NULL);
+ } else
+ continue;
+ i = m->i1 + m->chg1;
+ }
+ size += xdl_recs_copy(xe1, i, xe1->xdf2.nrec - i, 0,
+ dest ? dest + size : NULL);
+ return size;
+}
+
+/*
+ * Sometimes, changes are not quite identical, but differ in only a few
+ * lines. Try hard to show only these few lines as conflicting.
+ */
+static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m,
+ xpparam_t const *xpp)
+{
+ for (; m; m = m->next) {
+ mmfile_t t1, t2;
+ xdfenv_t xe;
+ xdchange_t *xscr, *x;
+ int i1 = m->i1, i2 = m->i2;
+
+ /* let's handle just the conflicts */
+ if (m->mode)
+ continue;
+
+ /* no sense refining a conflict when one side is empty */
+ if (m->chg1 == 0 || m->chg2 == 0)
+ continue;
+
+ /*
+ * This probably does not work outside git, since
+ * we have a very simple mmfile structure.
+ */
+ t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr;
+ t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr
+ + xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - t1.ptr;
+ t2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr;
+ t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr
+ + xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr;
+ if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0)
+ return -1;
+ if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 ||
+ xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 ||
+ xdl_build_script(&xe, &xscr) < 0) {
+ xdl_free_env(&xe);
+ return -1;
+ }
+ if (!xscr) {
+ /* If this happens, the changes are identical. */
+ xdl_free_env(&xe);
+ m->mode = 4;
+ continue;
+ }
+ x = xscr;
+ m->i1 = xscr->i1 + i1;
+ m->chg1 = xscr->chg1;
+ m->i2 = xscr->i2 + i2;
+ m->chg2 = xscr->chg2;
+ while (xscr->next) {
+ xdmerge_t *m2 = xdl_malloc(sizeof(xdmerge_t));
+ if (!m2) {
+ xdl_free_env(&xe);
+ xdl_free_script(x);
+ return -1;
+ }
+ xscr = xscr->next;
+ m2->next = m->next;
+ m->next = m2;
+ m = m2;
+ m->mode = 0;
+ m->i1 = xscr->i1 + i1;
+ m->chg1 = xscr->chg1;
+ m->i2 = xscr->i2 + i2;
+ m->chg2 = xscr->chg2;
+ }
+ xdl_free_env(&xe);
+ xdl_free_script(x);
+ }
+ return 0;
+}
+
+static int line_contains_alnum(const char *ptr, long size)
+{
+ while (size--)
+ if (isalnum((unsigned char)*(ptr++)))
+ return 1;
+ return 0;
+}
+
+static int lines_contain_alnum(xdfenv_t *xe, int i, int chg)
+{
+ for (; chg; chg--, i++)
+ if (line_contains_alnum(xe->xdf2.recs[i]->ptr,
+ xe->xdf2.recs[i]->size))
+ return 1;
+ return 0;
+}
+
+/*
+ * This function merges m and m->next, marking everything between those hunks
+ * as conflicting, too.
+ */
+static void xdl_merge_two_conflicts(xdmerge_t *m)
+{
+ xdmerge_t *next_m = m->next;
+ m->chg1 = next_m->i1 + next_m->chg1 - m->i1;
+ m->chg2 = next_m->i2 + next_m->chg2 - m->i2;
+ m->next = next_m->next;
+ free(next_m);
+}
+
+/*
+ * If there are less than 3 non-conflicting lines between conflicts,
+ * it appears simpler -- because it takes up less (or as many) lines --
+ * if the lines are moved into the conflicts.
+ */
+static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m,
+ int simplify_if_no_alnum)
+{
+ int result = 0;
+
+ if (!m)
+ return result;
+ for (;;) {
+ xdmerge_t *next_m = m->next;
+ int begin, end;
+
+ if (!next_m)
+ return result;
+
+ begin = m->i1 + m->chg1;
+ end = next_m->i1;
+
+ if (m->mode != 0 || next_m->mode != 0 ||
+ (end - begin > 3 &&
+ (!simplify_if_no_alnum ||
+ lines_contain_alnum(xe1, begin, end - begin)))) {
+ m = next_m;
+ } else {
+ result++;
+ xdl_merge_two_conflicts(m);
+ }
+ }
+}
+
+/*
+ * level == 0: mark all overlapping changes as conflict
+ * level == 1: mark overlapping changes as conflict only if not identical
+ * level == 2: analyze non-identical changes for minimal conflict set
+ * level == 3: analyze non-identical changes for minimal conflict set, but
+ * treat hunks not containing any letter or number as conflicting
+ *
+ * returns < 0 on error, == 0 for no conflicts, else number of conflicts
+ */
+static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1,
+ xdfenv_t *xe2, xdchange_t *xscr2,
+ xmparam_t const *xmp, mmbuffer_t *result)
+{
+ xdmerge_t *changes, *c;
+ xpparam_t const *xpp = &xmp->xpp;
+ const char *const ancestor_name = xmp->ancestor;
+ const char *const name1 = xmp->file1;
+ const char *const name2 = xmp->file2;
+ int i0, i1, i2, chg0, chg1, chg2;
+ int level = xmp->level;
+ int style = xmp->style;
+ int favor = xmp->favor;
+
+ if (style == XDL_MERGE_DIFF3) {
+ /*
+ * "diff3 -m" output does not make sense for anything
+ * more aggressive than XDL_MERGE_EAGER.
+ */
+ if (XDL_MERGE_EAGER < level)
+ level = XDL_MERGE_EAGER;
+ }
+
+ c = changes = NULL;
+
+ while (xscr1 && xscr2) {
+ if (!changes)
+ changes = c;
+ if (xscr1->i1 + xscr1->chg1 < xscr2->i1) {
+ i0 = xscr1->i1;
+ i1 = xscr1->i2;
+ i2 = xscr2->i2 - xscr2->i1 + xscr1->i1;
+ chg0 = xscr1->chg1;
+ chg1 = xscr1->chg2;
+ chg2 = xscr1->chg1;
+ if (xdl_append_merge(&c, 1,
+ i0, chg0, i1, chg1, i2, chg2)) {
+ xdl_cleanup_merge(changes);
+ return -1;
+ }
+ xscr1 = xscr1->next;
+ continue;
+ }
+ if (xscr2->i1 + xscr2->chg1 < xscr1->i1) {
+ i0 = xscr2->i1;
+ i1 = xscr1->i2 - xscr1->i1 + xscr2->i1;
+ i2 = xscr2->i2;
+ chg0 = xscr2->chg1;
+ chg1 = xscr2->chg1;
+ chg2 = xscr2->chg2;
+ if (xdl_append_merge(&c, 2,
+ i0, chg0, i1, chg1, i2, chg2)) {
+ xdl_cleanup_merge(changes);
+ return -1;
+ }
+ xscr2 = xscr2->next;
+ continue;
+ }
+ if (level == XDL_MERGE_MINIMAL || xscr1->i1 != xscr2->i1 ||
+ xscr1->chg1 != xscr2->chg1 ||
+ xscr1->chg2 != xscr2->chg2 ||
+ xdl_merge_cmp_lines(xe1, xscr1->i2,
+ xe2, xscr2->i2,
+ xscr1->chg2, xpp->flags)) {
+ /* conflict */
+ int off = xscr1->i1 - xscr2->i1;
+ int ffo = off + xscr1->chg1 - xscr2->chg1;
+
+ i0 = xscr1->i1;
+ i1 = xscr1->i2;
+ i2 = xscr2->i2;
+ if (off > 0) {
+ i0 -= off;
+ i1 -= off;
+ }
+ else
+ i2 += off;
+ chg0 = xscr1->i1 + xscr1->chg1 - i0;
+ chg1 = xscr1->i2 + xscr1->chg2 - i1;
+ chg2 = xscr2->i2 + xscr2->chg2 - i2;
+ if (ffo < 0) {
+ chg0 -= ffo;
+ chg1 -= ffo;
+ } else
+ chg2 += ffo;
+ if (xdl_append_merge(&c, 0,
+ i0, chg0, i1, chg1, i2, chg2)) {
+ xdl_cleanup_merge(changes);
+ return -1;
+ }
+ }
+
+ i1 = xscr1->i1 + xscr1->chg1;
+ i2 = xscr2->i1 + xscr2->chg1;
+
+ if (i1 >= i2)
+ xscr2 = xscr2->next;
+ if (i2 >= i1)
+ xscr1 = xscr1->next;
+ }
+ while (xscr1) {
+ if (!changes)
+ changes = c;
+ i0 = xscr1->i1;
+ i1 = xscr1->i2;
+ i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec;
+ chg0 = xscr1->chg1;
+ chg1 = xscr1->chg2;
+ chg2 = xscr1->chg1;
+ if (xdl_append_merge(&c, 1,
+ i0, chg0, i1, chg1, i2, chg2)) {
+ xdl_cleanup_merge(changes);
+ return -1;
+ }
+ xscr1 = xscr1->next;
+ }
+ while (xscr2) {
+ if (!changes)
+ changes = c;
+ i0 = xscr2->i1;
+ i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec;
+ i2 = xscr2->i2;
+ chg0 = xscr2->chg1;
+ chg1 = xscr2->chg1;
+ chg2 = xscr2->chg2;
+ if (xdl_append_merge(&c, 2,
+ i0, chg0, i1, chg1, i2, chg2)) {
+ xdl_cleanup_merge(changes);
+ return -1;
+ }
+ xscr2 = xscr2->next;
+ }
+ if (!changes)
+ changes = c;
+ /* refine conflicts */
+ if (XDL_MERGE_ZEALOUS <= level &&
+ (xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0 ||
+ xdl_simplify_non_conflicts(xe1, changes,
+ XDL_MERGE_ZEALOUS < level) < 0)) {
+ xdl_cleanup_merge(changes);
+ return -1;
+ }
+ /* output */
+ if (result) {
+ int marker_size = xmp->marker_size;
+ int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2,
+ ancestor_name,
+ favor, changes, NULL, style,
+ marker_size);
+ result->ptr = xdl_malloc(size);
+ if (!result->ptr) {
+ xdl_cleanup_merge(changes);
+ return -1;
+ }
+ result->size = size;
+ xdl_fill_merge_buffer(xe1, name1, xe2, name2,
+ ancestor_name, favor, changes,
+ result->ptr, style, marker_size);
+ }
+ return xdl_cleanup_merge(changes);
+}
+
+int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2,
+ xmparam_t const *xmp, mmbuffer_t *result)
+{
+ xdchange_t *xscr1, *xscr2;
+ xdfenv_t xe1, xe2;
+ int status;
+ xpparam_t const *xpp = &xmp->xpp;
+
+ result->ptr = NULL;
+ result->size = 0;
+
+ if (xdl_do_diff(orig, mf1, xpp, &xe1) < 0 ||
+ xdl_do_diff(orig, mf2, xpp, &xe2) < 0) {
+ return -1;
+ }
+ if (xdl_change_compact(&xe1.xdf1, &xe1.xdf2, xpp->flags) < 0 ||
+ xdl_change_compact(&xe1.xdf2, &xe1.xdf1, xpp->flags) < 0 ||
+ xdl_build_script(&xe1, &xscr1) < 0) {
+ xdl_free_env(&xe1);
+ return -1;
+ }
+ if (xdl_change_compact(&xe2.xdf1, &xe2.xdf2, xpp->flags) < 0 ||
+ xdl_change_compact(&xe2.xdf2, &xe2.xdf1, xpp->flags) < 0 ||
+ xdl_build_script(&xe2, &xscr2) < 0) {
+ xdl_free_env(&xe2);
+ return -1;
+ }
+ status = 0;
+ if (!xscr1) {
+ result->ptr = xdl_malloc(mf2->size);
+ memcpy(result->ptr, mf2->ptr, mf2->size);
+ result->size = mf2->size;
+ } else if (!xscr2) {
+ result->ptr = xdl_malloc(mf1->size);
+ memcpy(result->ptr, mf1->ptr, mf1->size);
+ result->size = mf1->size;
+ } else {
+ status = xdl_do_merge(&xe1, xscr1,
+ &xe2, xscr2,
+ xmp, result);
+ }
+ xdl_free_script(xscr1);
+ xdl_free_script(xscr2);
+
+ xdl_free_env(&xe1);
+ xdl_free_env(&xe2);
+
+ return status;
+}
diff --git a/src/xdiff/xpatience.c b/src/xdiff/xpatience.c
new file mode 100644
index 000000000..fdd7d0263
--- /dev/null
+++ b/src/xdiff/xpatience.c
@@ -0,0 +1,358 @@
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003-2009 Davide Libenzi, Johannes E. Schindelin
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+#include "xinclude.h"
+#include "xtypes.h"
+#include "xdiff.h"
+
+/*
+ * The basic idea of patience diff is to find lines that are unique in
+ * both files. These are intuitively the ones that we want to see as
+ * common lines.
+ *
+ * The maximal ordered sequence of such line pairs (where ordered means
+ * that the order in the sequence agrees with the order of the lines in
+ * both files) naturally defines an initial set of common lines.
+ *
+ * Now, the algorithm tries to extend the set of common lines by growing
+ * the line ranges where the files have identical lines.
+ *
+ * Between those common lines, the patience diff algorithm is applied
+ * recursively, until no unique line pairs can be found; these line ranges
+ * are handled by the well-known Myers algorithm.
+ */
+
+#define NON_UNIQUE ULONG_MAX
+
+/*
+ * This is a hash mapping from line hash to line numbers in the first and
+ * second file.
+ */
+struct hashmap {
+ int nr, alloc;
+ struct entry {
+ unsigned long hash;
+ /*
+ * 0 = unused entry, 1 = first line, 2 = second, etc.
+ * line2 is NON_UNIQUE if the line is not unique
+ * in either the first or the second file.
+ */
+ unsigned long line1, line2;
+ /*
+ * "next" & "previous" are used for the longest common
+ * sequence;
+ * initially, "next" reflects only the order in file1.
+ */
+ struct entry *next, *previous;
+ } *entries, *first, *last;
+ /* were common records found? */
+ unsigned long has_matches;
+ mmfile_t *file1, *file2;
+ xdfenv_t *env;
+ xpparam_t const *xpp;
+};
+
+/* The argument "pass" is 1 for the first file, 2 for the second. */
+static void insert_record(int line, struct hashmap *map, int pass)
+{
+ xrecord_t **records = pass == 1 ?
+ map->env->xdf1.recs : map->env->xdf2.recs;
+ xrecord_t *record = records[line - 1], *other;
+ /*
+ * After xdl_prepare_env() (or more precisely, due to
+ * xdl_classify_record()), the "ha" member of the records (AKA lines)
+ * is _not_ the hash anymore, but a linearized version of it. In
+ * other words, the "ha" member is guaranteed to start with 0 and
+ * the second record's ha can only be 0 or 1, etc.
+ *
+ * So we multiply ha by 2 in the hope that the hashing was
+ * "unique enough".
+ */
+ int index = (int)((record->ha << 1) % map->alloc);
+
+ while (map->entries[index].line1) {
+ other = map->env->xdf1.recs[map->entries[index].line1 - 1];
+ if (map->entries[index].hash != record->ha ||
+ !xdl_recmatch(record->ptr, record->size,
+ other->ptr, other->size,
+ map->xpp->flags)) {
+ if (++index >= map->alloc)
+ index = 0;
+ continue;
+ }
+ if (pass == 2)
+ map->has_matches = 1;
+ if (pass == 1 || map->entries[index].line2)
+ map->entries[index].line2 = NON_UNIQUE;
+ else
+ map->entries[index].line2 = line;
+ return;
+ }
+ if (pass == 2)
+ return;
+ map->entries[index].line1 = line;
+ map->entries[index].hash = record->ha;
+ if (!map->first)
+ map->first = map->entries + index;
+ if (map->last) {
+ map->last->next = map->entries + index;
+ map->entries[index].previous = map->last;
+ }
+ map->last = map->entries + index;
+ map->nr++;
+}
+
+/*
+ * This function has to be called for each recursion into the inter-hunk
+ * parts, as previously non-unique lines can become unique when being
+ * restricted to a smaller part of the files.
+ *
+ * It is assumed that env has been prepared using xdl_prepare().
+ */
+static int fill_hashmap(mmfile_t *file1, mmfile_t *file2,
+ xpparam_t const *xpp, xdfenv_t *env,
+ struct hashmap *result,
+ int line1, int count1, int line2, int count2)
+{
+ result->file1 = file1;
+ result->file2 = file2;
+ result->xpp = xpp;
+ result->env = env;
+
+ /* We know exactly how large we want the hash map */
+ result->alloc = count1 * 2;
+ result->entries = (struct entry *)
+ xdl_malloc(result->alloc * sizeof(struct entry));
+ if (!result->entries)
+ return -1;
+ memset(result->entries, 0, result->alloc * sizeof(struct entry));
+
+ /* First, fill with entries from the first file */
+ while (count1--)
+ insert_record(line1++, result, 1);
+
+ /* Then search for matches in the second file */
+ while (count2--)
+ insert_record(line2++, result, 2);
+
+ return 0;
+}
+
+/*
+ * Find the longest sequence with a smaller last element (meaning a smaller
+ * line2, as we construct the sequence with entries ordered by line1).
+ */
+static int binary_search(struct entry **sequence, int longest,
+ struct entry *entry)
+{
+ int left = -1, right = longest;
+
+ while (left + 1 < right) {
+ int middle = (left + right) / 2;
+ /* by construction, no two entries can be equal */
+ if (sequence[middle]->line2 > entry->line2)
+ right = middle;
+ else
+ left = middle;
+ }
+ /* return the index in "sequence", _not_ the sequence length */
+ return left;
+}
+
+/*
+ * The idea is to start with the list of common unique lines sorted by
+ * the order in file1. For each of these pairs, the longest (partial)
+ * sequence whose last element's line2 is smaller is determined.
+ *
+ * For efficiency, the sequences are kept in a list containing exactly one
+ * item per sequence length: the sequence with the smallest last
+ * element (in terms of line2).
+ */
+static struct entry *find_longest_common_sequence(struct hashmap *map)
+{
+ struct entry **sequence = xdl_malloc(map->nr * sizeof(struct entry *));
+ int longest = 0, i;
+ struct entry *entry;
+
+ for (entry = map->first; entry; entry = entry->next) {
+ if (!entry->line2 || entry->line2 == NON_UNIQUE)
+ continue;
+ i = binary_search(sequence, longest, entry);
+ entry->previous = i < 0 ? NULL : sequence[i];
+ sequence[++i] = entry;
+ if (i == longest)
+ longest++;
+ }
+
+ /* No common unique lines were found */
+ if (!longest) {
+ xdl_free(sequence);
+ return NULL;
+ }
+
+ /* Iterate starting at the last element, adjusting the "next" members */
+ entry = sequence[longest - 1];
+ entry->next = NULL;
+ while (entry->previous) {
+ entry->previous->next = entry;
+ entry = entry->previous;
+ }
+ xdl_free(sequence);
+ return entry;
+}
+
+static int match(struct hashmap *map, int line1, int line2)
+{
+ xrecord_t *record1 = map->env->xdf1.recs[line1 - 1];
+ xrecord_t *record2 = map->env->xdf2.recs[line2 - 1];
+ return xdl_recmatch(record1->ptr, record1->size,
+ record2->ptr, record2->size, map->xpp->flags);
+}
+
+static int patience_diff(mmfile_t *file1, mmfile_t *file2,
+ xpparam_t const *xpp, xdfenv_t *env,
+ int line1, int count1, int line2, int count2);
+
+static int walk_common_sequence(struct hashmap *map, struct entry *first,
+ int line1, int count1, int line2, int count2)
+{
+ int end1 = line1 + count1, end2 = line2 + count2;
+ int next1, next2;
+
+ for (;;) {
+ /* Try to grow the line ranges of common lines */
+ if (first) {
+ next1 = first->line1;
+ next2 = first->line2;
+ while (next1 > line1 && next2 > line2 &&
+ match(map, next1 - 1, next2 - 1)) {
+ next1--;
+ next2--;
+ }
+ } else {
+ next1 = end1;
+ next2 = end2;
+ }
+ while (line1 < next1 && line2 < next2 &&
+ match(map, line1, line2)) {
+ line1++;
+ line2++;
+ }
+
+ /* Recurse */
+ if (next1 > line1 || next2 > line2) {
+ struct hashmap submap;
+
+ memset(&submap, 0, sizeof(submap));
+ if (patience_diff(map->file1, map->file2,
+ map->xpp, map->env,
+ line1, next1 - line1,
+ line2, next2 - line2))
+ return -1;
+ }
+
+ if (!first)
+ return 0;
+
+ while (first->next &&
+ first->next->line1 == first->line1 + 1 &&
+ first->next->line2 == first->line2 + 1)
+ first = first->next;
+
+ line1 = first->line1 + 1;
+ line2 = first->line2 + 1;
+
+ first = first->next;
+ }
+}
+
+static int fall_back_to_classic_diff(struct hashmap *map,
+ int line1, int count1, int line2, int count2)
+{
+ xpparam_t xpp;
+ xpp.flags = map->xpp->flags & ~XDF_PATIENCE_DIFF;
+
+ return xdl_fall_back_diff(map->env, &xpp,
+ line1, count1, line2, count2);
+}
+
+/*
+ * Recursively find the longest common sequence of unique lines,
+ * and if none was found, ask xdl_do_diff() to do the job.
+ *
+ * This function assumes that env was prepared with xdl_prepare_env().
+ */
+static int patience_diff(mmfile_t *file1, mmfile_t *file2,
+ xpparam_t const *xpp, xdfenv_t *env,
+ int line1, int count1, int line2, int count2)
+{
+ struct hashmap map;
+ struct entry *first;
+ int result = 0;
+
+ /* trivial case: one side is empty */
+ if (!count1) {
+ while(count2--)
+ env->xdf2.rchg[line2++ - 1] = 1;
+ return 0;
+ } else if (!count2) {
+ while(count1--)
+ env->xdf1.rchg[line1++ - 1] = 1;
+ return 0;
+ }
+
+ memset(&map, 0, sizeof(map));
+ if (fill_hashmap(file1, file2, xpp, env, &map,
+ line1, count1, line2, count2))
+ return -1;
+
+ /* are there any matching lines at all? */
+ if (!map.has_matches) {
+ while(count1--)
+ env->xdf1.rchg[line1++ - 1] = 1;
+ while(count2--)
+ env->xdf2.rchg[line2++ - 1] = 1;
+ xdl_free(map.entries);
+ return 0;
+ }
+
+ first = find_longest_common_sequence(&map);
+ if (first)
+ result = walk_common_sequence(&map, first,
+ line1, count1, line2, count2);
+ else
+ result = fall_back_to_classic_diff(&map,
+ line1, count1, line2, count2);
+
+ xdl_free(map.entries);
+ return result;
+}
+
+int xdl_do_patience_diff(mmfile_t *file1, mmfile_t *file2,
+ xpparam_t const *xpp, xdfenv_t *env)
+{
+ if (xdl_prepare_env(file1, file2, xpp, env) < 0)
+ return -1;
+
+ /* environment is cleaned up in xdl_diff() */
+ return patience_diff(file1, file2, xpp, env,
+ 1, env->xdf1.nrec, 1, env->xdf2.nrec);
+}
diff --git a/src/xdiff/xprepare.c b/src/xdiff/xprepare.c
new file mode 100644
index 000000000..e419f4f72
--- /dev/null
+++ b/src/xdiff/xprepare.c
@@ -0,0 +1,483 @@
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#include "xinclude.h"
+
+
+#define XDL_KPDIS_RUN 4
+#define XDL_MAX_EQLIMIT 1024
+#define XDL_SIMSCAN_WINDOW 100
+#define XDL_GUESS_NLINES1 256
+#define XDL_GUESS_NLINES2 20
+
+
+typedef struct s_xdlclass {
+ struct s_xdlclass *next;
+ unsigned long ha;
+ char const *line;
+ long size;
+ long idx;
+ long len1, len2;
+} xdlclass_t;
+
+typedef struct s_xdlclassifier {
+ unsigned int hbits;
+ long hsize;
+ xdlclass_t **rchash;
+ chastore_t ncha;
+ xdlclass_t **rcrecs;
+ long alloc;
+ long count;
+ long flags;
+} xdlclassifier_t;
+
+
+
+
+static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags);
+static void xdl_free_classifier(xdlclassifier_t *cf);
+static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,
+ unsigned int hbits, xrecord_t *rec);
+static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
+ xdlclassifier_t *cf, xdfile_t *xdf);
+static void xdl_free_ctx(xdfile_t *xdf);
+static int xdl_clean_mmatch(char const *dis, long i, long s, long e);
+static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);
+static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2);
+static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);
+
+
+
+
+static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) {
+ cf->flags = flags;
+
+ cf->hbits = xdl_hashbits((unsigned int) size);
+ cf->hsize = 1 << cf->hbits;
+
+ if (xdl_cha_init(&cf->ncha, sizeof(xdlclass_t), size / 4 + 1) < 0) {
+
+ return -1;
+ }
+ if (!(cf->rchash = (xdlclass_t **) xdl_malloc(cf->hsize * sizeof(xdlclass_t *)))) {
+
+ xdl_cha_free(&cf->ncha);
+ return -1;
+ }
+ memset(cf->rchash, 0, cf->hsize * sizeof(xdlclass_t *));
+
+ cf->alloc = size;
+ if (!(cf->rcrecs = (xdlclass_t **) xdl_malloc(cf->alloc * sizeof(xdlclass_t *)))) {
+
+ xdl_free(cf->rchash);
+ xdl_cha_free(&cf->ncha);
+ return -1;
+ }
+
+ cf->count = 0;
+
+ return 0;
+}
+
+
+static void xdl_free_classifier(xdlclassifier_t *cf) {
+
+ xdl_free(cf->rcrecs);
+ xdl_free(cf->rchash);
+ xdl_cha_free(&cf->ncha);
+}
+
+
+static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,
+ unsigned int hbits, xrecord_t *rec) {
+ long hi;
+ char const *line;
+ xdlclass_t *rcrec;
+ xdlclass_t **rcrecs;
+
+ line = rec->ptr;
+ hi = (long) XDL_HASHLONG(rec->ha, cf->hbits);
+ for (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next)
+ if (rcrec->ha == rec->ha &&
+ xdl_recmatch(rcrec->line, rcrec->size,
+ rec->ptr, rec->size, cf->flags))
+ break;
+
+ if (!rcrec) {
+ if (!(rcrec = xdl_cha_alloc(&cf->ncha))) {
+
+ return -1;
+ }
+ rcrec->idx = cf->count++;
+ if (cf->count > cf->alloc) {
+ cf->alloc *= 2;
+ if (!(rcrecs = (xdlclass_t **) xdl_realloc(cf->rcrecs, cf->alloc * sizeof(xdlclass_t *)))) {
+
+ return -1;
+ }
+ cf->rcrecs = rcrecs;
+ }
+ cf->rcrecs[rcrec->idx] = rcrec;
+ rcrec->line = line;
+ rcrec->size = rec->size;
+ rcrec->ha = rec->ha;
+ rcrec->len1 = rcrec->len2 = 0;
+ rcrec->next = cf->rchash[hi];
+ cf->rchash[hi] = rcrec;
+ }
+
+ (pass == 1) ? rcrec->len1++ : rcrec->len2++;
+
+ rec->ha = (unsigned long) rcrec->idx;
+
+ hi = (long) XDL_HASHLONG(rec->ha, hbits);
+ rec->next = rhash[hi];
+ rhash[hi] = rec;
+
+ return 0;
+}
+
+
+static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
+ xdlclassifier_t *cf, xdfile_t *xdf) {
+ unsigned int hbits;
+ long nrec, hsize, bsize;
+ unsigned long hav;
+ char const *blk, *cur, *top, *prev;
+ xrecord_t *crec;
+ xrecord_t **recs, **rrecs;
+ xrecord_t **rhash;
+ unsigned long *ha;
+ char *rchg;
+ long *rindex;
+
+ ha = NULL;
+ rindex = NULL;
+ rchg = NULL;
+ rhash = NULL;
+ recs = NULL;
+
+ if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)
+ goto abort;
+ if (!(recs = (xrecord_t **) xdl_malloc(narec * sizeof(xrecord_t *))))
+ goto abort;
+
+ if (xpp->flags & XDF_HISTOGRAM_DIFF)
+ hbits = hsize = 0;
+ else {
+ hbits = xdl_hashbits((unsigned int) narec);
+ hsize = 1 << hbits;
+ if (!(rhash = (xrecord_t **) xdl_malloc(hsize * sizeof(xrecord_t *))))
+ goto abort;
+ memset(rhash, 0, hsize * sizeof(xrecord_t *));
+ }
+
+ nrec = 0;
+ if ((cur = blk = xdl_mmfile_first(mf, &bsize)) != NULL) {
+ for (top = blk + bsize; cur < top; ) {
+ prev = cur;
+ hav = xdl_hash_record(&cur, top, xpp->flags);
+ if (nrec >= narec) {
+ narec *= 2;
+ if (!(rrecs = (xrecord_t **) xdl_realloc(recs, narec * sizeof(xrecord_t *))))
+ goto abort;
+ recs = rrecs;
+ }
+ if (!(crec = xdl_cha_alloc(&xdf->rcha)))
+ goto abort;
+ crec->ptr = prev;
+ crec->size = (long) (cur - prev);
+ crec->ha = hav;
+ recs[nrec++] = crec;
+
+ if (!(xpp->flags & XDF_HISTOGRAM_DIFF) &&
+ xdl_classify_record(pass, cf, rhash, hbits, crec) < 0)
+ goto abort;
+ }
+ }
+
+ if (!(rchg = (char *) xdl_malloc((nrec + 2) * sizeof(char))))
+ goto abort;
+ memset(rchg, 0, (nrec + 2) * sizeof(char));
+
+ if (!(rindex = (long *) xdl_malloc((nrec + 1) * sizeof(long))))
+ goto abort;
+ if (!(ha = (unsigned long *) xdl_malloc((nrec + 1) * sizeof(unsigned long))))
+ goto abort;
+
+ xdf->nrec = nrec;
+ xdf->recs = recs;
+ xdf->hbits = hbits;
+ xdf->rhash = rhash;
+ xdf->rchg = rchg + 1;
+ xdf->rindex = rindex;
+ xdf->nreff = 0;
+ xdf->ha = ha;
+ xdf->dstart = 0;
+ xdf->dend = nrec - 1;
+
+ return 0;
+
+abort:
+ xdl_free(ha);
+ xdl_free(rindex);
+ xdl_free(rchg);
+ xdl_free(rhash);
+ xdl_free(recs);
+ xdl_cha_free(&xdf->rcha);
+ return -1;
+}
+
+
+static void xdl_free_ctx(xdfile_t *xdf) {
+
+ xdl_free(xdf->rhash);
+ xdl_free(xdf->rindex);
+ xdl_free(xdf->rchg - 1);
+ xdl_free(xdf->ha);
+ xdl_free(xdf->recs);
+ xdl_cha_free(&xdf->rcha);
+}
+
+
+int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+ xdfenv_t *xe) {
+ long enl1, enl2, sample;
+ xdlclassifier_t cf;
+
+ memset(&cf, 0, sizeof(cf));
+
+ /*
+ * For histogram diff, we can afford a smaller sample size and
+ * thus a poorer estimate of the number of lines, as the hash
+ * table (rhash) won't be filled up/grown. The number of lines
+ * (nrecs) will be updated correctly anyway by
+ * xdl_prepare_ctx().
+ */
+ sample = xpp->flags & XDF_HISTOGRAM_DIFF ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1;
+
+ enl1 = xdl_guess_lines(mf1, sample) + 1;
+ enl2 = xdl_guess_lines(mf2, sample) + 1;
+
+ if (!(xpp->flags & XDF_HISTOGRAM_DIFF) &&
+ xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0) {
+
+ return -1;
+ }
+
+ if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {
+
+ xdl_free_classifier(&cf);
+ return -1;
+ }
+ if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {
+
+ xdl_free_ctx(&xe->xdf1);
+ xdl_free_classifier(&cf);
+ return -1;
+ }
+
+ if (!(xpp->flags & XDF_PATIENCE_DIFF) &&
+ !(xpp->flags & XDF_HISTOGRAM_DIFF) &&
+ xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) {
+
+ xdl_free_ctx(&xe->xdf2);
+ xdl_free_ctx(&xe->xdf1);
+ return -1;
+ }
+
+ if (!(xpp->flags & XDF_HISTOGRAM_DIFF))
+ xdl_free_classifier(&cf);
+
+ return 0;
+}
+
+
+void xdl_free_env(xdfenv_t *xe) {
+
+ xdl_free_ctx(&xe->xdf2);
+ xdl_free_ctx(&xe->xdf1);
+}
+
+
+static int xdl_clean_mmatch(char const *dis, long i, long s, long e) {
+ long r, rdis0, rpdis0, rdis1, rpdis1;
+
+ /*
+ * Limits the window the is examined during the similar-lines
+ * scan. The loops below stops when dis[i - r] == 1 (line that
+ * has no match), but there are corner cases where the loop
+ * proceed all the way to the extremities by causing huge
+ * performance penalties in case of big files.
+ */
+ if (i - s > XDL_SIMSCAN_WINDOW)
+ s = i - XDL_SIMSCAN_WINDOW;
+ if (e - i > XDL_SIMSCAN_WINDOW)
+ e = i + XDL_SIMSCAN_WINDOW;
+
+ /*
+ * Scans the lines before 'i' to find a run of lines that either
+ * have no match (dis[j] == 0) or have multiple matches (dis[j] > 1).
+ * Note that we always call this function with dis[i] > 1, so the
+ * current line (i) is already a multimatch line.
+ */
+ for (r = 1, rdis0 = 0, rpdis0 = 1; (i - r) >= s; r++) {
+ if (!dis[i - r])
+ rdis0++;
+ else if (dis[i - r] == 2)
+ rpdis0++;
+ else
+ break;
+ }
+ /*
+ * If the run before the line 'i' found only multimatch lines, we
+ * return 0 and hence we don't make the current line (i) discarded.
+ * We want to discard multimatch lines only when they appear in the
+ * middle of runs with nomatch lines (dis[j] == 0).
+ */
+ if (rdis0 == 0)
+ return 0;
+ for (r = 1, rdis1 = 0, rpdis1 = 1; (i + r) <= e; r++) {
+ if (!dis[i + r])
+ rdis1++;
+ else if (dis[i + r] == 2)
+ rpdis1++;
+ else
+ break;
+ }
+ /*
+ * If the run after the line 'i' found only multimatch lines, we
+ * return 0 and hence we don't make the current line (i) discarded.
+ */
+ if (rdis1 == 0)
+ return 0;
+ rdis1 += rdis0;
+ rpdis1 += rpdis0;
+
+ return rpdis1 * XDL_KPDIS_RUN < (rpdis1 + rdis1);
+}
+
+
+/*
+ * Try to reduce the problem complexity, discard records that have no
+ * matches on the other file. Also, lines that have multiple matches
+ * might be potentially discarded if they happear in a run of discardable.
+ */
+static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {
+ long i, nm, nreff, mlim;
+ xrecord_t **recs;
+ xdlclass_t *rcrec;
+ char *dis, *dis1, *dis2;
+
+ if (!(dis = (char *) xdl_malloc(xdf1->nrec + xdf2->nrec + 2))) {
+
+ return -1;
+ }
+ memset(dis, 0, xdf1->nrec + xdf2->nrec + 2);
+ dis1 = dis;
+ dis2 = dis1 + xdf1->nrec + 1;
+
+ if ((mlim = xdl_bogosqrt(xdf1->nrec)) > XDL_MAX_EQLIMIT)
+ mlim = XDL_MAX_EQLIMIT;
+ for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) {
+ rcrec = cf->rcrecs[(*recs)->ha];
+ nm = rcrec ? rcrec->len2 : 0;
+ dis1[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1;
+ }
+
+ if ((mlim = xdl_bogosqrt(xdf2->nrec)) > XDL_MAX_EQLIMIT)
+ mlim = XDL_MAX_EQLIMIT;
+ for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) {
+ rcrec = cf->rcrecs[(*recs)->ha];
+ nm = rcrec ? rcrec->len1 : 0;
+ dis2[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1;
+ }
+
+ for (nreff = 0, i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart];
+ i <= xdf1->dend; i++, recs++) {
+ if (dis1[i] == 1 ||
+ (dis1[i] == 2 && !xdl_clean_mmatch(dis1, i, xdf1->dstart, xdf1->dend))) {
+ xdf1->rindex[nreff] = i;
+ xdf1->ha[nreff] = (*recs)->ha;
+ nreff++;
+ } else
+ xdf1->rchg[i] = 1;
+ }
+ xdf1->nreff = nreff;
+
+ for (nreff = 0, i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart];
+ i <= xdf2->dend; i++, recs++) {
+ if (dis2[i] == 1 ||
+ (dis2[i] == 2 && !xdl_clean_mmatch(dis2, i, xdf2->dstart, xdf2->dend))) {
+ xdf2->rindex[nreff] = i;
+ xdf2->ha[nreff] = (*recs)->ha;
+ nreff++;
+ } else
+ xdf2->rchg[i] = 1;
+ }
+ xdf2->nreff = nreff;
+
+ xdl_free(dis);
+
+ return 0;
+}
+
+
+/*
+ * Early trim initial and terminal matching records.
+ */
+static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) {
+ long i, lim;
+ xrecord_t **recs1, **recs2;
+
+ recs1 = xdf1->recs;
+ recs2 = xdf2->recs;
+ for (i = 0, lim = XDL_MIN(xdf1->nrec, xdf2->nrec); i < lim;
+ i++, recs1++, recs2++)
+ if ((*recs1)->ha != (*recs2)->ha)
+ break;
+
+ xdf1->dstart = xdf2->dstart = i;
+
+ recs1 = xdf1->recs + xdf1->nrec - 1;
+ recs2 = xdf2->recs + xdf2->nrec - 1;
+ for (lim -= i, i = 0; i < lim; i++, recs1--, recs2--)
+ if ((*recs1)->ha != (*recs2)->ha)
+ break;
+
+ xdf1->dend = xdf1->nrec - i - 1;
+ xdf2->dend = xdf2->nrec - i - 1;
+
+ return 0;
+}
+
+
+static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {
+
+ if (xdl_trim_ends(xdf1, xdf2) < 0 ||
+ xdl_cleanup_records(cf, xdf1, xdf2) < 0) {
+
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/src/xdiff/xprepare.h b/src/xdiff/xprepare.h
new file mode 100644
index 000000000..8fb06a537
--- /dev/null
+++ b/src/xdiff/xprepare.h
@@ -0,0 +1,34 @@
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#if !defined(XPREPARE_H)
+#define XPREPARE_H
+
+
+
+int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+ xdfenv_t *xe);
+void xdl_free_env(xdfenv_t *xe);
+
+
+
+#endif /* #if !defined(XPREPARE_H) */
diff --git a/src/xdiff/xtypes.h b/src/xdiff/xtypes.h
new file mode 100644
index 000000000..2511aef8d
--- /dev/null
+++ b/src/xdiff/xtypes.h
@@ -0,0 +1,67 @@
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#if !defined(XTYPES_H)
+#define XTYPES_H
+
+
+
+typedef struct s_chanode {
+ struct s_chanode *next;
+ long icurr;
+} chanode_t;
+
+typedef struct s_chastore {
+ chanode_t *head, *tail;
+ long isize, nsize;
+ chanode_t *ancur;
+ chanode_t *sncur;
+ long scurr;
+} chastore_t;
+
+typedef struct s_xrecord {
+ struct s_xrecord *next;
+ char const *ptr;
+ long size;
+ unsigned long ha;
+} xrecord_t;
+
+typedef struct s_xdfile {
+ chastore_t rcha;
+ long nrec;
+ unsigned int hbits;
+ xrecord_t **rhash;
+ long dstart, dend;
+ xrecord_t **recs;
+ char *rchg;
+ long *rindex;
+ long nreff;
+ unsigned long *ha;
+} xdfile_t;
+
+typedef struct s_xdfenv {
+ xdfile_t xdf1, xdf2;
+} xdfenv_t;
+
+
+
+#endif /* #if !defined(XTYPES_H) */
diff --git a/src/xdiff/xutils.c b/src/xdiff/xutils.c
new file mode 100644
index 000000000..bb7bdee49
--- /dev/null
+++ b/src/xdiff/xutils.c
@@ -0,0 +1,419 @@
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#include "xinclude.h"
+
+
+
+
+long xdl_bogosqrt(long n) {
+ long i;
+
+ /*
+ * Classical integer square root approximation using shifts.
+ */
+ for (i = 1; n > 0; n >>= 2)
+ i <<= 1;
+
+ return i;
+}
+
+
+int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize,
+ xdemitcb_t *ecb) {
+ int i = 2;
+ mmbuffer_t mb[3];
+
+ mb[0].ptr = (char *) pre;
+ mb[0].size = psize;
+ mb[1].ptr = (char *) rec;
+ mb[1].size = size;
+ if (size > 0 && rec[size - 1] != '\n') {
+ mb[2].ptr = (char *) "\n\\ No newline at end of file\n";
+ mb[2].size = strlen(mb[2].ptr);
+ i++;
+ }
+ if (ecb->outf(ecb->priv, mb, i) < 0) {
+
+ return -1;
+ }
+
+ return 0;
+}
+
+void *xdl_mmfile_first(mmfile_t *mmf, long *size)
+{
+ *size = (long)mmf->size;
+ return mmf->ptr;
+}
+
+
+long xdl_mmfile_size(mmfile_t *mmf)
+{
+ return (long)mmf->size;
+}
+
+
+int xdl_cha_init(chastore_t *cha, long isize, long icount) {
+
+ cha->head = cha->tail = NULL;
+ cha->isize = isize;
+ cha->nsize = icount * isize;
+ cha->ancur = cha->sncur = NULL;
+ cha->scurr = 0;
+
+ return 0;
+}
+
+
+void xdl_cha_free(chastore_t *cha) {
+ chanode_t *cur, *tmp;
+
+ for (cur = cha->head; (tmp = cur) != NULL;) {
+ cur = cur->next;
+ xdl_free(tmp);
+ }
+}
+
+
+void *xdl_cha_alloc(chastore_t *cha) {
+ chanode_t *ancur;
+ void *data;
+
+ if (!(ancur = cha->ancur) || ancur->icurr == cha->nsize) {
+ if (!(ancur = (chanode_t *) xdl_malloc(sizeof(chanode_t) + cha->nsize))) {
+
+ return NULL;
+ }
+ ancur->icurr = 0;
+ ancur->next = NULL;
+ if (cha->tail)
+ cha->tail->next = ancur;
+ if (!cha->head)
+ cha->head = ancur;
+ cha->tail = ancur;
+ cha->ancur = ancur;
+ }
+
+ data = (char *) ancur + sizeof(chanode_t) + ancur->icurr;
+ ancur->icurr += cha->isize;
+
+ return data;
+}
+
+
+void *xdl_cha_first(chastore_t *cha) {
+ chanode_t *sncur;
+
+ if (!(cha->sncur = sncur = cha->head))
+ return NULL;
+
+ cha->scurr = 0;
+
+ return (char *) sncur + sizeof(chanode_t) + cha->scurr;
+}
+
+
+void *xdl_cha_next(chastore_t *cha) {
+ chanode_t *sncur;
+
+ if (!(sncur = cha->sncur))
+ return NULL;
+ cha->scurr += cha->isize;
+ if (cha->scurr == sncur->icurr) {
+ if (!(sncur = cha->sncur = sncur->next))
+ return NULL;
+ cha->scurr = 0;
+ }
+
+ return (char *) sncur + sizeof(chanode_t) + cha->scurr;
+}
+
+
+long xdl_guess_lines(mmfile_t *mf, long sample) {
+ long nl = 0, size, tsize = 0;
+ char const *data, *cur, *top;
+
+ if ((cur = data = xdl_mmfile_first(mf, &size)) != NULL) {
+ for (top = data + size; nl < sample && cur < top; ) {
+ nl++;
+ if (!(cur = memchr(cur, '\n', top - cur)))
+ cur = top;
+ else
+ cur++;
+ }
+ tsize += (long) (cur - data);
+ }
+
+ if (nl && tsize)
+ nl = xdl_mmfile_size(mf) / (tsize / nl);
+
+ return nl + 1;
+}
+
+int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
+{
+ int i1, i2;
+
+ if (s1 == s2 && !memcmp(l1, l2, s1))
+ return 1;
+ if (!(flags & XDF_WHITESPACE_FLAGS))
+ return 0;
+
+ i1 = 0;
+ i2 = 0;
+
+ /*
+ * -w matches everything that matches with -b, and -b in turn
+ * matches everything that matches with --ignore-space-at-eol.
+ *
+ * Each flavor of ignoring needs different logic to skip whitespaces
+ * while we have both sides to compare.
+ */
+ if (flags & XDF_IGNORE_WHITESPACE) {
+ goto skip_ws;
+ while (i1 < s1 && i2 < s2) {
+ if (l1[i1++] != l2[i2++])
+ return 0;
+ skip_ws:
+ while (i1 < s1 && XDL_ISSPACE(l1[i1]))
+ i1++;
+ while (i2 < s2 && XDL_ISSPACE(l2[i2]))
+ i2++;
+ }
+ } else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) {
+ while (i1 < s1 && i2 < s2) {
+ if (XDL_ISSPACE(l1[i1]) && XDL_ISSPACE(l2[i2])) {
+ /* Skip matching spaces and try again */
+ while (i1 < s1 && XDL_ISSPACE(l1[i1]))
+ i1++;
+ while (i2 < s2 && XDL_ISSPACE(l2[i2]))
+ i2++;
+ continue;
+ }
+ if (l1[i1++] != l2[i2++])
+ return 0;
+ }
+ } else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL) {
+ while (i1 < s1 && i2 < s2 && l1[i1++] == l2[i2++])
+ ; /* keep going */
+ }
+
+ /*
+ * After running out of one side, the remaining side must have
+ * nothing but whitespace for the lines to match. Note that
+ * ignore-whitespace-at-eol case may break out of the loop
+ * while there still are characters remaining on both lines.
+ */
+ if (i1 < s1) {
+ while (i1 < s1 && XDL_ISSPACE(l1[i1]))
+ i1++;
+ if (s1 != i1)
+ return 0;
+ }
+ if (i2 < s2) {
+ while (i2 < s2 && XDL_ISSPACE(l2[i2]))
+ i2++;
+ return (s2 == i2);
+ }
+ return 1;
+}
+
+static unsigned long xdl_hash_record_with_whitespace(char const **data,
+ char const *top, long flags) {
+ unsigned long ha = 5381;
+ char const *ptr = *data;
+
+ for (; ptr < top && *ptr != '\n'; ptr++) {
+ if (XDL_ISSPACE(*ptr)) {
+ const char *ptr2 = ptr;
+ int at_eol;
+ while (ptr + 1 < top && XDL_ISSPACE(ptr[1])
+ && ptr[1] != '\n')
+ ptr++;
+ at_eol = (top <= ptr + 1 || ptr[1] == '\n');
+ if (flags & XDF_IGNORE_WHITESPACE)
+ ; /* already handled */
+ else if (flags & XDF_IGNORE_WHITESPACE_CHANGE
+ && !at_eol) {
+ ha += (ha << 5);
+ ha ^= (unsigned long) ' ';
+ }
+ else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL
+ && !at_eol) {
+ while (ptr2 != ptr + 1) {
+ ha += (ha << 5);
+ ha ^= (unsigned long) *ptr2;
+ ptr2++;
+ }
+ }
+ continue;
+ }
+ ha += (ha << 5);
+ ha ^= (unsigned long) *ptr;
+ }
+ *data = ptr < top ? ptr + 1: ptr;
+
+ return ha;
+}
+
+
+unsigned long xdl_hash_record(char const **data, char const *top, long flags) {
+ unsigned long ha = 5381;
+ char const *ptr = *data;
+
+ if (flags & XDF_WHITESPACE_FLAGS)
+ return xdl_hash_record_with_whitespace(data, top, flags);
+
+ for (; ptr < top && *ptr != '\n'; ptr++) {
+ ha += (ha << 5);
+ ha ^= (unsigned long) *ptr;
+ }
+ *data = ptr < top ? ptr + 1: ptr;
+
+ return ha;
+}
+
+
+unsigned int xdl_hashbits(unsigned int size) {
+ unsigned int val = 1, bits = 0;
+
+ for (; val < size && bits < CHAR_BIT * sizeof(unsigned int); val <<= 1, bits++);
+ return bits ? bits: 1;
+}
+
+
+int xdl_num_out(char *out, long val) {
+ char *ptr, *str = out;
+ char buf[32];
+
+ ptr = buf + sizeof(buf) - 1;
+ *ptr = '\0';
+ if (val < 0) {
+ *--ptr = '-';
+ val = -val;
+ }
+ for (; val && ptr > buf; val /= 10)
+ *--ptr = "0123456789"[val % 10];
+ if (*ptr)
+ for (; *ptr; ptr++, str++)
+ *str = *ptr;
+ else
+ *str++ = '0';
+ *str = '\0';
+
+ return (int)(str - out);
+}
+
+
+long xdl_atol(char const *str, char const **next) {
+ long val, base;
+ char const *top;
+
+ for (top = str; XDL_ISDIGIT(*top); top++);
+ if (next)
+ *next = top;
+ for (val = 0, base = 1, top--; top >= str; top--, base *= 10)
+ val += base * (long)(*top - '0');
+ return val;
+}
+
+
+int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,
+ const char *func, long funclen, xdemitcb_t *ecb) {
+ int nb = 0;
+ mmbuffer_t mb;
+ char buf[128];
+
+ memcpy(buf, "@@ -", 4);
+ nb += 4;
+
+ nb += xdl_num_out(buf + nb, c1 ? s1: s1 - 1);
+
+ if (c1 != 1) {
+ memcpy(buf + nb, ",", 1);
+ nb += 1;
+
+ nb += xdl_num_out(buf + nb, c1);
+ }
+
+ memcpy(buf + nb, " +", 2);
+ nb += 2;
+
+ nb += xdl_num_out(buf + nb, c2 ? s2: s2 - 1);
+
+ if (c2 != 1) {
+ memcpy(buf + nb, ",", 1);
+ nb += 1;
+
+ nb += xdl_num_out(buf + nb, c2);
+ }
+
+ memcpy(buf + nb, " @@", 3);
+ nb += 3;
+ if (func && funclen) {
+ buf[nb++] = ' ';
+ if (funclen > (long)sizeof(buf) - nb - 1)
+ funclen = (long)sizeof(buf) - nb - 1;
+ memcpy(buf + nb, func, funclen);
+ nb += funclen;
+ }
+ buf[nb++] = '\n';
+
+ mb.ptr = buf;
+ mb.size = nb;
+ if (ecb->outf(ecb->priv, &mb, 1) < 0)
+ return -1;
+
+ return 0;
+}
+
+int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
+ int line1, int count1, int line2, int count2)
+{
+ /*
+ * This probably does not work outside Git, since
+ * we have a very simple mmfile structure.
+ *
+ * Note: ideally, we would reuse the prepared environment, but
+ * the libxdiff interface does not (yet) allow for diffing only
+ * ranges of lines instead of the whole files.
+ */
+ mmfile_t subfile1, subfile2;
+ xdfenv_t env;
+
+ subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1]->ptr;
+ subfile1.size = diff_env->xdf1.recs[line1 + count1 - 2]->ptr +
+ diff_env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr;
+ subfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1]->ptr;
+ subfile2.size = diff_env->xdf2.recs[line2 + count2 - 2]->ptr +
+ diff_env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr;
+ if (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0)
+ return -1;
+
+ memcpy(diff_env->xdf1.rchg + line1 - 1, env.xdf1.rchg, count1);
+ memcpy(diff_env->xdf2.rchg + line2 - 1, env.xdf2.rchg, count2);
+
+ xdl_free_env(&env);
+
+ return 0;
+}
diff --git a/src/xdiff/xutils.h b/src/xdiff/xutils.h
new file mode 100644
index 000000000..714719a89
--- /dev/null
+++ b/src/xdiff/xutils.h
@@ -0,0 +1,49 @@
+/*
+ * LibXDiff by Davide Libenzi ( File Differential Library )
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#if !defined(XUTILS_H)
+#define XUTILS_H
+
+
+
+long xdl_bogosqrt(long n);
+int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize,
+ xdemitcb_t *ecb);
+int xdl_cha_init(chastore_t *cha, long isize, long icount);
+void xdl_cha_free(chastore_t *cha);
+void *xdl_cha_alloc(chastore_t *cha);
+void *xdl_cha_first(chastore_t *cha);
+void *xdl_cha_next(chastore_t *cha);
+long xdl_guess_lines(mmfile_t *mf, long sample);
+int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags);
+unsigned long xdl_hash_record(char const **data, char const *top, long flags);
+unsigned int xdl_hashbits(unsigned int size);
+int xdl_num_out(char *out, long val);
+long xdl_atol(char const *str, char const **next);
+int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,
+ const char *func, long funclen, xdemitcb_t *ecb);
+int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
+ int line1, int count1, int line2, int count2);
+
+
+
+#endif /* #if !defined(XUTILS_H) */
diff --git a/src/zstream.c b/src/zstream.c
new file mode 100644
index 000000000..e75fb265e
--- /dev/null
+++ b/src/zstream.c
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * 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 <zlib.h>
+
+#include "zstream.h"
+#include "buffer.h"
+
+#define ZSTREAM_BUFFER_SIZE (1024 * 1024)
+#define ZSTREAM_BUFFER_MIN_EXTRA 8
+
+static int zstream_seterr(git_zstream *zs)
+{
+ if (zs->zerr == Z_OK || zs->zerr == Z_STREAM_END)
+ return 0;
+
+ if (zs->zerr == Z_MEM_ERROR)
+ giterr_set_oom();
+ else if (zs->z.msg)
+ giterr_set(GITERR_ZLIB, zs->z.msg);
+ else
+ giterr_set(GITERR_ZLIB, "Unknown compression error");
+
+ return -1;
+}
+
+int git_zstream_init(git_zstream *zstream)
+{
+ zstream->zerr = deflateInit(&zstream->z, Z_DEFAULT_COMPRESSION);
+ return zstream_seterr(zstream);
+}
+
+void git_zstream_free(git_zstream *zstream)
+{
+ deflateEnd(&zstream->z);
+}
+
+void git_zstream_reset(git_zstream *zstream)
+{
+ deflateReset(&zstream->z);
+ zstream->in = NULL;
+ zstream->in_len = 0;
+ zstream->zerr = Z_STREAM_END;
+}
+
+int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len)
+{
+ zstream->in = in;
+ zstream->in_len = in_len;
+ zstream->zerr = Z_OK;
+ return 0;
+}
+
+bool git_zstream_done(git_zstream *zstream)
+{
+ return (!zstream->in_len && zstream->zerr == Z_STREAM_END);
+}
+
+size_t git_zstream_suggest_output_len(git_zstream *zstream)
+{
+ if (zstream->in_len > ZSTREAM_BUFFER_SIZE)
+ return ZSTREAM_BUFFER_SIZE;
+ else if (zstream->in_len > ZSTREAM_BUFFER_MIN_EXTRA)
+ return zstream->in_len;
+ else
+ return ZSTREAM_BUFFER_MIN_EXTRA;
+}
+
+int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream)
+{
+ int zflush = Z_FINISH;
+ size_t out_remain = *out_len;
+
+ while (out_remain > 0 && zstream->zerr != Z_STREAM_END) {
+ size_t out_queued, in_queued, out_used, in_used;
+
+ /* set up in data */
+ zstream->z.next_in = (Bytef *)zstream->in;
+ zstream->z.avail_in = (uInt)zstream->in_len;
+ if ((size_t)zstream->z.avail_in != zstream->in_len) {
+ zstream->z.avail_in = INT_MAX;
+ zflush = Z_NO_FLUSH;
+ } else {
+ zflush = Z_FINISH;
+ }
+ in_queued = (size_t)zstream->z.avail_in;
+
+ /* set up out data */
+ zstream->z.next_out = out;
+ zstream->z.avail_out = (uInt)out_remain;
+ if ((size_t)zstream->z.avail_out != out_remain)
+ zstream->z.avail_out = INT_MAX;
+ out_queued = (size_t)zstream->z.avail_out;
+
+ /* compress next chunk */
+ zstream->zerr = deflate(&zstream->z, zflush);
+
+ if (zstream->zerr == Z_STREAM_ERROR)
+ return zstream_seterr(zstream);
+
+ out_used = (out_queued - zstream->z.avail_out);
+ out_remain -= out_used;
+ out = ((char *)out) + out_used;
+
+ in_used = (in_queued - zstream->z.avail_in);
+ zstream->in_len -= in_used;
+ zstream->in += in_used;
+ }
+
+ /* either we finished the input or we did not flush the data */
+ assert(zstream->in_len > 0 || zflush == Z_FINISH);
+
+ /* set out_size to number of bytes actually written to output */
+ *out_len = *out_len - out_remain;
+
+ return 0;
+}
+
+int git_zstream_deflatebuf(git_buf *out, const void *in, size_t in_len)
+{
+ git_zstream zs = GIT_ZSTREAM_INIT;
+ int error = 0;
+
+ if ((error = git_zstream_init(&zs)) < 0)
+ return error;
+
+ if ((error = git_zstream_set_input(&zs, in, in_len)) < 0)
+ goto done;
+
+ while (!git_zstream_done(&zs)) {
+ size_t step = git_zstream_suggest_output_len(&zs), written;
+
+ if ((error = git_buf_grow(out, out->size + step)) < 0)
+ goto done;
+
+ written = out->asize - out->size;
+
+ if ((error = git_zstream_get_output(
+ out->ptr + out->size, &written, &zs)) < 0)
+ goto done;
+
+ out->size += written;
+ }
+
+ /* NULL terminate for consistency if possible */
+ if (out->size < out->asize)
+ out->ptr[out->size] = '\0';
+
+done:
+ git_zstream_free(&zs);
+ return error;
+}
diff --git a/src/zstream.h b/src/zstream.h
new file mode 100644
index 000000000..9b5bf6ace
--- /dev/null
+++ b/src/zstream.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_zstream_h__
+#define INCLUDE_zstream_h__
+
+#include <zlib.h>
+
+#include "common.h"
+#include "buffer.h"
+
+typedef struct {
+ z_stream z;
+ const char *in;
+ size_t in_len;
+ int zerr;
+} git_zstream;
+
+#define GIT_ZSTREAM_INIT {{0}}
+
+int git_zstream_init(git_zstream *zstream);
+void git_zstream_free(git_zstream *zstream);
+
+int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len);
+
+size_t git_zstream_suggest_output_len(git_zstream *zstream);
+
+int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream);
+
+bool git_zstream_done(git_zstream *zstream);
+
+void git_zstream_reset(git_zstream *zstream);
+
+int git_zstream_deflatebuf(git_buf *out, const void *in, size_t in_len);
+
+#endif /* INCLUDE_zstream_h__ */
diff --git a/tests/.gitignore b/tests/.gitignore
deleted file mode 100644
index 690624bdf..000000000
--- a/tests/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*.toc
diff --git a/tests/NAMING b/tests/NAMING
deleted file mode 100644
index c2da0163f..000000000
--- a/tests/NAMING
+++ /dev/null
@@ -1,52 +0,0 @@
-Test sources should be named:
-
- t????-function.c
-
-where ???? is a four digit code. The first two digits classify
-the test into a major category; the final two digits indicate the
-sequence of the test within that category. The function part of
-the test name should give a rough indication of what it does.
-
-Categories
-----------
-
-00__: Core library routines based only on the standard library,
- and that are essential for everything else to run. E.g.
- errno and malloc.
-
-01__: Basic hashing functions, needed to handle the content
- addressable store.
-
-02__: Basic object read access.
-
-03__: Basic object writing.
-
-04__: Parsing and loading commit data
-
-05__: Revision walking
-
-06__: Index reading, writing and searching
-
-07__: Tests for the internal hashtable code
-
-08__: Tag reading and writing
-
-09__: Reading tree objects
-
-10__: Symbolic, loose and packed references reading and writing.
-
-11__: SQLite backend
-
-12__: Repository init and opening
-
-13__: Threads, empty as of now
-
-14__: Redis backend
-
-15__: Configuration parsing
-
-16__: Remotes
-
-17__: Buffers
-
-18__: File Status
diff --git a/tests/README.md b/tests/README.md
new file mode 100644
index 000000000..3aeaaf464
--- /dev/null
+++ b/tests/README.md
@@ -0,0 +1,22 @@
+Writing Clar tests for libgit2
+==============================
+
+For information on the Clar testing framework and a detailed introduction
+please visit:
+
+https://github.com/vmg/clar
+
+
+* Write your modules and tests. Use good, meaningful names.
+
+* Make sure you actually build the tests by setting:
+
+ cmake -DBUILD_CLAR=ON build/
+
+* Test:
+
+ ./build/libgit2_clar
+
+* Make sure everything is fine.
+
+* Send your pull request. That's it.
diff --git a/tests/attr/attr_expect.h b/tests/attr/attr_expect.h
new file mode 100644
index 000000000..70f1ab4f5
--- /dev/null
+++ b/tests/attr/attr_expect.h
@@ -0,0 +1,43 @@
+#ifndef __CLAR_TEST_ATTR_EXPECT__
+#define __CLAR_TEST_ATTR_EXPECT__
+
+enum attr_expect_t {
+ EXPECT_FALSE,
+ EXPECT_TRUE,
+ EXPECT_UNDEFINED,
+ EXPECT_STRING
+};
+
+struct attr_expected {
+ const char *path;
+ const char *attr;
+ enum attr_expect_t expected;
+ const char *expected_str;
+};
+
+GIT_INLINE(void) attr_check_expected(
+ enum attr_expect_t expected,
+ const char *expected_str,
+ const char *name,
+ const char *value)
+{
+ switch (expected) {
+ case EXPECT_TRUE:
+ cl_assert_(GIT_ATTR_TRUE(value), name);
+ break;
+
+ case EXPECT_FALSE:
+ cl_assert_(GIT_ATTR_FALSE(value), name);
+ break;
+
+ case EXPECT_UNDEFINED:
+ cl_assert_(GIT_ATTR_UNSPECIFIED(value), name);
+ break;
+
+ case EXPECT_STRING:
+ cl_assert_equal_s(expected_str, value);
+ break;
+ }
+}
+
+#endif
diff --git a/tests/attr/file.c b/tests/attr/file.c
new file mode 100644
index 000000000..1f4108c3c
--- /dev/null
+++ b/tests/attr/file.c
@@ -0,0 +1,224 @@
+#include "clar_libgit2.h"
+#include "attr_file.h"
+#include "attr_expect.h"
+
+#define get_rule(X) ((git_attr_rule *)git_vector_get(&file->rules,(X)))
+#define get_assign(R,Y) ((git_attr_assignment *)git_vector_get(&(R)->assigns,(Y)))
+
+void test_attr_file__simple_read(void)
+{
+ git_attr_file *file;
+ git_attr_assignment *assign;
+ git_attr_rule *rule;
+
+ cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr0")));
+
+ cl_assert_equal_s(cl_fixture("attr/attr0"), file->entry->path);
+ cl_assert(file->rules.length == 1);
+
+ rule = get_rule(0);
+ cl_assert(rule != NULL);
+ cl_assert_equal_s("*", rule->match.pattern);
+ cl_assert(rule->match.length == 1);
+ cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0);
+
+ cl_assert(rule->assigns.length == 1);
+ assign = get_assign(rule, 0);
+ cl_assert(assign != NULL);
+ cl_assert_equal_s("binary", assign->name);
+ cl_assert(GIT_ATTR_TRUE(assign->value));
+
+ git_attr_file__free(file);
+}
+
+void test_attr_file__match_variants(void)
+{
+ git_attr_file *file;
+ git_attr_rule *rule;
+ git_attr_assignment *assign;
+
+ cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr1")));
+
+ cl_assert_equal_s(cl_fixture("attr/attr1"), file->entry->path);
+ cl_assert(file->rules.length == 10);
+
+ /* let's do a thorough check of this rule, then just verify
+ * the things that are unique for the later rules
+ */
+ rule = get_rule(0);
+ cl_assert(rule);
+ cl_assert_equal_s("pat0", rule->match.pattern);
+ cl_assert(rule->match.length == strlen("pat0"));
+ cl_assert(rule->assigns.length == 1);
+ assign = get_assign(rule,0);
+ cl_assert_equal_s("attr0", assign->name);
+ cl_assert(assign->name_hash == git_attr_file__name_hash(assign->name));
+ cl_assert(GIT_ATTR_TRUE(assign->value));
+
+ rule = get_rule(1);
+ cl_assert_equal_s("pat1", rule->match.pattern);
+ cl_assert(rule->match.length == strlen("pat1"));
+ cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0);
+
+ rule = get_rule(2);
+ cl_assert_equal_s("pat2", rule->match.pattern);
+ cl_assert(rule->match.length == strlen("pat2"));
+ cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_DIRECTORY) != 0);
+
+ rule = get_rule(3);
+ cl_assert_equal_s("pat3dir/pat3file", rule->match.pattern);
+ cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_FULLPATH) != 0);
+
+ rule = get_rule(4);
+ cl_assert_equal_s("pat4.*", rule->match.pattern);
+ cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0);
+
+ rule = get_rule(5);
+ cl_assert_equal_s("*.pat5", rule->match.pattern);
+ cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0);
+
+ rule = get_rule(7);
+ cl_assert_equal_s("pat7[a-e]??[xyz]", rule->match.pattern);
+ cl_assert(rule->assigns.length == 1);
+ cl_assert((rule->match.flags & GIT_ATTR_FNMATCH_HASWILD) != 0);
+ assign = get_assign(rule,0);
+ cl_assert_equal_s("attr7", assign->name);
+ cl_assert(GIT_ATTR_TRUE(assign->value));
+
+ rule = get_rule(8);
+ cl_assert_equal_s("pat8 with spaces", rule->match.pattern);
+ cl_assert(rule->match.length == strlen("pat8 with spaces"));
+
+ rule = get_rule(9);
+ cl_assert_equal_s("pat9", rule->match.pattern);
+
+ git_attr_file__free(file);
+}
+
+static void check_one_assign(
+ git_attr_file *file,
+ int rule_idx,
+ int assign_idx,
+ const char *pattern,
+ const char *name,
+ enum attr_expect_t expected,
+ const char *expected_str)
+{
+ git_attr_rule *rule = get_rule(rule_idx);
+ git_attr_assignment *assign = get_assign(rule, assign_idx);
+
+ cl_assert_equal_s(pattern, rule->match.pattern);
+ cl_assert(rule->assigns.length == 1);
+ cl_assert_equal_s(name, assign->name);
+ cl_assert(assign->name_hash == git_attr_file__name_hash(assign->name));
+
+ attr_check_expected(expected, expected_str, assign->name, assign->value);
+}
+
+void test_attr_file__assign_variants(void)
+{
+ git_attr_file *file;
+ git_attr_rule *rule;
+ git_attr_assignment *assign;
+
+ cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr2")));
+
+ cl_assert_equal_s(cl_fixture("attr/attr2"), file->entry->path);
+ cl_assert(file->rules.length == 11);
+
+ check_one_assign(file, 0, 0, "pat0", "simple", EXPECT_TRUE, NULL);
+ check_one_assign(file, 1, 0, "pat1", "neg", EXPECT_FALSE, NULL);
+ check_one_assign(file, 2, 0, "*", "notundef", EXPECT_TRUE, NULL);
+ check_one_assign(file, 3, 0, "pat2", "notundef", EXPECT_UNDEFINED, NULL);
+ check_one_assign(file, 4, 0, "pat3", "assigned", EXPECT_STRING, "test-value");
+ check_one_assign(file, 5, 0, "pat4", "rule-with-more-chars", EXPECT_STRING, "value-with-more-chars");
+ check_one_assign(file, 6, 0, "pat5", "empty", EXPECT_TRUE, NULL);
+ check_one_assign(file, 7, 0, "pat6", "negempty", EXPECT_FALSE, NULL);
+
+ rule = get_rule(8);
+ cl_assert_equal_s("pat7", rule->match.pattern);
+ cl_assert(rule->assigns.length == 5);
+ /* assignments will be sorted by hash value, so we have to do
+ * lookups by search instead of by position
+ */
+ assign = git_attr_rule__lookup_assignment(rule, "multiple");
+ cl_assert(assign);
+ cl_assert_equal_s("multiple", assign->name);
+ cl_assert(GIT_ATTR_TRUE(assign->value));
+ assign = git_attr_rule__lookup_assignment(rule, "single");
+ cl_assert(assign);
+ cl_assert_equal_s("single", assign->name);
+ cl_assert(GIT_ATTR_FALSE(assign->value));
+ assign = git_attr_rule__lookup_assignment(rule, "values");
+ cl_assert(assign);
+ cl_assert_equal_s("values", assign->name);
+ cl_assert_equal_s("1", assign->value);
+ assign = git_attr_rule__lookup_assignment(rule, "also");
+ cl_assert(assign);
+ cl_assert_equal_s("also", assign->name);
+ cl_assert_equal_s("a-really-long-value/*", assign->value);
+ assign = git_attr_rule__lookup_assignment(rule, "happy");
+ cl_assert(assign);
+ cl_assert_equal_s("happy", assign->name);
+ cl_assert_equal_s("yes!", assign->value);
+ assign = git_attr_rule__lookup_assignment(rule, "other");
+ cl_assert(!assign);
+
+ rule = get_rule(9);
+ cl_assert_equal_s("pat8", rule->match.pattern);
+ cl_assert(rule->assigns.length == 2);
+ assign = git_attr_rule__lookup_assignment(rule, "again");
+ cl_assert(assign);
+ cl_assert_equal_s("again", assign->name);
+ cl_assert(GIT_ATTR_TRUE(assign->value));
+ assign = git_attr_rule__lookup_assignment(rule, "another");
+ cl_assert(assign);
+ cl_assert_equal_s("another", assign->name);
+ cl_assert_equal_s("12321", assign->value);
+
+ check_one_assign(file, 10, 0, "pat9", "at-eof", EXPECT_FALSE, NULL);
+
+ git_attr_file__free(file);
+}
+
+void test_attr_file__check_attr_examples(void)
+{
+ git_attr_file *file;
+ git_attr_rule *rule;
+ git_attr_assignment *assign;
+
+ cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr3")));
+ cl_assert_equal_s(cl_fixture("attr/attr3"), file->entry->path);
+ cl_assert(file->rules.length == 3);
+
+ rule = get_rule(0);
+ cl_assert_equal_s("*.java", rule->match.pattern);
+ cl_assert(rule->assigns.length == 3);
+ assign = git_attr_rule__lookup_assignment(rule, "diff");
+ cl_assert_equal_s("diff", assign->name);
+ cl_assert_equal_s("java", assign->value);
+ assign = git_attr_rule__lookup_assignment(rule, "crlf");
+ cl_assert_equal_s("crlf", assign->name);
+ cl_assert(GIT_ATTR_FALSE(assign->value));
+ assign = git_attr_rule__lookup_assignment(rule, "myAttr");
+ cl_assert_equal_s("myAttr", assign->name);
+ cl_assert(GIT_ATTR_TRUE(assign->value));
+ assign = git_attr_rule__lookup_assignment(rule, "missing");
+ cl_assert(assign == NULL);
+
+ rule = get_rule(1);
+ cl_assert_equal_s("NoMyAttr.java", rule->match.pattern);
+ cl_assert(rule->assigns.length == 1);
+ assign = get_assign(rule, 0);
+ cl_assert_equal_s("myAttr", assign->name);
+ cl_assert(GIT_ATTR_UNSPECIFIED(assign->value));
+
+ rule = get_rule(2);
+ cl_assert_equal_s("README", rule->match.pattern);
+ cl_assert(rule->assigns.length == 1);
+ assign = get_assign(rule, 0);
+ cl_assert_equal_s("caveat", assign->name);
+ cl_assert_equal_s("unspecified", assign->value);
+
+ git_attr_file__free(file);
+}
diff --git a/tests/attr/flags.c b/tests/attr/flags.c
new file mode 100644
index 000000000..80c6e1171
--- /dev/null
+++ b/tests/attr/flags.c
@@ -0,0 +1,108 @@
+#include "clar_libgit2.h"
+#include "git2/attr.h"
+
+void test_attr_flags__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_attr_flags__bare(void)
+{
+ git_repository *repo = cl_git_sandbox_init("testrepo.git");
+ const char *value;
+
+ cl_assert(git_repository_is_bare(repo));
+
+ cl_git_pass(git_attr_get(
+ &value, repo, GIT_ATTR_CHECK_NO_SYSTEM, "README.md", "diff"));
+ cl_assert(GIT_ATTR_UNSPECIFIED(value));
+}
+
+void test_attr_flags__index_vs_workdir(void)
+{
+ git_repository *repo = cl_git_sandbox_init("attr_index");
+ const char *value;
+
+ cl_assert(!git_repository_is_bare(repo));
+
+ /* wd then index */
+ cl_git_pass(git_attr_get(
+ &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX,
+ "README.md", "bar"));
+ cl_assert(GIT_ATTR_FALSE(value));
+
+ cl_git_pass(git_attr_get(
+ &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX,
+ "README.md", "blargh"));
+ cl_assert_equal_s(value, "goop");
+
+ cl_git_pass(git_attr_get(
+ &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX,
+ "README.txt", "foo"));
+ cl_assert(GIT_ATTR_FALSE(value));
+
+ /* index then wd */
+ cl_git_pass(git_attr_get(
+ &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE,
+ "README.md", "bar"));
+ cl_assert(GIT_ATTR_TRUE(value));
+
+ cl_git_pass(git_attr_get(
+ &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE,
+ "README.md", "blargh"));
+ cl_assert_equal_s(value, "garble");
+
+ cl_git_pass(git_attr_get(
+ &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE,
+ "README.txt", "foo"));
+ cl_assert(GIT_ATTR_TRUE(value));
+}
+
+void test_attr_flags__subdir(void)
+{
+ git_repository *repo = cl_git_sandbox_init("attr_index");
+ const char *value;
+
+ /* wd then index */
+ cl_git_pass(git_attr_get(
+ &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX,
+ "sub/sub/README.md", "bar"));
+ cl_assert_equal_s(value, "1234");
+
+ cl_git_pass(git_attr_get(
+ &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX,
+ "sub/sub/README.txt", "another"));
+ cl_assert_equal_s(value, "one");
+
+ cl_git_pass(git_attr_get(
+ &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX,
+ "sub/sub/README.txt", "again"));
+ cl_assert(GIT_ATTR_TRUE(value));
+
+ cl_git_pass(git_attr_get(
+ &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_FILE_THEN_INDEX,
+ "sub/sub/README.txt", "beep"));
+ cl_assert_equal_s(value, "10");
+
+ /* index then wd */
+ cl_git_pass(git_attr_get(
+ &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE,
+ "sub/sub/README.md", "bar"));
+ cl_assert_equal_s(value, "1337");
+
+ cl_git_pass(git_attr_get(
+ &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE,
+ "sub/sub/README.txt", "another"));
+ cl_assert_equal_s(value, "one");
+
+ cl_git_pass(git_attr_get(
+ &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE,
+ "sub/sub/README.txt", "again"));
+ cl_assert(GIT_ATTR_TRUE(value));
+
+ cl_git_pass(git_attr_get(
+ &value, repo, GIT_ATTR_CHECK_NO_SYSTEM | GIT_ATTR_CHECK_INDEX_THEN_FILE,
+ "sub/sub/README.txt", "beep"));
+ cl_assert_equal_s(value, "5");
+}
+
diff --git a/tests/attr/ignore.c b/tests/attr/ignore.c
new file mode 100644
index 000000000..b187db01c
--- /dev/null
+++ b/tests/attr/ignore.c
@@ -0,0 +1,175 @@
+#include "clar_libgit2.h"
+#include "posix.h"
+#include "path.h"
+#include "fileops.h"
+
+static git_repository *g_repo = NULL;
+
+void test_attr_ignore__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("attr");
+}
+
+void test_attr_ignore__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+ g_repo = NULL;
+}
+
+static void assert_is_ignored_(
+ bool expected, const char *filepath, const char *file, int line)
+{
+ int is_ignored = 0;
+
+ cl_git_pass_(
+ git_ignore_path_is_ignored(&is_ignored, g_repo, filepath), file, line);
+
+ clar__assert_equal(
+ file, line, "expected != is_ignored", 1, "%d",
+ (int)(expected != 0), (int)(is_ignored != 0));
+}
+#define assert_is_ignored(expected, filepath) \
+ assert_is_ignored_(expected, filepath, __FILE__, __LINE__)
+
+void test_attr_ignore__honor_temporary_rules(void)
+{
+ cl_git_rewritefile("attr/.gitignore", "/NewFolder\n/NewFolder/NewFolder");
+
+ assert_is_ignored(false, "File.txt");
+ assert_is_ignored(true, "NewFolder");
+ assert_is_ignored(true, "NewFolder/NewFolder");
+ assert_is_ignored(true, "NewFolder/NewFolder/File.txt");
+}
+
+void test_attr_ignore__allow_root(void)
+{
+ cl_git_rewritefile("attr/.gitignore", "/");
+
+ assert_is_ignored(false, "File.txt");
+ assert_is_ignored(false, "NewFolder");
+ assert_is_ignored(false, "NewFolder/NewFolder");
+ assert_is_ignored(false, "NewFolder/NewFolder/File.txt");
+}
+
+void test_attr_ignore__ignore_root(void)
+{
+ cl_git_rewritefile("attr/.gitignore", "/\n\n/NewFolder\n/NewFolder/NewFolder");
+
+ assert_is_ignored(false, "File.txt");
+ assert_is_ignored(true, "NewFolder");
+ assert_is_ignored(true, "NewFolder/NewFolder");
+ assert_is_ignored(true, "NewFolder/NewFolder/File.txt");
+}
+
+void test_attr_ignore__full_paths(void)
+{
+ cl_git_rewritefile("attr/.gitignore", "Folder/*/Contained");
+
+ assert_is_ignored(true, "Folder/Middle/Contained");
+ assert_is_ignored(false, "Folder/Middle/More/More/Contained");
+
+ cl_git_rewritefile("attr/.gitignore", "Folder/**/Contained");
+
+ assert_is_ignored(true, "Folder/Middle/Contained");
+ assert_is_ignored(true, "Folder/Middle/More/More/Contained");
+
+ cl_git_rewritefile("attr/.gitignore", "Folder/**/Contained/*/Child");
+
+ assert_is_ignored(true, "Folder/Middle/Contained/Happy/Child");
+ assert_is_ignored(false, "Folder/Middle/Contained/Not/Happy/Child");
+ assert_is_ignored(true, "Folder/Middle/More/More/Contained/Happy/Child");
+ assert_is_ignored(false, "Folder/Middle/More/More/Contained/Not/Happy/Child");
+}
+
+void test_attr_ignore__more_starstar_cases(void)
+{
+ cl_must_pass(p_unlink("attr/.gitignore"));
+ cl_git_mkfile(
+ "attr/dir/.gitignore",
+ "sub/**/*.html\n");
+
+ assert_is_ignored(false, "aaa.html");
+ assert_is_ignored(false, "dir");
+ assert_is_ignored(false, "dir/sub");
+ assert_is_ignored(true, "dir/sub/sub2/aaa.html");
+ assert_is_ignored(true, "dir/sub/aaa.html");
+ assert_is_ignored(false, "dir/aaa.html");
+ assert_is_ignored(false, "sub");
+ assert_is_ignored(false, "sub/aaa.html");
+ assert_is_ignored(false, "sub/sub2/aaa.html");
+}
+
+void test_attr_ignore__leading_stars(void)
+{
+ cl_git_rewritefile(
+ "attr/.gitignore",
+ "*/onestar\n"
+ "**/twostars\n"
+ "*/parent1/kid1/*\n"
+ "**/parent2/kid2/*\n");
+
+ assert_is_ignored(true, "dir1/onestar");
+ assert_is_ignored(true, "dir1/onestar/child"); /* in ignored dir */
+ assert_is_ignored(false, "dir1/dir2/onestar");
+
+ assert_is_ignored(true, "dir1/twostars");
+ assert_is_ignored(true, "dir1/twostars/child"); /* in ignored dir */
+ assert_is_ignored(true, "dir1/dir2/twostars");
+ assert_is_ignored(true, "dir1/dir2/twostars/child"); /* in ignored dir */
+ assert_is_ignored(true, "dir1/dir2/dir3/twostars");
+
+ assert_is_ignored(true, "dir1/parent1/kid1/file");
+ assert_is_ignored(true, "dir1/parent1/kid1/file/inside/parent");
+ assert_is_ignored(false, "dir1/dir2/parent1/kid1/file");
+ assert_is_ignored(false, "dir1/parent1/file");
+ assert_is_ignored(false, "dir1/kid1/file");
+
+ assert_is_ignored(true, "dir1/parent2/kid2/file");
+ assert_is_ignored(true, "dir1/parent2/kid2/file/inside/parent");
+ assert_is_ignored(true, "dir1/dir2/parent2/kid2/file");
+ assert_is_ignored(true, "dir1/dir2/dir3/parent2/kid2/file");
+ assert_is_ignored(false, "dir1/parent2/file");
+ assert_is_ignored(false, "dir1/kid2/file");
+}
+
+void test_attr_ignore__skip_gitignore_directory(void)
+{
+ cl_git_rewritefile("attr/.git/info/exclude", "/NewFolder\n/NewFolder/NewFolder");
+ p_unlink("attr/.gitignore");
+ cl_assert(!git_path_exists("attr/.gitignore"));
+ p_mkdir("attr/.gitignore", 0777);
+ cl_git_mkfile("attr/.gitignore/garbage.txt", "new_file\n");
+
+ assert_is_ignored(false, "File.txt");
+ assert_is_ignored(true, "NewFolder");
+ assert_is_ignored(true, "NewFolder/NewFolder");
+ assert_is_ignored(true, "NewFolder/NewFolder/File.txt");
+}
+
+void test_attr_ignore__expand_tilde_to_homedir(void)
+{
+ git_config *cfg;
+
+ assert_is_ignored(false, "example.global_with_tilde");
+
+ cl_fake_home();
+
+ /* construct fake home with fake global excludes */
+ cl_git_mkfile("home/globalexclude", "# found me\n*.global_with_tilde\n");
+
+ cl_git_pass(git_repository_config(&cfg, g_repo));
+ cl_git_pass(git_config_set_string(cfg, "core.excludesfile", "~/globalexclude"));
+ git_config_free(cfg);
+
+ git_attr_cache_flush(g_repo); /* must reset to pick up change */
+
+ assert_is_ignored(true, "example.global_with_tilde");
+
+ cl_git_pass(git_futils_rmdir_r("home", NULL, GIT_RMDIR_REMOVE_FILES));
+
+ cl_fake_home_cleanup(NULL);
+
+ git_attr_cache_flush(g_repo); /* must reset to pick up change */
+
+ assert_is_ignored(false, "example.global_with_tilde");
+}
diff --git a/tests/attr/lookup.c b/tests/attr/lookup.c
new file mode 100644
index 000000000..030ea075d
--- /dev/null
+++ b/tests/attr/lookup.c
@@ -0,0 +1,262 @@
+#include "clar_libgit2.h"
+#include "attr_file.h"
+
+#include "attr_expect.h"
+
+void test_attr_lookup__simple(void)
+{
+ git_attr_file *file;
+ git_attr_path path;
+ const char *value = NULL;
+
+ cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr0")));
+ cl_assert_equal_s(cl_fixture("attr/attr0"), file->entry->path);
+ cl_assert(file->rules.length == 1);
+
+ cl_git_pass(git_attr_path__init(&path, "test", NULL));
+ cl_assert_equal_s("test", path.path);
+ cl_assert_equal_s("test", path.basename);
+ cl_assert(!path.is_dir);
+
+ cl_git_pass(git_attr_file__lookup_one(file,&path,"binary",&value));
+ cl_assert(GIT_ATTR_TRUE(value));
+
+ cl_git_pass(git_attr_file__lookup_one(file,&path,"missing",&value));
+ cl_assert(!value);
+
+ git_attr_path__free(&path);
+ git_attr_file__free(file);
+}
+
+static void run_test_cases(git_attr_file *file, struct attr_expected *cases, int force_dir)
+{
+ git_attr_path path;
+ const char *value = NULL;
+ struct attr_expected *c;
+ int error;
+
+ for (c = cases; c->path != NULL; c++) {
+ cl_git_pass(git_attr_path__init(&path, c->path, NULL));
+
+ if (force_dir)
+ path.is_dir = 1;
+
+ error = git_attr_file__lookup_one(file,&path,c->attr,&value);
+ cl_git_pass(error);
+
+ attr_check_expected(c->expected, c->expected_str, c->attr, value);
+
+ git_attr_path__free(&path);
+ }
+}
+
+void test_attr_lookup__match_variants(void)
+{
+ git_attr_file *file;
+ git_attr_path path;
+
+ struct attr_expected dir_cases[] = {
+ { "pat2", "attr2", EXPECT_TRUE, NULL },
+ { "/testing/for/pat2", "attr2", EXPECT_TRUE, NULL },
+ { "/not/pat2/yousee", "attr2", EXPECT_UNDEFINED, NULL },
+ { "/fun/fun/fun/pat4.dir", "attr4", EXPECT_TRUE, NULL },
+ { "foo.pat5", "attr5", EXPECT_TRUE, NULL },
+ { NULL, NULL, 0, NULL }
+ };
+
+ struct attr_expected cases[] = {
+ /* pat0 -> simple match */
+ { "pat0", "attr0", EXPECT_TRUE, NULL },
+ { "/testing/for/pat0", "attr0", EXPECT_TRUE, NULL },
+ { "relative/to/pat0", "attr0", EXPECT_TRUE, NULL },
+ { "this-contains-pat0-inside", "attr0", EXPECT_UNDEFINED, NULL },
+ { "this-aint-right", "attr0", EXPECT_UNDEFINED, NULL },
+ { "/this/pat0/dont/match", "attr0", EXPECT_UNDEFINED, NULL },
+ /* negative match */
+ { "pat0", "attr1", EXPECT_TRUE, NULL },
+ { "pat1", "attr1", EXPECT_UNDEFINED, NULL },
+ { "/testing/for/pat1", "attr1", EXPECT_UNDEFINED, NULL },
+ { "/testing/for/pat0", "attr1", EXPECT_TRUE, NULL },
+ { "/testing/for/pat1/inside", "attr1", EXPECT_TRUE, NULL },
+ { "misc", "attr1", EXPECT_TRUE, NULL },
+ /* dir match */
+ { "pat2", "attr2", EXPECT_UNDEFINED, NULL },
+ { "/testing/for/pat2", "attr2", EXPECT_UNDEFINED, NULL },
+ { "/not/pat2/yousee", "attr2", EXPECT_UNDEFINED, NULL },
+ /* path match */
+ { "pat3file", "attr3", EXPECT_UNDEFINED, NULL },
+ { "/pat3dir/pat3file", "attr3", EXPECT_TRUE, NULL },
+ { "pat3dir/pat3file", "attr3", EXPECT_TRUE, NULL },
+ /* pattern* match */
+ { "pat4.txt", "attr4", EXPECT_TRUE, NULL },
+ { "/fun/fun/fun/pat4.c", "attr4", EXPECT_TRUE, NULL },
+ { "pat4.", "attr4", EXPECT_TRUE, NULL },
+ { "pat4", "attr4", EXPECT_UNDEFINED, NULL },
+ /* *pattern match */
+ { "foo.pat5", "attr5", EXPECT_TRUE, NULL },
+ { "/this/is/ok.pat5", "attr5", EXPECT_TRUE, NULL },
+ { "/this/is/bad.pat5/yousee.txt", "attr5", EXPECT_UNDEFINED, NULL },
+ { "foo.pat5", "attr100", EXPECT_UNDEFINED, NULL },
+ /* glob match with slashes */
+ { "foo.pat6", "attr6", EXPECT_UNDEFINED, NULL },
+ { "pat6/pat6/foobar.pat6", "attr6", EXPECT_TRUE, NULL },
+ { "pat6/pat6/.pat6", "attr6", EXPECT_TRUE, NULL },
+ { "pat6/pat6/extra/foobar.pat6", "attr6", EXPECT_UNDEFINED, NULL },
+ { "/prefix/pat6/pat6/foobar.pat6", "attr6", EXPECT_UNDEFINED, NULL },
+ { "/pat6/pat6/foobar.pat6", "attr6", EXPECT_TRUE, NULL },
+ /* complex pattern */
+ { "pat7a12z", "attr7", EXPECT_TRUE, NULL },
+ { "pat7e__x", "attr7", EXPECT_TRUE, NULL },
+ { "pat7b/1y", "attr7", EXPECT_UNDEFINED, NULL }, /* ? does not match / */
+ { "pat7e_x", "attr7", EXPECT_UNDEFINED, NULL },
+ { "pat7aaaa", "attr7", EXPECT_UNDEFINED, NULL },
+ { "pat7zzzz", "attr7", EXPECT_UNDEFINED, NULL },
+ { "/this/can/be/anything/pat7a12z", "attr7", EXPECT_TRUE, NULL },
+ { "but/it/still/must/match/pat7aaaa", "attr7", EXPECT_UNDEFINED, NULL },
+ { "pat7aaay.fail", "attr7", EXPECT_UNDEFINED, NULL },
+ /* pattern with spaces */
+ { "pat8 with spaces", "attr8", EXPECT_TRUE, NULL },
+ { "/gotta love/pat8 with spaces", "attr8", EXPECT_TRUE, NULL },
+ { "failing pat8 with spaces", "attr8", EXPECT_UNDEFINED, NULL },
+ { "spaces", "attr8", EXPECT_UNDEFINED, NULL },
+ /* pattern at eof */
+ { "pat9", "attr9", EXPECT_TRUE, NULL },
+ { "/eof/pat9", "attr9", EXPECT_TRUE, NULL },
+ { "pat", "attr9", EXPECT_UNDEFINED, NULL },
+ { "at9", "attr9", EXPECT_UNDEFINED, NULL },
+ { "pat9.fail", "attr9", EXPECT_UNDEFINED, NULL },
+ /* sentinel at end */
+ { NULL, NULL, 0, NULL }
+ };
+
+ cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr1")));
+ cl_assert_equal_s(cl_fixture("attr/attr1"), file->entry->path);
+ cl_assert(file->rules.length == 10);
+
+ cl_git_pass(git_attr_path__init(&path, "/testing/for/pat0", NULL));
+ cl_assert_equal_s("pat0", path.basename);
+
+ run_test_cases(file, cases, 0);
+ run_test_cases(file, dir_cases, 1);
+
+ git_attr_file__free(file);
+ git_attr_path__free(&path);
+}
+
+void test_attr_lookup__assign_variants(void)
+{
+ git_attr_file *file;
+
+ struct attr_expected cases[] = {
+ /* pat0 -> simple assign */
+ { "pat0", "simple", EXPECT_TRUE, NULL },
+ { "/testing/pat0", "simple", EXPECT_TRUE, NULL },
+ { "pat0", "fail", EXPECT_UNDEFINED, NULL },
+ { "/testing/pat0", "fail", EXPECT_UNDEFINED, NULL },
+ /* negative assign */
+ { "pat1", "neg", EXPECT_FALSE, NULL },
+ { "/testing/pat1", "neg", EXPECT_FALSE, NULL },
+ { "pat1", "fail", EXPECT_UNDEFINED, NULL },
+ { "/testing/pat1", "fail", EXPECT_UNDEFINED, NULL },
+ /* forced undef */
+ { "pat1", "notundef", EXPECT_TRUE, NULL },
+ { "pat2", "notundef", EXPECT_UNDEFINED, NULL },
+ { "/lead/in/pat1", "notundef", EXPECT_TRUE, NULL },
+ { "/lead/in/pat2", "notundef", EXPECT_UNDEFINED, NULL },
+ /* assign value */
+ { "pat3", "assigned", EXPECT_STRING, "test-value" },
+ { "pat3", "notassigned", EXPECT_UNDEFINED, NULL },
+ /* assign value */
+ { "pat4", "rule-with-more-chars", EXPECT_STRING, "value-with-more-chars" },
+ { "pat4", "notassigned-rule-with-more-chars", EXPECT_UNDEFINED, NULL },
+ /* empty assignments */
+ { "pat5", "empty", EXPECT_TRUE, NULL },
+ { "pat6", "negempty", EXPECT_FALSE, NULL },
+ /* multiple assignment */
+ { "pat7", "multiple", EXPECT_TRUE, NULL },
+ { "pat7", "single", EXPECT_FALSE, NULL },
+ { "pat7", "values", EXPECT_STRING, "1" },
+ { "pat7", "also", EXPECT_STRING, "a-really-long-value/*" },
+ { "pat7", "happy", EXPECT_STRING, "yes!" },
+ { "pat8", "again", EXPECT_TRUE, NULL },
+ { "pat8", "another", EXPECT_STRING, "12321" },
+ /* bad assignment */
+ { "patbad0", "simple", EXPECT_UNDEFINED, NULL },
+ { "patbad0", "notundef", EXPECT_TRUE, NULL },
+ { "patbad1", "simple", EXPECT_UNDEFINED, NULL },
+ /* eof assignment */
+ { "pat9", "at-eof", EXPECT_FALSE, NULL },
+ /* sentinel at end */
+ { NULL, NULL, 0, NULL }
+ };
+
+ cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr2")));
+ cl_assert(file->rules.length == 11);
+
+ run_test_cases(file, cases, 0);
+
+ git_attr_file__free(file);
+}
+
+void test_attr_lookup__check_attr_examples(void)
+{
+ git_attr_file *file;
+
+ struct attr_expected cases[] = {
+ { "foo.java", "diff", EXPECT_STRING, "java" },
+ { "foo.java", "crlf", EXPECT_FALSE, NULL },
+ { "foo.java", "myAttr", EXPECT_TRUE, NULL },
+ { "foo.java", "other", EXPECT_UNDEFINED, NULL },
+ { "/prefix/dir/foo.java", "diff", EXPECT_STRING, "java" },
+ { "/prefix/dir/foo.java", "crlf", EXPECT_FALSE, NULL },
+ { "/prefix/dir/foo.java", "myAttr", EXPECT_TRUE, NULL },
+ { "/prefix/dir/foo.java", "other", EXPECT_UNDEFINED, NULL },
+ { "NoMyAttr.java", "crlf", EXPECT_FALSE, NULL },
+ { "NoMyAttr.java", "myAttr", EXPECT_UNDEFINED, NULL },
+ { "NoMyAttr.java", "other", EXPECT_UNDEFINED, NULL },
+ { "/prefix/dir/NoMyAttr.java", "crlf", EXPECT_FALSE, NULL },
+ { "/prefix/dir/NoMyAttr.java", "myAttr", EXPECT_UNDEFINED, NULL },
+ { "/prefix/dir/NoMyAttr.java", "other", EXPECT_UNDEFINED, NULL },
+ { "README", "caveat", EXPECT_STRING, "unspecified" },
+ { "/specific/path/README", "caveat", EXPECT_STRING, "unspecified" },
+ { "README", "missing", EXPECT_UNDEFINED, NULL },
+ { "/specific/path/README", "missing", EXPECT_UNDEFINED, NULL },
+ /* sentinel at end */
+ { NULL, NULL, 0, NULL }
+ };
+
+ cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr3")));
+ cl_assert(file->rules.length == 3);
+
+ run_test_cases(file, cases, 0);
+
+ git_attr_file__free(file);
+}
+
+void test_attr_lookup__from_buffer(void)
+{
+ git_attr_file *file;
+
+ struct attr_expected cases[] = {
+ { "abc", "foo", EXPECT_TRUE, NULL },
+ { "abc", "bar", EXPECT_TRUE, NULL },
+ { "abc", "baz", EXPECT_TRUE, NULL },
+ { "aaa", "foo", EXPECT_TRUE, NULL },
+ { "aaa", "bar", EXPECT_UNDEFINED, NULL },
+ { "aaa", "baz", EXPECT_TRUE, NULL },
+ { "qqq", "foo", EXPECT_UNDEFINED, NULL },
+ { "qqq", "bar", EXPECT_UNDEFINED, NULL },
+ { "qqq", "baz", EXPECT_TRUE, NULL },
+ { NULL, NULL, 0, NULL }
+ };
+
+ cl_git_pass(git_attr_file__new(&file, NULL, 0));
+
+ cl_git_pass(git_attr_file__parse_buffer(NULL, file, "a* foo\nabc bar\n* baz"));
+
+ cl_assert(file->rules.length == 3);
+
+ run_test_cases(file, cases, 0);
+
+ git_attr_file__free(file);
+}
diff --git a/tests/attr/repo.c b/tests/attr/repo.c
new file mode 100644
index 000000000..5e812a72b
--- /dev/null
+++ b/tests/attr/repo.c
@@ -0,0 +1,335 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+#include "git2/attr.h"
+#include "attr.h"
+
+#include "attr_expect.h"
+
+static git_repository *g_repo = NULL;
+
+void test_attr_repo__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("attr");
+}
+
+void test_attr_repo__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+ g_repo = NULL;
+}
+
+static struct attr_expected get_one_test_cases[] = {
+ { "root_test1", "repoattr", EXPECT_TRUE, NULL },
+ { "root_test1", "rootattr", EXPECT_TRUE, NULL },
+ { "root_test1", "missingattr", EXPECT_UNDEFINED, NULL },
+ { "root_test1", "subattr", EXPECT_UNDEFINED, NULL },
+ { "root_test1", "negattr", EXPECT_UNDEFINED, NULL },
+ { "root_test2", "repoattr", EXPECT_TRUE, NULL },
+ { "root_test2", "rootattr", EXPECT_FALSE, NULL },
+ { "root_test2", "missingattr", EXPECT_UNDEFINED, NULL },
+ { "root_test2", "multiattr", EXPECT_FALSE, NULL },
+ { "root_test3", "repoattr", EXPECT_TRUE, NULL },
+ { "root_test3", "rootattr", EXPECT_UNDEFINED, NULL },
+ { "root_test3", "multiattr", EXPECT_STRING, "3" },
+ { "root_test3", "multi2", EXPECT_UNDEFINED, NULL },
+ { "sub/subdir_test1", "repoattr", EXPECT_TRUE, NULL },
+ { "sub/subdir_test1", "rootattr", EXPECT_TRUE, NULL },
+ { "sub/subdir_test1", "missingattr", EXPECT_UNDEFINED, NULL },
+ { "sub/subdir_test1", "subattr", EXPECT_STRING, "yes" },
+ { "sub/subdir_test1", "negattr", EXPECT_FALSE, NULL },
+ { "sub/subdir_test1", "another", EXPECT_UNDEFINED, NULL },
+ { "sub/subdir_test2.txt", "repoattr", EXPECT_TRUE, NULL },
+ { "sub/subdir_test2.txt", "rootattr", EXPECT_TRUE, NULL },
+ { "sub/subdir_test2.txt", "missingattr", EXPECT_UNDEFINED, NULL },
+ { "sub/subdir_test2.txt", "subattr", EXPECT_STRING, "yes" },
+ { "sub/subdir_test2.txt", "negattr", EXPECT_FALSE, NULL },
+ { "sub/subdir_test2.txt", "another", EXPECT_STRING, "zero" },
+ { "sub/subdir_test2.txt", "reposub", EXPECT_TRUE, NULL },
+ { "sub/sub/subdir.txt", "another", EXPECT_STRING, "one" },
+ { "sub/sub/subdir.txt", "reposubsub", EXPECT_TRUE, NULL },
+ { "sub/sub/subdir.txt", "reposub", EXPECT_UNDEFINED, NULL },
+ { "does-not-exist", "foo", EXPECT_STRING, "yes" },
+ { "sub/deep/file", "deepdeep", EXPECT_TRUE, NULL },
+ { "sub/sub/d/no", "test", EXPECT_STRING, "a/b/d/*" },
+ { "sub/sub/d/yes", "test", EXPECT_UNDEFINED, NULL },
+};
+
+void test_attr_repo__get_one(void)
+{
+ int i;
+
+ for (i = 0; i < (int)ARRAY_SIZE(get_one_test_cases); ++i) {
+ struct attr_expected *scan = &get_one_test_cases[i];
+ const char *value;
+
+ cl_git_pass(git_attr_get(&value, g_repo, 0, scan->path, scan->attr));
+ attr_check_expected(
+ scan->expected, scan->expected_str, scan->attr, value);
+ }
+
+ cl_assert(git_attr_cache__is_cached(
+ g_repo, GIT_ATTR_FILE__FROM_FILE, ".git/info/attributes"));
+ cl_assert(git_attr_cache__is_cached(
+ g_repo, GIT_ATTR_FILE__FROM_FILE, ".gitattributes"));
+ cl_assert(git_attr_cache__is_cached(
+ g_repo, GIT_ATTR_FILE__FROM_FILE, "sub/.gitattributes"));
+}
+
+void test_attr_repo__get_one_start_deep(void)
+{
+ int i;
+
+ for (i = (int)ARRAY_SIZE(get_one_test_cases) - 1; i >= 0; --i) {
+ struct attr_expected *scan = &get_one_test_cases[i];
+ const char *value;
+
+ cl_git_pass(git_attr_get(&value, g_repo, 0, scan->path, scan->attr));
+ attr_check_expected(
+ scan->expected, scan->expected_str, scan->attr, value);
+ }
+
+ cl_assert(git_attr_cache__is_cached(
+ g_repo, GIT_ATTR_FILE__FROM_FILE, ".git/info/attributes"));
+ cl_assert(git_attr_cache__is_cached(
+ g_repo, GIT_ATTR_FILE__FROM_FILE, ".gitattributes"));
+ cl_assert(git_attr_cache__is_cached(
+ g_repo, GIT_ATTR_FILE__FROM_FILE, "sub/.gitattributes"));
+}
+
+void test_attr_repo__get_many(void)
+{
+ const char *names[4] = { "repoattr", "rootattr", "missingattr", "subattr" };
+ const char *values[4];
+
+ cl_git_pass(git_attr_get_many(values, g_repo, 0, "root_test1", 4, names));
+
+ cl_assert(GIT_ATTR_TRUE(values[0]));
+ cl_assert(GIT_ATTR_TRUE(values[1]));
+ cl_assert(GIT_ATTR_UNSPECIFIED(values[2]));
+ cl_assert(GIT_ATTR_UNSPECIFIED(values[3]));
+
+ cl_git_pass(git_attr_get_many(values, g_repo, 0, "root_test2", 4, names));
+
+ cl_assert(GIT_ATTR_TRUE(values[0]));
+ cl_assert(GIT_ATTR_FALSE(values[1]));
+ cl_assert(GIT_ATTR_UNSPECIFIED(values[2]));
+ cl_assert(GIT_ATTR_UNSPECIFIED(values[3]));
+
+ cl_git_pass(git_attr_get_many(values, g_repo, 0, "sub/subdir_test1", 4, names));
+
+ cl_assert(GIT_ATTR_TRUE(values[0]));
+ cl_assert(GIT_ATTR_TRUE(values[1]));
+ cl_assert(GIT_ATTR_UNSPECIFIED(values[2]));
+ cl_assert_equal_s("yes", values[3]);
+}
+
+void test_attr_repo__get_many_in_place(void)
+{
+ const char *vals[4] = { "repoattr", "rootattr", "missingattr", "subattr" };
+
+ /* it should be legal to look up values into the same array that has
+ * the attribute names, overwriting each name as the value is found.
+ */
+
+ cl_git_pass(git_attr_get_many(vals, g_repo, 0, "sub/subdir_test1", 4, vals));
+
+ cl_assert(GIT_ATTR_TRUE(vals[0]));
+ cl_assert(GIT_ATTR_TRUE(vals[1]));
+ cl_assert(GIT_ATTR_UNSPECIFIED(vals[2]));
+ cl_assert_equal_s("yes", vals[3]);
+}
+
+static int count_attrs(
+ const char *name,
+ const char *value,
+ void *payload)
+{
+ GIT_UNUSED(name);
+ GIT_UNUSED(value);
+
+ *((int *)payload) += 1;
+
+ return 0;
+}
+
+#define CANCEL_VALUE 12345
+
+static int cancel_iteration(
+ const char *name,
+ const char *value,
+ void *payload)
+{
+ GIT_UNUSED(name);
+ GIT_UNUSED(value);
+
+ *((int *)payload) -= 1;
+
+ if (*((int *)payload) < 0)
+ return CANCEL_VALUE;
+
+ return 0;
+}
+
+void test_attr_repo__foreach(void)
+{
+ int count;
+
+ count = 0;
+ cl_git_pass(git_attr_foreach(
+ g_repo, 0, "root_test1", &count_attrs, &count));
+ cl_assert(count == 2);
+
+ count = 0;
+ cl_git_pass(git_attr_foreach(g_repo, 0, "sub/subdir_test1",
+ &count_attrs, &count));
+ cl_assert(count == 4); /* repoattr, rootattr, subattr, negattr */
+
+ count = 0;
+ cl_git_pass(git_attr_foreach(g_repo, 0, "sub/subdir_test2.txt",
+ &count_attrs, &count));
+ cl_assert(count == 6); /* repoattr, rootattr, subattr, reposub, negattr, another */
+
+ count = 2;
+ cl_assert_equal_i(
+ CANCEL_VALUE, git_attr_foreach(
+ g_repo, 0, "sub/subdir_test1", &cancel_iteration, &count)
+ );
+}
+
+void test_attr_repo__manpage_example(void)
+{
+ const char *value;
+
+ cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "foo"));
+ cl_assert(GIT_ATTR_TRUE(value));
+
+ cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "bar"));
+ cl_assert(GIT_ATTR_UNSPECIFIED(value));
+
+ cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "baz"));
+ cl_assert(GIT_ATTR_FALSE(value));
+
+ cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "merge"));
+ cl_assert_equal_s("filfre", value);
+
+ cl_git_pass(git_attr_get(&value, g_repo, 0, "sub/abc", "frotz"));
+ cl_assert(GIT_ATTR_UNSPECIFIED(value));
+}
+
+void test_attr_repo__macros(void)
+{
+ const char *names[5] = { "rootattr", "binary", "diff", "crlf", "frotz" };
+ const char *names2[5] = { "mymacro", "positive", "negative", "rootattr", "another" };
+ const char *names3[3] = { "macro2", "multi2", "multi3" };
+ const char *values[5];
+
+ cl_git_pass(git_attr_get_many(values, g_repo, 0, "binfile", 5, names));
+
+ cl_assert(GIT_ATTR_TRUE(values[0]));
+ cl_assert(GIT_ATTR_TRUE(values[1]));
+ cl_assert(GIT_ATTR_FALSE(values[2]));
+ cl_assert(GIT_ATTR_FALSE(values[3]));
+ cl_assert(GIT_ATTR_UNSPECIFIED(values[4]));
+
+ cl_git_pass(git_attr_get_many(values, g_repo, 0, "macro_test", 5, names2));
+
+ cl_assert(GIT_ATTR_TRUE(values[0]));
+ cl_assert(GIT_ATTR_TRUE(values[1]));
+ cl_assert(GIT_ATTR_FALSE(values[2]));
+ cl_assert(GIT_ATTR_UNSPECIFIED(values[3]));
+ cl_assert_equal_s("77", values[4]);
+
+ cl_git_pass(git_attr_get_many(values, g_repo, 0, "macro_test", 3, names3));
+
+ cl_assert(GIT_ATTR_TRUE(values[0]));
+ cl_assert(GIT_ATTR_FALSE(values[1]));
+ cl_assert_equal_s("answer", values[2]);
+}
+
+void test_attr_repo__bad_macros(void)
+{
+ const char *names[6] = { "rootattr", "positive", "negative",
+ "firstmacro", "secondmacro", "thirdmacro" };
+ const char *values[6];
+
+ cl_git_pass(git_attr_get_many(values, g_repo, 0, "macro_bad", 6, names));
+
+ /* these three just confirm that the "mymacro" rule ran */
+ cl_assert(GIT_ATTR_UNSPECIFIED(values[0]));
+ cl_assert(GIT_ATTR_TRUE(values[1]));
+ cl_assert(GIT_ATTR_FALSE(values[2]));
+
+ /* file contains:
+ * # let's try some malicious macro defs
+ * [attr]firstmacro -thirdmacro -secondmacro
+ * [attr]secondmacro firstmacro -firstmacro
+ * [attr]thirdmacro secondmacro=hahaha -firstmacro
+ * macro_bad firstmacro secondmacro thirdmacro
+ *
+ * firstmacro assignment list ends up with:
+ * -thirdmacro -secondmacro
+ * secondmacro assignment list expands "firstmacro" and ends up with:
+ * -thirdmacro -secondmacro -firstmacro
+ * thirdmacro assignment don't expand so list ends up with:
+ * secondmacro="hahaha"
+ *
+ * macro_bad assignment list ends up with:
+ * -thirdmacro -secondmacro firstmacro &&
+ * -thirdmacro -secondmacro -firstmacro secondmacro &&
+ * secondmacro="hahaha" thirdmacro
+ *
+ * so summary results should be:
+ * -firstmacro secondmacro="hahaha" thirdmacro
+ */
+ cl_assert(GIT_ATTR_FALSE(values[3]));
+ cl_assert_equal_s("hahaha", values[4]);
+ cl_assert(GIT_ATTR_TRUE(values[5]));
+}
+
+#define CONTENT "I'm going to be dynamically processed\r\n" \
+ "And my line endings...\r\n" \
+ "...are going to be\n" \
+ "normalized!\r\n"
+
+#define GITATTR "* text=auto\n" \
+ "*.txt text\n" \
+ "*.data binary\n"
+
+static void add_to_workdir(const char *filename, const char *content)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ cl_git_pass(git_buf_joinpath(&buf, "attr", filename));
+ cl_git_rewritefile(git_buf_cstr(&buf), content);
+
+ git_buf_free(&buf);
+}
+
+static void assert_proper_normalization(git_index *index, const char *filename, const char *expected_sha)
+{
+ size_t index_pos;
+ const git_index_entry *entry;
+
+ add_to_workdir(filename, CONTENT);
+ cl_git_pass(git_index_add_bypath(index, filename));
+
+ cl_assert(!git_index_find(&index_pos, index, filename));
+
+ entry = git_index_get_byindex(index, index_pos);
+ cl_assert_equal_i(0, git_oid_streq(&entry->id, expected_sha));
+}
+
+void test_attr_repo__staging_properly_normalizes_line_endings_according_to_gitattributes_directives(void)
+{
+ git_index* index;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ add_to_workdir(".gitattributes", GITATTR);
+
+ assert_proper_normalization(index, "text.txt", "22c74203bace3c2e950278c7ab08da0fca9f4e9b");
+ assert_proper_normalization(index, "huh.dunno", "22c74203bace3c2e950278c7ab08da0fca9f4e9b");
+ assert_proper_normalization(index, "binary.data", "66eeff1fcbacf589e6d70aa70edd3fce5be2b37c");
+
+ git_index_free(index);
+}
diff --git a/tests/blame/blame_helpers.c b/tests/blame/blame_helpers.c
new file mode 100644
index 000000000..56240dbde
--- /dev/null
+++ b/tests/blame/blame_helpers.c
@@ -0,0 +1,67 @@
+#include "blame_helpers.h"
+
+void hunk_message(size_t idx, const git_blame_hunk *hunk, const char *fmt, ...)
+{
+ va_list arglist;
+
+ printf("Hunk %zd (line %d +%d): ", idx,
+ hunk->final_start_line_number, hunk->lines_in_hunk-1);
+
+ va_start(arglist, fmt);
+ vprintf(fmt, arglist);
+ va_end(arglist);
+
+ printf("\n");
+}
+
+void check_blame_hunk_index(git_repository *repo, git_blame *blame, int idx,
+ int start_line, int len, char boundary, const char *commit_id, const char *orig_path)
+{
+ char expected[41] = {0}, actual[41] = {0};
+ const git_blame_hunk *hunk = git_blame_get_hunk_byindex(blame, idx);
+ cl_assert(hunk);
+
+ if (!strncmp(commit_id, "0000", 4)) {
+ strcpy(expected, "0000000000000000000000000000000000000000");
+ } else {
+ git_object *obj;
+ cl_git_pass(git_revparse_single(&obj, repo, commit_id));
+ git_oid_fmt(expected, git_object_id(obj));
+ git_object_free(obj);
+ }
+
+ if (hunk->final_start_line_number != start_line) {
+ hunk_message(idx, hunk, "mismatched start line number: expected %d, got %d",
+ start_line, hunk->final_start_line_number);
+ }
+ cl_assert_equal_i(hunk->final_start_line_number, start_line);
+
+ if (hunk->lines_in_hunk != len) {
+ hunk_message(idx, hunk, "mismatched line count: expected %d, got %d",
+ len, hunk->lines_in_hunk);
+ }
+ cl_assert_equal_i(hunk->lines_in_hunk, len);
+
+ git_oid_fmt(actual, &hunk->final_commit_id);
+ if (strcmp(expected, actual)) {
+ hunk_message(idx, hunk, "has mismatched original id (got %s, expected %s)\n",
+ actual, expected);
+ }
+ cl_assert_equal_s(actual, expected);
+ cl_assert_equal_i(git_oid_cmp(&hunk->final_commit_id, &hunk->orig_commit_id), 0);
+
+
+ if (strcmp(hunk->orig_path, orig_path)) {
+ hunk_message(idx, hunk, "has mismatched original path (got '%s', expected '%s')\n",
+ hunk->orig_path, orig_path);
+ }
+ cl_assert_equal_s(hunk->orig_path, orig_path);
+
+ if (hunk->boundary != boundary) {
+ hunk_message(idx, hunk, "doesn't match boundary flag (got %d, expected %d)\n",
+ hunk->boundary, boundary);
+ }
+ cl_assert_equal_i(boundary, hunk->boundary);
+}
+
+
diff --git a/tests/blame/blame_helpers.h b/tests/blame/blame_helpers.h
new file mode 100644
index 000000000..94321a5b5
--- /dev/null
+++ b/tests/blame/blame_helpers.h
@@ -0,0 +1,16 @@
+#include "clar_libgit2.h"
+#include "blame.h"
+
+void hunk_message(size_t idx, const git_blame_hunk *hunk, const char *fmt, ...);
+
+void check_blame_hunk_index(
+ git_repository *repo,
+ git_blame *blame,
+ int idx,
+ int start_line,
+ int len,
+ char boundary,
+ const char *commit_id,
+ const char *orig_path);
+
+
diff --git a/tests/blame/buffer.c b/tests/blame/buffer.c
new file mode 100644
index 000000000..340b1dced
--- /dev/null
+++ b/tests/blame/buffer.c
@@ -0,0 +1,166 @@
+#include "blame_helpers.h"
+
+static git_repository *g_repo;
+static git_blame *g_fileblame, *g_bufferblame;
+
+void test_blame_buffer__initialize(void)
+{
+ cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git")));
+ cl_git_pass(git_blame_file(&g_fileblame, g_repo, "b.txt", NULL));
+ g_bufferblame = NULL;
+}
+
+void test_blame_buffer__cleanup(void)
+{
+ git_blame_free(g_fileblame);
+ git_blame_free(g_bufferblame);
+ git_repository_free(g_repo);
+}
+
+void test_blame_buffer__added_line(void)
+{
+ const git_blame_hunk *hunk;
+
+ const char *buffer = "\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+\n\
+abcdefg\n\
+BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
+BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
+BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
+BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
+\n\
+CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
+CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
+CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
+CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n";
+
+ cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer)));
+ cl_assert_equal_i(5, git_blame_get_hunk_count(g_bufferblame));
+ check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 1, 0, "000000", "b.txt");
+
+ hunk = git_blame_get_hunk_byline(g_bufferblame, 16);
+ cl_assert(hunk);
+ cl_assert_equal_s("Ben Straub", hunk->final_signature->name);
+}
+
+void test_blame_buffer__deleted_line(void)
+{
+ const char *buffer = "\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+\n\
+BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
+BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
+BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
+\n\
+CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
+CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
+CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
+CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n";
+
+ cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer)));
+ check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 3, 0, "63d671eb", "b.txt");
+ check_blame_hunk_index(g_repo, g_bufferblame, 3, 9, 1, 0, "63d671eb", "b.txt");
+ check_blame_hunk_index(g_repo, g_bufferblame, 4, 10, 5, 0, "aa06ecca", "b.txt");
+}
+
+void test_blame_buffer__add_splits_hunk(void)
+{
+ const char *buffer = "\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+\n\
+BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
+BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
+abc\n\
+BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
+BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
+\n\
+CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
+CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
+CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
+CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n";
+
+ cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer)));
+ check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 2, 0, "63d671eb", "b.txt");
+ check_blame_hunk_index(g_repo, g_bufferblame, 3, 8, 1, 0, "00000000", "b.txt");
+ check_blame_hunk_index(g_repo, g_bufferblame, 4, 9, 3, 0, "63d671eb", "b.txt");
+}
+
+void test_blame_buffer__delete_crosses_hunk_boundary(void)
+{
+ const char *buffer = "\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+\n\
+BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
+CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n";
+
+ cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer)));
+ check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 1, 0, "63d671eb", "b.txt");
+ check_blame_hunk_index(g_repo, g_bufferblame, 3, 7, 2, 0, "aa06ecca", "b.txt");
+}
+
+void test_blame_buffer__replace_line(void)
+{
+ const char *buffer = "\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+\n\
+BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
+abc\n\
+BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
+BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
+\n\
+CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
+CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
+CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
+CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n";
+
+ cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer)));
+ check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 1, 0, "63d671eb", "b.txt");
+ check_blame_hunk_index(g_repo, g_bufferblame, 3, 7, 1, 0, "00000000", "b.txt");
+ check_blame_hunk_index(g_repo, g_bufferblame, 4, 8, 3, 0, "63d671eb", "b.txt");
+}
+
+void test_blame_buffer__add_lines_at_end(void)
+{
+ const char *buffer = "\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
+\n\
+BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
+BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
+BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
+BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
+\n\
+CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
+CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
+CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
+CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
+\n\
+abc\n\
+def\n";
+ cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer)));
+
+ cl_assert_equal_i(5, git_blame_get_hunk_count(g_bufferblame));
+ check_blame_hunk_index(g_repo, g_bufferblame, 0, 1, 4, 0, "da237394", "b.txt");
+ check_blame_hunk_index(g_repo, g_bufferblame, 1, 5, 1, 1, "b99f7ac0", "b.txt");
+ check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 5, 0, "63d671eb", "b.txt");
+ check_blame_hunk_index(g_repo, g_bufferblame, 3, 11, 5, 0, "aa06ecca", "b.txt");
+ check_blame_hunk_index(g_repo, g_bufferblame, 4, 16, 2, 0, "00000000", "b.txt");
+}
diff --git a/tests/blame/getters.c b/tests/blame/getters.c
new file mode 100644
index 000000000..66eaeecf9
--- /dev/null
+++ b/tests/blame/getters.c
@@ -0,0 +1,56 @@
+#include "clar_libgit2.h"
+
+#include "blame.h"
+
+git_blame *g_blame;
+
+void test_blame_getters__initialize(void)
+{
+ size_t i;
+ git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
+
+ git_blame_hunk hunks[] = {
+ { 3, {{0}}, 1, NULL, {{0}}, "a", 0},
+ { 3, {{0}}, 4, NULL, {{0}}, "b", 0},
+ { 3, {{0}}, 7, NULL, {{0}}, "c", 0},
+ { 3, {{0}}, 10, NULL, {{0}}, "d", 0},
+ { 3, {{0}}, 13, NULL, {{0}}, "e", 0},
+ };
+
+ g_blame = git_blame__alloc(NULL, opts, "");
+
+ for (i=0; i<5; i++) {
+ git_blame_hunk *h = git__calloc(1, sizeof(git_blame_hunk));
+ h->final_start_line_number = hunks[i].final_start_line_number;
+ h->orig_path = git__strdup(hunks[i].orig_path);
+ h->lines_in_hunk = hunks[i].lines_in_hunk;
+
+ git_vector_insert(&g_blame->hunks, h);
+ }
+}
+
+void test_blame_getters__cleanup(void)
+{
+ git_blame_free(g_blame);
+}
+
+
+void test_blame_getters__byindex(void)
+{
+ const git_blame_hunk *h = git_blame_get_hunk_byindex(g_blame, 2);
+ cl_assert(h);
+ cl_assert_equal_s(h->orig_path, "c");
+
+ h = git_blame_get_hunk_byindex(g_blame, 95);
+ cl_assert_equal_p(h, NULL);
+}
+
+void test_blame_getters__byline(void)
+{
+ const git_blame_hunk *h = git_blame_get_hunk_byline(g_blame, 5);
+ cl_assert(h);
+ cl_assert_equal_s(h->orig_path, "b");
+
+ h = git_blame_get_hunk_byline(g_blame, 95);
+ cl_assert_equal_p(h, NULL);
+}
diff --git a/tests/blame/harder.c b/tests/blame/harder.c
new file mode 100644
index 000000000..e77741720
--- /dev/null
+++ b/tests/blame/harder.c
@@ -0,0 +1,79 @@
+#include "clar_libgit2.h"
+
+#include "blame.h"
+
+
+/**
+ * The test repo has a history that looks like this:
+ *
+ * * (A) bc7c5ac
+ * |\
+ * | * (B) aa06ecc
+ * * | (C) 63d671e
+ * |/
+ * * (D) da23739
+ * * (E) b99f7ac
+ *
+ */
+
+static git_repository *g_repo = NULL;
+
+void test_blame_harder__initialize(void)
+{
+ cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git")));
+}
+
+void test_blame_harder__cleanup(void)
+{
+ git_repository_free(g_repo);
+ g_repo = NULL;
+}
+
+
+
+void test_blame_harder__m(void)
+{
+ /* TODO */
+ git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
+
+ GIT_UNUSED(opts);
+
+ opts.flags = GIT_BLAME_TRACK_COPIES_SAME_FILE;
+}
+
+
+void test_blame_harder__c(void)
+{
+ git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
+
+ GIT_UNUSED(opts);
+
+ /* Attribute the first hunk in b.txt to (E), since it was cut/pasted from
+ * a.txt in (D).
+ */
+ opts.flags = GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES;
+}
+
+void test_blame_harder__cc(void)
+{
+ git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
+
+ GIT_UNUSED(opts);
+
+ /* Attribute the second hunk in b.txt to (E), since it was copy/pasted from
+ * a.txt in (C).
+ */
+ opts.flags = GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES;
+}
+
+void test_blame_harder__ccc(void)
+{
+ git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
+
+ GIT_UNUSED(opts);
+
+ /* Attribute the third hunk in b.txt to (E). This hunk was deleted from
+ * a.txt in (D), but reintroduced in (B).
+ */
+ opts.flags = GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES;
+}
diff --git a/tests/blame/simple.c b/tests/blame/simple.c
new file mode 100644
index 000000000..83e5e056b
--- /dev/null
+++ b/tests/blame/simple.c
@@ -0,0 +1,324 @@
+#include "blame_helpers.h"
+
+static git_repository *g_repo;
+static git_blame *g_blame;
+
+void test_blame_simple__initialize(void)
+{
+ g_repo = NULL;
+ g_blame = NULL;
+}
+
+void test_blame_simple__cleanup(void)
+{
+ git_blame_free(g_blame);
+ git_repository_free(g_repo);
+}
+
+/*
+ * $ git blame -s branch_file.txt
+ * orig line no final line no
+ * commit V author timestamp V
+ * c47800c7 1 (Scott Chacon 2010-05-25 11:58:14 -0700 1
+ * a65fedf3 2 (Scott Chacon 2011-08-09 19:33:46 -0700 2
+ */
+void test_blame_simple__trivial_testrepo(void)
+{
+ cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo/.gitted")));
+ cl_git_pass(git_blame_file(&g_blame, g_repo, "branch_file.txt", NULL));
+
+ cl_assert_equal_i(2, git_blame_get_hunk_count(g_blame));
+ check_blame_hunk_index(g_repo, g_blame, 0, 1, 1, 0, "c47800c7", "branch_file.txt");
+ check_blame_hunk_index(g_repo, g_blame, 1, 2, 1, 0, "a65fedf3", "branch_file.txt");
+}
+
+/*
+ * $ git blame -n b.txt
+ * orig line no final line no
+ * commit V author timestamp V
+ * da237394 1 (Ben Straub 2013-02-12 15:11:30 -0800 1
+ * da237394 2 (Ben Straub 2013-02-12 15:11:30 -0800 2
+ * da237394 3 (Ben Straub 2013-02-12 15:11:30 -0800 3
+ * da237394 4 (Ben Straub 2013-02-12 15:11:30 -0800 4
+ * ^b99f7ac 1 (Ben Straub 2013-02-12 15:10:12 -0800 5
+ * 63d671eb 6 (Ben Straub 2013-02-12 15:13:04 -0800 6
+ * 63d671eb 7 (Ben Straub 2013-02-12 15:13:04 -0800 7
+ * 63d671eb 8 (Ben Straub 2013-02-12 15:13:04 -0800 8
+ * 63d671eb 9 (Ben Straub 2013-02-12 15:13:04 -0800 9
+ * 63d671eb 10 (Ben Straub 2013-02-12 15:13:04 -0800 10
+ * aa06ecca 6 (Ben Straub 2013-02-12 15:14:46 -0800 11
+ * aa06ecca 7 (Ben Straub 2013-02-12 15:14:46 -0800 12
+ * aa06ecca 8 (Ben Straub 2013-02-12 15:14:46 -0800 13
+ * aa06ecca 9 (Ben Straub 2013-02-12 15:14:46 -0800 14
+ * aa06ecca 10 (Ben Straub 2013-02-12 15:14:46 -0800 15
+ */
+void test_blame_simple__trivial_blamerepo(void)
+{
+ cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git")));
+ cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", NULL));
+
+ cl_assert_equal_i(4, git_blame_get_hunk_count(g_blame));
+ check_blame_hunk_index(g_repo, g_blame, 0, 1, 4, 0, "da237394", "b.txt");
+ check_blame_hunk_index(g_repo, g_blame, 1, 5, 1, 1, "b99f7ac0", "b.txt");
+ check_blame_hunk_index(g_repo, g_blame, 2, 6, 5, 0, "63d671eb", "b.txt");
+ check_blame_hunk_index(g_repo, g_blame, 3, 11, 5, 0, "aa06ecca", "b.txt");
+}
+
+
+/*
+ * $ git blame -n 359fc2d -- include/git2.h
+ * orig line no final line no
+ * commit orig path V author timestamp V
+ * d12299fe src/git.h 1 (Vicent Martí 2010-12-03 22:22:10 +0200 1
+ * 359fc2d2 include/git2.h 2 (Edward Thomson 2013-01-08 17:07:25 -0600 2
+ * d12299fe src/git.h 5 (Vicent Martí 2010-12-03 22:22:10 +0200 3
+ * bb742ede include/git2.h 4 (Vicent Martí 2011-09-19 01:54:32 +0300 4
+ * bb742ede include/git2.h 5 (Vicent Martí 2011-09-19 01:54:32 +0300 5
+ * d12299fe src/git.h 24 (Vicent Martí 2010-12-03 22:22:10 +0200 6
+ * d12299fe src/git.h 25 (Vicent Martí 2010-12-03 22:22:10 +0200 7
+ * d12299fe src/git.h 26 (Vicent Martí 2010-12-03 22:22:10 +0200 8
+ * d12299fe src/git.h 27 (Vicent Martí 2010-12-03 22:22:10 +0200 9
+ * d12299fe src/git.h 28 (Vicent Martí 2010-12-03 22:22:10 +0200 10
+ * 96fab093 include/git2.h 11 (Sven Strickroth 2011-10-09 18:37:41 +0200 11
+ * 9d1dcca2 src/git2.h 33 (Vicent Martí 2011-02-07 10:35:58 +0200 12
+ * 44908fe7 src/git2.h 29 (Vicent Martí 2010-12-06 23:03:16 +0200 13
+ * a15c550d include/git2.h 14 (Vicent Martí 2011-11-16 14:09:44 +0100 14
+ * 44908fe7 src/git2.h 30 (Vicent Martí 2010-12-06 23:03:16 +0200 15
+ * d12299fe src/git.h 32 (Vicent Martí 2010-12-03 22:22:10 +0200 16
+ * 44908fe7 src/git2.h 33 (Vicent Martí 2010-12-06 23:03:16 +0200 17
+ * d12299fe src/git.h 34 (Vicent Martí 2010-12-03 22:22:10 +0200 18
+ * 44908fe7 src/git2.h 35 (Vicent Martí 2010-12-06 23:03:16 +0200 19
+ * 638c2ca4 src/git2.h 36 (Vicent Martí 2010-12-18 02:10:25 +0200 20
+ * 44908fe7 src/git2.h 36 (Vicent Martí 2010-12-06 23:03:16 +0200 21
+ * d12299fe src/git.h 37 (Vicent Martí 2010-12-03 22:22:10 +0200 22
+ * 44908fe7 src/git2.h 38 (Vicent Martí 2010-12-06 23:03:16 +0200 23
+ * 44908fe7 src/git2.h 39 (Vicent Martí 2010-12-06 23:03:16 +0200 24
+ * bf787bd8 include/git2.h 25 (Carlos Martín Nieto 2012-04-08 18:56:50 +0200 25
+ * 0984c876 include/git2.h 26 (Scott J. Goldman 2012-11-28 18:27:43 -0800 26
+ * 2f8a8ab2 src/git2.h 41 (Vicent Martí 2011-01-29 01:56:25 +0200 27
+ * 27df4275 include/git2.h 47 (Michael Schubert 2011-06-28 14:13:12 +0200 28
+ * a346992f include/git2.h 28 (Ben Straub 2012-05-10 09:47:14 -0700 29
+ * d12299fe src/git.h 40 (Vicent Martí 2010-12-03 22:22:10 +0200 30
+ * 44908fe7 src/git2.h 41 (Vicent Martí 2010-12-06 23:03:16 +0200 31
+ * 44908fe7 src/git2.h 42 (Vicent Martí 2010-12-06 23:03:16 +0200 32
+ * 44908fe7 src/git2.h 43 (Vicent Martí 2010-12-06 23:03:16 +0200 33
+ * 44908fe7 src/git2.h 44 (Vicent Martí 2010-12-06 23:03:16 +0200 34
+ * 44908fe7 src/git2.h 45 (Vicent Martí 2010-12-06 23:03:16 +0200 35
+ * 65b09b1d include/git2.h 33 (Russell Belfer 2012-02-02 18:03:43 -0800 36
+ * d12299fe src/git.h 46 (Vicent Martí 2010-12-03 22:22:10 +0200 37
+ * 44908fe7 src/git2.h 47 (Vicent Martí 2010-12-06 23:03:16 +0200 38
+ * 5d4cd003 include/git2.h 55 (Carlos Martín Nieto 2011-03-28 17:02:45 +0200 39
+ * 41fb1ca0 include/git2.h 39 (Philip Kelley 2012-10-29 13:41:14 -0400 40
+ * 2dc31040 include/git2.h 56 (Carlos Martín Nieto 2011-06-20 18:58:57 +0200 41
+ * 764df57e include/git2.h 40 (Ben Straub 2012-06-15 13:14:43 -0700 42
+ * 5280f4e6 include/git2.h 41 (Ben Straub 2012-07-31 19:39:06 -0700 43
+ * 613d5eb9 include/git2.h 43 (Philip Kelley 2012-11-28 11:42:37 -0500 44
+ * d12299fe src/git.h 48 (Vicent Martí 2010-12-03 22:22:10 +0200 45
+ * 111ee3fe include/git2.h 41 (Vicent Martí 2012-07-11 14:37:26 +0200 46
+ * f004c4a8 include/git2.h 44 (Russell Belfer 2012-08-21 17:26:39 -0700 47
+ * 111ee3fe include/git2.h 42 (Vicent Martí 2012-07-11 14:37:26 +0200 48
+ * 9c82357b include/git2.h 58 (Carlos Martín Nieto 2011-06-17 18:13:14 +0200 49
+ * d6258deb include/git2.h 61 (Carlos Martín Nieto 2011-06-25 15:10:09 +0200 50
+ * b311e313 include/git2.h 63 (Julien Miotte 2011-07-27 18:31:13 +0200 51
+ * 3412391d include/git2.h 63 (Carlos Martín Nieto 2011-07-07 11:47:31 +0200 52
+ * bfc9ca59 include/git2.h 43 (Russell Belfer 2012-03-28 16:45:36 -0700 53
+ * bf477ed4 include/git2.h 44 (Michael Schubert 2012-02-15 00:33:38 +0100 54
+ * edebceff include/git2.h 46 (nulltoken 2012-05-01 13:57:45 +0200 55
+ * 743a4b3b include/git2.h 48 (nulltoken 2012-06-15 22:24:59 +0200 56
+ * 0a32dca5 include/git2.h 54 (Michael Schubert 2012-08-19 22:26:32 +0200 57
+ * 590fb68b include/git2.h 55 (nulltoken 2012-10-04 13:47:45 +0200 58
+ * bf477ed4 include/git2.h 45 (Michael Schubert 2012-02-15 00:33:38 +0100 59
+ * d12299fe src/git.h 49 (Vicent Martí 2010-12-03 22:22:10 +0200 60
+ */
+void test_blame_simple__trivial_libgit2(void)
+{
+ git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
+ git_object *obj;
+
+ /* If we can't open the libgit2 repo or if it isn't a full repo
+ * with proper history, just skip this test */
+ if (git_repository_open(&g_repo, cl_fixture("../..")) < 0)
+ cl_skip();
+
+ if (git_repository_is_shallow(g_repo))
+ cl_skip();
+
+ if (git_revparse_single(&obj, g_repo, "359fc2d") < 0)
+ cl_skip();
+
+ git_oid_cpy(&opts.newest_commit, git_object_id(obj));
+ git_object_free(obj);
+
+ cl_git_pass(git_blame_file(&g_blame, g_repo, "include/git2.h", &opts));
+
+ check_blame_hunk_index(g_repo, g_blame, 0, 1, 1, 0, "d12299fe", "src/git.h");
+ check_blame_hunk_index(g_repo, g_blame, 1, 2, 1, 0, "359fc2d2", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 2, 3, 1, 0, "d12299fe", "src/git.h");
+ check_blame_hunk_index(g_repo, g_blame, 3, 4, 2, 0, "bb742ede", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 4, 6, 5, 0, "d12299fe", "src/git.h");
+ check_blame_hunk_index(g_repo, g_blame, 5, 11, 1, 0, "96fab093", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 6, 12, 1, 0, "9d1dcca2", "src/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 7, 13, 1, 0, "44908fe7", "src/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 8, 14, 1, 0, "a15c550d", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 9, 15, 1, 0, "44908fe7", "src/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 10, 16, 1, 0, "d12299fe", "src/git.h");
+ check_blame_hunk_index(g_repo, g_blame, 11, 17, 1, 0, "44908fe7", "src/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 12, 18, 1, 0, "d12299fe", "src/git.h");
+ check_blame_hunk_index(g_repo, g_blame, 13, 19, 1, 0, "44908fe7", "src/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 14, 20, 1, 0, "638c2ca4", "src/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 15, 21, 1, 0, "44908fe7", "src/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 16, 22, 1, 0, "d12299fe", "src/git.h");
+ check_blame_hunk_index(g_repo, g_blame, 17, 23, 2, 0, "44908fe7", "src/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 18, 25, 1, 0, "bf787bd8", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 19, 26, 1, 0, "0984c876", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 20, 27, 1, 0, "2f8a8ab2", "src/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 21, 28, 1, 0, "27df4275", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 22, 29, 1, 0, "a346992f", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 23, 30, 1, 0, "d12299fe", "src/git.h");
+ check_blame_hunk_index(g_repo, g_blame, 24, 31, 5, 0, "44908fe7", "src/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 25, 36, 1, 0, "65b09b1d", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 26, 37, 1, 0, "d12299fe", "src/git.h");
+ check_blame_hunk_index(g_repo, g_blame, 27, 38, 1, 0, "44908fe7", "src/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 28, 39, 1, 0, "5d4cd003", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 29, 40, 1, 0, "41fb1ca0", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 30, 41, 1, 0, "2dc31040", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 31, 42, 1, 0, "764df57e", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 32, 43, 1, 0, "5280f4e6", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 33, 44, 1, 0, "613d5eb9", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 34, 45, 1, 0, "d12299fe", "src/git.h");
+ check_blame_hunk_index(g_repo, g_blame, 35, 46, 1, 0, "111ee3fe", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 36, 47, 1, 0, "f004c4a8", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 37, 48, 1, 0, "111ee3fe", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 38, 49, 1, 0, "9c82357b", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 39, 50, 1, 0, "d6258deb", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 40, 51, 1, 0, "b311e313", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 41, 52, 1, 0, "3412391d", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 42, 53, 1, 0, "bfc9ca59", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 43, 54, 1, 0, "bf477ed4", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 44, 55, 1, 0, "edebceff", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 45, 56, 1, 0, "743a4b3b", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 46, 57, 1, 0, "0a32dca5", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 47, 58, 1, 0, "590fb68b", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 48, 59, 1, 0, "bf477ed4", "include/git2.h");
+ check_blame_hunk_index(g_repo, g_blame, 49, 60, 1, 0, "d12299fe", "src/git.h");
+}
+
+
+/*
+ * $ git blame -n b.txt -L 8
+ * orig line no final line no
+ * commit V author timestamp V
+ * 63d671eb 8 (Ben Straub 2013-02-12 15:13:04 -0800 8
+ * 63d671eb 9 (Ben Straub 2013-02-12 15:13:04 -0800 9
+ * 63d671eb 10 (Ben Straub 2013-02-12 15:13:04 -0800 10
+ * aa06ecca 6 (Ben Straub 2013-02-12 15:14:46 -0800 11
+ * aa06ecca 7 (Ben Straub 2013-02-12 15:14:46 -0800 12
+ * aa06ecca 8 (Ben Straub 2013-02-12 15:14:46 -0800 13
+ * aa06ecca 9 (Ben Straub 2013-02-12 15:14:46 -0800 14
+ * aa06ecca 10 (Ben Straub 2013-02-12 15:14:46 -0800 15
+ */
+void test_blame_simple__can_restrict_lines_min(void)
+{
+ git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
+
+ cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git")));
+
+ opts.min_line = 8;
+ cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", &opts));
+ cl_assert_equal_i(2, git_blame_get_hunk_count(g_blame));
+ check_blame_hunk_index(g_repo, g_blame, 0, 8, 3, 0, "63d671eb", "b.txt");
+ check_blame_hunk_index(g_repo, g_blame, 1, 11, 5, 0, "aa06ecca", "b.txt");
+}
+
+/*
+ * $ git blame -n b.txt -L ,6
+ * orig line no final line no
+ * commit V author timestamp V
+ * da237394 1 (Ben Straub 2013-02-12 15:11:30 -0800 1
+ * da237394 2 (Ben Straub 2013-02-12 15:11:30 -0800 2
+ * da237394 3 (Ben Straub 2013-02-12 15:11:30 -0800 3
+ * da237394 4 (Ben Straub 2013-02-12 15:11:30 -0800 4
+ * ^b99f7ac 1 (Ben Straub 2013-02-12 15:10:12 -0800 5
+ * 63d671eb 6 (Ben Straub 2013-02-12 15:13:04 -0800 6
+ */
+void test_blame_simple__can_restrict_lines_max(void)
+{
+ git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
+
+ cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git")));
+
+ opts.max_line = 6;
+ cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", &opts));
+ cl_assert_equal_i(3, git_blame_get_hunk_count(g_blame));
+ check_blame_hunk_index(g_repo, g_blame, 0, 1, 4, 0, "da237394", "b.txt");
+ check_blame_hunk_index(g_repo, g_blame, 1, 5, 1, 1, "b99f7ac0", "b.txt");
+ check_blame_hunk_index(g_repo, g_blame, 2, 6, 1, 0, "63d671eb", "b.txt");
+}
+
+/*
+ * $ git blame -n b.txt -L 2,7
+ * orig line no final line no
+ * commit V author timestamp V
+ * da237394 2 (Ben Straub 2013-02-12 15:11:30 -0800 2
+ * da237394 3 (Ben Straub 2013-02-12 15:11:30 -0800 3
+ * da237394 4 (Ben Straub 2013-02-12 15:11:30 -0800 4
+ * ^b99f7ac 1 (Ben Straub 2013-02-12 15:10:12 -0800 5
+ * 63d671eb 6 (Ben Straub 2013-02-12 15:13:04 -0800 6
+ * 63d671eb 7 (Ben Straub 2013-02-12 15:13:04 -0800 7
+ */
+void test_blame_simple__can_restrict_lines_both(void)
+{
+ git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
+
+ cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git")));
+
+ opts.min_line = 2;
+ opts.max_line = 7;
+ cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", &opts));
+ cl_assert_equal_i(3, git_blame_get_hunk_count(g_blame));
+ check_blame_hunk_index(g_repo, g_blame, 0, 2, 3, 0, "da237394", "b.txt");
+ check_blame_hunk_index(g_repo, g_blame, 1, 5, 1, 1, "b99f7ac0", "b.txt");
+ check_blame_hunk_index(g_repo, g_blame, 2, 6, 2, 0, "63d671eb", "b.txt");
+}
+
+/*
+ * $ git blame -n branch_file.txt be3563a..HEAD
+ * orig line no final line no
+ * commit V author timestamp V
+ * ^be3563a 1 (Scott Chacon 2010-05-25 11:58:27 -0700 1) hi
+ * a65fedf3 2 (Scott Chacon 2011-08-09 19:33:46 -0700 2) bye!
+ */
+void test_blame_simple__can_restrict_to_newish_commits(void)
+{
+ git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
+
+ cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git")));
+
+ {
+ git_object *obj;
+ cl_git_pass(git_revparse_single(&obj, g_repo, "be3563a"));
+ git_oid_cpy(&opts.oldest_commit, git_object_id(obj));
+ git_object_free(obj);
+ }
+
+ cl_git_pass(git_blame_file(&g_blame, g_repo, "branch_file.txt", &opts));
+
+ cl_assert_equal_i(2, git_blame_get_hunk_count(g_blame));
+ check_blame_hunk_index(g_repo, g_blame, 0, 1, 1, 1, "be3563a", "branch_file.txt");
+ check_blame_hunk_index(g_repo, g_blame, 1, 2, 1, 0, "a65fedf", "branch_file.txt");
+}
+
+void test_blame_simple__can_restrict_to_first_parent_commits(void)
+{
+ git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
+ opts.flags |= GIT_BLAME_FIRST_PARENT;
+
+ cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git")));
+
+ cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", &opts));
+ cl_assert_equal_i(4, git_blame_get_hunk_count(g_blame));
+ check_blame_hunk_index(g_repo, g_blame, 0, 1, 4, 0, "da237394", "b.txt");
+ check_blame_hunk_index(g_repo, g_blame, 1, 5, 1, 1, "b99f7ac0", "b.txt");
+ check_blame_hunk_index(g_repo, g_blame, 2, 6, 5, 0, "63d671eb", "b.txt");
+ check_blame_hunk_index(g_repo, g_blame, 3, 11, 5, 0, "bc7c5ac2", "b.txt");
+}
diff --git a/tests/buf/basic.c b/tests/buf/basic.c
new file mode 100644
index 000000000..d558757a9
--- /dev/null
+++ b/tests/buf/basic.c
@@ -0,0 +1,29 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+
+static const char *test_string = "Have you seen that? Have you seeeen that??";
+
+void test_buf_basic__resize(void)
+{
+ git_buf buf1 = GIT_BUF_INIT;
+ git_buf_puts(&buf1, test_string);
+ cl_assert(git_buf_oom(&buf1) == 0);
+ cl_assert_equal_s(git_buf_cstr(&buf1), test_string);
+
+ git_buf_puts(&buf1, test_string);
+ cl_assert(strlen(git_buf_cstr(&buf1)) == strlen(test_string) * 2);
+ git_buf_free(&buf1);
+}
+
+void test_buf_basic__printf(void)
+{
+ git_buf buf2 = GIT_BUF_INIT;
+ git_buf_printf(&buf2, "%s %s %d ", "shoop", "da", 23);
+ cl_assert(git_buf_oom(&buf2) == 0);
+ cl_assert_equal_s(git_buf_cstr(&buf2), "shoop da 23 ");
+
+ git_buf_printf(&buf2, "%s %d", "woop", 42);
+ cl_assert(git_buf_oom(&buf2) == 0);
+ cl_assert_equal_s(git_buf_cstr(&buf2), "shoop da 23 woop 42");
+ git_buf_free(&buf2);
+}
diff --git a/tests/buf/splice.c b/tests/buf/splice.c
new file mode 100644
index 000000000..e80c93105
--- /dev/null
+++ b/tests/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/checkout/binaryunicode.c b/tests/checkout/binaryunicode.c
new file mode 100644
index 000000000..1172816c7
--- /dev/null
+++ b/tests/checkout/binaryunicode.c
@@ -0,0 +1,58 @@
+#include "clar_libgit2.h"
+#include "refs.h"
+#include "repo/repo_helpers.h"
+#include "path.h"
+#include "fileops.h"
+
+static git_repository *g_repo;
+
+void test_checkout_binaryunicode__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("binaryunicode");
+}
+
+void test_checkout_binaryunicode__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+static void execute_test(void)
+{
+ git_oid oid, check;
+ git_commit *commit;
+ git_tree *tree;
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/branch1"));
+ cl_git_pass(git_commit_lookup(&commit, g_repo, &oid));
+ cl_git_pass(git_commit_tree(&tree, commit));
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE;
+
+ cl_git_pass(git_checkout_tree(g_repo, (git_object *)tree, &opts));
+
+ git_tree_free(tree);
+ git_commit_free(commit);
+
+ /* Verify that the lenna.jpg file was checked out correctly */
+ cl_git_pass(git_oid_fromstr(&check, "8ab005d890fe53f65eda14b23672f60d9f4ec5a1"));
+ cl_git_pass(git_odb_hashfile(&oid, "binaryunicode/lenna.jpg", GIT_OBJ_BLOB));
+ cl_assert(git_oid_equal(&oid, &check));
+
+ /* Verify that the text file was checked out correctly */
+ cl_git_pass(git_oid_fromstr(&check, "965b223880dd4249e2c66a0cc0b4cffe1dc40f5a"));
+ cl_git_pass(git_odb_hashfile(&oid, "binaryunicode/utf16_withbom_noeol_crlf.txt", GIT_OBJ_BLOB));
+ cl_assert(git_oid_equal(&oid, &check));
+}
+
+void test_checkout_binaryunicode__noautocrlf(void)
+{
+ cl_repo_set_bool(g_repo, "core.autocrlf", false);
+ execute_test();
+}
+
+void test_checkout_binaryunicode__autocrlf(void)
+{
+ cl_repo_set_bool(g_repo, "core.autocrlf", true);
+ execute_test();
+}
diff --git a/tests/checkout/checkout_helpers.c b/tests/checkout/checkout_helpers.c
new file mode 100644
index 000000000..06b4e0682
--- /dev/null
+++ b/tests/checkout/checkout_helpers.c
@@ -0,0 +1,130 @@
+#include "clar_libgit2.h"
+#include "checkout_helpers.h"
+#include "refs.h"
+#include "fileops.h"
+
+void assert_on_branch(git_repository *repo, const char *branch)
+{
+ git_reference *head;
+ git_buf bname = GIT_BUF_INIT;
+
+ cl_git_pass(git_reference_lookup(&head, repo, GIT_HEAD_FILE));
+ cl_assert_(git_reference_type(head) == GIT_REF_SYMBOLIC, branch);
+
+ cl_git_pass(git_buf_joinpath(&bname, "refs/heads", branch));
+ cl_assert_equal_s(bname.ptr, git_reference_symbolic_target(head));
+
+ git_reference_free(head);
+ git_buf_free(&bname);
+}
+
+void reset_index_to_treeish(git_object *treeish)
+{
+ git_object *tree;
+ git_index *index;
+ git_repository *repo = git_object_owner(treeish);
+
+ cl_git_pass(git_object_peel(&tree, treeish, GIT_OBJ_TREE));
+
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_git_pass(git_index_read_tree(index, (git_tree *)tree));
+ cl_git_pass(git_index_write(index));
+
+ git_object_free(tree);
+ git_index_free(index);
+}
+
+int checkout_count_callback(
+ git_checkout_notify_t why,
+ const char *path,
+ const git_diff_file *baseline,
+ const git_diff_file *target,
+ const git_diff_file *workdir,
+ void *payload)
+{
+ checkout_counts *ct = payload;
+
+ GIT_UNUSED(baseline); GIT_UNUSED(target); GIT_UNUSED(workdir);
+
+ if (why & GIT_CHECKOUT_NOTIFY_CONFLICT) {
+ ct->n_conflicts++;
+
+ if (ct->debug) {
+ if (workdir) {
+ if (baseline) {
+ if (target)
+ fprintf(stderr, "M %s (conflicts with M %s)\n",
+ workdir->path, target->path);
+ else
+ fprintf(stderr, "M %s (conflicts with D %s)\n",
+ workdir->path, baseline->path);
+ } else {
+ if (target)
+ fprintf(stderr, "Existing %s (conflicts with A %s)\n",
+ workdir->path, target->path);
+ else
+ fprintf(stderr, "How can an untracked file be a conflict (%s)\n", workdir->path);
+ }
+ } else {
+ if (baseline) {
+ if (target)
+ fprintf(stderr, "D %s (conflicts with M %s)\n",
+ target->path, baseline->path);
+ else
+ fprintf(stderr, "D %s (conflicts with D %s)\n",
+ baseline->path, baseline->path);
+ } else {
+ if (target)
+ fprintf(stderr, "How can an added file with no workdir be a conflict (%s)\n", target->path);
+ else
+ fprintf(stderr, "How can a nonexistent file be a conflict (%s)\n", path);
+ }
+ }
+ }
+ }
+
+ if (why & GIT_CHECKOUT_NOTIFY_DIRTY) {
+ ct->n_dirty++;
+
+ if (ct->debug) {
+ if (workdir)
+ fprintf(stderr, "M %s\n", workdir->path);
+ else
+ fprintf(stderr, "D %s\n", baseline->path);
+ }
+ }
+
+ if (why & GIT_CHECKOUT_NOTIFY_UPDATED) {
+ ct->n_updates++;
+
+ if (ct->debug) {
+ if (baseline) {
+ if (target)
+ fprintf(stderr, "update: M %s\n", path);
+ else
+ fprintf(stderr, "update: D %s\n", path);
+ } else {
+ if (target)
+ fprintf(stderr, "update: A %s\n", path);
+ else
+ fprintf(stderr, "update: this makes no sense %s\n", path);
+ }
+ }
+ }
+
+ if (why & GIT_CHECKOUT_NOTIFY_UNTRACKED) {
+ ct->n_untracked++;
+
+ if (ct->debug)
+ fprintf(stderr, "? %s\n", path);
+ }
+
+ if (why & GIT_CHECKOUT_NOTIFY_IGNORED) {
+ ct->n_ignored++;
+
+ if (ct->debug)
+ fprintf(stderr, "I %s\n", path);
+ }
+
+ return 0;
+}
diff --git a/tests/checkout/checkout_helpers.h b/tests/checkout/checkout_helpers.h
new file mode 100644
index 000000000..705ee903d
--- /dev/null
+++ b/tests/checkout/checkout_helpers.h
@@ -0,0 +1,29 @@
+#include "buffer.h"
+#include "git2/object.h"
+#include "git2/repository.h"
+
+extern void assert_on_branch(git_repository *repo, const char *branch);
+extern void reset_index_to_treeish(git_object *treeish);
+
+#define check_file_contents(PATH,EXP) \
+ cl_assert_equal_file(EXP,0,PATH)
+
+#define check_file_contents_nocr(PATH,EXP) \
+ cl_assert_equal_file_ignore_cr(EXP,0,PATH)
+
+typedef struct {
+ int n_conflicts;
+ int n_dirty;
+ int n_updates;
+ int n_untracked;
+ int n_ignored;
+ int debug;
+} checkout_counts;
+
+extern int checkout_count_callback(
+ git_checkout_notify_t why,
+ const char *path,
+ const git_diff_file *baseline,
+ const git_diff_file *target,
+ const git_diff_file *workdir,
+ void *payload);
diff --git a/tests/checkout/conflict.c b/tests/checkout/conflict.c
new file mode 100644
index 000000000..2cb7c224d
--- /dev/null
+++ b/tests/checkout/conflict.c
@@ -0,0 +1,1134 @@
+#include "clar_libgit2.h"
+#include "git2/repository.h"
+#include "git2/sys/index.h"
+#include "fileops.h"
+
+static git_repository *g_repo;
+static git_index *g_index;
+
+#define TEST_REPO_PATH "merge-resolve"
+
+#define CONFLICTING_ANCESTOR_OID "d427e0b2e138501a3d15cc376077a3631e15bd46"
+#define CONFLICTING_OURS_OID "4e886e602529caa9ab11d71f86634bd1b6e0de10"
+#define CONFLICTING_THEIRS_OID "2bd0a343aeef7a2cf0d158478966a6e587ff3863"
+
+#define AUTOMERGEABLE_ANCESTOR_OID "6212c31dab5e482247d7977e4f0dd3601decf13b"
+#define AUTOMERGEABLE_OURS_OID "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"
+#define AUTOMERGEABLE_THEIRS_OID "058541fc37114bfc1dddf6bd6bffc7fae5c2e6fe"
+
+#define LINK_ANCESTOR_OID "1a010b1c0f081b2e8901d55307a15c29ff30af0e"
+#define LINK_OURS_OID "72ea499e108df5ff0a4a913e7655bbeeb1fb69f2"
+#define LINK_THEIRS_OID "8bfb012a6d809e499bd8d3e194a3929bc8995b93"
+
+#define LINK_ANCESTOR_TARGET "file"
+#define LINK_OURS_TARGET "other-file"
+#define LINK_THEIRS_TARGET "still-another-file"
+
+#define CONFLICTING_OURS_FILE \
+ "this file is changed in master and branch\n"
+#define CONFLICTING_THEIRS_FILE \
+ "this file is changed in branch and master\n"
+#define CONFLICTING_DIFF3_FILE \
+ "<<<<<<< ours\n" \
+ "this file is changed in master and branch\n" \
+ "=======\n" \
+ "this file is changed in branch and master\n" \
+ ">>>>>>> theirs\n"
+
+#define AUTOMERGEABLE_MERGED_FILE \
+ "this file is changed in master\n" \
+ "this file is automergeable\n" \
+ "this file is automergeable\n" \
+ "this file is automergeable\n" \
+ "this file is automergeable\n" \
+ "this file is automergeable\n" \
+ "this file is automergeable\n" \
+ "this file is automergeable\n" \
+ "this file is changed in branch\n"
+
+struct checkout_index_entry {
+ uint16_t mode;
+ char oid_str[41];
+ int stage;
+ char path[128];
+};
+
+struct checkout_name_entry {
+ char ancestor[64];
+ char ours[64];
+ char theirs[64];
+};
+
+void test_checkout_conflict__initialize(void)
+{
+ git_config *cfg;
+
+ g_repo = cl_git_sandbox_init(TEST_REPO_PATH);
+ git_repository_index(&g_index, g_repo);
+
+ cl_git_rewritefile(
+ TEST_REPO_PATH "/.gitattributes",
+ "* text eol=lf\n");
+
+ /* Ensure that the user's merge.conflictstyle doesn't interfere */
+ cl_git_pass(git_repository_config(&cfg, g_repo));
+ cl_git_pass(git_config_set_string(cfg, "merge.conflictstyle", "merge"));
+ git_config_free(cfg);
+}
+
+void test_checkout_conflict__cleanup(void)
+{
+ git_index_free(g_index);
+ cl_git_sandbox_cleanup();
+}
+
+static void create_index(struct checkout_index_entry *entries, size_t entries_len)
+{
+ git_buf path = GIT_BUF_INIT;
+ size_t i;
+
+ for (i = 0; i < entries_len; i++) {
+ git_buf_joinpath(&path, TEST_REPO_PATH, entries[i].path);
+
+ if (entries[i].stage == 3 && (i == 0 || strcmp(entries[i-1].path, entries[i].path) != 0 || entries[i-1].stage != 2))
+ p_unlink(git_buf_cstr(&path));
+
+ git_index_remove_bypath(g_index, entries[i].path);
+ }
+
+ for (i = 0; i < entries_len; i++) {
+ git_index_entry entry;
+
+ memset(&entry, 0x0, sizeof(git_index_entry));
+
+ entry.mode = entries[i].mode;
+ entry.flags = entries[i].stage << GIT_IDXENTRY_STAGESHIFT;
+ git_oid_fromstr(&entry.id, entries[i].oid_str);
+ entry.path = entries[i].path;
+
+ cl_git_pass(git_index_add(g_index, &entry));
+ }
+
+ git_buf_free(&path);
+}
+
+static void create_index_names(struct checkout_name_entry *entries, size_t entries_len)
+{
+ size_t i;
+
+ for (i = 0; i < entries_len; i++) {
+ cl_git_pass(git_index_name_add(g_index,
+ strlen(entries[i].ancestor) == 0 ? NULL : entries[i].ancestor,
+ strlen(entries[i].ours) == 0 ? NULL : entries[i].ours,
+ strlen(entries[i].theirs) == 0 ? NULL : entries[i].theirs));
+ }
+}
+
+static void create_conflicting_index(void)
+{
+ struct checkout_index_entry checkout_index_entries[] = {
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting.txt" },
+ { 0100644, CONFLICTING_OURS_OID, 2, "conflicting.txt" },
+ { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting.txt" },
+ };
+
+ create_index(checkout_index_entries, 3);
+ git_index_write(g_index);
+}
+
+static void ensure_workdir_contents(const char *path, const char *contents)
+{
+ git_buf fullpath = GIT_BUF_INIT, data_buf = GIT_BUF_INIT;
+
+ cl_git_pass(
+ git_buf_joinpath(&fullpath, git_repository_workdir(g_repo), path));
+
+ cl_git_pass(git_futils_readbuffer(&data_buf, git_buf_cstr(&fullpath)));
+ cl_assert(strcmp(git_buf_cstr(&data_buf), contents) == 0);
+
+ git_buf_free(&fullpath);
+ git_buf_free(&data_buf);
+}
+
+static void ensure_workdir_oid(const char *path, const char *oid_str)
+{
+ git_oid expected, actual;
+
+ cl_git_pass(git_oid_fromstr(&expected, oid_str));
+ cl_git_pass(git_repository_hashfile(&actual, g_repo, path, GIT_OBJ_BLOB, NULL));
+ cl_assert(git_oid_cmp(&expected, &actual) == 0);
+}
+
+static void ensure_workdir_mode(const char *path, int mode)
+{
+#ifndef GIT_WIN32
+ git_buf fullpath = GIT_BUF_INIT;
+ struct stat st;
+
+ cl_git_pass(
+ git_buf_joinpath(&fullpath, git_repository_workdir(g_repo), path));
+
+ cl_git_pass(p_stat(git_buf_cstr(&fullpath), &st));
+ cl_assert_equal_i(mode, st.st_mode);
+
+ git_buf_free(&fullpath);
+#endif
+}
+
+static void ensure_workdir(const char *path, int mode, const char *oid_str)
+{
+ ensure_workdir_mode(path, mode);
+ ensure_workdir_oid(path, oid_str);
+}
+
+static void ensure_workdir_link(const char *path, const char *target)
+{
+#ifdef GIT_WIN32
+ ensure_workdir_contents(path, target);
+#else
+ git_buf fullpath = GIT_BUF_INIT;
+ char actual[1024];
+ struct stat st;
+ int len;
+
+ cl_git_pass(
+ git_buf_joinpath(&fullpath, git_repository_workdir(g_repo), path));
+
+ cl_git_pass(p_lstat(git_buf_cstr(&fullpath), &st));
+ cl_assert(S_ISLNK(st.st_mode));
+
+ cl_assert((len = p_readlink(git_buf_cstr(&fullpath), actual, 1024)) > 0);
+ actual[len] = '\0';
+ cl_assert(strcmp(actual, target) == 0);
+
+ git_buf_free(&fullpath);
+#endif
+}
+
+void test_checkout_conflict__ignored(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ opts.checkout_strategy |= GIT_CHECKOUT_SKIP_UNMERGED;
+
+ create_conflicting_index();
+ cl_git_pass(p_unlink(TEST_REPO_PATH "/conflicting.txt"));
+
+ cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
+
+ cl_assert(!git_path_exists(TEST_REPO_PATH "/conflicting.txt"));
+}
+
+void test_checkout_conflict__ours(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ opts.checkout_strategy |= GIT_CHECKOUT_USE_OURS;
+
+ create_conflicting_index();
+
+ cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
+
+ ensure_workdir_contents("conflicting.txt", CONFLICTING_OURS_FILE);
+}
+
+void test_checkout_conflict__theirs(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ opts.checkout_strategy |= GIT_CHECKOUT_USE_THEIRS;
+
+ create_conflicting_index();
+
+ cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
+
+ ensure_workdir_contents("conflicting.txt", CONFLICTING_THEIRS_FILE);
+
+}
+
+void test_checkout_conflict__diff3(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ create_conflicting_index();
+
+ cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
+
+ ensure_workdir_contents("conflicting.txt", CONFLICTING_DIFF3_FILE);
+}
+
+void test_checkout_conflict__automerge(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ struct checkout_index_entry checkout_index_entries[] = {
+ { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "automergeable.txt" },
+ { 0100644, AUTOMERGEABLE_OURS_OID, 2, "automergeable.txt" },
+ { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "automergeable.txt" },
+ };
+
+ create_index(checkout_index_entries, 3);
+ git_index_write(g_index);
+
+ cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
+
+ ensure_workdir_contents("automergeable.txt", AUTOMERGEABLE_MERGED_FILE);
+}
+
+void test_checkout_conflict__directory_file(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ struct checkout_index_entry checkout_index_entries[] = {
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-1" },
+ { 0100644, CONFLICTING_OURS_OID, 2, "df-1" },
+ { 0100644, CONFLICTING_THEIRS_OID, 0, "df-1/file" },
+
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-2" },
+ { 0100644, CONFLICTING_THEIRS_OID, 3, "df-2" },
+ { 0100644, CONFLICTING_OURS_OID, 0, "df-2/file" },
+
+ { 0100644, CONFLICTING_THEIRS_OID, 3, "df-3" },
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-3/file" },
+ { 0100644, CONFLICTING_OURS_OID, 2, "df-3/file" },
+
+ { 0100644, CONFLICTING_OURS_OID, 2, "df-4" },
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-4/file" },
+ { 0100644, CONFLICTING_THEIRS_OID, 3, "df-4/file" },
+ };
+
+ opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
+
+ create_index(checkout_index_entries, 12);
+ git_index_write(g_index);
+
+ cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
+
+ ensure_workdir_oid("df-1/file", CONFLICTING_THEIRS_OID);
+ ensure_workdir_oid("df-1~ours", CONFLICTING_OURS_OID);
+ ensure_workdir_oid("df-2/file", CONFLICTING_OURS_OID);
+ ensure_workdir_oid("df-2~theirs", CONFLICTING_THEIRS_OID);
+ ensure_workdir_oid("df-3/file", CONFLICTING_OURS_OID);
+ ensure_workdir_oid("df-3~theirs", CONFLICTING_THEIRS_OID);
+ ensure_workdir_oid("df-4~ours", CONFLICTING_OURS_OID);
+ ensure_workdir_oid("df-4/file", CONFLICTING_THEIRS_OID);
+}
+
+void test_checkout_conflict__directory_file_with_custom_labels(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ struct checkout_index_entry checkout_index_entries[] = {
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-1" },
+ { 0100644, CONFLICTING_OURS_OID, 2, "df-1" },
+ { 0100644, CONFLICTING_THEIRS_OID, 0, "df-1/file" },
+
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-2" },
+ { 0100644, CONFLICTING_THEIRS_OID, 3, "df-2" },
+ { 0100644, CONFLICTING_OURS_OID, 0, "df-2/file" },
+
+ { 0100644, CONFLICTING_THEIRS_OID, 3, "df-3" },
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-3/file" },
+ { 0100644, CONFLICTING_OURS_OID, 2, "df-3/file" },
+
+ { 0100644, CONFLICTING_OURS_OID, 2, "df-4" },
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "df-4/file" },
+ { 0100644, CONFLICTING_THEIRS_OID, 3, "df-4/file" },
+ };
+
+ opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
+ opts.our_label = "HEAD";
+ opts.their_label = "branch";
+
+ create_index(checkout_index_entries, 12);
+ git_index_write(g_index);
+
+ cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
+
+ ensure_workdir_oid("df-1/file", CONFLICTING_THEIRS_OID);
+ ensure_workdir_oid("df-1~HEAD", CONFLICTING_OURS_OID);
+ ensure_workdir_oid("df-2/file", CONFLICTING_OURS_OID);
+ ensure_workdir_oid("df-2~branch", CONFLICTING_THEIRS_OID);
+ ensure_workdir_oid("df-3/file", CONFLICTING_OURS_OID);
+ ensure_workdir_oid("df-3~branch", CONFLICTING_THEIRS_OID);
+ ensure_workdir_oid("df-4~HEAD", CONFLICTING_OURS_OID);
+ ensure_workdir_oid("df-4/file", CONFLICTING_THEIRS_OID);
+}
+
+void test_checkout_conflict__link_file(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ struct checkout_index_entry checkout_index_entries[] = {
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "link-1" },
+ { 0100644, CONFLICTING_OURS_OID, 2, "link-1" },
+ { 0120000, LINK_THEIRS_OID, 3, "link-1" },
+
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "link-2" },
+ { 0120000, LINK_OURS_OID, 2, "link-2" },
+ { 0100644, CONFLICTING_THEIRS_OID, 3, "link-2" },
+
+ { 0120000, LINK_ANCESTOR_OID, 1, "link-3" },
+ { 0100644, CONFLICTING_OURS_OID, 2, "link-3" },
+ { 0120000, LINK_THEIRS_OID, 3, "link-3" },
+
+ { 0120000, LINK_ANCESTOR_OID, 1, "link-4" },
+ { 0120000, LINK_OURS_OID, 2, "link-4" },
+ { 0100644, CONFLICTING_THEIRS_OID, 3, "link-4" },
+ };
+
+ opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
+
+ create_index(checkout_index_entries, 12);
+ git_index_write(g_index);
+
+ cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
+
+ /* Typechange conflicts always keep the file in the workdir */
+ ensure_workdir_oid("link-1", CONFLICTING_OURS_OID);
+ ensure_workdir_oid("link-2", CONFLICTING_THEIRS_OID);
+ ensure_workdir_oid("link-3", CONFLICTING_OURS_OID);
+ ensure_workdir_oid("link-4", CONFLICTING_THEIRS_OID);
+}
+
+void test_checkout_conflict__links(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ struct checkout_index_entry checkout_index_entries[] = {
+ { 0120000, LINK_ANCESTOR_OID, 1, "link-1" },
+ { 0120000, LINK_OURS_OID, 2, "link-1" },
+ { 0120000, LINK_THEIRS_OID, 3, "link-1" },
+
+ { 0120000, LINK_OURS_OID, 2, "link-2" },
+ { 0120000, LINK_THEIRS_OID, 3, "link-2" },
+ };
+
+ opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
+
+ create_index(checkout_index_entries, 5);
+ git_index_write(g_index);
+
+ cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
+
+ /* Conflicts with links always keep the ours side (even with -Xtheirs) */
+ ensure_workdir_link("link-1", LINK_OURS_TARGET);
+ ensure_workdir_link("link-2", LINK_OURS_TARGET);
+}
+
+void test_checkout_conflict__add_add(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ struct checkout_index_entry checkout_index_entries[] = {
+ { 0100644, CONFLICTING_OURS_OID, 2, "conflicting.txt" },
+ { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting.txt" },
+ };
+
+ opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
+
+ create_index(checkout_index_entries, 2);
+ git_index_write(g_index);
+
+ cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
+
+ /* Add/add writes diff3 files */
+ ensure_workdir_contents("conflicting.txt", CONFLICTING_DIFF3_FILE);
+}
+
+void test_checkout_conflict__mode_change(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ struct checkout_index_entry checkout_index_entries[] = {
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "executable-1" },
+ { 0100755, CONFLICTING_ANCESTOR_OID, 2, "executable-1" },
+ { 0100644, CONFLICTING_THEIRS_OID, 3, "executable-1" },
+
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "executable-2" },
+ { 0100644, CONFLICTING_OURS_OID, 2, "executable-2" },
+ { 0100755, CONFLICTING_ANCESTOR_OID, 3, "executable-2" },
+
+ { 0100755, CONFLICTING_ANCESTOR_OID, 1, "executable-3" },
+ { 0100644, CONFLICTING_ANCESTOR_OID, 2, "executable-3" },
+ { 0100755, CONFLICTING_THEIRS_OID, 3, "executable-3" },
+
+ { 0100755, CONFLICTING_ANCESTOR_OID, 1, "executable-4" },
+ { 0100755, CONFLICTING_OURS_OID, 2, "executable-4" },
+ { 0100644, CONFLICTING_ANCESTOR_OID, 3, "executable-4" },
+
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "executable-5" },
+ { 0100755, CONFLICTING_OURS_OID, 2, "executable-5" },
+ { 0100644, CONFLICTING_THEIRS_OID, 3, "executable-5" },
+
+ { 0100755, CONFLICTING_ANCESTOR_OID, 1, "executable-6" },
+ { 0100644, CONFLICTING_OURS_OID, 2, "executable-6" },
+ { 0100755, CONFLICTING_THEIRS_OID, 3, "executable-6" },
+ };
+
+ opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
+
+ create_index(checkout_index_entries, 18);
+ git_index_write(g_index);
+
+ cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
+
+ /* Keep the modified mode */
+ ensure_workdir_oid("executable-1", CONFLICTING_THEIRS_OID);
+ ensure_workdir_mode("executable-1", 0100755);
+
+ ensure_workdir_oid("executable-2", CONFLICTING_OURS_OID);
+ ensure_workdir_mode("executable-2", 0100755);
+
+ ensure_workdir_oid("executable-3", CONFLICTING_THEIRS_OID);
+ ensure_workdir_mode("executable-3", 0100644);
+
+ ensure_workdir_oid("executable-4", CONFLICTING_OURS_OID);
+ ensure_workdir_mode("executable-4", 0100644);
+
+ ensure_workdir_contents("executable-5", CONFLICTING_DIFF3_FILE);
+ ensure_workdir_mode("executable-5", 0100755);
+
+ ensure_workdir_contents("executable-6", CONFLICTING_DIFF3_FILE);
+ ensure_workdir_mode("executable-6", 0100644);
+}
+
+void test_checkout_conflict__renames(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ struct checkout_index_entry checkout_index_entries[] = {
+ { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" },
+ { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" },
+ { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 1, "0b-rewritten-in-ours.txt" },
+ { 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 2, "0b-rewritten-in-ours.txt" },
+ { 0100644, "b2d399ae15224e1d58066e3c8df70ce37de7a656", 3, "0b-rewritten-in-ours.txt" },
+ { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" },
+ { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 1, "0c-rewritten-in-theirs.txt" },
+ { 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 2, "0c-rewritten-in-theirs.txt" },
+ { 0100644, "712ebba6669ea847d9829e4f1059d6c830c8b531", 3, "0c-rewritten-in-theirs.txt" },
+ { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" },
+ { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" },
+ { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" },
+ { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" },
+ { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" },
+ { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 2, "3a-newname-in-ours-deleted-in-theirs.txt" },
+ { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 1, "3a-renamed-in-ours-deleted-in-theirs.txt" },
+ { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 3, "3b-newname-in-theirs-deleted-in-ours.txt" },
+ { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 1, "3b-renamed-in-theirs-deleted-in-ours.txt" },
+ { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 2, "4a-newname-in-ours-added-in-theirs.txt" },
+ { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 3, "4a-newname-in-ours-added-in-theirs.txt" },
+ { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 1, "4a-renamed-in-ours-added-in-theirs.txt" },
+ { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 2, "4b-newname-in-theirs-added-in-ours.txt" },
+ { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 3, "4b-newname-in-theirs-added-in-ours.txt" },
+ { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 1, "4b-renamed-in-theirs-added-in-ours.txt" },
+ { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 2, "5a-newname-in-ours-added-in-theirs.txt" },
+ { 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 3, "5a-newname-in-ours-added-in-theirs.txt" },
+ { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 1, "5a-renamed-in-ours-added-in-theirs.txt" },
+ { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 3, "5a-renamed-in-ours-added-in-theirs.txt" },
+ { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 2, "5b-newname-in-theirs-added-in-ours.txt" },
+ { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 3, "5b-newname-in-theirs-added-in-ours.txt" },
+ { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 1, "5b-renamed-in-theirs-added-in-ours.txt" },
+ { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 2, "5b-renamed-in-theirs-added-in-ours.txt" },
+ { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 2, "6-both-renamed-1-to-2-ours.txt" },
+ { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 3, "6-both-renamed-1-to-2-theirs.txt" },
+ { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 1, "6-both-renamed-1-to-2.txt" },
+ { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "7-both-renamed-side-1.txt" },
+ { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "7-both-renamed-side-1.txt" },
+ { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "7-both-renamed-side-2.txt" },
+ { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "7-both-renamed-side-2.txt" },
+ { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "7-both-renamed.txt" },
+ { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "7-both-renamed.txt" }
+ };
+
+ struct checkout_name_entry checkout_name_entries[] = {
+ {
+ "3a-renamed-in-ours-deleted-in-theirs.txt",
+ "3a-newname-in-ours-deleted-in-theirs.txt",
+ ""
+ },
+
+ {
+ "3b-renamed-in-theirs-deleted-in-ours.txt",
+ "",
+ "3b-newname-in-theirs-deleted-in-ours.txt"
+ },
+
+ {
+ "4a-renamed-in-ours-added-in-theirs.txt",
+ "4a-newname-in-ours-added-in-theirs.txt",
+ ""
+ },
+
+ {
+ "4b-renamed-in-theirs-added-in-ours.txt",
+ "",
+ "4b-newname-in-theirs-added-in-ours.txt"
+ },
+
+ {
+ "5a-renamed-in-ours-added-in-theirs.txt",
+ "5a-newname-in-ours-added-in-theirs.txt",
+ "5a-renamed-in-ours-added-in-theirs.txt"
+ },
+
+ {
+ "5b-renamed-in-theirs-added-in-ours.txt",
+ "5b-renamed-in-theirs-added-in-ours.txt",
+ "5b-newname-in-theirs-added-in-ours.txt"
+ },
+
+ {
+ "6-both-renamed-1-to-2.txt",
+ "6-both-renamed-1-to-2-ours.txt",
+ "6-both-renamed-1-to-2-theirs.txt"
+ },
+
+ {
+ "7-both-renamed-side-1.txt",
+ "7-both-renamed.txt",
+ "7-both-renamed-side-1.txt"
+ },
+
+ {
+ "7-both-renamed-side-2.txt",
+ "7-both-renamed-side-2.txt",
+ "7-both-renamed.txt"
+ }
+ };
+
+ opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
+
+ create_index(checkout_index_entries, 41);
+ create_index_names(checkout_name_entries, 9);
+ git_index_write(g_index);
+
+ cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
+
+ ensure_workdir("0a-no-change.txt",
+ 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e");
+
+ ensure_workdir("0b-duplicated-in-ours.txt",
+ 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6");
+
+ ensure_workdir("0b-rewritten-in-ours.txt",
+ 0100644, "4c7e515d6d52d820496858f2f059ece69e99e2e3");
+
+ ensure_workdir("0c-duplicated-in-theirs.txt",
+ 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31");
+
+ ensure_workdir("0c-rewritten-in-theirs.txt",
+ 0100644, "4648d658682d1155c2a3db5b0c53305e26884ea5");
+
+ ensure_workdir("1a-newname-in-ours-edited-in-theirs.txt",
+ 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638");
+
+ ensure_workdir("1a-newname-in-ours.txt",
+ 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb");
+
+ ensure_workdir("1b-newname-in-theirs-edited-in-ours.txt",
+ 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a");
+
+ ensure_workdir("1b-newname-in-theirs.txt",
+ 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136");
+
+ ensure_workdir("2-newname-in-both.txt",
+ 0100644, "178940b450f238a56c0d75b7955cb57b38191982");
+
+ ensure_workdir("3a-newname-in-ours-deleted-in-theirs.txt",
+ 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9");
+
+ ensure_workdir("3b-newname-in-theirs-deleted-in-ours.txt",
+ 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495");
+
+ ensure_workdir("4a-newname-in-ours-added-in-theirs.txt~ours",
+ 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c");
+
+ ensure_workdir("4a-newname-in-ours-added-in-theirs.txt~theirs",
+ 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a");
+
+ ensure_workdir("4b-newname-in-theirs-added-in-ours.txt~ours",
+ 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9");
+
+ ensure_workdir("4b-newname-in-theirs-added-in-ours.txt~theirs",
+ 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db");
+
+ ensure_workdir("5a-newname-in-ours-added-in-theirs.txt~ours",
+ 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436");
+
+ ensure_workdir("5a-newname-in-ours-added-in-theirs.txt~theirs",
+ 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714");
+
+ ensure_workdir("5b-newname-in-theirs-added-in-ours.txt~ours",
+ 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced");
+
+ ensure_workdir("5b-newname-in-theirs-added-in-ours.txt~theirs",
+ 0100644, "63247125386de9ec90a27ad36169307bf8a11a38");
+
+ ensure_workdir("6-both-renamed-1-to-2-ours.txt",
+ 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450");
+
+ ensure_workdir("6-both-renamed-1-to-2-theirs.txt",
+ 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450");
+
+ ensure_workdir("7-both-renamed.txt~ours",
+ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11");
+
+ ensure_workdir("7-both-renamed.txt~theirs",
+ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07");
+}
+
+void test_checkout_conflict__rename_keep_ours(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ struct checkout_index_entry checkout_index_entries[] = {
+ { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" },
+ { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" },
+ { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 1, "0b-rewritten-in-ours.txt" },
+ { 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 2, "0b-rewritten-in-ours.txt" },
+ { 0100644, "b2d399ae15224e1d58066e3c8df70ce37de7a656", 3, "0b-rewritten-in-ours.txt" },
+ { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" },
+ { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 1, "0c-rewritten-in-theirs.txt" },
+ { 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 2, "0c-rewritten-in-theirs.txt" },
+ { 0100644, "712ebba6669ea847d9829e4f1059d6c830c8b531", 3, "0c-rewritten-in-theirs.txt" },
+ { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" },
+ { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" },
+ { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" },
+ { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" },
+ { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" },
+ { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 2, "3a-newname-in-ours-deleted-in-theirs.txt" },
+ { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 1, "3a-renamed-in-ours-deleted-in-theirs.txt" },
+ { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 3, "3b-newname-in-theirs-deleted-in-ours.txt" },
+ { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 1, "3b-renamed-in-theirs-deleted-in-ours.txt" },
+ { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 2, "4a-newname-in-ours-added-in-theirs.txt" },
+ { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 3, "4a-newname-in-ours-added-in-theirs.txt" },
+ { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 1, "4a-renamed-in-ours-added-in-theirs.txt" },
+ { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 2, "4b-newname-in-theirs-added-in-ours.txt" },
+ { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 3, "4b-newname-in-theirs-added-in-ours.txt" },
+ { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 1, "4b-renamed-in-theirs-added-in-ours.txt" },
+ { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 2, "5a-newname-in-ours-added-in-theirs.txt" },
+ { 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 3, "5a-newname-in-ours-added-in-theirs.txt" },
+ { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 1, "5a-renamed-in-ours-added-in-theirs.txt" },
+ { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 3, "5a-renamed-in-ours-added-in-theirs.txt" },
+ { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 2, "5b-newname-in-theirs-added-in-ours.txt" },
+ { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 3, "5b-newname-in-theirs-added-in-ours.txt" },
+ { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 1, "5b-renamed-in-theirs-added-in-ours.txt" },
+ { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 2, "5b-renamed-in-theirs-added-in-ours.txt" },
+ { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 2, "6-both-renamed-1-to-2-ours.txt" },
+ { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 3, "6-both-renamed-1-to-2-theirs.txt" },
+ { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 1, "6-both-renamed-1-to-2.txt" },
+ { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "7-both-renamed-side-1.txt" },
+ { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "7-both-renamed-side-1.txt" },
+ { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "7-both-renamed-side-2.txt" },
+ { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "7-both-renamed-side-2.txt" },
+ { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "7-both-renamed.txt" },
+ { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "7-both-renamed.txt" }
+ };
+
+ struct checkout_name_entry checkout_name_entries[] = {
+ {
+ "3a-renamed-in-ours-deleted-in-theirs.txt",
+ "3a-newname-in-ours-deleted-in-theirs.txt",
+ ""
+ },
+
+ {
+ "3b-renamed-in-theirs-deleted-in-ours.txt",
+ "",
+ "3b-newname-in-theirs-deleted-in-ours.txt"
+ },
+
+ {
+ "4a-renamed-in-ours-added-in-theirs.txt",
+ "4a-newname-in-ours-added-in-theirs.txt",
+ ""
+ },
+
+ {
+ "4b-renamed-in-theirs-added-in-ours.txt",
+ "",
+ "4b-newname-in-theirs-added-in-ours.txt"
+ },
+
+ {
+ "5a-renamed-in-ours-added-in-theirs.txt",
+ "5a-newname-in-ours-added-in-theirs.txt",
+ "5a-renamed-in-ours-added-in-theirs.txt"
+ },
+
+ {
+ "5b-renamed-in-theirs-added-in-ours.txt",
+ "5b-renamed-in-theirs-added-in-ours.txt",
+ "5b-newname-in-theirs-added-in-ours.txt"
+ },
+
+ {
+ "6-both-renamed-1-to-2.txt",
+ "6-both-renamed-1-to-2-ours.txt",
+ "6-both-renamed-1-to-2-theirs.txt"
+ },
+
+ {
+ "7-both-renamed-side-1.txt",
+ "7-both-renamed.txt",
+ "7-both-renamed-side-1.txt"
+ },
+
+ {
+ "7-both-renamed-side-2.txt",
+ "7-both-renamed-side-2.txt",
+ "7-both-renamed.txt"
+ }
+ };
+
+ opts.checkout_strategy |= GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS;
+
+ create_index(checkout_index_entries, 41);
+ create_index_names(checkout_name_entries, 9);
+ git_index_write(g_index);
+
+ cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
+
+ ensure_workdir("0a-no-change.txt",
+ 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e");
+
+ ensure_workdir("0b-duplicated-in-ours.txt",
+ 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6");
+
+ ensure_workdir("0b-rewritten-in-ours.txt",
+ 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e");
+
+ ensure_workdir("0c-duplicated-in-theirs.txt",
+ 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31");
+
+ ensure_workdir("0c-rewritten-in-theirs.txt",
+ 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09");
+
+ ensure_workdir("1a-newname-in-ours-edited-in-theirs.txt",
+ 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638");
+
+ ensure_workdir("1a-newname-in-ours.txt",
+ 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb");
+
+ ensure_workdir("1b-newname-in-theirs-edited-in-ours.txt",
+ 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a");
+
+ ensure_workdir("1b-newname-in-theirs.txt",
+ 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136");
+
+ ensure_workdir("2-newname-in-both.txt",
+ 0100644, "178940b450f238a56c0d75b7955cb57b38191982");
+
+ ensure_workdir("3a-newname-in-ours-deleted-in-theirs.txt",
+ 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9");
+
+ ensure_workdir("3b-newname-in-theirs-deleted-in-ours.txt",
+ 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495");
+
+ ensure_workdir("4a-newname-in-ours-added-in-theirs.txt",
+ 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c");
+
+ ensure_workdir("4b-newname-in-theirs-added-in-ours.txt",
+ 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9");
+
+ ensure_workdir("5a-newname-in-ours-added-in-theirs.txt",
+ 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436");
+
+ ensure_workdir("5b-newname-in-theirs-added-in-ours.txt",
+ 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced");
+
+ ensure_workdir("6-both-renamed-1-to-2-ours.txt",
+ 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450");
+
+ ensure_workdir("7-both-renamed.txt",
+ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11");
+}
+
+void test_checkout_conflict__name_mangled_file_exists_in_workdir(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ struct checkout_index_entry checkout_index_entries[] = {
+ { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "test-one-side-one.txt" },
+ { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "test-one-side-one.txt" },
+ { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "test-one-side-two.txt" },
+ { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "test-one-side-two.txt" },
+ { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "test-one.txt" },
+ { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "test-one.txt" },
+
+ { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "test-two-side-one.txt" },
+ { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "test-two-side-one.txt" },
+ { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "test-two-side-two.txt" },
+ { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "test-two-side-two.txt" },
+ { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "test-two.txt" },
+ { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "test-two.txt" },
+
+ { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "test-three-side-one.txt" },
+ { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "test-three-side-one.txt" },
+ { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "test-three-side-two.txt" },
+ { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "test-three-side-two.txt" },
+ { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "test-three.txt" },
+ { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "test-three.txt" },
+
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "directory_file-one" },
+ { 0100644, CONFLICTING_OURS_OID, 2, "directory_file-one" },
+ { 0100644, CONFLICTING_THEIRS_OID, 0, "directory_file-one/file" },
+
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "directory_file-two" },
+ { 0100644, CONFLICTING_OURS_OID, 0, "directory_file-two/file" },
+ { 0100644, CONFLICTING_THEIRS_OID, 3, "directory_file-two" },
+ };
+
+ struct checkout_name_entry checkout_name_entries[] = {
+ {
+ "test-one-side-one.txt",
+ "test-one.txt",
+ "test-one-side-one.txt"
+ },
+ {
+ "test-one-side-two.txt",
+ "test-one-side-two.txt",
+ "test-one.txt"
+ },
+
+ {
+ "test-two-side-one.txt",
+ "test-two.txt",
+ "test-two-side-one.txt"
+ },
+ {
+ "test-two-side-two.txt",
+ "test-two-side-two.txt",
+ "test-two.txt"
+ },
+
+ {
+ "test-three-side-one.txt",
+ "test-three.txt",
+ "test-three-side-one.txt"
+ },
+ {
+ "test-three-side-two.txt",
+ "test-three-side-two.txt",
+ "test-three.txt"
+ }
+ };
+
+ opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
+
+ create_index(checkout_index_entries, 24);
+ create_index_names(checkout_name_entries, 6);
+ git_index_write(g_index);
+
+ /* Add some files on disk that conflict with the names that would be chosen
+ * for the files written for each side. */
+
+ cl_git_rewritefile("merge-resolve/test-one.txt~ours",
+ "Expect index contents to be written to ~ours_0");
+ cl_git_rewritefile("merge-resolve/test-one.txt~theirs",
+ "Expect index contents to be written to ~theirs_0");
+
+ cl_git_rewritefile("merge-resolve/test-two.txt~ours",
+ "Expect index contents to be written to ~ours_3");
+ cl_git_rewritefile("merge-resolve/test-two.txt~theirs",
+ "Expect index contents to be written to ~theirs_3");
+ cl_git_rewritefile("merge-resolve/test-two.txt~ours_0",
+ "Expect index contents to be written to ~ours_3");
+ cl_git_rewritefile("merge-resolve/test-two.txt~theirs_0",
+ "Expect index contents to be written to ~theirs_3");
+ cl_git_rewritefile("merge-resolve/test-two.txt~ours_1",
+ "Expect index contents to be written to ~ours_3");
+ cl_git_rewritefile("merge-resolve/test-two.txt~theirs_1",
+ "Expect index contents to be written to ~theirs_3");
+ cl_git_rewritefile("merge-resolve/test-two.txt~ours_2",
+ "Expect index contents to be written to ~ours_3");
+ cl_git_rewritefile("merge-resolve/test-two.txt~theirs_2",
+ "Expect index contents to be written to ~theirs_3");
+
+ cl_git_rewritefile("merge-resolve/test-three.txt~Ours",
+ "Expect case insensitive filesystems to create ~ours_0");
+ cl_git_rewritefile("merge-resolve/test-three.txt~THEIRS",
+ "Expect case insensitive filesystems to create ~theirs_0");
+
+ cl_git_rewritefile("merge-resolve/directory_file-one~ours",
+ "Index contents written to ~ours_0 in this D/F conflict");
+ cl_git_rewritefile("merge-resolve/directory_file-two~theirs",
+ "Index contents written to ~theirs_0 in this D/F conflict");
+
+ cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
+
+ ensure_workdir("test-one.txt~ours_0",
+ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11");
+ ensure_workdir("test-one.txt~theirs_0",
+ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07");
+
+ ensure_workdir("test-two.txt~ours_3",
+ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11");
+ ensure_workdir("test-two.txt~theirs_3",
+ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07");
+
+ /* Name is mangled on case insensitive only */
+#if defined(GIT_WIN32) || defined(__APPLE__)
+ ensure_workdir("test-three.txt~ours_0",
+ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11");
+ ensure_workdir("test-three.txt~theirs_0",
+ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07");
+#else
+ ensure_workdir("test-three.txt~ours",
+ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11");
+ ensure_workdir("test-three.txt~theirs",
+ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07");
+#endif
+
+ ensure_workdir("directory_file-one~ours_0", 0100644, CONFLICTING_OURS_OID);
+ ensure_workdir("directory_file-two~theirs_0", 0100644, CONFLICTING_THEIRS_OID);
+}
+
+void test_checkout_conflict__update_only(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ struct checkout_index_entry checkout_index_entries[] = {
+ { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "automergeable.txt" },
+ { 0100644, AUTOMERGEABLE_OURS_OID, 2, "automergeable.txt" },
+ { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "automergeable.txt" },
+
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "modify-delete" },
+ { 0100644, CONFLICTING_THEIRS_OID, 3, "modify-delete" },
+
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "directory_file-one" },
+ { 0100644, CONFLICTING_OURS_OID, 2, "directory_file-one" },
+ { 0100644, CONFLICTING_THEIRS_OID, 0, "directory_file-one/file" },
+
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "directory_file-two" },
+ { 0100644, CONFLICTING_OURS_OID, 0, "directory_file-two/file" },
+ { 0100644, CONFLICTING_THEIRS_OID, 3, "directory_file-two" },
+ };
+
+ opts.checkout_strategy |= GIT_CHECKOUT_UPDATE_ONLY;
+
+ create_index(checkout_index_entries, 3);
+ git_index_write(g_index);
+
+ cl_git_pass(p_mkdir("merge-resolve/directory_file-two", 0777));
+ cl_git_rewritefile("merge-resolve/directory_file-two/file", CONFLICTING_OURS_FILE);
+
+ cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
+
+ ensure_workdir_contents("automergeable.txt", AUTOMERGEABLE_MERGED_FILE);
+ ensure_workdir("directory_file-two/file", 0100644, CONFLICTING_OURS_OID);
+
+ cl_assert(!git_path_exists("merge-resolve/modify-delete"));
+ cl_assert(!git_path_exists("merge-resolve/test-one.txt"));
+ cl_assert(!git_path_exists("merge-resolve/test-one-side-one.txt"));
+ cl_assert(!git_path_exists("merge-resolve/test-one-side-two.txt"));
+ cl_assert(!git_path_exists("merge-resolve/test-one.txt~ours"));
+ cl_assert(!git_path_exists("merge-resolve/test-one.txt~theirs"));
+ cl_assert(!git_path_exists("merge-resolve/directory_file-one/file"));
+ cl_assert(!git_path_exists("merge-resolve/directory_file-one~ours"));
+ cl_assert(!git_path_exists("merge-resolve/directory_file-two~theirs"));
+}
+
+void test_checkout_conflict__path_filters(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ char *paths[] = { "conflicting-1.txt", "conflicting-3.txt" };
+ git_strarray patharray = {0};
+
+ struct checkout_index_entry checkout_index_entries[] = {
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting-1.txt" },
+ { 0100644, CONFLICTING_OURS_OID, 2, "conflicting-1.txt" },
+ { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting-1.txt" },
+
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting-2.txt" },
+ { 0100644, CONFLICTING_OURS_OID, 2, "conflicting-2.txt" },
+ { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting-2.txt" },
+
+ { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "conflicting-3.txt" },
+ { 0100644, AUTOMERGEABLE_OURS_OID, 2, "conflicting-3.txt" },
+ { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "conflicting-3.txt" },
+
+ { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "conflicting-4.txt" },
+ { 0100644, AUTOMERGEABLE_OURS_OID, 2, "conflicting-4.txt" },
+ { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "conflicting-4.txt" },
+ };
+
+ patharray.count = 2;
+ patharray.strings = paths;
+
+ opts.paths = patharray;
+
+ create_index(checkout_index_entries, 12);
+ git_index_write(g_index);
+
+ cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
+
+ ensure_workdir_contents("conflicting-1.txt", CONFLICTING_DIFF3_FILE);
+ cl_assert(!git_path_exists("merge-resolve/conflicting-2.txt"));
+ ensure_workdir_contents("conflicting-3.txt", AUTOMERGEABLE_MERGED_FILE);
+ cl_assert(!git_path_exists("merge-resolve/conflicting-4.txt"));
+}
+
+static void collect_progress(
+ const char *path,
+ size_t completed_steps,
+ size_t total_steps,
+ void *payload)
+{
+ git_vector *paths = payload;
+
+ (void)completed_steps;
+ (void)total_steps;
+
+ if (path == NULL)
+ return;
+
+ git_vector_insert(paths, strdup(path));
+}
+
+void test_checkout_conflict__report_progress(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_vector paths = GIT_VECTOR_INIT;
+ char *path;
+ size_t i;
+
+ struct checkout_index_entry checkout_index_entries[] = {
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting-1.txt" },
+ { 0100644, CONFLICTING_OURS_OID, 2, "conflicting-1.txt" },
+ { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting-1.txt" },
+
+ { 0100644, CONFLICTING_ANCESTOR_OID, 1, "conflicting-2.txt" },
+ { 0100644, CONFLICTING_OURS_OID, 2, "conflicting-2.txt" },
+ { 0100644, CONFLICTING_THEIRS_OID, 3, "conflicting-2.txt" },
+
+ { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "conflicting-3.txt" },
+ { 0100644, AUTOMERGEABLE_OURS_OID, 2, "conflicting-3.txt" },
+ { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "conflicting-3.txt" },
+
+ { 0100644, AUTOMERGEABLE_ANCESTOR_OID, 1, "conflicting-4.txt" },
+ { 0100644, AUTOMERGEABLE_OURS_OID, 2, "conflicting-4.txt" },
+ { 0100644, AUTOMERGEABLE_THEIRS_OID, 3, "conflicting-4.txt" },
+ };
+
+ opts.progress_cb = collect_progress;
+ opts.progress_payload = &paths;
+
+
+ create_index(checkout_index_entries, 12);
+ git_index_write(g_index);
+
+ cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
+
+ cl_assert_equal_i(4, git_vector_length(&paths));
+ cl_assert_equal_s("conflicting-1.txt", git_vector_get(&paths, 0));
+ cl_assert_equal_s("conflicting-2.txt", git_vector_get(&paths, 1));
+ cl_assert_equal_s("conflicting-3.txt", git_vector_get(&paths, 2));
+ cl_assert_equal_s("conflicting-4.txt", git_vector_get(&paths, 3));
+
+ git_vector_foreach(&paths, i, path)
+ git__free(path);
+
+ git_vector_free(&paths);
+}
diff --git a/tests/checkout/crlf.c b/tests/checkout/crlf.c
new file mode 100644
index 000000000..6b2c1b122
--- /dev/null
+++ b/tests/checkout/crlf.c
@@ -0,0 +1,325 @@
+#include "clar_libgit2.h"
+#include "checkout_helpers.h"
+#include "../filter/crlf.h"
+
+#include "git2/checkout.h"
+#include "repository.h"
+#include "posix.h"
+
+static git_repository *g_repo;
+
+void test_checkout_crlf__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("crlf");
+}
+
+void test_checkout_crlf__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_checkout_crlf__detect_crlf_autocrlf_false(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", false);
+
+ git_checkout_head(g_repo, &opts);
+
+ check_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW);
+ check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW);
+}
+
+void test_checkout_crlf__autocrlf_false_index_size_is_unfiltered_size(void)
+{
+ git_index *index;
+ const git_index_entry *entry;
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", false);
+
+ git_checkout_head(g_repo, &opts);
+
+ git_repository_index(&index, g_repo);
+
+ cl_assert((entry = git_index_get_bypath(index, "all-lf", 0)) != NULL);
+ cl_assert(entry->file_size == strlen(ALL_LF_TEXT_RAW));
+
+ cl_assert((entry = git_index_get_bypath(index, "all-crlf", 0)) != NULL);
+ cl_assert(entry->file_size == strlen(ALL_CRLF_TEXT_RAW));
+
+ git_index_free(index);
+}
+
+void test_checkout_crlf__detect_crlf_autocrlf_true(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", true);
+
+ git_checkout_head(g_repo, &opts);
+
+ if (GIT_EOL_NATIVE == GIT_EOL_LF)
+ check_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW);
+ else
+ check_file_contents("./crlf/all-lf", ALL_LF_TEXT_AS_CRLF);
+
+ check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW);
+}
+
+void test_checkout_crlf__more_lf_autocrlf_true(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", true);
+
+ git_checkout_head(g_repo, &opts);
+
+ if (GIT_EOL_NATIVE == GIT_EOL_LF)
+ check_file_contents("./crlf/more-lf", MORE_LF_TEXT_RAW);
+ else
+ check_file_contents("./crlf/more-lf", MORE_LF_TEXT_AS_CRLF);
+}
+
+void test_checkout_crlf__more_crlf_autocrlf_true(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", true);
+
+ git_checkout_head(g_repo, &opts);
+
+ if (GIT_EOL_NATIVE == GIT_EOL_LF)
+ check_file_contents("./crlf/more-crlf", MORE_CRLF_TEXT_RAW);
+ else
+ check_file_contents("./crlf/more-crlf", MORE_CRLF_TEXT_AS_CRLF);
+}
+
+void test_checkout_crlf__all_crlf_autocrlf_true(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", true);
+
+ git_checkout_head(g_repo, &opts);
+
+ check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW);
+}
+
+void test_checkout_crlf__autocrlf_true_index_size_is_filtered_size(void)
+{
+ git_index *index;
+ const git_index_entry *entry;
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", true);
+
+ git_checkout_head(g_repo, &opts);
+
+ git_repository_index(&index, g_repo);
+
+ cl_assert((entry = git_index_get_bypath(index, "all-lf", 0)) != NULL);
+
+ if (GIT_EOL_NATIVE == GIT_EOL_LF)
+ cl_assert_equal_sz(strlen(ALL_LF_TEXT_RAW), entry->file_size);
+ else
+ cl_assert_equal_sz(strlen(ALL_LF_TEXT_AS_CRLF), entry->file_size);
+
+ cl_assert((entry = git_index_get_bypath(index, "all-crlf", 0)) != NULL);
+ cl_assert_equal_sz(strlen(ALL_CRLF_TEXT_RAW), entry->file_size);
+
+ git_index_free(index);
+}
+
+void test_checkout_crlf__with_ident(void)
+{
+ git_index *index;
+ git_blob *blob;
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ cl_git_mkfile("crlf/.gitattributes",
+ "*.txt text\n*.bin binary\n"
+ "*.crlf text eol=crlf\n"
+ "*.lf text eol=lf\n"
+ "*.ident text ident\n"
+ "*.identcrlf ident text eol=crlf\n"
+ "*.identlf ident text eol=lf\n");
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", true);
+
+ /* add files with $Id$ */
+
+ cl_git_mkfile("crlf/lf.ident", ALL_LF_TEXT_RAW "\n$Id: initial content$\n");
+ cl_git_mkfile("crlf/crlf.ident", ALL_CRLF_TEXT_RAW "\r\n$Id$\r\n\r\n");
+ cl_git_mkfile("crlf/more1.identlf", "$Id$\n" MORE_LF_TEXT_RAW);
+ cl_git_mkfile("crlf/more2.identcrlf", "\r\n$Id: $\r\n" MORE_CRLF_TEXT_RAW);
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_index_add_bypath(index, "lf.ident"));
+ cl_git_pass(git_index_add_bypath(index, "crlf.ident"));
+ cl_git_pass(git_index_add_bypath(index, "more1.identlf"));
+ cl_git_pass(git_index_add_bypath(index, "more2.identcrlf"));
+ cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "Some ident files\n");
+
+ git_checkout_head(g_repo, &opts);
+
+ /* check that blobs have $Id$ */
+
+ cl_git_pass(git_blob_lookup(&blob, g_repo,
+ & git_index_get_bypath(index, "lf.ident", 0)->id));
+ cl_assert_equal_s(
+ ALL_LF_TEXT_RAW "\n$Id$\n", git_blob_rawcontent(blob));
+ git_blob_free(blob);
+
+ cl_git_pass(git_blob_lookup(&blob, g_repo,
+ & git_index_get_bypath(index, "more2.identcrlf", 0)->id));
+ cl_assert_equal_s(
+ "\n$Id$\n" MORE_CRLF_TEXT_AS_LF, git_blob_rawcontent(blob));
+ git_blob_free(blob);
+
+ /* check that filesystem is initially untouched - matching core Git */
+
+ cl_assert_equal_file(
+ ALL_LF_TEXT_RAW "\n$Id: initial content$\n", 0, "crlf/lf.ident");
+
+ /* check that forced checkout rewrites correctly */
+
+ p_unlink("crlf/lf.ident");
+ p_unlink("crlf/crlf.ident");
+ p_unlink("crlf/more1.identlf");
+ p_unlink("crlf/more2.identcrlf");
+
+ git_checkout_head(g_repo, &opts);
+
+ if (GIT_EOL_NATIVE == GIT_EOL_LF) {
+ cl_assert_equal_file(
+ ALL_LF_TEXT_RAW
+ "\n$Id: fcf6d4d9c212dc66563b1171b1cd99953c756467$\n",
+ 0, "crlf/lf.ident");
+ cl_assert_equal_file(
+ ALL_CRLF_TEXT_AS_LF
+ "\n$Id: f2c66ad9b2b5a734d9bf00d5000cc10a62b8a857$\n\n",
+ 0, "crlf/crlf.ident");
+ } else {
+ cl_assert_equal_file(
+ ALL_LF_TEXT_AS_CRLF
+ "\r\n$Id: fcf6d4d9c212dc66563b1171b1cd99953c756467$\r\n",
+ 0, "crlf/lf.ident");
+ cl_assert_equal_file(
+ ALL_CRLF_TEXT_RAW
+ "\r\n$Id: f2c66ad9b2b5a734d9bf00d5000cc10a62b8a857$\r\n\r\n",
+ 0, "crlf/crlf.ident");
+ }
+
+ cl_assert_equal_file(
+ "$Id: f7830382dac1f1583422be5530fdfbd26289431b$\n"
+ MORE_LF_TEXT_AS_LF, 0, "crlf/more1.identlf");
+
+ cl_assert_equal_file(
+ "\r\n$Id: 74677a68413012ce8d7e7cfc3f12603df3a3eac4$\r\n"
+ MORE_CRLF_TEXT_AS_CRLF, 0, "crlf/more2.identcrlf");
+
+ git_index_free(index);
+}
+
+void test_checkout_crlf__autocrlf_false_no_attrs(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", false);
+
+ git_checkout_head(g_repo, &opts);
+
+ check_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW);
+ check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW);
+}
+
+void test_checkout_crlf__autocrlf_true_no_attrs(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", true);
+
+ git_checkout_head(g_repo, &opts);
+
+ if (GIT_EOL_NATIVE == GIT_EOL_CRLF) {
+ check_file_contents("./crlf/all-lf", ALL_LF_TEXT_AS_CRLF);
+ check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_AS_CRLF);
+ } else {
+ check_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW);
+ check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW);
+ }
+}
+
+void test_checkout_crlf__autocrlf_input_no_attrs(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ cl_repo_set_string(g_repo, "core.autocrlf", "input");
+
+ git_checkout_head(g_repo, &opts);
+
+ check_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW);
+ check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW);
+}
+
+void test_checkout_crlf__autocrlf_false_text_auto_attr(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n");
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", false);
+
+ git_checkout_head(g_repo, &opts);
+
+ check_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW);
+ check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW);
+}
+
+void test_checkout_crlf__autocrlf_true_text_auto_attr(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n");
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", true);
+
+ git_checkout_head(g_repo, &opts);
+
+ if (GIT_EOL_NATIVE == GIT_EOL_CRLF) {
+ check_file_contents("./crlf/all-lf", ALL_LF_TEXT_AS_CRLF);
+ check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_AS_CRLF);
+ } else {
+ check_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW);
+ check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW);
+ }
+}
+
+void test_checkout_crlf__autocrlf_input_text_auto_attr(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n");
+
+ cl_repo_set_string(g_repo, "core.autocrlf", "input");
+
+ git_checkout_head(g_repo, &opts);
+
+ check_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW);
+ check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW);
+}
diff --git a/tests/checkout/head.c b/tests/checkout/head.c
new file mode 100644
index 000000000..07cc1d209
--- /dev/null
+++ b/tests/checkout/head.c
@@ -0,0 +1,62 @@
+#include "clar_libgit2.h"
+#include "refs.h"
+#include "repo/repo_helpers.h"
+#include "path.h"
+#include "fileops.h"
+
+static git_repository *g_repo;
+
+void test_checkout_head__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo");
+}
+
+void test_checkout_head__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_checkout_head__unborn_head_returns_GIT_EUNBORNBRANCH(void)
+{
+ make_head_unborn(g_repo, NON_EXISTING_HEAD);
+
+ cl_assert_equal_i(GIT_EUNBORNBRANCH, git_checkout_head(g_repo, NULL));
+}
+
+void test_checkout_head__with_index_only_tree(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_index *index;
+
+ /* let's start by getting things into a known state */
+
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+ cl_git_pass(git_checkout_head(g_repo, &opts));
+
+ /* now let's stage some new stuff including a new directory */
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ p_mkdir("testrepo/newdir", 0777);
+ cl_git_mkfile("testrepo/newdir/newfile.txt", "new file\n");
+
+ cl_git_pass(git_index_add_bypath(index, "newdir/newfile.txt"));
+ cl_git_pass(git_index_write(index));
+
+ cl_assert(git_path_isfile("testrepo/newdir/newfile.txt"));
+ cl_assert(git_index_get_bypath(index, "newdir/newfile.txt", 0) != NULL);
+
+ git_index_free(index);
+
+ /* okay, so now we have staged this new file; let's see if we can remove */
+
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED;
+ cl_git_pass(git_checkout_head(g_repo, &opts));
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ cl_assert(!git_path_isfile("testrepo/newdir/newfile.txt"));
+ cl_assert(git_index_get_bypath(index, "newdir/newfile.txt", 0) == NULL);
+
+ git_index_free(index);
+}
diff --git a/tests/checkout/index.c b/tests/checkout/index.c
new file mode 100644
index 000000000..7f641b329
--- /dev/null
+++ b/tests/checkout/index.c
@@ -0,0 +1,620 @@
+#include "clar_libgit2.h"
+#include "checkout_helpers.h"
+
+#include "git2/checkout.h"
+#include "fileops.h"
+#include "repository.h"
+
+static git_repository *g_repo;
+
+void test_checkout_index__initialize(void)
+{
+ git_tree *tree;
+
+ g_repo = cl_git_sandbox_init("testrepo");
+
+ cl_git_pass(git_repository_head_tree(&tree, g_repo));
+
+ reset_index_to_treeish((git_object *)tree);
+ git_tree_free(tree);
+
+ cl_git_rewritefile(
+ "./testrepo/.gitattributes",
+ "* text eol=lf\n");
+}
+
+void test_checkout_index__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+
+ /* try to remove alternative dir */
+ if (git_path_isdir("alternative"))
+ git_futils_rmdir_r("alternative", NULL, GIT_RMDIR_REMOVE_FILES);
+}
+
+void test_checkout_index__cannot_checkout_a_bare_repository(void)
+{
+ test_checkout_index__cleanup();
+
+ g_repo = cl_git_sandbox_init("testrepo.git");
+
+ cl_git_fail(git_checkout_index(g_repo, NULL, NULL));
+}
+
+void test_checkout_index__can_create_missing_files(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ cl_assert_equal_i(false, git_path_isfile("./testrepo/README"));
+ cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt"));
+ cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt"));
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+
+ check_file_contents("./testrepo/README", "hey there\n");
+ check_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n");
+ check_file_contents("./testrepo/new.txt", "my new file\n");
+}
+
+void test_checkout_index__can_remove_untracked_files(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ git_futils_mkdir("./testrepo/dir/subdir/subsubdir", NULL, 0755, GIT_MKDIR_PATH);
+ cl_git_mkfile("./testrepo/dir/one", "one\n");
+ cl_git_mkfile("./testrepo/dir/subdir/two", "two\n");
+
+ cl_assert_equal_i(true, git_path_isdir("./testrepo/dir/subdir/subsubdir"));
+
+ opts.checkout_strategy =
+ GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_REMOVE_UNTRACKED;
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+
+ cl_assert_equal_i(false, git_path_isdir("./testrepo/dir"));
+}
+
+void test_checkout_index__honor_the_specified_pathspecs(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ char *entries[] = { "*.txt" };
+
+ opts.paths.strings = entries;
+ opts.paths.count = 1;
+
+ cl_assert_equal_i(false, git_path_isfile("./testrepo/README"));
+ cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt"));
+ cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt"));
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+
+ cl_assert_equal_i(false, git_path_isfile("./testrepo/README"));
+ check_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n");
+ check_file_contents("./testrepo/new.txt", "my new file\n");
+}
+
+void test_checkout_index__honor_the_gitattributes_directives(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ const char *attributes =
+ "branch_file.txt text eol=crlf\n"
+ "new.txt text eol=lf\n";
+
+ cl_git_mkfile("./testrepo/.gitattributes", attributes);
+ cl_repo_set_bool(g_repo, "core.autocrlf", false);
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+
+ check_file_contents("./testrepo/README", "hey there\n");
+ check_file_contents("./testrepo/new.txt", "my new file\n");
+ check_file_contents("./testrepo/branch_file.txt", "hi\r\nbye!\r\n");
+}
+
+void test_checkout_index__honor_coreautocrlf_setting_set_to_true(void)
+{
+#ifdef GIT_WIN32
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ const char *expected_readme_text = "hey there\r\n";
+
+ cl_git_pass(p_unlink("./testrepo/.gitattributes"));
+ cl_repo_set_bool(g_repo, "core.autocrlf", true);
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+
+ check_file_contents("./testrepo/README", expected_readme_text);
+#endif
+}
+
+void test_checkout_index__honor_coresymlinks_setting_set_to_true(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ cl_repo_set_bool(g_repo, "core.symlinks", true);
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+
+#ifdef GIT_WIN32
+ check_file_contents("./testrepo/link_to_new.txt", "new.txt");
+#else
+ {
+ char link_data[1024];
+ size_t link_size = 1024;
+
+ link_size = p_readlink("./testrepo/link_to_new.txt", link_data, link_size);
+ link_data[link_size] = '\0';
+ cl_assert_equal_i(link_size, strlen("new.txt"));
+ cl_assert_equal_s(link_data, "new.txt");
+ check_file_contents("./testrepo/link_to_new.txt", "my new file\n");
+ }
+#endif
+}
+
+void test_checkout_index__honor_coresymlinks_setting_set_to_false(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ cl_repo_set_bool(g_repo, "core.symlinks", false);
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+
+ check_file_contents("./testrepo/link_to_new.txt", "new.txt");
+}
+
+void test_checkout_index__donot_overwrite_modified_file_by_default(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!");
+
+ /* set this up to not return an error code on conflicts, but it
+ * still will not have permission to overwrite anything...
+ */
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS;
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+
+ check_file_contents("./testrepo/new.txt", "This isn't what's stored!");
+}
+
+void test_checkout_index__can_overwrite_modified_file(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!");
+
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+
+ check_file_contents("./testrepo/new.txt", "my new file\n");
+}
+
+void test_checkout_index__options_disable_filters(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ cl_git_mkfile("./testrepo/.gitattributes", "*.txt text eol=crlf\n");
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+ opts.disable_filters = false;
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+
+ check_file_contents("./testrepo/new.txt", "my new file\r\n");
+
+ p_unlink("./testrepo/new.txt");
+
+ opts.disable_filters = true;
+ cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+
+ check_file_contents("./testrepo/new.txt", "my new file\n");
+}
+
+void test_checkout_index__options_dir_modes(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ struct stat st;
+ git_oid oid;
+ git_commit *commit;
+ mode_t um;
+
+ if (!cl_is_chmod_supported())
+ return;
+
+ cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir"));
+ cl_git_pass(git_commit_lookup(&commit, g_repo, &oid));
+
+ reset_index_to_treeish((git_object *)commit);
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+ opts.dir_mode = 0701;
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+
+ /* umask will influence actual directory creation mode */
+ (void)p_umask(um = p_umask(022));
+
+ cl_git_pass(p_stat("./testrepo/a", &st));
+ cl_assert_equal_i_fmt(st.st_mode, (GIT_FILEMODE_TREE | 0701) & ~um, "%07o");
+
+ /* File-mode test, since we're on the 'dir' branch */
+ cl_git_pass(p_stat("./testrepo/a/b.txt", &st));
+ cl_assert_equal_i_fmt(st.st_mode, GIT_FILEMODE_BLOB_EXECUTABLE, "%07o");
+
+ git_commit_free(commit);
+}
+
+void test_checkout_index__options_override_file_modes(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ struct stat st;
+
+ if (!cl_is_chmod_supported())
+ return;
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+ opts.file_mode = 0700;
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+
+ cl_git_pass(p_stat("./testrepo/new.txt", &st));
+ cl_assert_equal_i_fmt(st.st_mode & GIT_MODE_PERMS_MASK, 0700, "%07o");
+}
+
+void test_checkout_index__options_open_flags(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ cl_git_mkfile("./testrepo/new.txt", "hi\n");
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+ opts.file_open_flags = O_CREAT | O_RDWR | O_APPEND;
+
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+ cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+
+ check_file_contents("./testrepo/new.txt", "hi\nmy new file\n");
+}
+
+struct notify_data {
+ const char *file;
+ const char *sha;
+};
+
+static int test_checkout_notify_cb(
+ git_checkout_notify_t why,
+ const char *path,
+ const git_diff_file *baseline,
+ const git_diff_file *target,
+ const git_diff_file *workdir,
+ void *payload)
+{
+ struct notify_data *expectations = (struct notify_data *)payload;
+
+ GIT_UNUSED(workdir);
+
+ cl_assert_equal_i(GIT_CHECKOUT_NOTIFY_CONFLICT, why);
+ cl_assert_equal_s(expectations->file, path);
+ cl_assert_equal_i(0, git_oid_streq(&baseline->id, expectations->sha));
+ cl_assert_equal_i(0, git_oid_streq(&target->id, expectations->sha));
+
+ return 0;
+}
+
+void test_checkout_index__can_notify_of_skipped_files(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ struct notify_data data;
+
+ cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!");
+
+ /*
+ * $ git ls-tree HEAD
+ * 100644 blob a8233120f6ad708f843d861ce2b7228ec4e3dec6 README
+ * 100644 blob 3697d64be941a53d4ae8f6a271e4e3fa56b022cc branch_file.txt
+ * 100644 blob a71586c1dfe8a71c6cbf6c129f404c5642ff31bd new.txt
+ */
+ data.file = "new.txt";
+ data.sha = "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd";
+
+ opts.checkout_strategy =
+ GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_ALLOW_CONFLICTS;
+ opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT;
+ opts.notify_cb = test_checkout_notify_cb;
+ opts.notify_payload = &data;
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+}
+
+static int dont_notify_cb(
+ git_checkout_notify_t why,
+ const char *path,
+ const git_diff_file *baseline,
+ const git_diff_file *target,
+ const git_diff_file *workdir,
+ void *payload)
+{
+ GIT_UNUSED(why);
+ GIT_UNUSED(path);
+ GIT_UNUSED(baseline);
+ GIT_UNUSED(target);
+ GIT_UNUSED(workdir);
+ GIT_UNUSED(payload);
+
+ cl_assert(false);
+
+ return 0;
+}
+
+void test_checkout_index__wont_notify_of_expected_line_ending_changes(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ cl_git_pass(p_unlink("./testrepo/.gitattributes"));
+ cl_repo_set_bool(g_repo, "core.autocrlf", true);
+
+ cl_git_mkfile("./testrepo/new.txt", "my new file\r\n");
+
+ opts.checkout_strategy =
+ GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_ALLOW_CONFLICTS;
+ opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT;
+ opts.notify_cb = dont_notify_cb;
+ opts.notify_payload = NULL;
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+}
+
+static void checkout_progress_counter(
+ const char *path, size_t cur, size_t tot, void *payload)
+{
+ GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot);
+ (*(int *)payload)++;
+}
+
+void test_checkout_index__calls_progress_callback(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ int calls = 0;
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+ opts.progress_cb = checkout_progress_counter;
+ opts.progress_payload = &calls;
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+ cl_assert(calls > 0);
+}
+
+void test_checkout_index__can_overcome_name_clashes(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ 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_bypath(index, "path0"));
+ cl_git_pass(git_index_add_bypath(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"));
+
+ opts.checkout_strategy =
+ GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_ALLOW_CONFLICTS;
+ cl_git_pass(git_checkout_index(g_repo, index, &opts));
+
+ cl_assert(git_path_isfile("./testrepo/path1"));
+ cl_assert(git_path_isfile("./testrepo/path0/file0"));
+
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+ cl_git_pass(git_checkout_index(g_repo, index, &opts));
+
+ cl_assert(git_path_isfile("./testrepo/path0"));
+ cl_assert(git_path_isfile("./testrepo/path1/file1"));
+
+ git_index_free(index);
+}
+
+void test_checkout_index__validates_struct_version(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ const git_error *err;
+
+ opts.version = 1024;
+ cl_git_fail(git_checkout_index(g_repo, NULL, &opts));
+
+ err = giterr_last();
+ cl_assert_equal_i(err->klass, GITERR_INVALID);
+
+ opts.version = 0;
+ giterr_clear();
+ cl_git_fail(git_checkout_index(g_repo, NULL, &opts));
+
+ err = giterr_last();
+ cl_assert_equal_i(err->klass, GITERR_INVALID);
+}
+
+void test_checkout_index__can_update_prefixed_files(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ cl_assert_equal_i(false, git_path_isfile("./testrepo/README"));
+ 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_mkfile("./testrepo/READ", "content\n");
+ cl_git_mkfile("./testrepo/README.after", "content\n");
+ cl_git_pass(p_mkdir("./testrepo/branch_file", 0777));
+ cl_git_pass(p_mkdir("./testrepo/branch_file/contained_dir", 0777));
+ cl_git_mkfile("./testrepo/branch_file/contained_file", "content\n");
+ cl_git_pass(p_mkdir("./testrepo/branch_file.txt.after", 0777));
+
+ opts.checkout_strategy =
+ GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_REMOVE_UNTRACKED;
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+
+ /* remove untracked will remove the .gitattributes file before the blobs
+ * were created, so they will have had crlf filtering applied on Windows
+ */
+ check_file_contents_nocr("./testrepo/README", "hey there\n");
+ check_file_contents_nocr("./testrepo/branch_file.txt", "hi\nbye!\n");
+ check_file_contents_nocr("./testrepo/new.txt", "my new file\n");
+
+ cl_assert(!git_path_exists("testrepo/READ"));
+ cl_assert(!git_path_exists("testrepo/README.after"));
+ cl_assert(!git_path_exists("testrepo/branch_file"));
+ cl_assert(!git_path_exists("testrepo/branch_file.txt.after"));
+}
+
+void test_checkout_index__can_checkout_a_newly_initialized_repository(void)
+{
+ test_checkout_index__cleanup();
+
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+ cl_git_remove_placeholders(git_repository_path(g_repo), "dummy-marker.txt");
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, NULL));
+}
+
+void test_checkout_index__issue_1397(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ test_checkout_index__cleanup();
+
+ g_repo = cl_git_sandbox_init("issue_1397");
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", true);
+
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+
+ check_file_contents("./issue_1397/crlf_file.txt", "first line\r\nsecond line\r\nboth with crlf");
+}
+
+void test_checkout_index__target_directory(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ checkout_counts cts;
+ memset(&cts, 0, sizeof(cts));
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+ opts.target_directory = "alternative";
+ cl_assert(!git_path_isdir("alternative"));
+
+ opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL;
+ opts.notify_cb = checkout_count_callback;
+ opts.notify_payload = &cts;
+
+ /* create some files that *would* conflict if we were using the wd */
+ cl_git_mkfile("testrepo/README", "I'm in the way!\n");
+ cl_git_mkfile("testrepo/new.txt", "my new file\n");
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+
+ cl_assert_equal_i(0, cts.n_untracked);
+ cl_assert_equal_i(0, cts.n_ignored);
+ cl_assert_equal_i(4, cts.n_updates);
+
+ check_file_contents("./alternative/README", "hey there\n");
+ check_file_contents("./alternative/branch_file.txt", "hi\nbye!\n");
+ check_file_contents("./alternative/new.txt", "my new file\n");
+
+ cl_git_pass(git_futils_rmdir_r(
+ "alternative", NULL, GIT_RMDIR_REMOVE_FILES));
+}
+
+void test_checkout_index__target_directory_from_bare(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_index *index;
+ git_object *head = NULL;
+ checkout_counts cts;
+ memset(&cts, 0, sizeof(cts));
+
+ test_checkout_index__cleanup();
+
+ g_repo = cl_git_sandbox_init("testrepo.git");
+ cl_assert(git_repository_is_bare(g_repo));
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_revparse_single(&head, g_repo, "HEAD^{tree}"));
+ cl_git_pass(git_index_read_tree(index, (const git_tree *)head));
+ cl_git_pass(git_index_write(index));
+ git_index_free(index);
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL;
+ opts.notify_cb = checkout_count_callback;
+ opts.notify_payload = &cts;
+
+ /* fail to checkout a bare repo */
+ cl_git_fail(git_checkout_index(g_repo, NULL, &opts));
+
+ opts.target_directory = "alternative";
+ cl_assert(!git_path_isdir("alternative"));
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+
+ cl_assert_equal_i(0, cts.n_untracked);
+ cl_assert_equal_i(0, cts.n_ignored);
+ cl_assert_equal_i(3, cts.n_updates);
+
+ /* files will have been filtered if needed, so strip CR */
+ check_file_contents_nocr("./alternative/README", "hey there\n");
+ check_file_contents_nocr("./alternative/branch_file.txt", "hi\nbye!\n");
+ check_file_contents_nocr("./alternative/new.txt", "my new file\n");
+
+ cl_git_pass(git_futils_rmdir_r(
+ "alternative", NULL, GIT_RMDIR_REMOVE_FILES));
+
+ git_object_free(head);
+}
+
+void test_checkout_index__can_get_repo_from_index(void)
+{
+ git_index *index;
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ cl_assert_equal_i(false, git_path_isfile("./testrepo/README"));
+ cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt"));
+ cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt"));
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ cl_git_pass(git_checkout_index(NULL, index, &opts));
+
+ check_file_contents("./testrepo/README", "hey there\n");
+ check_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n");
+ check_file_contents("./testrepo/new.txt", "my new file\n");
+
+ git_index_free(index);
+}
diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c
new file mode 100644
index 000000000..b61ef2dfc
--- /dev/null
+++ b/tests/checkout/tree.c
@@ -0,0 +1,950 @@
+#include "clar_libgit2.h"
+#include "checkout_helpers.h"
+
+#include "git2/checkout.h"
+#include "repository.h"
+#include "buffer.h"
+#include "fileops.h"
+
+static git_repository *g_repo;
+static git_checkout_options g_opts;
+static git_object *g_object;
+
+void test_checkout_tree__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo");
+
+ GIT_INIT_STRUCTURE(&g_opts, GIT_CHECKOUT_OPTIONS_VERSION);
+ g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+}
+
+void test_checkout_tree__cleanup(void)
+{
+ git_object_free(g_object);
+ g_object = NULL;
+
+ cl_git_sandbox_cleanup();
+
+ if (git_path_isdir("alternative"))
+ git_futils_rmdir_r("alternative", NULL, GIT_RMDIR_REMOVE_FILES);
+}
+
+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));
+}
+
+void test_checkout_tree__can_checkout_a_subdirectory_from_a_commit(void)
+{
+ char *entries[] = { "ab/de/" };
+
+ g_opts.paths.strings = entries;
+ g_opts.paths.count = 1;
+
+ cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees"));
+
+ cl_assert_equal_i(false, git_path_isdir("./testrepo/ab/"));
+
+ 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"));
+}
+
+void test_checkout_tree__can_checkout_and_remove_directory(void)
+{
+ cl_assert_equal_i(false, git_path_isdir("./testrepo/ab/"));
+
+ /* Checkout brach "subtrees" and update HEAD, so that HEAD matches the
+ * current working tree
+ */
+ cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees"));
+ cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
+
+ cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees", NULL, NULL));
+
+ cl_assert_equal_i(true, git_path_isdir("./testrepo/ab/"));
+ 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"));
+
+ git_object_free(g_object);
+ g_object = NULL;
+
+ /* Checkout brach "master" and update HEAD, so that HEAD matches the
+ * current working tree
+ */
+ cl_git_pass(git_revparse_single(&g_object, g_repo, "master"));
+ cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
+
+ cl_git_pass(git_repository_set_head(g_repo, "refs/heads/master", NULL, NULL));
+
+ /* This directory should no longer exist */
+ cl_assert_equal_i(false, git_path_isdir("./testrepo/ab/"));
+}
+
+void test_checkout_tree__can_checkout_a_subdirectory_from_a_subtree(void)
+{
+ char *entries[] = { "de/" };
+
+ g_opts.paths.strings = entries;
+ g_opts.paths.count = 1;
+
+ cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees:ab"));
+
+ cl_assert_equal_i(false, git_path_isdir("./testrepo/de/"));
+
+ 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);
+}
+
+void test_checkout_tree__doesnt_write_unrequested_files_to_worktree(void)
+{
+ git_oid master_oid;
+ git_oid chomped_oid;
+ git_commit* p_master_commit;
+ git_commit* p_chomped_commit;
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ git_oid_fromstr(&master_oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ git_oid_fromstr(&chomped_oid, "e90810b8df3e80c413d903f631643c716887138d");
+ cl_git_pass(git_commit_lookup(&p_master_commit, g_repo, &master_oid));
+ cl_git_pass(git_commit_lookup(&p_chomped_commit, g_repo, &chomped_oid));
+
+ /* GIT_CHECKOUT_NONE should not add any file to the working tree from the
+ * index as it is supposed to be a dry run.
+ */
+ opts.checkout_strategy = GIT_CHECKOUT_NONE;
+ git_checkout_tree(g_repo, (git_object*)p_chomped_commit, &opts);
+ cl_assert_equal_i(false, git_path_isfile("testrepo/readme.txt"));
+
+ git_commit_free(p_master_commit);
+ git_commit_free(p_chomped_commit);
+}
+
+void test_checkout_tree__can_switch_branches(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_oid oid;
+ git_object *obj = NULL;
+
+ assert_on_branch(g_repo, "master");
+
+ /* do first checkout with FORCE because we don't know if testrepo
+ * base data is clean for a checkout or not
+ */
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir"));
+ cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+
+ cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
+ cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir", NULL, NULL));
+
+ cl_assert(git_path_isfile("testrepo/README"));
+ cl_assert(git_path_isfile("testrepo/branch_file.txt"));
+ cl_assert(git_path_isfile("testrepo/new.txt"));
+ cl_assert(git_path_isfile("testrepo/a/b.txt"));
+
+ cl_assert(!git_path_isdir("testrepo/ab"));
+
+ assert_on_branch(g_repo, "dir");
+
+ git_object_free(obj);
+
+ /* do second checkout safe because we should be clean after first */
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE;
+
+ cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/subtrees"));
+ cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+
+ cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
+ cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees", NULL, NULL));
+
+ cl_assert(git_path_isfile("testrepo/README"));
+ cl_assert(git_path_isfile("testrepo/branch_file.txt"));
+ cl_assert(git_path_isfile("testrepo/new.txt"));
+ cl_assert(git_path_isfile("testrepo/ab/4.txt"));
+ cl_assert(git_path_isfile("testrepo/ab/c/3.txt"));
+ cl_assert(git_path_isfile("testrepo/ab/de/2.txt"));
+ cl_assert(git_path_isfile("testrepo/ab/de/fgh/1.txt"));
+
+ cl_assert(!git_path_isdir("testrepo/a"));
+
+ assert_on_branch(g_repo, "subtrees");
+
+ git_object_free(obj);
+}
+
+void test_checkout_tree__can_remove_untracked(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_REMOVE_UNTRACKED;
+
+ cl_git_mkfile("testrepo/untracked_file", "as you wish");
+ cl_assert(git_path_isfile("testrepo/untracked_file"));
+
+ cl_git_pass(git_checkout_head(g_repo, &opts));
+
+ cl_assert(!git_path_isfile("testrepo/untracked_file"));
+}
+
+void test_checkout_tree__can_remove_ignored(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ int ignored = 0;
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_REMOVE_IGNORED;
+
+ cl_git_mkfile("testrepo/ignored_file", "as you wish");
+
+ cl_git_pass(git_ignore_add_rule(g_repo, "ignored_file\n"));
+
+ cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "ignored_file"));
+ cl_assert_equal_i(1, ignored);
+
+ cl_assert(git_path_isfile("testrepo/ignored_file"));
+
+ cl_git_pass(git_checkout_head(g_repo, &opts));
+
+ cl_assert(!git_path_isfile("testrepo/ignored_file"));
+}
+
+static int checkout_tree_with_blob_ignored_in_workdir(int strategy, bool isdir)
+{
+ git_oid oid;
+ git_object *obj = NULL;
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ int ignored = 0, error;
+
+ assert_on_branch(g_repo, "master");
+
+ /* do first checkout with FORCE because we don't know if testrepo
+ * base data is clean for a checkout or not
+ */
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir"));
+ cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+
+ cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
+ cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir", NULL, NULL));
+
+ cl_assert(git_path_isfile("testrepo/README"));
+ cl_assert(git_path_isfile("testrepo/branch_file.txt"));
+ cl_assert(git_path_isfile("testrepo/new.txt"));
+ cl_assert(git_path_isfile("testrepo/a/b.txt"));
+
+ cl_assert(!git_path_isdir("testrepo/ab"));
+
+ assert_on_branch(g_repo, "dir");
+
+ git_object_free(obj);
+
+ opts.checkout_strategy = strategy;
+
+ if (isdir) {
+ cl_must_pass(p_mkdir("testrepo/ab", 0777));
+ cl_must_pass(p_mkdir("testrepo/ab/4.txt", 0777));
+
+ cl_git_mkfile("testrepo/ab/4.txt/file1.txt", "as you wish");
+ cl_git_mkfile("testrepo/ab/4.txt/file2.txt", "foo bar foo");
+ cl_git_mkfile("testrepo/ab/4.txt/file3.txt", "inky blinky pinky clyde");
+
+ cl_assert(git_path_isdir("testrepo/ab/4.txt"));
+ } else {
+ cl_must_pass(p_mkdir("testrepo/ab", 0777));
+ cl_git_mkfile("testrepo/ab/4.txt", "as you wish");
+
+ cl_assert(git_path_isfile("testrepo/ab/4.txt"));
+ }
+
+ cl_git_pass(git_ignore_add_rule(g_repo, "ab/4.txt\n"));
+
+ cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "ab/4.txt"));
+ cl_assert_equal_i(1, ignored);
+
+ cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/subtrees"));
+ cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+
+ error = git_checkout_tree(g_repo, obj, &opts);
+
+ git_object_free(obj);
+
+ return error;
+}
+
+void test_checkout_tree__conflict_on_ignored_when_not_overwriting(void)
+{
+ int error;
+
+ cl_git_fail(error = checkout_tree_with_blob_ignored_in_workdir(
+ GIT_CHECKOUT_SAFE | GIT_CHECKOUT_DONT_OVERWRITE_IGNORED, false));
+
+ cl_assert_equal_i(GIT_EMERGECONFLICT, error);
+}
+
+void test_checkout_tree__can_overwrite_ignored_by_default(void)
+{
+ cl_git_pass(checkout_tree_with_blob_ignored_in_workdir(GIT_CHECKOUT_SAFE, false));
+
+ cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees", NULL, NULL));
+
+ cl_assert(git_path_isfile("testrepo/ab/4.txt"));
+
+ assert_on_branch(g_repo, "subtrees");
+}
+
+void test_checkout_tree__conflict_on_ignored_folder_when_not_overwriting(void)
+{
+ int error;
+
+ cl_git_fail(error = checkout_tree_with_blob_ignored_in_workdir(
+ GIT_CHECKOUT_SAFE | GIT_CHECKOUT_DONT_OVERWRITE_IGNORED, true));
+
+ cl_assert_equal_i(GIT_EMERGECONFLICT, error);
+}
+
+void test_checkout_tree__can_overwrite_ignored_folder_by_default(void)
+{
+ cl_git_pass(checkout_tree_with_blob_ignored_in_workdir(GIT_CHECKOUT_SAFE, true));
+
+ cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees", NULL, NULL));
+
+ cl_assert(git_path_isfile("testrepo/ab/4.txt"));
+
+ assert_on_branch(g_repo, "subtrees");
+
+}
+
+void test_checkout_tree__can_update_only(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_oid oid;
+ git_object *obj = NULL;
+
+ /* first let's get things into a known state - by checkout out the HEAD */
+
+ assert_on_branch(g_repo, "master");
+
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+ cl_git_pass(git_checkout_head(g_repo, &opts));
+
+ cl_assert(!git_path_isdir("testrepo/a"));
+
+ check_file_contents_nocr("testrepo/branch_file.txt", "hi\nbye!\n");
+
+ /* now checkout branch but with update only */
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY;
+
+ cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir"));
+ cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+
+ cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
+ cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir", NULL, NULL));
+
+ assert_on_branch(g_repo, "dir");
+
+ /* this normally would have been created (which was tested separately in
+ * the test_checkout_tree__can_switch_branches test), but with
+ * UPDATE_ONLY it will not have been created.
+ */
+ cl_assert(!git_path_isdir("testrepo/a"));
+
+ /* but this file still should have been updated */
+ check_file_contents_nocr("testrepo/branch_file.txt", "hi\n");
+
+ git_object_free(obj);
+}
+
+void test_checkout_tree__can_checkout_with_pattern(void)
+{
+ char *entries[] = { "[l-z]*.txt" };
+
+ /* reset to beginning of history (i.e. just a README file) */
+
+ g_opts.checkout_strategy =
+ GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED;
+
+ cl_git_pass(git_revparse_single(&g_object, g_repo, "8496071c1b46c854b31185ea97743be6a8774479"));
+
+ cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
+ cl_git_pass(
+ git_repository_set_head_detached(g_repo, git_object_id(g_object), NULL, NULL));
+
+ git_object_free(g_object);
+ g_object = NULL;
+
+ cl_assert(git_path_exists("testrepo/README"));
+ cl_assert(!git_path_exists("testrepo/branch_file.txt"));
+ cl_assert(!git_path_exists("testrepo/link_to_new.txt"));
+ cl_assert(!git_path_exists("testrepo/new.txt"));
+
+ /* now to a narrow patterned checkout */
+
+ g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+ g_opts.paths.strings = entries;
+ g_opts.paths.count = 1;
+
+ cl_git_pass(git_revparse_single(&g_object, g_repo, "refs/heads/master"));
+
+ cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
+
+ cl_assert(git_path_exists("testrepo/README"));
+ cl_assert(!git_path_exists("testrepo/branch_file.txt"));
+ cl_assert(git_path_exists("testrepo/link_to_new.txt"));
+ cl_assert(git_path_exists("testrepo/new.txt"));
+}
+
+void test_checkout_tree__can_disable_pattern_match(void)
+{
+ char *entries[] = { "b*.txt" };
+
+ /* reset to beginning of history (i.e. just a README file) */
+
+ g_opts.checkout_strategy =
+ GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED;
+
+ cl_git_pass(git_revparse_single(&g_object, g_repo, "8496071c1b46c854b31185ea97743be6a8774479"));
+
+ cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
+ cl_git_pass(
+ git_repository_set_head_detached(g_repo, git_object_id(g_object), NULL, NULL));
+
+ git_object_free(g_object);
+ g_object = NULL;
+
+ cl_assert(!git_path_isfile("testrepo/branch_file.txt"));
+
+ /* now to a narrow patterned checkout, but disable pattern */
+
+ g_opts.checkout_strategy =
+ GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH;
+ g_opts.paths.strings = entries;
+ g_opts.paths.count = 1;
+
+ cl_git_pass(git_revparse_single(&g_object, g_repo, "refs/heads/master"));
+
+ cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
+
+ cl_assert(!git_path_isfile("testrepo/branch_file.txt"));
+
+ /* let's try that again, but allow the pattern match */
+
+ g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
+
+ cl_assert(git_path_isfile("testrepo/branch_file.txt"));
+}
+
+void assert_conflict(
+ const char *entry_path,
+ const char *new_content,
+ const char *parent_sha,
+ const char *commit_sha)
+{
+ git_index *index;
+ git_object *hack_tree;
+ git_reference *branch, *head;
+ git_buf file_path = GIT_BUF_INIT;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ /* Create a branch pointing at the parent */
+ cl_git_pass(git_revparse_single(&g_object, g_repo, parent_sha));
+ cl_git_pass(git_branch_create(&branch, g_repo,
+ "potential_conflict", (git_commit *)g_object, 0, NULL, NULL));
+
+ /* Make HEAD point to this branch */
+ cl_git_pass(git_reference_symbolic_create(
+ &head, g_repo, "HEAD", git_reference_name(branch), 1, NULL, NULL));
+ git_reference_free(head);
+ git_reference_free(branch);
+
+ /* Checkout the parent */
+ g_opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+ cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
+
+ /* Hack-ishy workaound to ensure *all* the index entries
+ * match the content of the tree
+ */
+ cl_git_pass(git_object_peel(&hack_tree, g_object, GIT_OBJ_TREE));
+ cl_git_pass(git_index_read_tree(index, (git_tree *)hack_tree));
+ git_object_free(hack_tree);
+ git_object_free(g_object);
+ g_object = NULL;
+
+ /* Create a conflicting file */
+ cl_git_pass(git_buf_joinpath(&file_path, "./testrepo", entry_path));
+ cl_git_mkfile(git_buf_cstr(&file_path), new_content);
+ git_buf_free(&file_path);
+
+ /* Trying to checkout the original commit */
+ cl_git_pass(git_revparse_single(&g_object, g_repo, commit_sha));
+
+ g_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
+ cl_assert_equal_i(
+ GIT_EMERGECONFLICT, git_checkout_tree(g_repo, g_object, &g_opts));
+
+ /* Stage the conflicting change */
+ cl_git_pass(git_index_add_bypath(index, entry_path));
+ cl_git_pass(git_index_write(index));
+ git_index_free(index);
+
+ cl_assert_equal_i(
+ GIT_EMERGECONFLICT, git_checkout_tree(g_repo, g_object, &g_opts));
+}
+
+void test_checkout_tree__checking_out_a_conflicting_type_change_returns_EMERGECONFLICT(void)
+{
+ /*
+ * 099faba adds a symlink named 'link_to_new.txt'
+ * a65fedf is the parent of 099faba
+ */
+
+ assert_conflict("link_to_new.txt", "old.txt", "a65fedf", "099faba");
+}
+
+void test_checkout_tree__checking_out_a_conflicting_type_change_returns_EMERGECONFLICT_2(void)
+{
+ /*
+ * cf80f8d adds a directory named 'a/'
+ * a4a7dce is the parent of cf80f8d
+ */
+
+ assert_conflict("a", "hello\n", "a4a7dce", "cf80f8d");
+}
+
+void test_checkout_tree__checking_out_a_conflicting_content_change_returns_EMERGECONFLICT(void)
+{
+ /*
+ * c47800c adds a symlink named 'branch_file.txt'
+ * 5b5b025 is the parent of 763d71a
+ */
+
+ assert_conflict("branch_file.txt", "hello\n", "5b5b025", "c47800c");
+}
+
+void test_checkout_tree__donot_update_deleted_file_by_default(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_oid old_id, new_id;
+ git_commit *old_commit = NULL, *new_commit = NULL;
+ git_index *index = NULL;
+ checkout_counts ct;
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE;
+
+ memset(&ct, 0, sizeof(ct));
+ opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL;
+ opts.notify_cb = checkout_count_callback;
+ opts.notify_payload = &ct;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ cl_git_pass(git_oid_fromstr(&old_id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"));
+ cl_git_pass(git_commit_lookup(&old_commit, g_repo, &old_id));
+ cl_git_pass(git_reset(g_repo, (git_object *)old_commit, GIT_RESET_HARD, NULL, NULL));
+
+ cl_git_pass(p_unlink("testrepo/branch_file.txt"));
+ cl_git_pass(git_index_remove_bypath(index ,"branch_file.txt"));
+ cl_git_pass(git_index_write(index));
+
+ cl_assert(!git_path_exists("testrepo/branch_file.txt"));
+
+ cl_git_pass(git_oid_fromstr(&new_id, "099fabac3a9ea935598528c27f866e34089c2eff"));
+ cl_git_pass(git_commit_lookup(&new_commit, g_repo, &new_id));
+
+
+ cl_git_fail(git_checkout_tree(g_repo, (git_object *)new_commit, &opts));
+
+ cl_assert_equal_i(1, ct.n_conflicts);
+ cl_assert_equal_i(1, ct.n_updates);
+
+ git_commit_free(old_commit);
+ git_commit_free(new_commit);
+ git_index_free(index);
+}
+
+struct checkout_cancel_at {
+ const char *filename;
+ int error;
+ int count;
+};
+
+static int checkout_cancel_cb(
+ git_checkout_notify_t why,
+ const char *path,
+ const git_diff_file *b,
+ const git_diff_file *t,
+ const git_diff_file *w,
+ void *payload)
+{
+ struct checkout_cancel_at *ca = payload;
+
+ GIT_UNUSED(why); GIT_UNUSED(b); GIT_UNUSED(t); GIT_UNUSED(w);
+
+ ca->count++;
+
+ if (!strcmp(path, ca->filename))
+ return ca->error;
+
+ return 0;
+}
+
+void test_checkout_tree__can_cancel_checkout_from_notify(void)
+{
+ struct checkout_cancel_at ca;
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_oid oid;
+ git_object *obj = NULL;
+
+ assert_on_branch(g_repo, "master");
+
+ cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir"));
+ cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+
+ ca.filename = "new.txt";
+ ca.error = -5555;
+ ca.count = 0;
+
+ opts.notify_flags = GIT_CHECKOUT_NOTIFY_UPDATED;
+ opts.notify_cb = checkout_cancel_cb;
+ opts.notify_payload = &ca;
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ cl_assert(!git_path_exists("testrepo/new.txt"));
+
+ cl_git_fail_with(git_checkout_tree(g_repo, obj, &opts), -5555);
+
+ cl_assert(!git_path_exists("testrepo/new.txt"));
+
+ /* on case-insensitive FS = a/b.txt, branch_file.txt, new.txt */
+ /* on case-sensitive FS = README, then above */
+
+ if (git_path_exists("testrepo/.git/CoNfIg")) /* case insensitive */
+ cl_assert_equal_i(3, ca.count);
+ else
+ cl_assert_equal_i(4, ca.count);
+
+ /* and again with a different stopping point and return code */
+ ca.filename = "README";
+ ca.error = 123;
+ ca.count = 0;
+
+ cl_git_fail_with(git_checkout_tree(g_repo, obj, &opts), 123);
+
+ cl_assert(!git_path_exists("testrepo/new.txt"));
+
+ if (git_path_exists("testrepo/.git/CoNfIg")) /* case insensitive */
+ cl_assert_equal_i(4, ca.count);
+ else
+ cl_assert_equal_i(1, ca.count);
+
+ git_object_free(obj);
+}
+
+void test_checkout_tree__can_checkout_with_last_workdir_item_missing(void)
+{
+ git_index *index = NULL;
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_oid tree_id, commit_id;
+ git_tree *tree = NULL;
+ git_commit *commit = NULL;
+
+ git_repository_index(&index, g_repo);
+
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ cl_git_pass(git_reference_name_to_id(&commit_id, g_repo, "refs/heads/master"));
+ cl_git_pass(git_commit_lookup(&commit, g_repo, &commit_id));
+
+ cl_git_pass(git_checkout_tree(g_repo, (git_object *)commit, &opts));
+ cl_git_pass(git_repository_set_head(g_repo, "refs/heads/master", NULL, NULL));
+
+ cl_git_pass(p_mkdir("./testrepo/this-is-dir", 0777));
+ cl_git_mkfile("./testrepo/this-is-dir/contained_file", "content\n");
+
+ cl_git_pass(git_index_add_bypath(index, "this-is-dir/contained_file"));
+ git_index_write_tree(&tree_id, index);
+ cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
+
+ cl_git_pass(p_unlink("./testrepo/this-is-dir/contained_file"));
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE;
+
+ opts.checkout_strategy = 1;
+ git_checkout_tree(g_repo, (git_object *)tree, &opts);
+
+ git_tree_free(tree);
+ git_commit_free(commit);
+ git_index_free(index);
+}
+
+void test_checkout_tree__issue_1397(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ const char *partial_oid = "8a7ef04";
+ git_object *tree = NULL;
+
+ test_checkout_tree__cleanup(); /* cleanup default checkout */
+
+ g_repo = cl_git_sandbox_init("issue_1397");
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", true);
+
+ cl_git_pass(git_revparse_single(&tree, g_repo, partial_oid));
+
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ cl_git_pass(git_checkout_tree(g_repo, tree, &opts));
+
+ check_file_contents("./issue_1397/crlf_file.txt", "first line\r\nsecond line\r\nboth with crlf");
+
+ git_object_free(tree);
+}
+
+void test_checkout_tree__can_write_to_empty_dirs(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_oid oid;
+ git_object *obj = NULL;
+
+ assert_on_branch(g_repo, "master");
+
+ cl_git_pass(p_mkdir("testrepo/a", 0777));
+
+ /* do first checkout with FORCE because we don't know if testrepo
+ * base data is clean for a checkout or not
+ */
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir"));
+ cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+
+ cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
+
+ cl_assert(git_path_isfile("testrepo/a/b.txt"));
+
+ git_object_free(obj);
+}
+
+void test_checkout_tree__fails_when_dir_in_use(void)
+{
+#ifdef GIT_WIN32
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_oid oid;
+ git_object *obj = NULL;
+
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir"));
+ cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+
+ cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
+
+ cl_assert(git_path_isfile("testrepo/a/b.txt"));
+
+ git_object_free(obj);
+
+ cl_git_pass(p_chdir("testrepo/a"));
+
+ cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/master"));
+ cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+
+ cl_git_fail(git_checkout_tree(g_repo, obj, &opts));
+
+ cl_git_pass(p_chdir("../.."));
+
+ cl_assert(git_path_is_empty_dir("testrepo/a"));
+
+ git_object_free(obj);
+#endif
+}
+
+void test_checkout_tree__can_continue_when_dir_in_use(void)
+{
+#ifdef GIT_WIN32
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_oid oid;
+ git_object *obj = NULL;
+
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE |
+ GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES;
+
+ cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir"));
+ cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+
+ cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
+
+ cl_assert(git_path_isfile("testrepo/a/b.txt"));
+
+ git_object_free(obj);
+
+ cl_git_pass(p_chdir("testrepo/a"));
+
+ cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/master"));
+ cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+
+ cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
+
+ cl_git_pass(p_chdir("../.."));
+
+ cl_assert(git_path_is_empty_dir("testrepo/a"));
+
+ git_object_free(obj);
+#endif
+}
+
+void test_checkout_tree__target_directory_from_bare(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_oid oid;
+ checkout_counts cts;
+ memset(&cts, 0, sizeof(cts));
+
+ test_checkout_tree__cleanup(); /* cleanup default checkout */
+
+ g_repo = cl_git_sandbox_init("testrepo.git");
+ cl_assert(git_repository_is_bare(g_repo));
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+
+ opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL;
+ opts.notify_cb = checkout_count_callback;
+ opts.notify_payload = &cts;
+
+ cl_git_pass(git_reference_name_to_id(&oid, g_repo, "HEAD"));
+ cl_git_pass(git_object_lookup(&g_object, g_repo, &oid, GIT_OBJ_ANY));
+
+ cl_git_fail(git_checkout_tree(g_repo, g_object, &opts));
+
+ opts.target_directory = "alternative";
+ cl_assert(!git_path_isdir("alternative"));
+
+ cl_git_pass(git_checkout_tree(g_repo, g_object, &opts));
+
+ cl_assert_equal_i(0, cts.n_untracked);
+ cl_assert_equal_i(0, cts.n_ignored);
+ cl_assert_equal_i(3, cts.n_updates);
+
+ check_file_contents_nocr("./alternative/README", "hey there\n");
+ check_file_contents_nocr("./alternative/branch_file.txt", "hi\nbye!\n");
+ check_file_contents_nocr("./alternative/new.txt", "my new file\n");
+
+ cl_git_pass(git_futils_rmdir_r(
+ "alternative", NULL, GIT_RMDIR_REMOVE_FILES));
+}
+
+void test_checkout_tree__extremely_long_file_name(void)
+{
+ // A utf-8 string with 83 characters, but 249 bytes.
+ const char *longname = "\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97";
+ char path[1024];
+
+ g_opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+ cl_git_pass(git_revparse_single(&g_object, g_repo, "long-file-name"));
+ cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
+
+ sprintf(path, "testrepo/%s.txt", longname);
+ cl_assert(git_path_exists(path));
+
+ git_object_free(g_object);
+ 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(!git_path_exists(path));
+}
+
+static void create_conflict(void)
+{
+ git_index *index;
+ git_index_entry entry;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ memset(&entry, 0x0, sizeof(git_index_entry));
+ entry.mode = 0100644;
+ entry.flags = 1 << GIT_IDXENTRY_STAGESHIFT;
+ git_oid_fromstr(&entry.id, "d427e0b2e138501a3d15cc376077a3631e15bd46");
+ entry.path = "conflicts.txt";
+ cl_git_pass(git_index_add(index, &entry));
+
+ entry.flags = 2 << GIT_IDXENTRY_STAGESHIFT;
+ git_oid_fromstr(&entry.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf");
+ cl_git_pass(git_index_add(index, &entry));
+
+ entry.flags = 3 << GIT_IDXENTRY_STAGESHIFT;
+ git_oid_fromstr(&entry.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863");
+ cl_git_pass(git_index_add(index, &entry));
+
+ git_index_write(index);
+ git_index_free(index);
+}
+
+void test_checkout_tree__fails_when_conflicts_exist_in_index(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_oid oid;
+ git_object *obj = NULL;
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE;
+
+ cl_git_pass(git_reference_name_to_id(&oid, g_repo, "HEAD"));
+ cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+
+ create_conflict();
+
+ cl_git_fail(git_checkout_tree(g_repo, obj, &opts));
+
+ git_object_free(obj);
+}
+
+void test_checkout_tree__filemode_preserved_in_index(void)
+{
+ git_oid executable_oid;
+ git_commit *commit;
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_index *index;
+ const git_index_entry *entry;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ cl_git_pass(git_oid_fromstr(&executable_oid, "afe4393b2b2a965f06acf2ca9658eaa01e0cd6b6"));
+ cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid));
+
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts));
+ cl_assert(entry = git_index_get_bypath(index, "executable.txt", 0));
+ cl_assert_equal_i(0100755, entry->mode);
+
+ git_commit_free(commit);
+ git_index_free(index);
+}
diff --git a/tests/checkout/typechange.c b/tests/checkout/typechange.c
new file mode 100644
index 000000000..7aa14b36d
--- /dev/null
+++ b/tests/checkout/typechange.c
@@ -0,0 +1,240 @@
+#include "clar_libgit2.h"
+#include "git2/checkout.h"
+#include "path.h"
+#include "posix.h"
+#include "fileops.h"
+
+static git_repository *g_repo = NULL;
+
+static const char *g_typechange_oids[] = {
+ "79b9f23e85f55ea36a472a902e875bc1121a94cb",
+ "9bdb75b73836a99e3dbeea640a81de81031fdc29",
+ "0e7ed140b514b8cae23254cb8656fe1674403aff",
+ "9d0235c7a7edc0889a18f97a42ee6db9fe688447",
+ "9b19edf33a03a0c59cdfc113bfa5c06179bf9b1a",
+ "1b63caae4a5ca96f78e8dfefc376c6a39a142475",
+ "6eae26c90e8ccc4d16208972119c40635489c6f0",
+ NULL
+};
+
+static bool g_typechange_empty[] = {
+ true, false, false, false, false, false, true, true
+};
+
+void test_checkout_typechange__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("typechanges");
+
+ cl_fixture_sandbox("submod2_target");
+ p_rename("submod2_target/.gitted", "submod2_target/.git");
+}
+
+void test_checkout_typechange__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+ cl_fixture_cleanup("submod2_target");
+}
+
+static void assert_file_exists(const char *path)
+{
+ cl_assert_(git_path_isfile(path), path);
+}
+
+static void assert_dir_exists(const char *path)
+{
+ cl_assert_(git_path_isdir(path), path);
+}
+
+static void assert_workdir_matches_tree(
+ git_repository *repo, const git_oid *id, const char *root, bool recurse)
+{
+ git_object *obj;
+ git_tree *tree;
+ size_t i, max_i;
+ git_buf path = GIT_BUF_INIT;
+
+ if (!root)
+ root = git_repository_workdir(repo);
+ cl_assert(root);
+
+ cl_git_pass(git_object_lookup(&obj, repo, id, GIT_OBJ_ANY));
+ cl_git_pass(git_object_peel((git_object **)&tree, obj, GIT_OBJ_TREE));
+ git_object_free(obj);
+
+ max_i = git_tree_entrycount(tree);
+
+ for (i = 0; i < max_i; ++i) {
+ const git_tree_entry *te = git_tree_entry_byindex(tree, i);
+ cl_assert(te);
+
+ cl_git_pass(git_buf_joinpath(&path, root, git_tree_entry_name(te)));
+
+ switch (git_tree_entry_type(te)) {
+ case GIT_OBJ_COMMIT:
+ assert_dir_exists(path.ptr);
+ break;
+ case GIT_OBJ_TREE:
+ assert_dir_exists(path.ptr);
+ if (recurse)
+ assert_workdir_matches_tree(
+ repo, git_tree_entry_id(te), path.ptr, true);
+ break;
+ case GIT_OBJ_BLOB:
+ switch (git_tree_entry_filemode(te)) {
+ case GIT_FILEMODE_BLOB:
+ case GIT_FILEMODE_BLOB_EXECUTABLE:
+ assert_file_exists(path.ptr);
+ /* because of cross-platform, don't confirm exec bit yet */
+ break;
+ case GIT_FILEMODE_LINK:
+ cl_assert_(git_path_exists(path.ptr), path.ptr);
+ /* because of cross-platform, don't confirm link yet */
+ break;
+ default:
+ cl_assert(false); /* really?! */
+ }
+ break;
+ default:
+ cl_assert(false); /* really?!! */
+ }
+ }
+
+ git_tree_free(tree);
+ git_buf_free(&path);
+}
+
+void test_checkout_typechange__checkout_typechanges_safe(void)
+{
+ int i;
+ git_object *obj;
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ for (i = 0; g_typechange_oids[i] != NULL; ++i) {
+ cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i]));
+
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ /* There are bugs in some submodule->tree changes that prevent
+ * SAFE from passing here, even though the following should work:
+ */
+ /* !i ? GIT_CHECKOUT_FORCE : GIT_CHECKOUT_SAFE; */
+
+ cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
+
+ cl_git_pass(
+ git_repository_set_head_detached(g_repo, git_object_id(obj), NULL, NULL));
+
+ assert_workdir_matches_tree(g_repo, git_object_id(obj), NULL, true);
+
+ git_object_free(obj);
+
+ if (!g_typechange_empty[i]) {
+ cl_assert(git_path_isdir("typechanges"));
+ cl_assert(git_path_exists("typechanges/a"));
+ cl_assert(git_path_exists("typechanges/b"));
+ cl_assert(git_path_exists("typechanges/c"));
+ cl_assert(git_path_exists("typechanges/d"));
+ cl_assert(git_path_exists("typechanges/e"));
+ } else {
+ cl_assert(git_path_isdir("typechanges"));
+ cl_assert(!git_path_exists("typechanges/a"));
+ cl_assert(!git_path_exists("typechanges/b"));
+ cl_assert(!git_path_exists("typechanges/c"));
+ cl_assert(!git_path_exists("typechanges/d"));
+ cl_assert(!git_path_exists("typechanges/e"));
+ }
+ }
+}
+
+typedef struct {
+ int conflicts;
+ int dirty;
+ int updates;
+ int untracked;
+ int ignored;
+} notify_counts;
+
+static int notify_counter(
+ git_checkout_notify_t why,
+ const char *path,
+ const git_diff_file *baseline,
+ const git_diff_file *target,
+ const git_diff_file *workdir,
+ void *payload)
+{
+ notify_counts *cts = payload;
+
+ GIT_UNUSED(path);
+ GIT_UNUSED(baseline);
+ GIT_UNUSED(target);
+ GIT_UNUSED(workdir);
+
+ switch (why) {
+ case GIT_CHECKOUT_NOTIFY_CONFLICT: cts->conflicts++; break;
+ case GIT_CHECKOUT_NOTIFY_DIRTY: cts->dirty++; break;
+ case GIT_CHECKOUT_NOTIFY_UPDATED: cts->updates++; break;
+ case GIT_CHECKOUT_NOTIFY_UNTRACKED: cts->untracked++; break;
+ case GIT_CHECKOUT_NOTIFY_IGNORED: cts->ignored++; break;
+ default: break;
+ }
+
+ return 0;
+}
+
+static void force_create_file(const char *file)
+{
+ int error = git_futils_rmdir_r(file, NULL,
+ GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS);
+ cl_assert(!error || error == GIT_ENOTFOUND);
+ cl_git_pass(git_futils_mkpath2file(file, 0777));
+ cl_git_rewritefile(file, "yowza!!");
+}
+
+void test_checkout_typechange__checkout_with_conflicts(void)
+{
+ int i;
+ git_object *obj;
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ notify_counts cts = {0};
+
+ opts.notify_flags =
+ GIT_CHECKOUT_NOTIFY_CONFLICT | GIT_CHECKOUT_NOTIFY_UNTRACKED;
+ opts.notify_cb = notify_counter;
+ opts.notify_payload = &cts;
+
+ for (i = 0; g_typechange_oids[i] != NULL; ++i) {
+ cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i]));
+
+ force_create_file("typechanges/a/blocker");
+ force_create_file("typechanges/b");
+ force_create_file("typechanges/c/sub/sub/file");
+ git_futils_rmdir_r("typechanges/d", NULL, GIT_RMDIR_REMOVE_FILES);
+ p_mkdir("typechanges/d", 0777); /* intentionally empty dir */
+ force_create_file("typechanges/untracked");
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+ memset(&cts, 0, sizeof(cts));
+
+ cl_git_fail(git_checkout_tree(g_repo, obj, &opts));
+ cl_assert(cts.conflicts > 0);
+ cl_assert(cts.untracked > 0);
+
+ opts.checkout_strategy =
+ GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED;
+ memset(&cts, 0, sizeof(cts));
+
+ cl_assert(git_path_exists("typechanges/untracked"));
+
+ cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
+ cl_assert_equal_i(0, cts.conflicts);
+
+ cl_assert(!git_path_exists("typechanges/untracked"));
+
+ cl_git_pass(
+ git_repository_set_head_detached(g_repo, git_object_id(obj), NULL, NULL));
+
+ assert_workdir_matches_tree(g_repo, git_object_id(obj), NULL, true);
+
+ git_object_free(obj);
+ }
+}
diff --git a/tests/cherrypick/bare.c b/tests/cherrypick/bare.c
new file mode 100644
index 000000000..7ac1054a1
--- /dev/null
+++ b/tests/cherrypick/bare.c
@@ -0,0 +1,106 @@
+#include "clar.h"
+#include "clar_libgit2.h"
+
+#include "buffer.h"
+#include "fileops.h"
+#include "git2/cherrypick.h"
+
+#include "../merge/merge_helpers.h"
+
+#define TEST_REPO_PATH "cherrypick"
+
+static git_repository *repo;
+
+void test_cherrypick_bare__initialize(void)
+{
+ repo = cl_git_sandbox_init(TEST_REPO_PATH);
+}
+
+void test_cherrypick_bare__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_cherrypick_bare__automerge(void)
+{
+ git_commit *head = NULL, *commit = NULL;
+ git_index *index = NULL;
+ git_oid head_oid, cherry_oid;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "38c05a857e831a7e759d83778bfc85d003e21c45", 0, "file1.txt" },
+ { 0100644, "a661b5dec1004e2c62654ded3762370c27cf266b", 0, "file2.txt" },
+ { 0100644, "df6b290e0bd6a89b01d69f66687e8abf385283ca", 0, "file3.txt" },
+ };
+
+ git_oid_fromstr(&head_oid, "d3d77487660ee3c0194ee01dc5eaf478782b1c7e");
+ cl_git_pass(git_commit_lookup(&head, repo, &head_oid));
+
+ git_oid_fromstr(&cherry_oid, "cfc4f0999a8367568e049af4f72e452d40828a15");
+ cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid));
+
+ cl_git_pass(git_cherry_pick_commit(&index, repo, commit, head, 0, NULL));
+ cl_assert(merge_test_index(index, merge_index_entries, 3));
+
+ git_index_free(index);
+ git_commit_free(head);
+ git_commit_free(commit);
+}
+
+void test_cherrypick_bare__conflicts(void)
+{
+ git_commit *head = NULL, *commit = NULL;
+ git_index *index = NULL;
+ git_oid head_oid, cherry_oid;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "242e7977ba73637822ffb265b46004b9b0e5153b", 0, "file1.txt" },
+ { 0100644, "a58ca3fee5eb68b11adc2703e5843f968c9dad1e", 1, "file2.txt" },
+ { 0100644, "bd6ffc8c6c41f0f85ff9e3d61c9479516bac0024", 2, "file2.txt" },
+ { 0100644, "563f6473a3858f99b80e5f93c660512ed38e1e6f", 3, "file2.txt" },
+ { 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 1, "file3.txt" },
+ { 0100644, "1124c2c1ae07b26fded662d6c3f3631d9dc16f88", 2, "file3.txt" },
+ { 0100644, "e233b9ed408a95e9d4b65fec7fc34943a556deb2", 3, "file3.txt" },
+ };
+
+ git_oid_fromstr(&head_oid, "bafbf6912c09505ac60575cd43d3f2aba3bd84d8");
+ cl_git_pass(git_commit_lookup(&head, repo, &head_oid));
+
+ git_oid_fromstr(&cherry_oid, "e9b63f3655b2ad80c0ff587389b5a9589a3a7110");
+ cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid));
+
+ cl_git_pass(git_cherry_pick_commit(&index, repo, commit, head, 0, NULL));
+ cl_assert(merge_test_index(index, merge_index_entries, 7));
+
+ git_index_free(index);
+ git_commit_free(head);
+ git_commit_free(commit);
+}
+
+void test_cherrypick_bare__orphan(void)
+{
+ git_commit *head = NULL, *commit = NULL;
+ git_index *index = NULL;
+ git_oid head_oid, cherry_oid;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "38c05a857e831a7e759d83778bfc85d003e21c45", 0, "file1.txt" },
+ { 0100644, "a661b5dec1004e2c62654ded3762370c27cf266b", 0, "file2.txt" },
+ { 0100644, "85a4a1d791973644f24c72f5e89420d3064cc452", 0, "file3.txt" },
+ { 0100644, "9ccb9bf50c011fd58dcbaa65df917bf79539717f", 0, "orphan.txt" },
+ };
+
+ git_oid_fromstr(&head_oid, "d3d77487660ee3c0194ee01dc5eaf478782b1c7e");
+ cl_git_pass(git_commit_lookup(&head, repo, &head_oid));
+
+ git_oid_fromstr(&cherry_oid, "74f06b5bfec6d33d7264f73606b57a7c0b963819");
+ cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid));
+
+ cl_git_pass(git_cherry_pick_commit(&index, repo, commit, head, 0, NULL));
+ cl_assert(merge_test_index(index, merge_index_entries, 4));
+
+ git_index_free(index);
+ git_commit_free(head);
+ git_commit_free(commit);
+}
+
diff --git a/tests/cherrypick/workdir.c b/tests/cherrypick/workdir.c
new file mode 100644
index 000000000..581a5f997
--- /dev/null
+++ b/tests/cherrypick/workdir.c
@@ -0,0 +1,429 @@
+#include "clar.h"
+#include "clar_libgit2.h"
+
+#include "buffer.h"
+#include "fileops.h"
+#include "git2/cherrypick.h"
+
+#include "../merge/merge_helpers.h"
+
+#define TEST_REPO_PATH "cherrypick"
+
+static git_repository *repo;
+static git_index *repo_index;
+
+// Fixture setup and teardown
+void test_cherrypick_workdir__initialize(void)
+{
+ repo = cl_git_sandbox_init(TEST_REPO_PATH);
+ git_repository_index(&repo_index, repo);
+}
+
+void test_cherrypick_workdir__cleanup(void)
+{
+ git_index_free(repo_index);
+ cl_git_sandbox_cleanup();
+}
+
+/* git reset --hard d3d77487660ee3c0194ee01dc5eaf478782b1c7e
+ * git cherry-pick cfc4f0999a8367568e049af4f72e452d40828a15
+ * git cherry-pick 964ea3da044d9083181a88ba6701de9e35778bf4
+ * git cherry-pick a43a050c588d4e92f11a6b139680923e9728477d
+ */
+void test_cherrypick_workdir__automerge(void)
+{
+ git_oid head_oid;
+ git_signature *signature = NULL;
+ size_t i;
+
+ const char *cherry_pick_oids[] = {
+ "cfc4f0999a8367568e049af4f72e452d40828a15",
+ "964ea3da044d9083181a88ba6701de9e35778bf4",
+ "a43a050c588d4e92f11a6b139680923e9728477d",
+ };
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "38c05a857e831a7e759d83778bfc85d003e21c45", 0, "file1.txt" },
+ { 0100644, "a661b5dec1004e2c62654ded3762370c27cf266b", 0, "file2.txt" },
+ { 0100644, "df6b290e0bd6a89b01d69f66687e8abf385283ca", 0, "file3.txt" },
+
+ { 0100644, "38c05a857e831a7e759d83778bfc85d003e21c45", 0, "file1.txt" },
+ { 0100644, "bd8fc3c59fb52d3c8b5907ace7defa5803f82419", 0, "file2.txt" },
+ { 0100644, "df6b290e0bd6a89b01d69f66687e8abf385283ca", 0, "file3.txt" },
+
+ { 0100644, "f06427bee380364bc7e0cb26a9245158e4726ce0", 0, "file1.txt" },
+ { 0100644, "bd8fc3c59fb52d3c8b5907ace7defa5803f82419", 0, "file2.txt" },
+ { 0100644, "df6b290e0bd6a89b01d69f66687e8abf385283ca", 0, "file3.txt" },
+ };
+
+ cl_git_pass(git_signature_new(&signature, "Picker", "picker@example.org", time(NULL), 0));
+
+ git_oid_fromstr(&head_oid, "d3d77487660ee3c0194ee01dc5eaf478782b1c7e");
+
+ for (i = 0; i < 3; ++i) {
+ git_commit *head = NULL, *commit = NULL;
+ git_oid cherry_oid, cherry_picked_oid, cherry_picked_tree_oid;
+ git_tree *cherry_picked_tree = NULL;
+
+ cl_git_pass(git_commit_lookup(&head, repo, &head_oid));
+ cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL, NULL));
+
+ git_oid_fromstr(&cherry_oid, cherry_pick_oids[i]);
+ cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid));
+ cl_git_pass(git_cherry_pick(repo, commit, NULL));
+
+ cl_assert(git_path_exists(TEST_REPO_PATH "/.git/CHERRY_PICK_HEAD"));
+ cl_assert(git_path_exists(TEST_REPO_PATH "/.git/MERGE_MSG"));
+
+ cl_git_pass(git_index_write_tree(&cherry_picked_tree_oid, repo_index));
+ cl_git_pass(git_tree_lookup(&cherry_picked_tree, repo, &cherry_picked_tree_oid));
+ cl_git_pass(git_commit_create(&cherry_picked_oid, repo, "HEAD", signature, signature, NULL,
+ "Cherry picked!", cherry_picked_tree, 1, (const git_commit **)&head));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries + i * 3, 3));
+
+ git_oid_cpy(&head_oid, &cherry_picked_oid);
+
+ git_tree_free(cherry_picked_tree);
+ git_commit_free(head);
+ git_commit_free(commit);
+ }
+
+ git_signature_free(signature);
+}
+
+/* git reset --hard bafbf6912c09505ac60575cd43d3f2aba3bd84d8
+ * git cherry-pick e9b63f3655b2ad80c0ff587389b5a9589a3a7110
+ */
+void test_cherrypick_workdir__conflicts(void)
+{
+ git_commit *head = NULL, *commit = NULL;
+ git_oid head_oid, cherry_oid;
+ git_buf conflicting_buf = GIT_BUF_INIT, mergemsg_buf = GIT_BUF_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "242e7977ba73637822ffb265b46004b9b0e5153b", 0, "file1.txt" },
+ { 0100644, "a58ca3fee5eb68b11adc2703e5843f968c9dad1e", 1, "file2.txt" },
+ { 0100644, "bd6ffc8c6c41f0f85ff9e3d61c9479516bac0024", 2, "file2.txt" },
+ { 0100644, "563f6473a3858f99b80e5f93c660512ed38e1e6f", 3, "file2.txt" },
+ { 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 1, "file3.txt" },
+ { 0100644, "1124c2c1ae07b26fded662d6c3f3631d9dc16f88", 2, "file3.txt" },
+ { 0100644, "e233b9ed408a95e9d4b65fec7fc34943a556deb2", 3, "file3.txt" },
+ };
+
+ git_oid_fromstr(&head_oid, "bafbf6912c09505ac60575cd43d3f2aba3bd84d8");
+
+ cl_git_pass(git_commit_lookup(&head, repo, &head_oid));
+ cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL, NULL));
+
+ git_oid_fromstr(&cherry_oid, "e9b63f3655b2ad80c0ff587389b5a9589a3a7110");
+ cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid));
+ cl_git_pass(git_cherry_pick(repo, commit, NULL));
+
+ cl_assert(git_path_exists(TEST_REPO_PATH "/.git/CHERRY_PICK_HEAD"));
+ cl_assert(git_path_exists(TEST_REPO_PATH "/.git/MERGE_MSG"));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 7));
+
+ cl_git_pass(git_futils_readbuffer(&mergemsg_buf,
+ TEST_REPO_PATH "/.git/MERGE_MSG"));
+ cl_assert(strcmp(git_buf_cstr(&mergemsg_buf),
+ "Change all files\n" \
+ "\n" \
+ "Conflicts:\n" \
+ "\tfile2.txt\n" \
+ "\tfile3.txt\n") == 0);
+
+ cl_git_pass(git_futils_readbuffer(&conflicting_buf,
+ TEST_REPO_PATH "/file2.txt"));
+
+ cl_assert(strcmp(git_buf_cstr(&conflicting_buf),
+ "!File 2\n" \
+ "File 2\n" \
+ "File 2\n" \
+ "File 2\n" \
+ "File 2\n" \
+ "File 2\n" \
+ "File 2\n" \
+ "File 2\n" \
+ "File 2\n" \
+ "File 2\n" \
+ "File 2!!\n" \
+ "File 2\n" \
+ "File 2\n" \
+ "File 2\n" \
+ "<<<<<<< HEAD\n" \
+ "File 2\n" \
+ "=======\n" \
+ "File 2!\n" \
+ "File 2\n" \
+ "File 2!\n" \
+ ">>>>>>> e9b63f3... Change all files\n") == 0);
+
+ cl_git_pass(git_futils_readbuffer(&conflicting_buf,
+ TEST_REPO_PATH "/file3.txt"));
+
+ cl_assert(strcmp(git_buf_cstr(&conflicting_buf),
+ "!File 3\n" \
+ "File 3\n" \
+ "File 3\n" \
+ "File 3\n" \
+ "File 3\n" \
+ "File 3\n" \
+ "File 3\n" \
+ "File 3\n" \
+ "File 3\n" \
+ "File 3\n" \
+ "File 3\n" \
+ "File 3!!\n" \
+ "File 3\n" \
+ "File 3\n" \
+ "File 3\n" \
+ "<<<<<<< HEAD\n" \
+ "=======\n" \
+ "File 3!\n" \
+ "File 3!\n" \
+ ">>>>>>> e9b63f3... Change all files\n") == 0);
+
+ git_commit_free(commit);
+ git_commit_free(head);
+ git_buf_free(&mergemsg_buf);
+ git_buf_free(&conflicting_buf);
+}
+
+/* git reset --hard bafbf6912c09505ac60575cd43d3f2aba3bd84d8
+ * git cherry-pick -X ours e9b63f3655b2ad80c0ff587389b5a9589a3a7110
+ */
+void test_cherrypick_workdir__conflict_use_ours(void)
+{
+ git_commit *head = NULL, *commit = NULL;
+ git_oid head_oid, cherry_oid;
+ git_cherry_pick_options opts = GIT_CHERRY_PICK_OPTIONS_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "242e7977ba73637822ffb265b46004b9b0e5153b", 0, "file1.txt" },
+ { 0100644, "a58ca3fee5eb68b11adc2703e5843f968c9dad1e", 1, "file2.txt" },
+ { 0100644, "bd6ffc8c6c41f0f85ff9e3d61c9479516bac0024", 2, "file2.txt" },
+ { 0100644, "563f6473a3858f99b80e5f93c660512ed38e1e6f", 3, "file2.txt" },
+ { 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 1, "file3.txt" },
+ { 0100644, "1124c2c1ae07b26fded662d6c3f3631d9dc16f88", 2, "file3.txt" },
+ { 0100644, "e233b9ed408a95e9d4b65fec7fc34943a556deb2", 3, "file3.txt" },
+ };
+
+ struct merge_index_entry merge_filesystem_entries[] = {
+ { 0100644, "242e7977ba73637822ffb265b46004b9b0e5153b", 0, "file1.txt" },
+ { 0100644, "bd6ffc8c6c41f0f85ff9e3d61c9479516bac0024", 0, "file2.txt" },
+ { 0100644, "1124c2c1ae07b26fded662d6c3f3631d9dc16f88", 0, "file3.txt" },
+ };
+
+ /* leave the index in a conflicted state, but checkout "ours" to the workdir */
+ opts.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS;
+
+ git_oid_fromstr(&head_oid, "bafbf6912c09505ac60575cd43d3f2aba3bd84d8");
+
+ cl_git_pass(git_commit_lookup(&head, repo, &head_oid));
+ cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL, NULL));
+
+ git_oid_fromstr(&cherry_oid, "e9b63f3655b2ad80c0ff587389b5a9589a3a7110");
+ cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid));
+ cl_git_pass(git_cherry_pick(repo, commit, &opts));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 7));
+ cl_assert(merge_test_workdir(repo, merge_filesystem_entries, 3));
+
+ /* resolve conflicts in the index by taking "ours" */
+ opts.merge_opts.file_favor = GIT_MERGE_FILE_FAVOR_OURS;
+
+ cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL, NULL));
+ cl_git_pass(git_cherry_pick(repo, commit, &opts));
+
+ cl_assert(merge_test_index(repo_index, merge_filesystem_entries, 3));
+ cl_assert(merge_test_workdir(repo, merge_filesystem_entries, 3));
+
+ git_commit_free(commit);
+ git_commit_free(head);
+}
+
+/* git reset --hard cfc4f0999a8367568e049af4f72e452d40828a15
+ * git cherry-pick 2a26c7e88b285613b302ba76712bc998863f3cbc
+ */
+void test_cherrypick_workdir__rename(void)
+{
+ git_commit *head, *commit;
+ git_oid head_oid, cherry_oid;
+ git_cherry_pick_options opts = GIT_CHERRY_PICK_OPTIONS_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "19c5c7207054604b69c84d08a7571ef9672bb5c2", 0, "file1.txt" },
+ { 0100644, "a58ca3fee5eb68b11adc2703e5843f968c9dad1e", 0, "file2.txt" },
+ { 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 0, "file3.txt.renamed" },
+ };
+
+ opts.merge_opts.flags |= GIT_MERGE_TREE_FIND_RENAMES;
+ opts.merge_opts.rename_threshold = 50;
+
+ git_oid_fromstr(&head_oid, "cfc4f0999a8367568e049af4f72e452d40828a15");
+ cl_git_pass(git_commit_lookup(&head, repo, &head_oid));
+ cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL, NULL));
+
+ git_oid_fromstr(&cherry_oid, "2a26c7e88b285613b302ba76712bc998863f3cbc");
+ cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid));
+ cl_git_pass(git_cherry_pick(repo, commit, &opts));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 3));
+
+ git_commit_free(commit);
+ git_commit_free(head);
+}
+
+/* git reset --hard 44cd2ed2052c9c68f9a439d208e9614dc2a55c70
+ * git cherry-pick 2a26c7e88b285613b302ba76712bc998863f3cbc
+ */
+void test_cherrypick_workdir__both_renamed(void)
+{
+ git_commit *head, *commit;
+ git_oid head_oid, cherry_oid;
+ git_buf mergemsg_buf = GIT_BUF_INIT;
+ git_cherry_pick_options opts = GIT_CHERRY_PICK_OPTIONS_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "19c5c7207054604b69c84d08a7571ef9672bb5c2", 0, "file1.txt" },
+ { 0100644, "a58ca3fee5eb68b11adc2703e5843f968c9dad1e", 0, "file2.txt" },
+ { 0100644, "e233b9ed408a95e9d4b65fec7fc34943a556deb2", 1, "file3.txt" },
+ { 0100644, "e233b9ed408a95e9d4b65fec7fc34943a556deb2", 3, "file3.txt.renamed" },
+ { 0100644, "28d9eb4208074ad1cc84e71ccc908b34573f05d2", 2, "file3.txt.renamed_on_branch" },
+ };
+
+ opts.merge_opts.flags |= GIT_MERGE_TREE_FIND_RENAMES;
+ opts.merge_opts.rename_threshold = 50;
+
+ git_oid_fromstr(&head_oid, "44cd2ed2052c9c68f9a439d208e9614dc2a55c70");
+ cl_git_pass(git_commit_lookup(&head, repo, &head_oid));
+ cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL, NULL));
+
+ git_oid_fromstr(&cherry_oid, "2a26c7e88b285613b302ba76712bc998863f3cbc");
+ cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid));
+ cl_git_pass(git_cherry_pick(repo, commit, &opts));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 5));
+
+ cl_git_pass(git_futils_readbuffer(&mergemsg_buf,
+ TEST_REPO_PATH "/.git/MERGE_MSG"));
+ cl_assert(strcmp(git_buf_cstr(&mergemsg_buf),
+ "Renamed file3.txt -> file3.txt.renamed\n" \
+ "\n" \
+ "Conflicts:\n" \
+ "\tfile3.txt\n" \
+ "\tfile3.txt.renamed\n" \
+ "\tfile3.txt.renamed_on_branch\n") == 0);
+
+ git_buf_free(&mergemsg_buf);
+ git_commit_free(commit);
+ git_commit_free(head);
+}
+
+void test_cherrypick_workdir__nonmerge_fails_mainline_specified(void)
+{
+ git_reference *head;
+ git_commit *commit;
+ git_cherry_pick_options opts = GIT_CHERRY_PICK_OPTIONS_INIT;
+
+ cl_git_pass(git_repository_head(&head, repo));
+ cl_git_pass(git_reference_peel((git_object **)&commit, head, GIT_OBJ_COMMIT));
+
+ opts.mainline = 1;
+ cl_must_fail(git_cherry_pick(repo, commit, &opts));
+ cl_assert(!git_path_exists(TEST_REPO_PATH "/.git/CHERRY_PICK_HEAD"));
+ cl_assert(!git_path_exists(TEST_REPO_PATH "/.git/MERGE_MSG"));
+
+ git_reference_free(head);
+ git_commit_free(commit);
+}
+
+/* git reset --hard cfc4f0999a8367568e049af4f72e452d40828a15
+ * git cherry-pick abe4603bc7cd5b8167a267e0e2418fd2348f8cff
+ */
+void test_cherrypick_workdir__merge_fails_without_mainline_specified(void)
+{
+ git_commit *head, *commit;
+ git_oid head_oid, cherry_oid;
+
+ git_oid_fromstr(&head_oid, "cfc4f0999a8367568e049af4f72e452d40828a15");
+ cl_git_pass(git_commit_lookup(&head, repo, &head_oid));
+ cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL, NULL));
+
+ git_oid_fromstr(&cherry_oid, "abe4603bc7cd5b8167a267e0e2418fd2348f8cff");
+ cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid));
+
+ cl_must_fail(git_cherry_pick(repo, commit, NULL));
+ cl_assert(!git_path_exists(TEST_REPO_PATH "/.git/CHERRY_PICK_HEAD"));
+ cl_assert(!git_path_exists(TEST_REPO_PATH "/.git/MERGE_MSG"));
+
+ git_commit_free(commit);
+ git_commit_free(head);
+}
+
+/* git reset --hard cfc4f0999a8367568e049af4f72e452d40828a15
+ * git cherry-pick -m1 abe4603bc7cd5b8167a267e0e2418fd2348f8cff
+ */
+void test_cherrypick_workdir__merge_first_parent(void)
+{
+ git_commit *head, *commit;
+ git_oid head_oid, cherry_oid;
+ git_cherry_pick_options opts = GIT_CHERRY_PICK_OPTIONS_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "f90f9dcbdac2cce5cc166346160e19cb693ef4e8", 0, "file1.txt" },
+ { 0100644, "563f6473a3858f99b80e5f93c660512ed38e1e6f", 0, "file2.txt" },
+ { 0100644, "e233b9ed408a95e9d4b65fec7fc34943a556deb2", 0, "file3.txt" },
+ };
+
+ opts.mainline = 1;
+
+ git_oid_fromstr(&head_oid, "cfc4f0999a8367568e049af4f72e452d40828a15");
+ cl_git_pass(git_commit_lookup(&head, repo, &head_oid));
+ cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL, NULL));
+
+ git_oid_fromstr(&cherry_oid, "abe4603bc7cd5b8167a267e0e2418fd2348f8cff");
+ cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid));
+
+ cl_git_pass(git_cherry_pick(repo, commit, &opts));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 3));
+
+ git_commit_free(commit);
+ git_commit_free(head);
+}
+
+/* git reset --hard cfc4f0999a8367568e049af4f72e452d40828a15
+ * git cherry-pick -m2 abe4603bc7cd5b8167a267e0e2418fd2348f8cff
+ */
+void test_cherrypick_workdir__merge_second_parent(void)
+{
+ git_commit *head, *commit;
+ git_oid head_oid, cherry_oid;
+ git_cherry_pick_options opts = GIT_CHERRY_PICK_OPTIONS_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "487434cace79238a7091e2220611d4f20a765690", 0, "file1.txt" },
+ { 0100644, "e5183bfd18e3a0a691fadde2f0d5610b73282d31", 0, "file2.txt" },
+ { 0100644, "409a1bec58bf35348e8b62b72bb9c1f45cf5a587", 0, "file3.txt" },
+ };
+
+ opts.mainline = 2;
+
+ git_oid_fromstr(&head_oid, "cfc4f0999a8367568e049af4f72e452d40828a15");
+ cl_git_pass(git_commit_lookup(&head, repo, &head_oid));
+ cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL, NULL));
+
+ git_oid_fromstr(&cherry_oid, "abe4603bc7cd5b8167a267e0e2418fd2348f8cff");
+ cl_git_pass(git_commit_lookup(&commit, repo, &cherry_oid));
+
+ cl_git_pass(git_cherry_pick(repo, commit, &opts));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 3));
+
+ git_commit_free(commit);
+ git_commit_free(head);
+}
+
diff --git a/tests/clar.c b/tests/clar.c
new file mode 100644
index 000000000..154644783
--- /dev/null
+++ b/tests/clar.c
@@ -0,0 +1,568 @@
+/*
+ * Copyright (c) Vicent Marti. All rights reserved.
+ *
+ * This file is part of clar, distributed under the ISC license.
+ * For full terms see the included COPYING file.
+ */
+#include <assert.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <stdarg.h>
+
+/* required for sandboxing */
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef _WIN32
+# include <windows.h>
+# include <io.h>
+# include <shellapi.h>
+# include <direct.h>
+
+# define _MAIN_CC __cdecl
+
+# ifndef stat
+# define stat(path, st) _stat(path, st)
+# endif
+# ifndef mkdir
+# define mkdir(path, mode) _mkdir(path)
+# endif
+# ifndef chdir
+# define chdir(path) _chdir(path)
+# endif
+# ifndef access
+# define access(path, mode) _access(path, mode)
+# endif
+# ifndef strdup
+# define strdup(str) _strdup(str)
+# endif
+# ifndef strcasecmp
+# define strcasecmp(a,b) _stricmp(a,b)
+# endif
+
+# ifndef __MINGW32__
+# pragma comment(lib, "shell32")
+# ifndef strncpy
+# define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE)
+# endif
+# ifndef W_OK
+# define W_OK 02
+# endif
+# ifndef S_ISDIR
+# define S_ISDIR(x) ((x & _S_IFDIR) != 0)
+# endif
+# define p_snprintf(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__)
+# else
+# define p_snprintf snprintf
+# endif
+
+# ifndef PRIuZ
+# define PRIuZ "Iu"
+# endif
+# ifndef PRIxZ
+# define PRIxZ "Ix"
+# endif
+
+# ifdef _MSC_VER
+ typedef struct stat STAT_T;
+# else
+ typedef struct _stat STAT_T;
+# endif
+#else
+# include <sys/wait.h> /* waitpid(2) */
+# include <unistd.h>
+# define _MAIN_CC
+# define p_snprintf snprintf
+# ifndef PRIuZ
+# define PRIuZ "zu"
+# endif
+# ifndef PRIxZ
+# define PRIxZ "zx"
+# endif
+ typedef struct stat STAT_T;
+#endif
+
+#include "clar.h"
+
+static void fs_rm(const char *_source);
+static void fs_copy(const char *_source, const char *dest);
+
+static const char *
+fixture_path(const char *base, const char *fixture_name);
+
+struct clar_error {
+ const char *test;
+ int test_number;
+ const char *suite;
+ const char *file;
+ int line_number;
+ const char *error_msg;
+ char *description;
+
+ struct clar_error *next;
+};
+
+static struct {
+ int argc;
+ char **argv;
+
+ enum cl_test_status test_status;
+ const char *active_test;
+ const char *active_suite;
+
+ int total_skipped;
+ int total_errors;
+
+ int tests_ran;
+ int suites_ran;
+
+ int report_errors_only;
+ int exit_on_error;
+ int report_suite_names;
+
+ struct clar_error *errors;
+ struct clar_error *last_error;
+
+ void (*local_cleanup)(void *);
+ void *local_cleanup_payload;
+
+ jmp_buf trampoline;
+ int trampoline_enabled;
+} _clar;
+
+struct clar_func {
+ const char *name;
+ void (*ptr)(void);
+};
+
+struct clar_suite {
+ const char *name;
+ struct clar_func initialize;
+ struct clar_func cleanup;
+ const struct clar_func *tests;
+ size_t test_count;
+ int enabled;
+};
+
+/* From clar_print_*.c */
+static void clar_print_init(int test_count, int suite_count, const char *suite_names);
+static void clar_print_shutdown(int test_count, int suite_count, int error_count);
+static void clar_print_error(int num, const struct clar_error *error);
+static void clar_print_ontest(const char *test_name, int test_number, enum cl_test_status failed);
+static void clar_print_onsuite(const char *suite_name, int suite_index);
+static void clar_print_onabort(const char *msg, ...);
+
+/* From clar_sandbox.c */
+static void clar_unsandbox(void);
+static int clar_sandbox(void);
+
+/* Load the declarations for the test suite */
+#include "clar.suite"
+
+/* Core test functions */
+static void
+clar_report_errors(void)
+{
+ int i = 1;
+ struct clar_error *error, *next;
+
+ error = _clar.errors;
+ while (error != NULL) {
+ next = error->next;
+ clar_print_error(i++, error);
+ free(error->description);
+ free(error);
+ error = next;
+ }
+
+ _clar.errors = _clar.last_error = NULL;
+}
+
+static void
+clar_run_test(
+ const struct clar_func *test,
+ const struct clar_func *initialize,
+ const struct clar_func *cleanup)
+{
+ _clar.test_status = CL_TEST_OK;
+ _clar.trampoline_enabled = 1;
+
+ if (setjmp(_clar.trampoline) == 0) {
+ if (initialize->ptr != NULL)
+ initialize->ptr();
+
+ test->ptr();
+ }
+
+ _clar.trampoline_enabled = 0;
+
+ if (_clar.local_cleanup != NULL)
+ _clar.local_cleanup(_clar.local_cleanup_payload);
+
+ if (cleanup->ptr != NULL)
+ cleanup->ptr();
+
+ _clar.tests_ran++;
+
+ /* remove any local-set cleanup methods */
+ _clar.local_cleanup = NULL;
+ _clar.local_cleanup_payload = NULL;
+
+ if (_clar.report_errors_only) {
+ clar_report_errors();
+ } else {
+ clar_print_ontest(test->name, _clar.tests_ran, _clar.test_status);
+ }
+}
+
+static void
+clar_run_suite(const struct clar_suite *suite, const char *filter)
+{
+ const struct clar_func *test = suite->tests;
+ size_t i, matchlen;
+
+ if (!suite->enabled)
+ return;
+
+ if (_clar.exit_on_error && _clar.total_errors)
+ return;
+
+ if (!_clar.report_errors_only)
+ clar_print_onsuite(suite->name, ++_clar.suites_ran);
+
+ _clar.active_suite = suite->name;
+
+ if (filter) {
+ size_t suitelen = strlen(suite->name);
+ matchlen = strlen(filter);
+ if (matchlen <= suitelen) {
+ filter = NULL;
+ } else {
+ filter += suitelen;
+ while (*filter == ':')
+ ++filter;
+ matchlen = strlen(filter);
+ }
+ }
+
+ for (i = 0; i < suite->test_count; ++i) {
+ if (filter && strncmp(test[i].name, filter, matchlen))
+ continue;
+
+ _clar.active_test = test[i].name;
+ clar_run_test(&test[i], &suite->initialize, &suite->cleanup);
+
+ if (_clar.exit_on_error && _clar.total_errors)
+ return;
+ }
+}
+
+static void
+clar_usage(const char *arg)
+{
+ printf("Usage: %s [options]\n\n", arg);
+ printf("Options:\n");
+ printf(" -sname\tRun only the suite with `name` (can go to individual test name)\n");
+ printf(" -iname\tInclude the suite with `name`\n");
+ printf(" -xname\tExclude the suite with `name`\n");
+ printf(" -q \tOnly report tests that had an error\n");
+ printf(" -Q \tQuit as soon as a test fails\n");
+ printf(" -l \tPrint suite names\n");
+ exit(-1);
+}
+
+static void
+clar_parse_args(int argc, char **argv)
+{
+ int i;
+
+ for (i = 1; i < argc; ++i) {
+ char *argument = argv[i];
+
+ if (argument[0] != '-')
+ clar_usage(argv[0]);
+
+ switch (argument[1]) {
+ case 's':
+ case 'i':
+ case 'x': { /* given suite name */
+ int offset = (argument[2] == '=') ? 3 : 2, found = 0;
+ char action = argument[1];
+ size_t j, arglen, suitelen, cmplen;
+
+ argument += offset;
+ arglen = strlen(argument);
+
+ if (arglen == 0)
+ clar_usage(argv[0]);
+
+ for (j = 0; j < _clar_suite_count; ++j) {
+ suitelen = strlen(_clar_suites[j].name);
+ cmplen = (arglen < suitelen) ? arglen : suitelen;
+
+ if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) {
+ int exact = (arglen >= suitelen);
+
+ ++found;
+
+ if (!exact)
+ _clar.report_suite_names = 1;
+
+ switch (action) {
+ case 's': clar_run_suite(&_clar_suites[j], argument); break;
+ case 'i': _clar_suites[j].enabled = 1; break;
+ case 'x': _clar_suites[j].enabled = 0; break;
+ }
+
+ if (exact)
+ break;
+ }
+ }
+
+ if (!found) {
+ clar_print_onabort("No suite matching '%s' found.\n", argument);
+ exit(-1);
+ }
+ break;
+ }
+
+ case 'q':
+ _clar.report_errors_only = 1;
+ break;
+
+ case 'Q':
+ _clar.exit_on_error = 1;
+ break;
+
+ case 'l': {
+ size_t j;
+ printf("Test suites (use -s<name> to run just one):\n");
+ for (j = 0; j < _clar_suite_count; ++j)
+ printf(" %3d: %s\n", (int)j, _clar_suites[j].name);
+
+ exit(0);
+ }
+
+ default:
+ clar_usage(argv[0]);
+ }
+ }
+}
+
+void
+clar_test_init(int argc, char **argv)
+{
+ clar_print_init(
+ (int)_clar_callback_count,
+ (int)_clar_suite_count,
+ ""
+ );
+
+ if (clar_sandbox() < 0) {
+ clar_print_onabort("Failed to sandbox the test runner.\n");
+ exit(-1);
+ }
+
+ _clar.argc = argc;
+ _clar.argv = argv;
+}
+
+int
+clar_test_run()
+{
+ if (_clar.argc > 1)
+ clar_parse_args(_clar.argc, _clar.argv);
+
+ if (!_clar.suites_ran) {
+ size_t i;
+ for (i = 0; i < _clar_suite_count; ++i)
+ clar_run_suite(&_clar_suites[i], NULL);
+ }
+
+ return _clar.total_errors;
+}
+
+void
+clar_test_shutdown()
+{
+ clar_print_shutdown(
+ _clar.tests_ran,
+ (int)_clar_suite_count,
+ _clar.total_errors
+ );
+
+ clar_unsandbox();
+}
+
+int
+clar_test(int argc, char **argv)
+{
+ int errors;
+
+ clar_test_init(argc, argv);
+ errors = clar_test_run();
+ clar_test_shutdown();
+
+ return errors;
+}
+
+static void abort_test(void)
+{
+ if (!_clar.trampoline_enabled) {
+ clar_print_onabort(
+ "Fatal error: a cleanup method raised an exception.");
+ clar_report_errors();
+ exit(-1);
+ }
+
+ longjmp(_clar.trampoline, -1);
+}
+
+void clar__skip(void)
+{
+ _clar.test_status = CL_TEST_SKIP;
+ _clar.total_skipped++;
+ abort_test();
+}
+
+void clar__fail(
+ const char *file,
+ int line,
+ const char *error_msg,
+ const char *description,
+ int should_abort)
+{
+ struct clar_error *error = calloc(1, sizeof(struct clar_error));
+
+ if (_clar.errors == NULL)
+ _clar.errors = error;
+
+ if (_clar.last_error != NULL)
+ _clar.last_error->next = error;
+
+ _clar.last_error = error;
+
+ error->test = _clar.active_test;
+ error->test_number = _clar.tests_ran;
+ error->suite = _clar.active_suite;
+ error->file = file;
+ error->line_number = line;
+ error->error_msg = error_msg;
+
+ if (description != NULL)
+ error->description = strdup(description);
+
+ _clar.total_errors++;
+ _clar.test_status = CL_TEST_FAILURE;
+
+ if (should_abort)
+ abort_test();
+}
+
+void clar__assert(
+ int condition,
+ const char *file,
+ int line,
+ const char *error_msg,
+ const char *description,
+ int should_abort)
+{
+ if (condition)
+ return;
+
+ clar__fail(file, line, error_msg, description, should_abort);
+}
+
+void clar__assert_equal(
+ const char *file,
+ int line,
+ const char *err,
+ int should_abort,
+ const char *fmt,
+ ...)
+{
+ va_list args;
+ char buf[4096];
+ int is_equal = 1;
+
+ va_start(args, fmt);
+
+ if (!strcmp("%s", fmt)) {
+ const char *s1 = va_arg(args, const char *);
+ const char *s2 = va_arg(args, const char *);
+ is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2);
+
+ if (!is_equal) {
+ if (s1 && s2) {
+ int pos;
+ for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)",
+ s1, s2, pos);
+ } else {
+ p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2);
+ }
+ }
+ }
+ else if(!strcmp("%.*s", fmt)) {
+ const char *s1 = va_arg(args, const char *);
+ const char *s2 = va_arg(args, const char *);
+ int len = va_arg(args, int);
+ is_equal = (!s1 || !s2) ? (s1 == s2) : !strncmp(s1, s2, len);
+
+ if (!is_equal) {
+ if (s1 && s2) {
+ int pos;
+ for (pos = 0; s1[pos] == s2[pos] && pos < len; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s' (at byte %d)",
+ len, s1, len, s2, pos);
+ } else {
+ p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s'", len, s1, len, s2);
+ }
+ }
+ }
+ else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) {
+ size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t);
+ is_equal = (sz1 == sz2);
+ if (!is_equal) {
+ int offset = p_snprintf(buf, sizeof(buf), fmt, sz1);
+ strncat(buf, " != ", sizeof(buf) - offset);
+ p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, sz2);
+ }
+ }
+ else if (!strcmp("%p", fmt)) {
+ void *p1 = va_arg(args, void *), *p2 = va_arg(args, void *);
+ is_equal = (p1 == p2);
+ if (!is_equal)
+ p_snprintf(buf, sizeof(buf), "%p != %p", p1, p2);
+ }
+ else {
+ int i1 = va_arg(args, int), i2 = va_arg(args, int);
+ is_equal = (i1 == i2);
+ if (!is_equal) {
+ int offset = p_snprintf(buf, sizeof(buf), fmt, i1);
+ strncat(buf, " != ", sizeof(buf) - offset);
+ p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, i2);
+ }
+ }
+
+ va_end(args);
+
+ if (!is_equal)
+ clar__fail(file, line, err, buf, should_abort);
+}
+
+void cl_set_cleanup(void (*cleanup)(void *), void *opaque)
+{
+ _clar.local_cleanup = cleanup;
+ _clar.local_cleanup_payload = opaque;
+}
+
+#include "clar/sandbox.h"
+#include "clar/fixtures.h"
+#include "clar/fs.h"
+#include "clar/print.h"
diff --git a/tests/clar.h b/tests/clar.h
new file mode 100644
index 000000000..f9df72e8c
--- /dev/null
+++ b/tests/clar.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) Vicent Marti. All rights reserved.
+ *
+ * This file is part of clar, distributed under the ISC license.
+ * For full terms see the included COPYING file.
+ */
+#ifndef __CLAR_TEST_H__
+#define __CLAR_TEST_H__
+
+#include <stdlib.h>
+
+enum cl_test_status {
+ CL_TEST_OK,
+ CL_TEST_FAILURE,
+ CL_TEST_SKIP
+};
+
+void clar_test_init(int argc, char *argv[]);
+int clar_test_run(void);
+void clar_test_shutdown(void);
+
+int clar_test(int argc, char *argv[]);
+
+const char *clar_sandbox_path(void);
+
+void cl_set_cleanup(void (*cleanup)(void *), void *opaque);
+void cl_fs_cleanup(void);
+
+#ifdef CLAR_FIXTURE_PATH
+const char *cl_fixture(const char *fixture_name);
+void cl_fixture_sandbox(const char *fixture_name);
+void cl_fixture_cleanup(const char *fixture_name);
+#endif
+
+/**
+ * Assertion macros with explicit error message
+ */
+#define cl_must_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __LINE__, "Function call failed: " #expr, desc, 1)
+#define cl_must_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __LINE__, "Expected function call to fail: " #expr, desc, 1)
+#define cl_assert_(expr, desc) clar__assert((expr) != 0, __FILE__, __LINE__, "Expression is not true: " #expr, desc, 1)
+
+/**
+ * Check macros with explicit error message
+ */
+#define cl_check_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __LINE__, "Function call failed: " #expr, desc, 0)
+#define cl_check_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __LINE__, "Expected function call to fail: " #expr, desc, 0)
+#define cl_check_(expr, desc) clar__assert((expr) != 0, __FILE__, __LINE__, "Expression is not true: " #expr, desc, 0)
+
+/**
+ * Assertion macros with no error message
+ */
+#define cl_must_pass(expr) cl_must_pass_(expr, NULL)
+#define cl_must_fail(expr) cl_must_fail_(expr, NULL)
+#define cl_assert(expr) cl_assert_(expr, NULL)
+
+/**
+ * Check macros with no error message
+ */
+#define cl_check_pass(expr) cl_check_pass_(expr, NULL)
+#define cl_check_fail(expr) cl_check_fail_(expr, NULL)
+#define cl_check(expr) cl_check_(expr, NULL)
+
+/**
+ * Forced failure/warning
+ */
+#define cl_fail(desc) clar__fail(__FILE__, __LINE__, "Test failed.", desc, 1)
+#define cl_warning(desc) clar__fail(__FILE__, __LINE__, "Warning during test execution:", desc, 0)
+
+#define cl_skip() clar__skip()
+
+/**
+ * Typed assertion macros
+ */
+#define cl_assert_equal_s(s1,s2) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%s", (s1), (s2))
+#define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%s", (s1), (s2))
+
+#define cl_assert_equal_strn(s1,s2,len) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%.*s", (s1), (s2), (int)(len))
+#define cl_assert_equal_strn_(s1,s2,len,note) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%.*s", (s1), (s2), (int)(len))
+
+#define cl_assert_equal_i(i1,i2) clar__assert_equal(__FILE__,__LINE__,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2))
+#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(__FILE__,__LINE__,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2))
+#define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(__FILE__,__LINE__,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2))
+
+#define cl_assert_equal_b(b1,b2) clar__assert_equal(__FILE__,__LINE__,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0))
+
+#define cl_assert_equal_p(p1,p2) clar__assert_equal(__FILE__,__LINE__,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2))
+
+void clar__skip(void);
+
+void clar__fail(
+ const char *file,
+ int line,
+ const char *error,
+ const char *description,
+ int should_abort);
+
+void clar__assert(
+ int condition,
+ const char *file,
+ int line,
+ const char *error,
+ const char *description,
+ int should_abort);
+
+void clar__assert_equal(
+ const char *file,
+ int line,
+ const char *err,
+ int should_abort,
+ const char *fmt,
+ ...);
+
+#endif
diff --git a/tests/clar/fixtures.h b/tests/clar/fixtures.h
new file mode 100644
index 000000000..264cd7f4f
--- /dev/null
+++ b/tests/clar/fixtures.h
@@ -0,0 +1,38 @@
+static const char *
+fixture_path(const char *base, const char *fixture_name)
+{
+ static char _path[4096];
+ size_t root_len;
+
+ root_len = strlen(base);
+ strncpy(_path, base, sizeof(_path));
+
+ if (_path[root_len - 1] != '/')
+ _path[root_len++] = '/';
+
+ if (fixture_name[0] == '/')
+ fixture_name++;
+
+ strncpy(_path + root_len,
+ fixture_name,
+ sizeof(_path) - root_len);
+
+ return _path;
+}
+
+#ifdef CLAR_FIXTURE_PATH
+const char *cl_fixture(const char *fixture_name)
+{
+ return fixture_path(CLAR_FIXTURE_PATH, fixture_name);
+}
+
+void cl_fixture_sandbox(const char *fixture_name)
+{
+ fs_copy(cl_fixture(fixture_name), _clar_path);
+}
+
+void cl_fixture_cleanup(const char *fixture_name)
+{
+ fs_rm(fixture_path(_clar_path, fixture_name));
+}
+#endif
diff --git a/tests/clar/fs.h b/tests/clar/fs.h
new file mode 100644
index 000000000..7c7dde6fc
--- /dev/null
+++ b/tests/clar/fs.h
@@ -0,0 +1,333 @@
+#ifdef _WIN32
+
+#define RM_RETRY_COUNT 5
+#define RM_RETRY_DELAY 10
+
+#ifdef __MINGW32__
+
+/* These security-enhanced functions are not available
+ * in MinGW, so just use the vanilla ones */
+#define wcscpy_s(a, b, c) wcscpy((a), (c))
+#define wcscat_s(a, b, c) wcscat((a), (c))
+
+#endif /* __MINGW32__ */
+
+static int
+fs__dotordotdot(WCHAR *_tocheck)
+{
+ return _tocheck[0] == '.' &&
+ (_tocheck[1] == '\0' ||
+ (_tocheck[1] == '.' && _tocheck[2] == '\0'));
+}
+
+static int
+fs_rmdir_rmdir(WCHAR *_wpath)
+{
+ unsigned retries = 1;
+
+ while (!RemoveDirectoryW(_wpath)) {
+ /* Only retry when we have retries remaining, and the
+ * error was ERROR_DIR_NOT_EMPTY. */
+ if (retries++ > RM_RETRY_COUNT ||
+ ERROR_DIR_NOT_EMPTY != GetLastError())
+ return -1;
+
+ /* Give whatever has a handle to a child item some time
+ * to release it before trying again */
+ Sleep(RM_RETRY_DELAY * retries * retries);
+ }
+
+ return 0;
+}
+
+static void
+fs_rmdir_helper(WCHAR *_wsource)
+{
+ WCHAR buffer[MAX_PATH];
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+ size_t buffer_prefix_len;
+
+ /* Set up the buffer and capture the length */
+ wcscpy_s(buffer, MAX_PATH, _wsource);
+ wcscat_s(buffer, MAX_PATH, L"\\");
+ buffer_prefix_len = wcslen(buffer);
+
+ /* FindFirstFile needs a wildcard to match multiple items */
+ wcscat_s(buffer, MAX_PATH, L"*");
+ find_handle = FindFirstFileW(buffer, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+
+ do {
+ /* FindFirstFile/FindNextFile gives back . and ..
+ * entries at the beginning */
+ if (fs__dotordotdot(find_data.cFileName))
+ continue;
+
+ wcscpy_s(buffer + buffer_prefix_len, MAX_PATH - buffer_prefix_len, find_data.cFileName);
+
+ if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
+ fs_rmdir_helper(buffer);
+ else {
+ /* If set, the +R bit must be cleared before deleting */
+ if (FILE_ATTRIBUTE_READONLY & find_data.dwFileAttributes)
+ cl_assert(SetFileAttributesW(buffer, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY));
+
+ cl_assert(DeleteFileW(buffer));
+ }
+ }
+ while (FindNextFileW(find_handle, &find_data));
+
+ /* Ensure that we successfully completed the enumeration */
+ cl_assert(ERROR_NO_MORE_FILES == GetLastError());
+
+ /* Close the find handle */
+ FindClose(find_handle);
+
+ /* Now that the directory is empty, remove it */
+ cl_assert(0 == fs_rmdir_rmdir(_wsource));
+}
+
+static int
+fs_rm_wait(WCHAR *_wpath)
+{
+ unsigned retries = 1;
+ DWORD last_error;
+
+ do {
+ if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath))
+ last_error = GetLastError();
+ else
+ last_error = ERROR_SUCCESS;
+
+ /* Is the item gone? */
+ if (ERROR_FILE_NOT_FOUND == last_error ||
+ ERROR_PATH_NOT_FOUND == last_error)
+ return 0;
+
+ Sleep(RM_RETRY_DELAY * retries * retries);
+ }
+ while (retries++ <= RM_RETRY_COUNT);
+
+ return -1;
+}
+
+static void
+fs_rm(const char *_source)
+{
+ WCHAR wsource[MAX_PATH];
+ DWORD attrs;
+
+ /* The input path is UTF-8. Convert it to wide characters
+ * for use with the Windows API */
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _source,
+ -1, /* Indicates NULL termination */
+ wsource,
+ MAX_PATH));
+
+ /* Does the item exist? If not, we have no work to do */
+ attrs = GetFileAttributesW(wsource);
+
+ if (INVALID_FILE_ATTRIBUTES == attrs)
+ return;
+
+ if (FILE_ATTRIBUTE_DIRECTORY & attrs)
+ fs_rmdir_helper(wsource);
+ else {
+ /* The item is a file. Strip the +R bit */
+ if (FILE_ATTRIBUTE_READONLY & attrs)
+ cl_assert(SetFileAttributesW(wsource, attrs & ~FILE_ATTRIBUTE_READONLY));
+
+ cl_assert(DeleteFileW(wsource));
+ }
+
+ /* Wait for the DeleteFile or RemoveDirectory call to complete */
+ cl_assert(0 == fs_rm_wait(wsource));
+}
+
+static void
+fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest)
+{
+ WCHAR buf_source[MAX_PATH], buf_dest[MAX_PATH];
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+ size_t buf_source_prefix_len, buf_dest_prefix_len;
+
+ wcscpy_s(buf_source, MAX_PATH, _wsource);
+ wcscat_s(buf_source, MAX_PATH, L"\\");
+ buf_source_prefix_len = wcslen(buf_source);
+
+ wcscpy_s(buf_dest, MAX_PATH, _wdest);
+ wcscat_s(buf_dest, MAX_PATH, L"\\");
+ buf_dest_prefix_len = wcslen(buf_dest);
+
+ /* Get an enumerator for the items in the source. */
+ wcscat_s(buf_source, MAX_PATH, L"*");
+ find_handle = FindFirstFileW(buf_source, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+
+ /* Create the target directory. */
+ cl_assert(CreateDirectoryW(_wdest, NULL));
+
+ do {
+ /* FindFirstFile/FindNextFile gives back . and ..
+ * entries at the beginning */
+ if (fs__dotordotdot(find_data.cFileName))
+ continue;
+
+ wcscpy_s(buf_source + buf_source_prefix_len, MAX_PATH - buf_source_prefix_len, find_data.cFileName);
+ wcscpy_s(buf_dest + buf_dest_prefix_len, MAX_PATH - buf_dest_prefix_len, find_data.cFileName);
+
+ if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
+ fs_copydir_helper(buf_source, buf_dest);
+ else
+ cl_assert(CopyFileW(buf_source, buf_dest, TRUE));
+ }
+ while (FindNextFileW(find_handle, &find_data));
+
+ /* Ensure that we successfully completed the enumeration */
+ cl_assert(ERROR_NO_MORE_FILES == GetLastError());
+
+ /* Close the find handle */
+ FindClose(find_handle);
+}
+
+static void
+fs_copy(const char *_source, const char *_dest)
+{
+ WCHAR wsource[MAX_PATH], wdest[MAX_PATH];
+ DWORD source_attrs, dest_attrs;
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+
+ /* The input paths are UTF-8. Convert them to wide characters
+ * for use with the Windows API. */
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _source,
+ -1,
+ wsource,
+ MAX_PATH));
+
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _dest,
+ -1,
+ wdest,
+ MAX_PATH));
+
+ /* Check the source for existence */
+ source_attrs = GetFileAttributesW(wsource);
+ cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs);
+
+ /* Check the target for existence */
+ dest_attrs = GetFileAttributesW(wdest);
+
+ if (INVALID_FILE_ATTRIBUTES != dest_attrs) {
+ /* Target exists; append last path part of source to target.
+ * Use FindFirstFile to parse the path */
+ find_handle = FindFirstFileW(wsource, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+ wcscat_s(wdest, MAX_PATH, L"\\");
+ wcscat_s(wdest, MAX_PATH, find_data.cFileName);
+ FindClose(find_handle);
+
+ /* Check the new target for existence */
+ cl_assert(INVALID_FILE_ATTRIBUTES == GetFileAttributesW(wdest));
+ }
+
+ if (FILE_ATTRIBUTE_DIRECTORY & source_attrs)
+ fs_copydir_helper(wsource, wdest);
+ else
+ cl_assert(CopyFileW(wsource, wdest, TRUE));
+}
+
+void
+cl_fs_cleanup(void)
+{
+ fs_rm(fixture_path(_clar_path, "*"));
+}
+
+#else
+
+#include <errno.h>
+#include <string.h>
+
+static int
+shell_out(char * const argv[])
+{
+ int status, piderr;
+ pid_t pid;
+
+ pid = fork();
+
+ if (pid < 0) {
+ fprintf(stderr,
+ "System error: `fork()` call failed (%d) - %s\n",
+ errno, strerror(errno));
+ exit(-1);
+ }
+
+ if (pid == 0) {
+ execv(argv[0], argv);
+ }
+
+ do {
+ piderr = waitpid(pid, &status, WUNTRACED);
+ } while (piderr < 0 && (errno == EAGAIN || errno == EINTR));
+
+ return WEXITSTATUS(status);
+}
+
+static void
+fs_copy(const char *_source, const char *dest)
+{
+ char *argv[5];
+ char *source;
+ size_t source_len;
+
+ source = strdup(_source);
+ source_len = strlen(source);
+
+ if (source[source_len - 1] == '/')
+ source[source_len - 1] = 0;
+
+ argv[0] = "/bin/cp";
+ argv[1] = "-R";
+ argv[2] = source;
+ argv[3] = (char *)dest;
+ argv[4] = NULL;
+
+ cl_must_pass_(
+ shell_out(argv),
+ "Failed to copy test fixtures to sandbox"
+ );
+
+ free(source);
+}
+
+static void
+fs_rm(const char *source)
+{
+ char *argv[4];
+
+ argv[0] = "/bin/rm";
+ argv[1] = "-Rf";
+ argv[2] = (char *)source;
+ argv[3] = NULL;
+
+ cl_must_pass_(
+ shell_out(argv),
+ "Failed to cleanup the sandbox"
+ );
+}
+
+void
+cl_fs_cleanup(void)
+{
+ clar_unsandbox();
+ clar_sandbox();
+}
+#endif
diff --git a/tests/clar/print.h b/tests/clar/print.h
new file mode 100644
index 000000000..6529b6b4c
--- /dev/null
+++ b/tests/clar/print.h
@@ -0,0 +1,66 @@
+
+static void clar_print_init(int test_count, int suite_count, const char *suite_names)
+{
+ (void)test_count;
+ printf("Loaded %d suites: %s\n", (int)suite_count, suite_names);
+ printf("Started\n");
+}
+
+static void clar_print_shutdown(int test_count, int suite_count, int error_count)
+{
+ (void)test_count;
+ (void)suite_count;
+ (void)error_count;
+
+ printf("\n\n");
+ clar_report_errors();
+}
+
+static void clar_print_error(int num, const struct clar_error *error)
+{
+ printf(" %d) Failure:\n", num);
+
+ printf("%s::%s [%s:%d]\n",
+ error->suite,
+ error->test,
+ error->file,
+ error->line_number);
+
+ printf(" %s\n", error->error_msg);
+
+ if (error->description != NULL)
+ printf(" %s\n", error->description);
+
+ printf("\n");
+ fflush(stdout);
+}
+
+static void clar_print_ontest(const char *test_name, int test_number, enum cl_test_status status)
+{
+ (void)test_name;
+ (void)test_number;
+
+ switch(status) {
+ case CL_TEST_OK: printf("."); break;
+ case CL_TEST_FAILURE: printf("F"); break;
+ case CL_TEST_SKIP: printf("S"); break;
+ }
+
+ fflush(stdout);
+}
+
+static void clar_print_onsuite(const char *suite_name, int suite_index)
+{
+ if (_clar.report_suite_names)
+ printf("\n%s", suite_name);
+
+ (void)suite_index;
+}
+
+static void clar_print_onabort(const char *msg, ...)
+{
+ va_list argp;
+ va_start(argp, msg);
+ vfprintf(stderr, msg, argp);
+ va_end(argp);
+}
diff --git a/tests/clar/sandbox.h b/tests/clar/sandbox.h
new file mode 100644
index 000000000..a44e29116
--- /dev/null
+++ b/tests/clar/sandbox.h
@@ -0,0 +1,134 @@
+static char _clar_path[4096];
+
+static int
+is_valid_tmp_path(const char *path)
+{
+ STAT_T st;
+
+ if (stat(path, &st) != 0)
+ return 0;
+
+ if (!S_ISDIR(st.st_mode))
+ return 0;
+
+ return (access(path, W_OK) == 0);
+}
+
+static int
+find_tmp_path(char *buffer, size_t length)
+{
+#ifndef _WIN32
+ static const size_t var_count = 5;
+ static const char *env_vars[] = {
+ "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
+ };
+
+ size_t i;
+
+ for (i = 0; i < var_count; ++i) {
+ const char *env = getenv(env_vars[i]);
+ if (!env)
+ continue;
+
+ if (is_valid_tmp_path(env)) {
+ strncpy(buffer, env, length);
+ return 0;
+ }
+ }
+
+ /* If the environment doesn't say anything, try to use /tmp */
+ if (is_valid_tmp_path("/tmp")) {
+ strncpy(buffer, "/tmp", length);
+ return 0;
+ }
+
+#else
+ DWORD env_len = GetEnvironmentVariable("CLAR_TMP", buffer, (DWORD)length);
+ if (env_len > 0 && env_len < (DWORD)length)
+ return 0;
+
+ if (GetTempPath((DWORD)length, buffer))
+ return 0;
+#endif
+
+ /* This system doesn't like us, try to use the current directory */
+ if (is_valid_tmp_path(".")) {
+ strncpy(buffer, ".", length);
+ return 0;
+ }
+
+ return -1;
+}
+
+static void clar_unsandbox(void)
+{
+ if (_clar_path[0] == '\0')
+ return;
+
+ chdir("..");
+
+ fs_rm(_clar_path);
+}
+
+static int build_sandbox_path(void)
+{
+ const char path_tail[] = "clar_tmp_XXXXXX";
+ size_t len;
+
+ if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0)
+ return -1;
+
+ len = strlen(_clar_path);
+
+#ifdef _WIN32
+ { /* normalize path to POSIX forward slashes */
+ size_t i;
+ for (i = 0; i < len; ++i) {
+ if (_clar_path[i] == '\\')
+ _clar_path[i] = '/';
+ }
+ }
+#endif
+
+ if (_clar_path[len - 1] != '/') {
+ _clar_path[len++] = '/';
+ }
+
+ strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len);
+
+#if defined(__MINGW32__)
+ if (_mktemp(_clar_path) == NULL)
+ return -1;
+
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#elif defined(_WIN32)
+ if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0)
+ return -1;
+
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#else
+ if (mkdtemp(_clar_path) == NULL)
+ return -1;
+#endif
+
+ return 0;
+}
+
+static int clar_sandbox(void)
+{
+ if (_clar_path[0] == '\0' && build_sandbox_path() < 0)
+ return -1;
+
+ if (chdir(_clar_path) != 0)
+ return -1;
+
+ return 0;
+}
+
+const char *clar_sandbox_path(void)
+{
+ return _clar_path;
+}
+
diff --git a/tests/clar_libgit2.c b/tests/clar_libgit2.c
new file mode 100644
index 000000000..0a4c3e8e5
--- /dev/null
+++ b/tests/clar_libgit2.c
@@ -0,0 +1,533 @@
+#include "clar_libgit2.h"
+#include "posix.h"
+#include "path.h"
+#include "git2/sys/repository.h"
+
+void cl_git_report_failure(
+ int error, const char *file, int line, const char *fncall)
+{
+ char msg[4096];
+ const git_error *last = giterr_last();
+ p_snprintf(msg, 4096, "error %d - %s",
+ error, last ? last->message : "<no message>");
+ clar__assert(0, file, line, fncall, msg, 1);
+}
+
+void cl_git_mkfile(const char *filename, const char *content)
+{
+ int fd;
+
+ fd = p_creat(filename, 0666);
+ cl_assert(fd != 0);
+
+ if (content) {
+ cl_must_pass(p_write(fd, content, strlen(content)));
+ } else {
+ cl_must_pass(p_write(fd, filename, strlen(filename)));
+ cl_must_pass(p_write(fd, "\n", 1));
+ }
+
+ cl_must_pass(p_close(fd));
+}
+
+void cl_git_write2file(
+ const char *path, const char *content, size_t content_len,
+ int flags, unsigned int mode)
+{
+ int fd;
+ cl_assert(path && content);
+ cl_assert((fd = p_open(path, flags, mode)) >= 0);
+ if (!content_len)
+ content_len = strlen(content);
+ cl_must_pass(p_write(fd, content, content_len));
+ cl_must_pass(p_close(fd));
+}
+
+void cl_git_append2file(const char *path, const char *content)
+{
+ cl_git_write2file(path, content, 0, O_WRONLY | O_CREAT | O_APPEND, 0644);
+}
+
+void cl_git_rewritefile(const char *path, const char *content)
+{
+ cl_git_write2file(path, content, 0, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+}
+
+#ifdef GIT_WIN32
+
+#include "win32/utf-conv.h"
+
+char *cl_getenv(const char *name)
+{
+ wchar_t *wide_name, *wide_value;
+ char *utf8_value = NULL;
+ DWORD value_len;
+
+ cl_assert(git__utf8_to_16_alloc(&wide_name, name) >= 0);
+
+ value_len = GetEnvironmentVariableW(wide_name, NULL, 0);
+
+ if (value_len) {
+ cl_assert(wide_value = git__malloc(value_len * sizeof(wchar_t)));
+ cl_assert(GetEnvironmentVariableW(wide_name, wide_value, value_len));
+ cl_assert(git__utf16_to_8_alloc(&utf8_value, wide_value) >= 0);
+ git__free(wide_value);
+ }
+
+ git__free(wide_name);
+ return utf8_value;
+}
+
+int cl_setenv(const char *name, const char *value)
+{
+ wchar_t *wide_name, *wide_value;
+
+ cl_assert(git__utf8_to_16_alloc(&wide_name, name) >= 0);
+
+ if (value) {
+ cl_assert(git__utf8_to_16_alloc(&wide_value, value) >= 0);
+ cl_assert(SetEnvironmentVariableW(wide_name, wide_value));
+ } else {
+ /* 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 check the return value
+ * of SetEnvironmentVariable when passing NULL for lpValue. */
+ SetEnvironmentVariableW(wide_name, NULL);
+ }
+
+ return 0;
+}
+
+/* This function performs retries on calls to MoveFile in order
+ * to provide enhanced reliability in the face of antivirus
+ * agents that may be scanning the source (or in the case that
+ * the source is a directory, a child of the source). */
+int cl_rename(const char *source, const char *dest)
+{
+ git_win32_path source_utf16;
+ git_win32_path dest_utf16;
+ unsigned retries = 1;
+
+ cl_assert(git_win32_path_from_utf8(source_utf16, source) >= 0);
+ cl_assert(git_win32_path_from_utf8(dest_utf16, dest) >= 0);
+
+ while (!MoveFileW(source_utf16, dest_utf16)) {
+ /* Only retry if the error is ERROR_ACCESS_DENIED;
+ * this may indicate that an antivirus agent is
+ * preventing the rename from source to target */
+ if (retries > 5 ||
+ ERROR_ACCESS_DENIED != GetLastError())
+ return -1;
+
+ /* With 5 retries and a coefficient of 10ms, the maximum
+ * delay here is 550 ms */
+ Sleep(10 * retries * retries);
+ retries++;
+ }
+
+ return 0;
+}
+
+#else
+
+#include <stdlib.h>
+char *cl_getenv(const char *name)
+{
+ return getenv(name);
+}
+
+int cl_setenv(const char *name, const char *value)
+{
+ return (value == NULL) ? unsetenv(name) : setenv(name, value, 1);
+}
+
+int cl_rename(const char *source, const char *dest)
+{
+ return p_rename(source, dest);
+}
+
+#endif
+
+static const char *_cl_sandbox = NULL;
+static git_repository *_cl_repo = NULL;
+
+git_repository *cl_git_sandbox_init(const char *sandbox)
+{
+ /* Copy the whole sandbox folder from our fixtures to our test sandbox
+ * area. After this it can be accessed with `./sandbox`
+ */
+ cl_fixture_sandbox(sandbox);
+ _cl_sandbox = sandbox;
+
+ cl_git_pass(p_chdir(sandbox));
+
+ /* If this is not a bare repo, then rename `sandbox/.gitted` to
+ * `sandbox/.git` which must be done since we cannot store a folder
+ * named `.git` inside the fixtures folder of our libgit2 repo.
+ */
+ if (p_access(".gitted", F_OK) == 0)
+ cl_git_pass(cl_rename(".gitted", ".git"));
+
+ /* If we have `gitattributes`, rename to `.gitattributes`. This may
+ * be necessary if we don't want the attributes to be applied in the
+ * libgit2 repo, but just during testing.
+ */
+ if (p_access("gitattributes", F_OK) == 0)
+ cl_git_pass(cl_rename("gitattributes", ".gitattributes"));
+
+ /* As with `gitattributes`, we may need `gitignore` just for testing. */
+ if (p_access("gitignore", F_OK) == 0)
+ cl_git_pass(cl_rename("gitignore", ".gitignore"));
+
+ cl_git_pass(p_chdir(".."));
+
+ /* Now open the sandbox repository and make it available for tests */
+ cl_git_pass(git_repository_open(&_cl_repo, sandbox));
+
+ /* Adjust configs after copying to new filesystem */
+ cl_git_pass(git_repository_reinit_filesystem(_cl_repo, 0));
+
+ return _cl_repo;
+}
+
+git_repository *cl_git_sandbox_reopen(void)
+{
+ if (_cl_repo) {
+ git_repository_free(_cl_repo);
+ _cl_repo = NULL;
+
+ cl_git_pass(git_repository_open(&_cl_repo, _cl_sandbox));
+ }
+
+ return _cl_repo;
+}
+
+void cl_git_sandbox_cleanup(void)
+{
+ if (_cl_repo) {
+ git_repository_free(_cl_repo);
+ _cl_repo = NULL;
+ }
+ if (_cl_sandbox) {
+ cl_fixture_cleanup(_cl_sandbox);
+ _cl_sandbox = NULL;
+ }
+}
+
+bool cl_toggle_filemode(const char *filename)
+{
+ struct stat st1, st2;
+
+ cl_must_pass(p_stat(filename, &st1));
+ cl_must_pass(p_chmod(filename, st1.st_mode ^ 0100));
+ cl_must_pass(p_stat(filename, &st2));
+
+ return (st1.st_mode != st2.st_mode);
+}
+
+bool cl_is_chmod_supported(void)
+{
+ static int _is_supported = -1;
+
+ if (_is_supported < 0) {
+ cl_git_mkfile("filemode.t", "Test if filemode can be modified");
+ _is_supported = cl_toggle_filemode("filemode.t");
+ cl_must_pass(p_unlink("filemode.t"));
+ }
+
+ 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 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(&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, 0, 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;
+}
+
+#define CL_COMMIT_NAME "Libgit2 Tester"
+#define CL_COMMIT_EMAIL "libgit2-test@github.com"
+#define CL_COMMIT_MSG "Test commit of tree "
+
+void cl_repo_commit_from_index(
+ git_oid *out,
+ git_repository *repo,
+ git_signature *sig,
+ git_time_t time,
+ const char *msg)
+{
+ git_index *index;
+ git_oid commit_id, tree_id;
+ git_object *parent = NULL;
+ git_reference *ref = NULL;
+ git_tree *tree = NULL;
+ char buf[128];
+ int free_sig = (sig == NULL);
+
+ /* it is fine if looking up HEAD fails - we make this the first commit */
+ git_revparse_ext(&parent, &ref, repo, "HEAD");
+
+ /* write the index content as a tree */
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_git_pass(git_index_write_tree(&tree_id, index));
+ cl_git_pass(git_index_write(index));
+ git_index_free(index);
+
+ cl_git_pass(git_tree_lookup(&tree, repo, &tree_id));
+
+ if (sig)
+ cl_assert(sig->name && sig->email);
+ else if (!time)
+ cl_git_pass(git_signature_now(&sig, CL_COMMIT_NAME, CL_COMMIT_EMAIL));
+ else
+ cl_git_pass(git_signature_new(
+ &sig, CL_COMMIT_NAME, CL_COMMIT_EMAIL, time, 0));
+
+ if (!msg) {
+ strcpy(buf, CL_COMMIT_MSG);
+ git_oid_tostr(buf + strlen(CL_COMMIT_MSG),
+ sizeof(buf) - strlen(CL_COMMIT_MSG), &tree_id);
+ msg = buf;
+ }
+
+ cl_git_pass(git_commit_create_v(
+ &commit_id, repo, ref ? git_reference_name(ref) : "HEAD",
+ sig, sig, NULL, msg, tree, parent ? 1 : 0, parent));
+
+ if (out)
+ git_oid_cpy(out, &commit_id);
+
+ git_object_free(parent);
+ git_reference_free(ref);
+ if (free_sig)
+ git_signature_free(sig);
+ git_tree_free(tree);
+}
+
+void cl_repo_set_bool(git_repository *repo, const char *cfg, int value)
+{
+ git_config *config;
+ cl_git_pass(git_repository_config(&config, repo));
+ cl_git_pass(git_config_set_bool(config, cfg, value != 0));
+ git_config_free(config);
+}
+
+int cl_repo_get_bool(git_repository *repo, const char *cfg)
+{
+ int val = 0;
+ git_config *config;
+ cl_git_pass(git_repository_config(&config, repo));
+ if (git_config_get_bool(&val, config, cfg) < 0)
+ giterr_clear();
+ git_config_free(config);
+ return val;
+}
+
+void cl_repo_set_string(git_repository *repo, const char *cfg, const char *value)
+{
+ git_config *config;
+ cl_git_pass(git_repository_config(&config, repo));
+ cl_git_pass(git_config_set_string(config, cfg, value));
+ git_config_free(config);
+}
+
+/* this is essentially the code from git__unescape modified slightly */
+static size_t strip_cr_from_buf(char *start, size_t len)
+{
+ char *scan, *trail, *end = start + len;
+
+ for (scan = trail = start; scan < end; trail++, scan++) {
+ while (*scan == '\r')
+ scan++; /* skip '\r' */
+
+ if (trail != scan)
+ *trail = *scan;
+ }
+
+ *trail = '\0';
+
+ return (trail - start);
+}
+
+void clar__assert_equal_file(
+ const char *expected_data,
+ size_t expected_bytes,
+ int ignore_cr,
+ const char *path,
+ const char *file,
+ int line)
+{
+ char buf[4000];
+ ssize_t bytes, total_bytes = 0;
+ int fd = p_open(path, O_RDONLY | O_BINARY);
+ cl_assert(fd >= 0);
+
+ if (expected_data && !expected_bytes)
+ expected_bytes = strlen(expected_data);
+
+ while ((bytes = p_read(fd, buf, sizeof(buf))) != 0) {
+ clar__assert(
+ bytes > 0, file, line, "error reading from file", path, 1);
+
+ if (ignore_cr)
+ bytes = strip_cr_from_buf(buf, bytes);
+
+ if (memcmp(expected_data, buf, bytes) != 0) {
+ int pos;
+ for (pos = 0; pos < bytes && expected_data[pos] == buf[pos]; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(
+ buf, sizeof(buf), "file content mismatch at byte %d",
+ (int)(total_bytes + pos));
+ p_close(fd);
+ clar__fail(file, line, buf, path, 1);
+ }
+
+ expected_data += bytes;
+ total_bytes += bytes;
+ }
+
+ p_close(fd);
+
+ clar__assert(!bytes, file, line, "error reading from file", path, 1);
+ clar__assert_equal(file, line, "mismatched file length", 1, "%"PRIuZ,
+ (size_t)expected_bytes, (size_t)total_bytes);
+}
+
+static char *_cl_restore_home = NULL;
+
+void cl_fake_home_cleanup(void *payload)
+{
+ char *restore = _cl_restore_home;
+ _cl_restore_home = NULL;
+
+ GIT_UNUSED(payload);
+
+ if (restore) {
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, restore));
+ git__free(restore);
+ }
+}
+
+void cl_fake_home(void)
+{
+ git_buf path = GIT_BUF_INIT;
+
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &path));
+
+ _cl_restore_home = git_buf_detach(&path);
+ cl_set_cleanup(cl_fake_home_cleanup, NULL);
+
+ if (!git_path_exists("home"))
+ cl_must_pass(p_mkdir("home", 0777));
+ cl_git_pass(git_path_prettify(&path, "home", NULL));
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr));
+ git_buf_free(&path);
+}
+
+void cl_sandbox_set_search_path_defaults(void)
+{
+ const char *sandbox_path = clar_sandbox_path();
+
+ git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, sandbox_path);
+ git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, sandbox_path);
+ git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, sandbox_path);
+}
+
diff --git a/tests/clar_libgit2.h b/tests/clar_libgit2.h
new file mode 100644
index 000000000..da37bd655
--- /dev/null
+++ b/tests/clar_libgit2.h
@@ -0,0 +1,144 @@
+#ifndef __CLAR_LIBGIT2__
+#define __CLAR_LIBGIT2__
+
+#include "clar.h"
+#include <git2.h>
+#include "common.h"
+
+/**
+ * Replace for `clar_must_pass` that passes the last library error as the
+ * test failure message.
+ *
+ * Use this wrapper around all `git_` library calls that return error codes!
+ */
+#define cl_git_pass(expr) cl_git_pass_((expr), __FILE__, __LINE__)
+
+#define cl_git_pass_(expr, file, line) do { \
+ int _lg2_error; \
+ giterr_clear(); \
+ if ((_lg2_error = (expr)) != 0) \
+ cl_git_report_failure(_lg2_error, file, line, "Function call failed: " #expr); \
+ } while (0)
+
+/**
+ * Wrapper for `clar_must_fail` -- this one is
+ * just for consistency. Use with `git_` library
+ * calls that are supposed to fail!
+ */
+#define cl_git_fail(expr) cl_must_fail(expr)
+
+#define cl_git_fail_with(expr, error) cl_assert_equal_i(error,expr)
+
+/**
+ * Like cl_git_pass, only for Win32 error code conventions
+ */
+#define cl_win32_pass(expr) do { \
+ int _win32_res; \
+ if ((_win32_res = (expr)) == 0) { \
+ giterr_set(GITERR_OS, "Returned: %d, system error code: %d", _win32_res, GetLastError()); \
+ cl_git_report_failure(_win32_res, __FILE__, __LINE__, "System call failed: " #expr); \
+ } \
+ } while(0)
+
+void cl_git_report_failure(int, const char *, int, const char *);
+
+#define cl_assert_at_line(expr,file,line) \
+ clar__assert((expr) != 0, file, line, "Expression is not true: " #expr, NULL, 1)
+
+GIT_INLINE(void) clar__assert_in_range(
+ int lo, int val, int hi,
+ const char *file, int line, const char *err, int should_abort)
+{
+ if (lo > val || hi < val) {
+ char buf[128];
+ snprintf(buf, sizeof(buf), "%d not in [%d,%d]", val, lo, hi);
+ clar__fail(file, line, err, buf, should_abort);
+ }
+}
+
+#define cl_assert_equal_sz(sz1,sz2) do { \
+ size_t __sz1 = (size_t)(sz1), __sz2 = (size_t)(sz2); \
+ clar__assert_equal(__FILE__,__LINE__,#sz1 " != " #sz2, 1, "%"PRIuZ, __sz1, __sz2); \
+} while (0)
+
+#define cl_assert_in_range(L,V,H) \
+ clar__assert_in_range((L),(V),(H),__FILE__,__LINE__,"Range check: " #V " in [" #L "," #H "]", 1)
+
+#define cl_assert_equal_file(DATA,SIZE,PATH) \
+ clar__assert_equal_file(DATA,SIZE,0,PATH,__FILE__,(int)__LINE__)
+
+#define cl_assert_equal_file_ignore_cr(DATA,SIZE,PATH) \
+ clar__assert_equal_file(DATA,SIZE,1,PATH,__FILE__,(int)__LINE__)
+
+void clar__assert_equal_file(
+ const char *expected_data,
+ size_t expected_size,
+ int ignore_cr,
+ const char *path,
+ const char *file,
+ int line);
+
+/*
+ * Some utility macros for building long strings
+ */
+#define REP4(STR) STR STR STR STR
+#define REP15(STR) REP4(STR) REP4(STR) REP4(STR) STR STR STR
+#define REP16(STR) REP4(REP4(STR))
+#define REP256(STR) REP16(REP16(STR))
+#define REP1024(STR) REP4(REP256(STR))
+
+/* Write the contents of a buffer to disk */
+void cl_git_mkfile(const char *filename, const char *content);
+void cl_git_append2file(const char *filename, const char *new_content);
+void cl_git_rewritefile(const char *filename, const char *new_content);
+void cl_git_write2file(const char *path, const char *data,
+ size_t datalen, int flags, unsigned int mode);
+
+bool cl_toggle_filemode(const char *filename);
+bool cl_is_chmod_supported(void);
+
+/* Environment wrappers */
+char *cl_getenv(const char *name);
+int cl_setenv(const char *name, const char *value);
+
+/* Reliable rename */
+int cl_rename(const char *source, const char *dest);
+
+/* Git sandbox setup helpers */
+
+git_repository *cl_git_sandbox_init(const char *sandbox);
+void cl_git_sandbox_cleanup(void);
+git_repository *cl_git_sandbox_reopen(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);
+
+/* commit creation helpers */
+void cl_repo_commit_from_index(
+ git_oid *out,
+ git_repository *repo,
+ git_signature *sig,
+ git_time_t time,
+ const char *msg);
+
+/* config setting helpers */
+void cl_repo_set_bool(git_repository *repo, const char *cfg, int value);
+int cl_repo_get_bool(git_repository *repo, const char *cfg);
+
+void cl_repo_set_string(git_repository *repo, const char *cfg, const char *value);
+
+/* set up a fake "home" directory and set libgit2 GLOBAL search path.
+ *
+ * automatically configures cleanup function to restore the regular search
+ * path, although you can call it explicitly if you wish (with NULL).
+ */
+void cl_fake_home(void);
+void cl_fake_home_cleanup(void *);
+
+void cl_sandbox_set_search_path_defaults(void);
+
+#endif
diff --git a/tests/clone/empty.c b/tests/clone/empty.c
new file mode 100644
index 000000000..8f6071096
--- /dev/null
+++ b/tests/clone/empty.c
@@ -0,0 +1,85 @@
+#include "clar_libgit2.h"
+
+#include "git2/clone.h"
+#include "repository.h"
+
+static git_clone_options g_options;
+static git_repository *g_repo;
+static git_repository *g_repo_cloned;
+
+void test_clone_empty__initialize(void)
+{
+ git_repository *sandbox = cl_git_sandbox_init("empty_bare.git");
+ git_remote_callbacks dummy_callbacks = GIT_REMOTE_CALLBACKS_INIT;
+ cl_git_remove_placeholders(git_repository_path(sandbox), "dummy-marker.txt");
+
+ g_repo = NULL;
+
+ memset(&g_options, 0, sizeof(git_clone_options));
+ g_options.version = GIT_CLONE_OPTIONS_VERSION;
+ g_options.remote_callbacks = dummy_callbacks;
+}
+
+void test_clone_empty__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+static void cleanup_repository(void *path)
+{
+ cl_fixture_cleanup((const char *)path);
+
+ git_repository_free(g_repo_cloned);
+ g_repo_cloned = NULL;
+}
+
+void test_clone_empty__can_clone_an_empty_local_repo_barely(void)
+{
+ char *local_name = "refs/heads/master";
+ const char *expected_tracked_branch_name = "refs/remotes/origin/master";
+ const char *expected_remote_name = "origin";
+ git_buf buf = GIT_BUF_INIT;
+ git_reference *ref;
+
+ cl_set_cleanup(&cleanup_repository, "./empty");
+
+ g_options.bare = true;
+ cl_git_pass(git_clone(&g_repo_cloned, "./empty_bare.git", "./empty", &g_options));
+
+ /* Although the HEAD is unborn... */
+ cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&ref, g_repo_cloned, local_name));
+
+ /* ...one can still retrieve the name of the remote tracking reference */
+ cl_git_pass(git_branch_upstream_name(&buf, g_repo_cloned, local_name));
+
+ cl_assert_equal_s(expected_tracked_branch_name, buf.ptr);
+ git_buf_free(&buf);
+
+ /* ...and the name of the remote... */
+ cl_git_pass(git_branch_remote_name(&buf, g_repo_cloned, expected_tracked_branch_name));
+
+ cl_assert_equal_s(expected_remote_name, buf.ptr);
+ git_buf_free(&buf);
+
+ /* ...even when the remote HEAD is unborn as well */
+ cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&ref, g_repo_cloned,
+ expected_tracked_branch_name));
+}
+
+void test_clone_empty__can_clone_an_empty_local_repo(void)
+{
+ cl_set_cleanup(&cleanup_repository, "./empty");
+
+ cl_git_pass(git_clone(&g_repo_cloned, "./empty_bare.git", "./empty", &g_options));
+}
+
+void test_clone_empty__can_clone_an_empty_standard_repo(void)
+{
+ cl_git_sandbox_cleanup();
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+ cl_git_remove_placeholders(git_repository_path(g_repo), "dummy-marker.txt");
+
+ cl_set_cleanup(&cleanup_repository, "./empty");
+
+ cl_git_pass(git_clone(&g_repo_cloned, "./empty_standard_repo", "./empty", &g_options));
+}
diff --git a/tests/clone/local.c b/tests/clone/local.c
new file mode 100644
index 000000000..a4406c1cc
--- /dev/null
+++ b/tests/clone/local.c
@@ -0,0 +1,105 @@
+#include "clar_libgit2.h"
+
+#include "git2/clone.h"
+#include "clone.h"
+#include "buffer.h"
+#include "path.h"
+#include "posix.h"
+#include "fileops.h"
+
+void test_clone_local__should_clone_local(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+ const char *path;
+
+ /* we use a fixture path because it needs to exist for us to want to clone */
+
+ cl_git_pass(git_buf_printf(&buf, "file://%s", cl_fixture("testrepo.git")));
+ cl_assert_equal_i(false, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO));
+ cl_assert_equal_i(true, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL));
+ cl_assert_equal_i(true, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS));
+ cl_assert_equal_i(false, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL));
+ git_buf_free(&buf);
+
+ path = cl_fixture("testrepo.git");
+ cl_assert_equal_i(true, git_clone__should_clone_local(path, GIT_CLONE_LOCAL_AUTO));
+ cl_assert_equal_i(true, git_clone__should_clone_local(path, GIT_CLONE_LOCAL));
+ cl_assert_equal_i(true, git_clone__should_clone_local(path, GIT_CLONE_LOCAL_NO_LINKS));
+ cl_assert_equal_i(false, git_clone__should_clone_local(path, GIT_CLONE_NO_LOCAL));
+}
+
+void test_clone_local__hardlinks(void)
+{
+ git_repository *repo;
+ git_remote *remote;
+ git_signature *sig;
+ git_buf buf = GIT_BUF_INIT;
+ struct stat st;
+
+
+ /*
+ * In this first clone, we just copy over, since the temp dir
+ * will often be in a different filesystem, so we cannot
+ * link. It also allows us to control the number of links
+ */
+ cl_git_pass(git_repository_init(&repo, "./clone.git", true));
+ cl_git_pass(git_remote_create(&remote, repo, "origin", cl_fixture("testrepo.git")));
+ cl_git_pass(git_signature_now(&sig, "foo", "bar"));
+ cl_git_pass(git_clone_local_into(repo, remote, NULL, NULL, false, sig));
+
+ git_remote_free(remote);
+ git_repository_free(repo);
+
+ /* This second clone is in the same filesystem, so we can hardlink */
+
+ cl_git_pass(git_repository_init(&repo, "./clone2.git", true));
+ cl_git_pass(git_buf_puts(&buf, cl_git_path_url("clone.git")));
+ cl_git_pass(git_remote_create(&remote, repo, "origin", buf.ptr));
+ cl_git_pass(git_clone_local_into(repo, remote, NULL, NULL, true, sig));
+
+#ifndef GIT_WIN32
+ git_buf_clear(&buf);
+ cl_git_pass(git_buf_join_n(&buf, '/', 4, git_repository_path(repo), "objects", "08", "b041783f40edfe12bb406c9c9a8a040177c125"));
+
+ cl_git_pass(p_stat(buf.ptr, &st));
+ cl_assert_equal_i(2, st.st_nlink);
+#endif
+
+ git_remote_free(remote);
+ git_repository_free(repo);
+ git_buf_clear(&buf);
+
+ cl_git_pass(git_repository_init(&repo, "./clone3.git", true));
+ cl_git_pass(git_buf_puts(&buf, cl_git_path_url("clone.git")));
+ cl_git_pass(git_remote_create(&remote, repo, "origin", buf.ptr));
+ cl_git_pass(git_clone_local_into(repo, remote, NULL, NULL, false, sig));
+
+ git_buf_clear(&buf);
+ cl_git_pass(git_buf_join_n(&buf, '/', 4, git_repository_path(repo), "objects", "08", "b041783f40edfe12bb406c9c9a8a040177c125"));
+
+ cl_git_pass(p_stat(buf.ptr, &st));
+ cl_assert_equal_i(1, st.st_nlink);
+
+ git_remote_free(remote);
+ git_repository_free(repo);
+
+ /* this one should automatically use links */
+ cl_git_pass(git_clone(&repo, "./clone.git", "./clone4.git", NULL));
+
+#ifndef GIT_WIN32
+ git_buf_clear(&buf);
+ cl_git_pass(git_buf_join_n(&buf, '/', 4, git_repository_path(repo), "objects", "08", "b041783f40edfe12bb406c9c9a8a040177c125"));
+
+ cl_git_pass(p_stat(buf.ptr, &st));
+ cl_assert_equal_i(3, st.st_nlink);
+#endif
+
+ git_buf_free(&buf);
+ git_signature_free(sig);
+ git_repository_free(repo);
+
+ cl_git_pass(git_futils_rmdir_r("./clone.git", NULL, GIT_RMDIR_REMOVE_FILES));
+ cl_git_pass(git_futils_rmdir_r("./clone2.git", NULL, GIT_RMDIR_REMOVE_FILES));
+ cl_git_pass(git_futils_rmdir_r("./clone3.git", NULL, GIT_RMDIR_REMOVE_FILES));
+ cl_git_pass(git_futils_rmdir_r("./clone4.git", NULL, GIT_RMDIR_REMOVE_FILES));
+}
diff --git a/tests/clone/nonetwork.c b/tests/clone/nonetwork.c
new file mode 100644
index 000000000..4bdc6e13b
--- /dev/null
+++ b/tests/clone/nonetwork.c
@@ -0,0 +1,318 @@
+#include "clar_libgit2.h"
+
+#include "git2/clone.h"
+#include "remote.h"
+#include "fileops.h"
+#include "repository.h"
+
+#define LIVE_REPO_URL "git://github.com/libgit2/TestGitRepository"
+
+static git_clone_options g_options;
+static git_repository *g_repo;
+static git_reference* g_ref;
+static git_remote* g_remote;
+
+void test_clone_nonetwork__initialize(void)
+{
+ git_checkout_options dummy_opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_remote_callbacks dummy_callbacks = GIT_REMOTE_CALLBACKS_INIT;
+
+ g_repo = NULL;
+
+ memset(&g_options, 0, sizeof(git_clone_options));
+ g_options.version = GIT_CLONE_OPTIONS_VERSION;
+ g_options.checkout_opts = dummy_opts;
+ g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+ g_options.remote_callbacks = dummy_callbacks;
+ cl_git_pass(git_signature_now(&g_options.signature, "Me", "foo@example.com"));
+}
+
+void test_clone_nonetwork__cleanup(void)
+{
+ if (g_repo) {
+ git_repository_free(g_repo);
+ g_repo = NULL;
+ }
+
+ if (g_ref) {
+ git_reference_free(g_ref);
+ g_ref = NULL;
+ }
+
+ if (g_remote) {
+ git_remote_free(g_remote);
+ g_remote = NULL;
+ }
+
+ git_signature_free(g_options.signature);
+ cl_fixture_cleanup("./foo");
+}
+
+void test_clone_nonetwork__bad_urls(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", &g_options));
+ cl_assert(!git_path_exists("./foo"));
+ g_options.bare = true;
+ cl_git_fail(git_clone(&g_repo, "not_a_repo", "./foo", &g_options));
+ cl_assert(!git_path_exists("./foo"));
+
+ cl_git_fail(git_clone(&g_repo, "git://example.com:asdf", "./foo", &g_options));
+ cl_git_fail(git_clone(&g_repo, "https://example.com:asdf/foo", "./foo", &g_options));
+ cl_git_fail(git_clone(&g_repo, "git://github.com/git://github.com/foo/bar.git.git",
+ "./foo", &g_options));
+ cl_git_fail(git_clone(&g_repo, "arrbee:my/bad:password@github.com:1111/strange:words.git",
+ "./foo", &g_options));
+}
+
+void test_clone_nonetwork__do_not_clean_existing_directory(void)
+{
+ /* Clone should not remove the directory if it already exists, but
+ * Should clean up entries it creates. */
+ p_mkdir("./foo", GIT_DIR_MODE);
+ cl_git_fail(git_clone(&g_repo, "not_a_repo", "./foo", &g_options));
+ cl_assert(git_path_is_empty_dir("./foo"));
+
+ /* Try again with a bare repository. */
+ g_options.bare = true;
+ cl_git_fail(git_clone(&g_repo, "not_a_repo", "./foo", &g_options));
+ cl_assert(git_path_is_empty_dir("./foo"));
+}
+
+void test_clone_nonetwork__local(void)
+{
+ cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options));
+}
+
+void test_clone_nonetwork__local_absolute_path(void)
+{
+ const char *local_src;
+ local_src = cl_fixture("testrepo.git");
+ cl_git_pass(git_clone(&g_repo, local_src, "./foo", &g_options));
+}
+
+void test_clone_nonetwork__local_bare(void)
+{
+ g_options.bare = true;
+ cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options));
+}
+
+void test_clone_nonetwork__fail_when_the_target_is_a_file(void)
+{
+ cl_git_mkfile("./foo", "Bar!");
+ cl_git_fail(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options));
+}
+
+void test_clone_nonetwork__fail_with_already_existing_but_non_empty_directory(void)
+{
+ p_mkdir("./foo", GIT_DIR_MODE);
+ cl_git_mkfile("./foo/bar", "Baz!");
+ cl_git_fail(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options));
+}
+
+void test_clone_nonetwork__custom_origin_name(void)
+{
+ g_options.remote_name = "my_origin";
+ cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options));
+
+ cl_git_pass(git_remote_load(&g_remote, g_repo, "my_origin"));
+}
+
+void test_clone_nonetwork__defaults(void)
+{
+ cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", NULL));
+ cl_assert(g_repo);
+ cl_git_pass(git_remote_load(&g_remote, g_repo, "origin"));
+}
+
+void test_clone_nonetwork__cope_with_already_existing_directory(void)
+{
+ p_mkdir("./foo", GIT_DIR_MODE);
+ cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options));
+}
+
+void test_clone_nonetwork__can_prevent_the_checkout_of_a_standard_repo(void)
+{
+ git_buf path = GIT_BUF_INIT;
+
+ g_options.checkout_opts.checkout_strategy = 0;
+ cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options));
+
+ cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "master.txt"));
+ cl_assert_equal_i(false, git_path_isfile(git_buf_cstr(&path)));
+
+ git_buf_free(&path);
+}
+
+void test_clone_nonetwork__can_checkout_given_branch(void)
+{
+ g_options.checkout_branch = "test";
+ cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options));
+
+ cl_assert_equal_i(0, git_repository_head_unborn(g_repo));
+
+ cl_git_pass(git_repository_head(&g_ref, g_repo));
+ cl_assert_equal_s(git_reference_name(g_ref), "refs/heads/test");
+
+ cl_assert(git_path_exists("foo/readme.txt"));
+}
+
+static int clone_cancel_fetch_transfer_progress_cb(
+ const git_transfer_progress *stats, void *data)
+{
+ GIT_UNUSED(stats); GIT_UNUSED(data);
+ return -54321;
+}
+
+void test_clone_nonetwork__can_cancel_clone_in_fetch(void)
+{
+ g_options.checkout_branch = "test";
+
+ g_options.remote_callbacks.transfer_progress =
+ clone_cancel_fetch_transfer_progress_cb;
+
+ cl_git_fail_with(git_clone(
+ &g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options),
+ -54321);
+
+ cl_assert(!g_repo);
+ cl_assert(!git_path_exists("foo/readme.txt"));
+}
+
+static int clone_cancel_checkout_cb(
+ git_checkout_notify_t why,
+ const char *path,
+ const git_diff_file *b,
+ const git_diff_file *t,
+ const git_diff_file *w,
+ void *payload)
+{
+ const char *at_file = payload;
+ GIT_UNUSED(why); GIT_UNUSED(b); GIT_UNUSED(t); GIT_UNUSED(w);
+ if (!strcmp(path, at_file))
+ return -12345;
+ return 0;
+}
+
+void test_clone_nonetwork__can_cancel_clone_in_checkout(void)
+{
+ g_options.checkout_branch = "test";
+
+ g_options.checkout_opts.notify_flags = GIT_CHECKOUT_NOTIFY_UPDATED;
+ g_options.checkout_opts.notify_cb = clone_cancel_checkout_cb;
+ g_options.checkout_opts.notify_payload = "readme.txt";
+
+ cl_git_fail_with(git_clone(
+ &g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options),
+ -12345);
+
+ cl_assert(!g_repo);
+ cl_assert(!git_path_exists("foo/readme.txt"));
+}
+
+void test_clone_nonetwork__can_detached_head(void)
+{
+ git_object *obj;
+ git_repository *cloned;
+ git_reference *cloned_head;
+ git_reflog *log;
+ const git_reflog_entry *entry;
+
+ cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options));
+
+ cl_git_pass(git_revparse_single(&obj, g_repo, "master~1"));
+ cl_git_pass(git_repository_set_head_detached(g_repo, git_object_id(obj), NULL, NULL));
+
+ cl_git_pass(git_clone(&cloned, "./foo", "./foo1", &g_options));
+
+ cl_assert(git_repository_head_detached(cloned));
+
+ cl_git_pass(git_repository_head(&cloned_head, cloned));
+ cl_assert(!git_oid_cmp(git_object_id(obj), git_reference_target(cloned_head)));
+
+ cl_git_pass(git_reflog_read(&log, cloned, "HEAD"));
+ entry = git_reflog_entry_byindex(log, 0);
+ cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email);
+
+ git_object_free(obj);
+ git_reference_free(cloned_head);
+ git_reflog_free(log);
+ git_repository_free(cloned);
+
+ cl_fixture_cleanup("./foo1");
+}
+
+static void assert_correct_reflog(const char *name)
+{
+ git_reflog *log;
+ const git_reflog_entry *entry;
+ char expected_log_message[128] = {0};
+
+ sprintf(expected_log_message, "clone: from %s", cl_git_fixture_url("testrepo.git"));
+
+ cl_git_pass(git_reflog_read(&log, g_repo, name));
+ cl_assert_equal_i(1, git_reflog_entrycount(log));
+ entry = git_reflog_entry_byindex(log, 0);
+ cl_assert_equal_s(expected_log_message, git_reflog_entry_message(entry));
+ cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email);
+
+ git_reflog_free(log);
+}
+
+void test_clone_nonetwork__clone_updates_reflog_properly(void)
+{
+ cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options));
+ assert_correct_reflog("HEAD");
+ assert_correct_reflog("refs/heads/master");
+}
+
+void test_clone_nonetwork__clone_into_updates_reflog_properly(void)
+{
+ git_remote *remote;
+ git_signature *sig;
+ cl_git_pass(git_signature_now(&sig, "Me", "foo@example.com"));
+
+ cl_git_pass(git_repository_init(&g_repo, "./foo", false));
+ cl_git_pass(git_remote_create(&remote, g_repo, "origin", cl_git_fixture_url("testrepo.git")));
+ cl_git_pass(git_clone_into(g_repo, remote, NULL, NULL, sig));
+
+ assert_correct_reflog("HEAD");
+ assert_correct_reflog("refs/heads/master");
+
+ git_remote_free(remote);
+ git_signature_free(sig);
+}
+
+static void cleanup_repository(void *path)
+{
+ if (g_repo) {
+ git_repository_free(g_repo);
+ g_repo = NULL;
+ }
+
+ cl_fixture_cleanup((const char *)path);
+}
+
+void test_clone_nonetwork__clone_from_empty_sets_upstream(void)
+{
+ git_config *config;
+ git_repository *repo;
+ const char *str;
+
+ /* Create an empty repo to clone from */
+ cl_set_cleanup(&cleanup_repository, "./test1");
+ cl_git_pass(git_repository_init(&g_repo, "./test1", 0));
+ cl_set_cleanup(&cleanup_repository, "./repowithunborn");
+ cl_git_pass(git_clone(&repo, "./test1", "./repowithunborn", NULL));
+
+ cl_git_pass(git_repository_config(&config, repo));
+
+ cl_git_pass(git_config_get_string(&str, config, "branch.master.remote"));
+ cl_assert_equal_s("origin", str);
+ cl_git_pass(git_config_get_string(&str, config, "branch.master.merge"));
+ cl_assert_equal_s("refs/heads/master", str);
+
+ git_config_free(config);
+ git_repository_free(repo);
+ cl_fixture_cleanup("./repowithunborn");
+}
diff --git a/tests/commit/commit.c b/tests/commit/commit.c
new file mode 100644
index 000000000..fa181b703
--- /dev/null
+++ b/tests/commit/commit.c
@@ -0,0 +1,83 @@
+#include "clar_libgit2.h"
+#include "commit.h"
+#include "git2/commit.h"
+
+static git_repository *_repo;
+
+void test_commit_commit__initialize(void)
+{
+ cl_fixture_sandbox("testrepo.git");
+ cl_git_pass(git_repository_open(&_repo, "testrepo.git"));
+}
+
+void test_commit_commit__cleanup(void)
+{
+ git_repository_free(_repo);
+ _repo = NULL;
+
+ cl_fixture_cleanup("testrepo.git");
+}
+
+void test_commit_commit__create_unexisting_update_ref(void)
+{
+ git_oid oid;
+ git_tree *tree;
+ git_commit *commit;
+ git_signature *s;
+ git_reference *ref;
+
+ git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ cl_git_pass(git_commit_lookup(&commit, _repo, &oid));
+
+ git_oid_fromstr(&oid, "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162");
+ cl_git_pass(git_tree_lookup(&tree, _repo, &oid));
+
+ cl_git_pass(git_signature_now(&s, "alice", "alice@example.com"));
+
+ cl_git_fail(git_reference_lookup(&ref, _repo, "refs/heads/foo/bar"));
+ cl_git_pass(git_commit_create(&oid, _repo, "refs/heads/foo/bar", s, s,
+ NULL, "some msg", tree, 1, (const git_commit **) &commit));
+
+ /* fail because the parent isn't the tip of the branch anymore */
+ cl_git_fail(git_commit_create(&oid, _repo, "refs/heads/foo/bar", s, s,
+ NULL, "some msg", tree, 1, (const git_commit **) &commit));
+
+ cl_git_pass(git_reference_lookup(&ref, _repo, "refs/heads/foo/bar"));
+ cl_assert(!git_oid_cmp(&oid, git_reference_target(ref)));
+
+ git_tree_free(tree);
+ git_commit_free(commit);
+ git_signature_free(s);
+ git_reference_free(ref);
+}
+
+void assert_commit_summary(const char *expected, const char *given)
+{
+ git_commit *dummy;
+
+ cl_assert(dummy = git__calloc(1, sizeof(struct git_commit)));
+
+ dummy->raw_message = git__strdup(given);
+ cl_assert_equal_s(expected, git_commit_summary(dummy));
+
+ git_commit__free(dummy);
+}
+
+void test_commit_commit__summary(void)
+{
+ assert_commit_summary("One-liner with no trailing newline", "One-liner with no trailing newline");
+ assert_commit_summary("One-liner with trailing newline", "One-liner with trailing newline\n");
+ assert_commit_summary("Trimmed leading&trailing newlines", "\n\nTrimmed leading&trailing newlines\n\n");
+ assert_commit_summary("First paragraph only", "\nFirst paragraph only\n\n(There are more!)");
+ assert_commit_summary("First paragraph with unwrapped trailing\tlines", "\nFirst paragraph\nwith unwrapped\ntrailing\tlines\n\n(Yes, unwrapped!)");
+ assert_commit_summary("\tLeading \ttabs", "\tLeading\n\ttabs\n\nis preserved");
+ assert_commit_summary(" Leading Spaces", " Leading\n Spaces\n\nare preserved");
+ assert_commit_summary("Trailing tabs\tare removed", "Trailing tabs\tare removed\t\t");
+ assert_commit_summary("Trailing spaces are removed", "Trailing spaces are removed ");
+ assert_commit_summary("Trailing tabs", "Trailing tabs\t\n\nare removed");
+ assert_commit_summary("Trailing spaces", "Trailing spaces \n\nare removed");
+ assert_commit_summary("", "");
+ assert_commit_summary("", " ");
+ assert_commit_summary("", "\n");
+ assert_commit_summary("", "\n \n");
+}
diff --git a/tests/commit/parent.c b/tests/commit/parent.c
new file mode 100644
index 000000000..18ce0bba6
--- /dev/null
+++ b/tests/commit/parent.c
@@ -0,0 +1,60 @@
+#include "clar_libgit2.h"
+
+static git_repository *_repo;
+static git_commit *commit;
+
+void test_commit_parent__initialize(void)
+{
+ git_oid oid;
+
+ cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git")));
+
+ git_oid_fromstr(&oid, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ cl_git_pass(git_commit_lookup(&commit, _repo, &oid));
+}
+
+void test_commit_parent__cleanup(void)
+{
+ git_commit_free(commit);
+ commit = NULL;
+
+ git_repository_free(_repo);
+ _repo = NULL;
+}
+
+static void assert_nth_gen_parent(unsigned int gen, const char *expected_oid)
+{
+ git_commit *parent = NULL;
+ int error;
+
+ error = git_commit_nth_gen_ancestor(&parent, commit, gen);
+
+ if (expected_oid != NULL) {
+ cl_assert_equal_i(0, error);
+ cl_assert_equal_i(0, git_oid_streq(git_commit_id(parent), expected_oid));
+ } else
+ cl_assert_equal_i(GIT_ENOTFOUND, error);
+
+ git_commit_free(parent);
+}
+
+/*
+ * $ git show be35~0
+ * commit be3563ae3f795b2b4353bcce3a527ad0a4f7f644
+ *
+ * $ git show be35~1
+ * commit 9fd738e8f7967c078dceed8190330fc8648ee56a
+ *
+ * $ git show be35~3
+ * commit 5b5b025afb0b4c913b4c338a42934a3863bf3644
+ *
+ * $ git show be35~42
+ * fatal: ambiguous argument 'be35~42': unknown revision or path not in the working tree.
+ */
+void test_commit_parent__can_retrieve_nth_generation_parent(void)
+{
+ assert_nth_gen_parent(0, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ assert_nth_gen_parent(1, "9fd738e8f7967c078dceed8190330fc8648ee56a");
+ assert_nth_gen_parent(3, "5b5b025afb0b4c913b4c338a42934a3863bf3644");
+ assert_nth_gen_parent(42, NULL);
+}
diff --git a/tests/commit/parse.c b/tests/commit/parse.c
new file mode 100644
index 000000000..41e162440
--- /dev/null
+++ b/tests/commit/parse.c
@@ -0,0 +1,413 @@
+#include "clar_libgit2.h"
+#include <git2/types.h>
+#include "commit.h"
+#include "signature.h"
+
+// Fixture setup
+static git_repository *g_repo;
+void test_commit_parse__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo");
+}
+void test_commit_parse__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+
+// Header parsing
+typedef struct {
+ const char *line;
+ const char *header;
+} parse_test_case;
+
+static parse_test_case passing_header_cases[] = {
+ { "parent 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "parent " },
+ { "tree 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "tree " },
+ { "random_heading 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "random_heading " },
+ { "stuck_heading05452d6349abcd67aa396dfb28660d765d8b2a36\n", "stuck_heading" },
+ { "tree 5F4BEFFC0759261D015AA63A3A85613FF2F235DE\n", "tree " },
+ { "tree 1A669B8AB81B5EB7D9DB69562D34952A38A9B504\n", "tree " },
+ { "tree 5B20DCC6110FCC75D31C6CEDEBD7F43ECA65B503\n", "tree " },
+ { "tree 173E7BF00EA5C33447E99E6C1255954A13026BE4\n", "tree " },
+ { NULL, NULL }
+};
+
+static parse_test_case failing_header_cases[] = {
+ { "parent 05452d6349abcd67aa396dfb28660d765d8b2a36", "parent " },
+ { "05452d6349abcd67aa396dfb28660d765d8b2a36\n", "tree " },
+ { "parent05452d6349abcd67aa396dfb28660d765d8b2a6a\n", "parent " },
+ { "parent 05452d6349abcd67aa396dfb280d765d8b2a6\n", "parent " },
+ { "tree 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "tree " },
+ { "parent 0545xd6349abcd67aa396dfb28660d765d8b2a36\n", "parent " },
+ { "parent 0545xd6349abcd67aa396dfb28660d765d8b2a36FF\n", "parent " },
+ { "", "tree " },
+ { "", "" },
+ { NULL, NULL }
+};
+
+void test_commit_parse__header(void)
+{
+ git_oid oid;
+
+ parse_test_case *testcase;
+ for (testcase = passing_header_cases; testcase->line != NULL; testcase++)
+ {
+ const char *line = testcase->line;
+ const char *line_end = line + strlen(line);
+
+ cl_git_pass(git_oid__parse(&oid, &line, line_end, testcase->header));
+ cl_assert(line == line_end);
+ }
+
+ for (testcase = failing_header_cases; testcase->line != NULL; testcase++)
+ {
+ const char *line = testcase->line;
+ const char *line_end = line + strlen(line);
+
+ cl_git_fail(git_oid__parse(&oid, &line, line_end, testcase->header));
+ }
+}
+
+
+// Signature parsing
+typedef struct {
+ const char *string;
+ const char *header;
+ const char *name;
+ const char *email;
+ git_time_t time;
+ int offset;
+} passing_signature_test_case;
+
+passing_signature_test_case passing_signature_cases[] = {
+ {"author Vicent Marti <tanoku@gmail.com> 12345 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 12345, 0},
+ {"author Vicent Marti <> 12345 \n", "author ", "Vicent Marti", "", 12345, 0},
+ {"author Vicent Marti <tanoku@gmail.com> 231301 +1020\n", "author ", "Vicent Marti", "tanoku@gmail.com", 231301, 620},
+ {"author Vicent Marti with an outrageously long name which will probably overflow the buffer <tanoku@gmail.com> 12345 \n", "author ", "Vicent Marti with an outrageously long name which will probably overflow the buffer", "tanoku@gmail.com", 12345, 0},
+ {"author Vicent Marti <tanokuwithaveryveryverylongemailwhichwillprobablyvoverflowtheemailbuffer@gmail.com> 12345 \n", "author ", "Vicent Marti", "tanokuwithaveryveryverylongemailwhichwillprobablyvoverflowtheemailbuffer@gmail.com", 12345, 0},
+ {"committer Vicent Marti <tanoku@gmail.com> 123456 +0000 \n", "committer ", "Vicent Marti", "tanoku@gmail.com", 123456, 0},
+ {"committer Vicent Marti <tanoku@gmail.com> 123456 +0100 \n", "committer ", "Vicent Marti", "tanoku@gmail.com", 123456, 60},
+ {"committer Vicent Marti <tanoku@gmail.com> 123456 -0100 \n", "committer ", "Vicent Marti", "tanoku@gmail.com", 123456, -60},
+ // Parse a signature without an author field
+ {"committer <tanoku@gmail.com> 123456 -0100 \n", "committer ", "", "tanoku@gmail.com", 123456, -60},
+ // Parse a signature without an author field
+ {"committer <tanoku@gmail.com> 123456 -0100 \n", "committer ", "", "tanoku@gmail.com", 123456, -60},
+ // Parse a signature with an empty author field
+ {"committer <tanoku@gmail.com> 123456 -0100 \n", "committer ", "", "tanoku@gmail.com", 123456, -60},
+ // Parse a signature with an empty email field
+ {"committer Vicent Marti <> 123456 -0100 \n", "committer ", "Vicent Marti", "", 123456, -60},
+ // Parse a signature with an empty email field
+ {"committer Vicent Marti < > 123456 -0100 \n", "committer ", "Vicent Marti", "", 123456, -60},
+ // Parse a signature with empty name and email
+ {"committer <> 123456 -0100 \n", "committer ", "", "", 123456, -60},
+ // Parse a signature with empty name and email
+ {"committer <> 123456 -0100 \n", "committer ", "", "", 123456, -60},
+ // Parse a signature with empty name and email
+ {"committer < > 123456 -0100 \n", "committer ", "", "", 123456, -60},
+ // Parse an obviously invalid signature
+ {"committer foo<@bar> 123456 -0100 \n", "committer ", "foo", "@bar", 123456, -60},
+ // Parse an obviously invalid signature
+ {"committer foo<@bar> 123456 -0100 \n", "committer ", "foo", "@bar", 123456, -60},
+ // Parse an obviously invalid signature
+ {"committer <>\n", "committer ", "", "", 0, 0},
+ {"committer Vicent Marti <tanoku@gmail.com> 123456 -1500 \n", "committer ", "Vicent Marti", "tanoku@gmail.com", 123456, 0},
+ {"committer Vicent Marti <tanoku@gmail.com> 123456 +0163 \n", "committer ", "Vicent Marti", "tanoku@gmail.com", 123456, 0},
+ {"author Vicent Marti <tanoku@gmail.com>\n", "author ", "Vicent Marti", "tanoku@gmail.com", 0, 0},
+ /* a variety of dates */
+ {"author Vicent Marti <tanoku@gmail.com> 0 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 0, 0},
+ {"author Vicent Marti <tanoku@gmail.com> 1234567890 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 1234567890, 0},
+ {"author Vicent Marti <tanoku@gmail.com> 2147483647 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 0x7fffffff, 0},
+ {"author Vicent Marti <tanoku@gmail.com> 4294967295 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 0xffffffff, 0},
+ {"author Vicent Marti <tanoku@gmail.com> 4294967296 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 4294967296, 0},
+ {"author Vicent Marti <tanoku@gmail.com> 8589934592 \n", "author ", "Vicent Marti", "tanoku@gmail.com", 8589934592, 0},
+
+ {NULL,NULL,NULL,NULL,0,0}
+};
+
+typedef struct {
+ const char *string;
+ const char *header;
+} failing_signature_test_case;
+
+failing_signature_test_case failing_signature_cases[] = {
+ {"committer Vicent Marti tanoku@gmail.com> 123456 -0100 \n", "committer "},
+ {"author Vicent Marti <tanoku@gmail.com> 12345 \n", "author "},
+ {"author Vicent Marti <tanoku@gmail.com> 12345 \n", "committer "},
+ {"author Vicent Marti 12345 \n", "author "},
+ {"author Vicent Marti <broken@email 12345 \n", "author "},
+ {"committer Vicent Marti ><\n", "committer "},
+ {"author ", "author "},
+ {NULL, NULL,}
+};
+
+void test_commit_parse__signature(void)
+{
+ passing_signature_test_case *passcase;
+ failing_signature_test_case *failcase;
+
+ for (passcase = passing_signature_cases; passcase->string != NULL; passcase++)
+ {
+ const char *str = passcase->string;
+ size_t len = strlen(passcase->string);
+ struct git_signature person = {0};
+
+ cl_git_pass(git_signature__parse(&person, &str, str + len, passcase->header, '\n'));
+ cl_assert_equal_s(passcase->name, person.name);
+ cl_assert_equal_s(passcase->email, person.email);
+ cl_assert_equal_i((int)passcase->time, (int)person.when.time);
+ cl_assert_equal_i(passcase->offset, person.when.offset);
+ git__free(person.name); git__free(person.email);
+ }
+
+ for (failcase = failing_signature_cases; failcase->string != NULL; failcase++)
+ {
+ const char *str = failcase->string;
+ size_t len = strlen(failcase->string);
+ git_signature person = {0};
+ cl_git_fail(git_signature__parse(&person, &str, str + len, failcase->header, '\n'));
+ git__free(person.name); git__free(person.email);
+ }
+}
+
+
+
+static char *failing_commit_cases[] = {
+// empty commit
+"",
+// random garbage
+"asd97sa9du902e9a0jdsuusad09as9du098709aweu8987sd\n",
+// broken endlines 1
+"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\r\n\
+parent 05452d6349abcd67aa396dfb28660d765d8b2a36\r\n\
+author Vicent Marti <tanoku@gmail.com> 1273848544 +0200\r\n\
+committer Vicent Marti <tanoku@gmail.com> 1273848544 +0200\r\n\
+\r\n\
+a test commit with broken endlines\r\n",
+// broken endlines 2
+"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\
+parent 05452d6349abcd67aa396dfb28660d765d8b2a36\
+author Vicent Marti <tanoku@gmail.com> 1273848544 +0200\
+committer Vicent Marti <tanoku@gmail.com> 1273848544 +0200\
+\
+another test commit with broken endlines",
+// starting endlines
+"\ntree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\
+parent 05452d6349abcd67aa396dfb28660d765d8b2a36\n\
+author Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
+committer Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
+\n\
+a test commit with a starting endline\n",
+// corrupted commit 1
+"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\
+parent 05452d6349abcd67aa396df",
+// corrupted commit 2
+"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\
+parent ",
+// corrupted commit 3
+"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\
+parent ",
+// corrupted commit 4
+"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\
+par",
+};
+
+
+static char *passing_commit_cases[] = {
+// simple commit with no message
+"tree 1810dff58d8a660512d4832e740f692884338ccd\n\
+author Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
+committer Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
+\n",
+// simple commit, no parent
+"tree 1810dff58d8a660512d4832e740f692884338ccd\n\
+author Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
+committer Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
+\n\
+a simple commit which works\n",
+// simple commit, no parent, no newline in message
+"tree 1810dff58d8a660512d4832e740f692884338ccd\n\
+author Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
+committer Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
+\n\
+a simple commit which works",
+// simple commit, 1 parent
+"tree 1810dff58d8a660512d4832e740f692884338ccd\n\
+parent e90810b8df3e80c413d903f631643c716887138d\n\
+author Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
+committer Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
+\n\
+a simple commit which works\n",
+/* simple commit with GPG signature */
+"tree 6b79e22d69bf46e289df0345a14ca059dfc9bdf6\n\
+parent 34734e478d6cf50c27c9d69026d93974d052c454\n\
+author Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
+committer Ben Burkert <ben@benburkert.com> 1358451456 -0800\n\
+gpgsig -----BEGIN PGP SIGNATURE-----\n\
+ Version: GnuPG v1.4.12 (Darwin)\n\
+ \n\
+ iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\
+ o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\
+ JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\
+ AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\
+ SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\
+ who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\
+ 6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\
+ cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\
+ c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\
+ ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\
+ 7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\
+ cpxtDQQMGYFpXK/71stq\n\
+ =ozeK\n\
+ -----END PGP SIGNATURE-----\n\
+\n\
+a simple commit which works\n",
+};
+
+static int parse_commit(git_commit **out, const char *buffer)
+{
+ git_commit *commit;
+ git_odb_object fake_odb_object;
+ int error;
+
+ commit = (git_commit*)git__malloc(sizeof(git_commit));
+ memset(commit, 0x0, sizeof(git_commit));
+ commit->object.repo = g_repo;
+
+ memset(&fake_odb_object, 0x0, sizeof(git_odb_object));
+ fake_odb_object.buffer = (char *)buffer;
+ fake_odb_object.cached.size = strlen(fake_odb_object.buffer);
+
+ error = git_commit__parse(commit, &fake_odb_object);
+
+ *out = commit;
+ return error;
+}
+
+void test_commit_parse__entire_commit(void)
+{
+ const int failing_commit_count = ARRAY_SIZE(failing_commit_cases);
+ const int passing_commit_count = ARRAY_SIZE(passing_commit_cases);
+ int i;
+ git_commit *commit;
+
+ for (i = 0; i < failing_commit_count; ++i) {
+ cl_git_fail(parse_commit(&commit, failing_commit_cases[i]));
+ git_commit__free(commit);
+ }
+
+ for (i = 0; i < passing_commit_count; ++i) {
+ cl_git_pass(parse_commit(&commit, passing_commit_cases[i]));
+
+ if (!i)
+ cl_assert_equal_s("", git_commit_message(commit));
+ else
+ cl_assert(git__prefixcmp(
+ git_commit_message(commit), "a simple commit which works") == 0);
+
+ git_commit__free(commit);
+ }
+}
+
+
+// query the details on a parsed commit
+void test_commit_parse__details0(void) {
+ static const char *commit_ids[] = {
+ "a4a7dce85cf63874e984719f4fdd239f5145052f", /* 0 */
+ "9fd738e8f7967c078dceed8190330fc8648ee56a", /* 1 */
+ "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", /* 2 */
+ "c47800c7266a2be04c571c04d5a6614691ea99bd", /* 3 */
+ "8496071c1b46c854b31185ea97743be6a8774479", /* 4 */
+ "5b5b025afb0b4c913b4c338a42934a3863bf3644", /* 5 */
+ "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", /* 6 */
+ };
+ const size_t commit_count = sizeof(commit_ids) / sizeof(const char *);
+ unsigned int i;
+
+ for (i = 0; i < commit_count; ++i) {
+ git_oid id;
+ git_commit *commit;
+
+ const git_signature *author, *committer;
+ const char *message;
+ git_time_t commit_time;
+ unsigned int parents, p;
+ git_commit *parent = NULL, *old_parent = NULL;
+
+ git_oid_fromstr(&id, commit_ids[i]);
+
+ cl_git_pass(git_commit_lookup(&commit, g_repo, &id));
+
+ message = git_commit_message(commit);
+ author = git_commit_author(commit);
+ committer = git_commit_committer(commit);
+ commit_time = git_commit_time(commit);
+ parents = git_commit_parentcount(commit);
+
+ cl_assert_equal_s("Scott Chacon", author->name);
+ cl_assert_equal_s("schacon@gmail.com", author->email);
+ cl_assert_equal_s("Scott Chacon", committer->name);
+ cl_assert_equal_s("schacon@gmail.com", committer->email);
+ cl_assert(message != NULL);
+ cl_assert(commit_time > 0);
+ cl_assert(parents <= 2);
+ for (p = 0;p < parents;p++) {
+ if (old_parent != NULL)
+ git_commit_free(old_parent);
+
+ old_parent = parent;
+ cl_git_pass(git_commit_parent(&parent, commit, p));
+ cl_assert(parent != NULL);
+ cl_assert(git_commit_author(parent) != NULL); // is it really a commit?
+ }
+ git_commit_free(old_parent);
+ git_commit_free(parent);
+
+ cl_git_fail(git_commit_parent(&parent, commit, parents));
+ git_commit_free(commit);
+ }
+}
+
+void test_commit_parse__leading_lf(void)
+{
+ git_commit *commit;
+ const char *buffer =
+"tree 1810dff58d8a660512d4832e740f692884338ccd\n\
+parent e90810b8df3e80c413d903f631643c716887138d\n\
+author Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
+committer Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
+\n\
+\n\
+\n\
+This commit has a few LF at the start of the commit message";
+ const char *message =
+"This commit has a few LF at the start of the commit message";
+ const char *raw_message =
+"\n\
+\n\
+This commit has a few LF at the start of the commit message";
+ cl_git_pass(parse_commit(&commit, buffer));
+ cl_assert_equal_s(message, git_commit_message(commit));
+ cl_assert_equal_s(raw_message, git_commit_message_raw(commit));
+ git_commit__free(commit);
+}
+
+void test_commit_parse__only_lf(void)
+{
+ git_commit *commit;
+ const char *buffer =
+"tree 1810dff58d8a660512d4832e740f692884338ccd\n\
+parent e90810b8df3e80c413d903f631643c716887138d\n\
+author Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
+committer Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
+\n\
+\n\
+\n";
+ const char *message = "";
+ const char *raw_message = "\n\n";
+
+ cl_git_pass(parse_commit(&commit, buffer));
+ cl_assert_equal_s(message, git_commit_message(commit));
+ cl_assert_equal_s(raw_message, git_commit_message_raw(commit));
+ git_commit__free(commit);
+}
diff --git a/tests/commit/signature.c b/tests/commit/signature.c
new file mode 100644
index 000000000..e9dcfab41
--- /dev/null
+++ b/tests/commit/signature.c
@@ -0,0 +1,81 @@
+#include "clar_libgit2.h"
+
+static int try_build_signature(const char *name, const char *email, git_time_t time, int offset)
+{
+ git_signature *sign;
+ int error = 0;
+
+ if ((error = git_signature_new(&sign, name, email, time, offset)) < 0)
+ return error;
+
+ git_signature_free((git_signature *)sign);
+
+ return error;
+}
+
+static void assert_name_and_email(
+ const char *expected_name,
+ const char *expected_email,
+ const char *name,
+ const char *email)
+{
+ git_signature *sign;
+
+ cl_git_pass(git_signature_new(&sign, name, email, 1234567890, 60));
+ cl_assert_equal_s(expected_name, sign->name);
+ cl_assert_equal_s(expected_email, sign->email);
+
+ git_signature_free(sign);
+}
+
+void test_commit_signature__leading_and_trailing_spaces_are_trimmed(void)
+{
+ assert_name_and_email("nulltoken", "emeric.fermas@gmail.com", " nulltoken ", " emeric.fermas@gmail.com ");
+ assert_name_and_email("nulltoken", "emeric.fermas@gmail.com", " nulltoken ", " emeric.fermas@gmail.com \n");
+ assert_name_and_email("nulltoken", "emeric.fermas@gmail.com", " \t nulltoken \n", " \n emeric.fermas@gmail.com \n");
+}
+
+void test_commit_signature__angle_brackets_in_names_are_not_supported(void)
+{
+ cl_git_fail(try_build_signature("<Phil Haack", "phil@haack", 1234567890, 60));
+ cl_git_fail(try_build_signature("Phil>Haack", "phil@haack", 1234567890, 60));
+ cl_git_fail(try_build_signature("<Phil Haack>", "phil@haack", 1234567890, 60));
+}
+
+void test_commit_signature__angle_brackets_in_email_are_not_supported(void)
+{
+ cl_git_fail(try_build_signature("Phil Haack", ">phil@haack", 1234567890, 60));
+ cl_git_fail(try_build_signature("Phil Haack", "phil@>haack", 1234567890, 60));
+ cl_git_fail(try_build_signature("Phil Haack", "<phil@haack>", 1234567890, 60));
+}
+
+void test_commit_signature__create_empties(void)
+{
+ // can not create a signature with empty name or email
+ cl_git_pass(try_build_signature("nulltoken", "emeric.fermas@gmail.com", 1234567890, 60));
+
+ cl_git_fail(try_build_signature("", "emeric.fermas@gmail.com", 1234567890, 60));
+ cl_git_fail(try_build_signature(" ", "emeric.fermas@gmail.com", 1234567890, 60));
+ cl_git_pass(try_build_signature("nulltoken", "", 1234567890, 60));
+ cl_git_pass(try_build_signature("nulltoken", " ", 1234567890, 60));
+}
+
+void test_commit_signature__create_one_char(void)
+{
+ // creating a one character signature
+ assert_name_and_email("x", "foo@bar.baz", "x", "foo@bar.baz");
+}
+
+void test_commit_signature__create_two_char(void)
+{
+ // creating a two character signature
+ assert_name_and_email("xx", "foo@bar.baz", "xx", "foo@bar.baz");
+}
+
+void test_commit_signature__create_zero_char(void)
+{
+ // creating a zero character signature
+ git_signature *sign;
+ cl_git_fail(git_signature_new(&sign, "", "x@y.z", 1234567890, 60));
+ cl_assert(sign == NULL);
+}
diff --git a/tests/commit/write.c b/tests/commit/write.c
new file mode 100644
index 000000000..b1cdf4485
--- /dev/null
+++ b/tests/commit/write.c
@@ -0,0 +1,160 @@
+#include "clar_libgit2.h"
+
+static const char *committer_name = "Vicent Marti";
+static const char *committer_email = "vicent@github.com";
+static const char *commit_message = "This commit has been created in memory\n\
+ This is a commit created in memory and it will be written back to disk\n";
+static const char *tree_oid = "1810dff58d8a660512d4832e740f692884338ccd";
+static const char *root_commit_message = "This is a root commit\n\
+ This is a root commit and should be the only one in this branch\n";
+static const char *root_reflog_message = "commit (initial): This is a root commit \
+ This is a root commit and should be the only one in this branch";
+static char *head_old;
+static git_reference *head, *branch;
+static git_commit *commit;
+
+// Fixture setup
+static git_repository *g_repo;
+void test_commit_write__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo");
+}
+
+void test_commit_write__cleanup(void)
+{
+ git_reference_free(head);
+ head = NULL;
+
+ git_reference_free(branch);
+ branch = NULL;
+
+ git_commit_free(commit);
+ commit = NULL;
+
+ git__free(head_old);
+ head_old = NULL;
+
+ cl_git_sandbox_cleanup();
+}
+
+
+// write a new commit object from memory to disk
+void test_commit_write__from_memory(void)
+{
+ git_oid tree_id, parent_id, commit_id;
+ git_signature *author, *committer;
+ const git_signature *author1, *committer1;
+ git_commit *parent;
+ git_tree *tree;
+ const char *commit_id_str = "8496071c1b46c854b31185ea97743be6a8774479";
+
+ git_oid_fromstr(&tree_id, tree_oid);
+ cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
+
+ git_oid_fromstr(&parent_id, commit_id_str);
+ cl_git_pass(git_commit_lookup(&parent, g_repo, &parent_id));
+
+ /* create signatures */
+ cl_git_pass(git_signature_new(&committer, committer_name, committer_email, 123456789, 60));
+ cl_git_pass(git_signature_new(&author, committer_name, committer_email, 987654321, 90));
+
+ cl_git_pass(git_commit_create_v(
+ &commit_id, /* out id */
+ g_repo,
+ NULL, /* do not update the HEAD */
+ author,
+ committer,
+ NULL,
+ commit_message,
+ tree,
+ 1, parent));
+
+ git_object_free((git_object *)parent);
+ git_object_free((git_object *)tree);
+
+ git_signature_free(committer);
+ git_signature_free(author);
+
+ cl_git_pass(git_commit_lookup(&commit, g_repo, &commit_id));
+
+ /* Check attributes were set correctly */
+ author1 = git_commit_author(commit);
+ cl_assert(author1 != NULL);
+ cl_assert_equal_s(committer_name, author1->name);
+ cl_assert_equal_s(committer_email, author1->email);
+ cl_assert(author1->when.time == 987654321);
+ cl_assert(author1->when.offset == 90);
+
+ committer1 = git_commit_committer(commit);
+ cl_assert(committer1 != NULL);
+ cl_assert_equal_s(committer_name, committer1->name);
+ cl_assert_equal_s(committer_email, committer1->email);
+ cl_assert(committer1->when.time == 123456789);
+ cl_assert(committer1->when.offset == 60);
+
+ cl_assert_equal_s(commit_message, git_commit_message(commit));
+}
+
+// create a root commit
+void test_commit_write__root(void)
+{
+ git_oid tree_id, commit_id;
+ const git_oid *branch_oid;
+ git_signature *author, *committer;
+ const char *branch_name = "refs/heads/root-commit-branch";
+ git_tree *tree;
+ git_reflog *log;
+ const git_reflog_entry *entry;
+
+ git_oid_fromstr(&tree_id, tree_oid);
+ cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
+
+ /* create signatures */
+ cl_git_pass(git_signature_new(&committer, committer_name, committer_email, 123456789, 60));
+ cl_git_pass(git_signature_new(&author, committer_name, committer_email, 987654321, 90));
+
+ /* First we need to update HEAD so it points to our non-existant branch */
+ cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD"));
+ cl_assert(git_reference_type(head) == GIT_REF_SYMBOLIC);
+ head_old = git__strdup(git_reference_symbolic_target(head));
+ cl_assert(head_old != NULL);
+ git_reference_free(head);
+
+ cl_git_pass(git_reference_symbolic_create(&head, g_repo, "HEAD", branch_name, 1, NULL, NULL));
+
+ cl_git_pass(git_commit_create_v(
+ &commit_id, /* out id */
+ g_repo,
+ "HEAD",
+ author,
+ committer,
+ NULL,
+ root_commit_message,
+ tree,
+ 0));
+
+ git_object_free((git_object *)tree);
+ git_signature_free(author);
+
+ /*
+ * The fact that creating a commit works has already been
+ * tested. Here we just make sure it's our commit and that it was
+ * written as a root commit.
+ */
+ cl_git_pass(git_commit_lookup(&commit, g_repo, &commit_id));
+ cl_assert(git_commit_parentcount(commit) == 0);
+ cl_git_pass(git_reference_lookup(&branch, g_repo, branch_name));
+ branch_oid = git_reference_target(branch);
+ cl_git_pass(git_oid_cmp(branch_oid, &commit_id));
+ cl_assert_equal_s(root_commit_message, git_commit_message(commit));
+
+ cl_git_pass(git_reflog_read(&log, g_repo, branch_name));
+ cl_assert_equal_i(1, git_reflog_entrycount(log));
+ entry = git_reflog_entry_byindex(log, 0);
+ cl_assert_equal_s(committer->email, git_reflog_entry_committer(entry)->email);
+ cl_assert_equal_s(committer->name, git_reflog_entry_committer(entry)->name);
+ cl_assert_equal_s(root_reflog_message, git_reflog_entry_message(entry));
+
+ git_signature_free(committer);
+ git_reflog_free(log);
+}
diff --git a/tests/config/add.c b/tests/config/add.c
new file mode 100644
index 000000000..405f1e2c9
--- /dev/null
+++ b/tests/config/add.c
@@ -0,0 +1,37 @@
+#include "clar_libgit2.h"
+
+void test_config_add__initialize(void)
+{
+ cl_fixture_sandbox("config/config10");
+}
+
+void test_config_add__cleanup(void)
+{
+ cl_fixture_cleanup("config10");
+}
+
+void test_config_add__to_existing_section(void)
+{
+ git_config *cfg;
+ int32_t i;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config10"));
+ cl_git_pass(git_config_set_int32(cfg, "empty.tmp", 5));
+ cl_git_pass(git_config_get_int32(&i, cfg, "empty.tmp"));
+ cl_assert(i == 5);
+ cl_git_pass(git_config_delete_entry(cfg, "empty.tmp"));
+ git_config_free(cfg);
+}
+
+void test_config_add__to_new_section(void)
+{
+ git_config *cfg;
+ int32_t i;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config10"));
+ cl_git_pass(git_config_set_int32(cfg, "section.tmp", 5));
+ cl_git_pass(git_config_get_int32(&i, cfg, "section.tmp"));
+ cl_assert(i == 5);
+ cl_git_pass(git_config_delete_entry(cfg, "section.tmp"));
+ git_config_free(cfg);
+}
diff --git a/tests/config/backend.c b/tests/config/backend.c
new file mode 100644
index 000000000..3fd6eb114
--- /dev/null
+++ b/tests/config/backend.c
@@ -0,0 +1,24 @@
+#include "clar_libgit2.h"
+#include "git2/sys/config.h"
+
+void test_config_backend__checks_version(void)
+{
+ git_config *cfg;
+ git_config_backend backend = GIT_CONFIG_BACKEND_INIT;
+ const git_error *err;
+
+ backend.version = 1024;
+
+ cl_git_pass(git_config_new(&cfg));
+ cl_git_fail(git_config_add_backend(cfg, &backend, 0, false));
+ err = giterr_last();
+ cl_assert_equal_i(GITERR_INVALID, err->klass);
+
+ giterr_clear();
+ backend.version = 1024;
+ cl_git_fail(git_config_add_backend(cfg, &backend, 0, false));
+ err = giterr_last();
+ cl_assert_equal_i(GITERR_INVALID, err->klass);
+
+ git_config_free(cfg);
+}
diff --git a/tests/config/config_helpers.c b/tests/config/config_helpers.c
new file mode 100644
index 000000000..35da720e0
--- /dev/null
+++ b/tests/config/config_helpers.c
@@ -0,0 +1,65 @@
+#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);
+}
+
+static int count_config_entries_cb(
+ const git_config_entry *entry,
+ void *payload)
+{
+ int *how_many = (int *)payload;
+
+ GIT_UNUSED(entry);
+
+ (*how_many)++;
+
+ return 0;
+}
+
+int count_config_entries_match(git_repository *repo, const char *pattern)
+{
+ git_config *config;
+ int how_many = 0;
+
+ cl_git_pass(git_repository_config(&config, repo));
+
+ cl_assert_equal_i(0, git_config_foreach_match(
+ config, pattern, count_config_entries_cb, &how_many));
+
+ git_config_free(config);
+
+ return how_many;
+}
diff --git a/tests/config/config_helpers.h b/tests/config/config_helpers.h
new file mode 100644
index 000000000..440645730
--- /dev/null
+++ b/tests/config/config_helpers.h
@@ -0,0 +1,13 @@
+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);
+
+extern int count_config_entries_match(
+ git_repository *repo,
+ const char *pattern);
diff --git a/tests/config/configlevel.c b/tests/config/configlevel.c
new file mode 100644
index 000000000..1c22e8d9f
--- /dev/null
+++ b/tests/config/configlevel.c
@@ -0,0 +1,71 @@
+#include "clar_libgit2.h"
+
+void test_config_configlevel__adding_the_same_level_twice_returns_EEXISTS(void)
+{
+ int error;
+ git_config *cfg;
+
+ cl_git_pass(git_config_new(&cfg));
+ cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"),
+ GIT_CONFIG_LEVEL_LOCAL, 0));
+ cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"),
+ GIT_CONFIG_LEVEL_GLOBAL, 0));
+ error = git_config_add_file_ondisk(cfg, cl_fixture("config/config16"),
+ GIT_CONFIG_LEVEL_GLOBAL, 0);
+
+ cl_git_fail(error);
+ cl_assert_equal_i(GIT_EEXISTS, error);
+
+ git_config_free(cfg);
+}
+
+void test_config_configlevel__can_replace_a_config_file_at_an_existing_level(void)
+{
+ git_config *cfg;
+ const char *s;
+
+ cl_git_pass(git_config_new(&cfg));
+ cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config18"),
+ GIT_CONFIG_LEVEL_LOCAL, 1));
+ cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config19"),
+ GIT_CONFIG_LEVEL_LOCAL, 1));
+
+ cl_git_pass(git_config_get_string(&s, cfg, "core.stringglobal"));
+ cl_assert_equal_s("don't find me!", s);
+
+ git_config_free(cfg);
+}
+
+void test_config_configlevel__can_read_from_a_single_level_focused_file_after_parent_config_has_been_freed(void)
+{
+ git_config *cfg;
+ git_config *single_level_cfg;
+ const char *s;
+
+ cl_git_pass(git_config_new(&cfg));
+ cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config18"),
+ GIT_CONFIG_LEVEL_GLOBAL, 0));
+ cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config19"),
+ GIT_CONFIG_LEVEL_LOCAL, 0));
+
+ cl_git_pass(git_config_open_level(&single_level_cfg, cfg, GIT_CONFIG_LEVEL_LOCAL));
+
+ git_config_free(cfg);
+
+ cl_git_pass(git_config_get_string(&s, single_level_cfg, "core.stringglobal"));
+ cl_assert_equal_s("don't find me!", s);
+
+ 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/config/global.c b/tests/config/global.c
new file mode 100644
index 000000000..fc471f90d
--- /dev/null
+++ b/tests/config/global.c
@@ -0,0 +1,65 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "fileops.h"
+
+void test_config_global__initialize(void)
+{
+ git_buf path = GIT_BUF_INIT;
+
+ cl_git_pass(git_futils_mkdir_r("home", NULL, 0777));
+ cl_git_pass(git_path_prettify(&path, "home", NULL));
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr));
+
+ cl_git_pass(git_futils_mkdir_r("xdg/git", NULL, 0777));
+ cl_git_pass(git_path_prettify(&path, "xdg/git", NULL));
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr));
+
+ cl_git_pass(git_futils_mkdir_r("etc", NULL, 0777));
+ cl_git_pass(git_path_prettify(&path, "etc", NULL));
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr));
+
+ git_buf_free(&path);
+}
+
+void test_config_global__cleanup(void)
+{
+ cl_sandbox_set_search_path_defaults();
+}
+
+void test_config_global__open_global(void)
+{
+ git_config *cfg, *global, *selected, *dummy;
+
+ cl_git_pass(git_config_open_default(&cfg));
+ cl_git_pass(git_config_open_level(&global, cfg, GIT_CONFIG_LEVEL_GLOBAL));
+ cl_git_fail(git_config_open_level(&dummy, cfg, GIT_CONFIG_LEVEL_XDG));
+ cl_git_pass(git_config_open_global(&selected, cfg));
+
+ git_config_free(selected);
+ git_config_free(global);
+ git_config_free(cfg);
+}
+
+void test_config_global__open_xdg(void)
+{
+ git_config *cfg, *xdg, *selected;
+ const char *val, *str = "teststring";
+ const char *key = "this.variable";
+
+ cl_git_mkfile("xdg/git/config", "# XDG config\n[core]\n test = 1\n");
+
+ cl_git_pass(git_config_open_default(&cfg));
+ cl_git_pass(git_config_open_level(&xdg, cfg, GIT_CONFIG_LEVEL_XDG));
+ cl_git_pass(git_config_open_global(&selected, cfg));
+
+ cl_git_pass(git_config_set_string(xdg, key, str));
+ cl_git_pass(git_config_get_string(&val, selected, key));
+ cl_assert_equal_s(str, val);
+
+ git_config_free(selected);
+ git_config_free(xdg);
+ git_config_free(cfg);
+}
diff --git a/tests/config/include.c b/tests/config/include.c
new file mode 100644
index 000000000..58bc690ff
--- /dev/null
+++ b/tests/config/include.c
@@ -0,0 +1,111 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "fileops.h"
+
+void test_config_include__relative(void)
+{
+ git_config *cfg;
+ const char *str;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config-include")));
+
+ cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz"));
+ cl_assert_equal_s(str, "huzzah");
+
+ git_config_free(cfg);
+}
+
+void test_config_include__absolute(void)
+{
+ git_config *cfg;
+ const char *str;
+ git_buf buf = GIT_BUF_INIT;
+
+ cl_git_pass(git_buf_printf(&buf, "[include]\npath = %s/config-included", cl_fixture("config")));
+
+ cl_git_mkfile("config-include-absolute", git_buf_cstr(&buf));
+ git_buf_free(&buf);
+ cl_git_pass(git_config_open_ondisk(&cfg, "config-include-absolute"));
+
+ cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz"));
+ cl_assert_equal_s(str, "huzzah");
+
+ git_config_free(cfg);
+}
+
+void test_config_include__homedir(void)
+{
+ git_config *cfg;
+ const char *str;
+
+ cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, cl_fixture("config")));
+ cl_git_mkfile("config-include-homedir", "[include]\npath = ~/config-included");
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config-include-homedir"));
+
+ cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz"));
+ cl_assert_equal_s(str, "huzzah");
+
+ git_config_free(cfg);
+
+ cl_sandbox_set_search_path_defaults();
+}
+
+void test_config_include__refresh(void)
+{
+ git_config *cfg;
+ const char *str;
+
+ cl_fixture_sandbox("config");
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config/config-include"));
+
+ cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz"));
+ cl_assert_equal_s(str, "huzzah");
+
+ /* Change the included file and see if we refresh */
+ cl_git_mkfile("config/config-included", "[foo \"bar\"]\nbaz = hurrah");
+ cl_git_pass(git_config_refresh(cfg));
+
+ cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz"));
+ cl_assert_equal_s(str, "hurrah");
+
+ git_config_free(cfg);
+ cl_fixture_cleanup("config");
+}
+
+/* We need to pretend that the variables were defined where the file was included */
+void test_config_include__ordering(void)
+{
+ git_config *cfg;
+ const char *str;
+
+ cl_git_mkfile("included", "[foo \"bar\"]\nbaz = hurrah\nfrotz = hiya");
+ cl_git_mkfile("including",
+ "[foo \"bar\"]\nfrotz = hello\n"
+ "[include]\npath = included\n"
+ "[foo \"bar\"]\nbaz = huzzah\n");
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "including"));
+
+ cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.frotz"));
+ cl_assert_equal_s(str, "hiya");
+ cl_git_pass(git_config_get_string(&str, cfg, "foo.bar.baz"));
+ cl_assert_equal_s(str, "huzzah");
+
+ git_config_free(cfg);
+}
+
+/* We need to pretend that the variables were defined where the file was included */
+void test_config_include__depth(void)
+{
+ git_config *cfg;
+
+ cl_git_mkfile("a", "[include]\npath = b");
+ cl_git_mkfile("b", "[include]\npath = a");
+
+ cl_git_fail(git_config_open_ondisk(&cfg, "a"));
+
+ unlink("a");
+ unlink("b");
+}
diff --git a/tests/config/multivar.c b/tests/config/multivar.c
new file mode 100644
index 000000000..015008992
--- /dev/null
+++ b/tests/config/multivar.c
@@ -0,0 +1,288 @@
+#include "clar_libgit2.h"
+
+static const char *_name = "remote.ab.url";
+
+void test_config_multivar__initialize(void)
+{
+ cl_fixture_sandbox("config");
+}
+
+void test_config_multivar__cleanup(void)
+{
+ cl_fixture_cleanup("config");
+}
+
+static int mv_read_cb(const git_config_entry *entry, void *data)
+{
+ int *n = (int *) data;
+
+ if (!strcmp(entry->name, _name))
+ (*n)++;
+
+ return 0;
+}
+
+void test_config_multivar__foreach(void)
+{
+ git_config *cfg;
+ int n = 0;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config11")));
+
+ cl_git_pass(git_config_foreach(cfg, mv_read_cb, &n));
+ cl_assert(n == 2);
+
+ git_config_free(cfg);
+}
+
+static int cb(const git_config_entry *entry, void *data)
+{
+ int *n = (int *) data;
+
+ GIT_UNUSED(entry);
+
+ (*n)++;
+
+ return 0;
+}
+
+static void check_get_multivar_foreach(
+ git_config *cfg, int expected, int expected_patterned)
+{
+ int n = 0;
+
+ if (expected > 0) {
+ cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n));
+ cl_assert_equal_i(expected, n);
+ } else {
+ cl_assert_equal_i(GIT_ENOTFOUND,
+ git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n));
+ }
+
+ n = 0;
+
+ if (expected_patterned > 0) {
+ cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "example", cb, &n));
+ cl_assert_equal_i(expected_patterned, n);
+ } else {
+ cl_assert_equal_i(GIT_ENOTFOUND,
+ git_config_get_multivar_foreach(cfg, _name, "example", cb, &n));
+ }
+}
+
+static void check_get_multivar(git_config *cfg, int expected)
+{
+ git_config_iterator *iter;
+ git_config_entry *entry;
+ int n = 0;
+
+ cl_git_pass(git_config_multivar_iterator_new(&iter, cfg, _name, NULL));
+
+ while (git_config_next(&entry, iter) == 0)
+ n++;
+
+ cl_assert_equal_i(expected, n);
+ git_config_iterator_free(iter);
+
+}
+
+void test_config_multivar__get(void)
+{
+ git_config *cfg;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config/config11"));
+ check_get_multivar_foreach(cfg, 2, 1);
+
+ /* add another that has the _name entry */
+ cl_git_pass(git_config_add_file_ondisk(cfg, "config/config9", GIT_CONFIG_LEVEL_SYSTEM, 1));
+ check_get_multivar_foreach(cfg, 3, 2);
+
+ /* add another that does not have the _name entry */
+ cl_git_pass(git_config_add_file_ondisk(cfg, "config/config0", GIT_CONFIG_LEVEL_GLOBAL, 1));
+ check_get_multivar_foreach(cfg, 3, 2);
+
+ /* add another that does not have the _name entry at the end */
+ cl_git_pass(git_config_add_file_ondisk(cfg, "config/config1", GIT_CONFIG_LEVEL_APP, 1));
+ check_get_multivar_foreach(cfg, 3, 2);
+
+ /* drop original file */
+ cl_git_pass(git_config_add_file_ondisk(cfg, "config/config2", GIT_CONFIG_LEVEL_LOCAL, 1));
+ check_get_multivar_foreach(cfg, 1, 1);
+
+ /* drop other file with match */
+ cl_git_pass(git_config_add_file_ondisk(cfg, "config/config3", GIT_CONFIG_LEVEL_SYSTEM, 1));
+ check_get_multivar_foreach(cfg, 0, 0);
+
+ /* reload original file (add different place in order) */
+ cl_git_pass(git_config_add_file_ondisk(cfg, "config/config11", GIT_CONFIG_LEVEL_SYSTEM, 1));
+ check_get_multivar_foreach(cfg, 2, 1);
+
+ check_get_multivar(cfg, 2);
+
+ git_config_free(cfg);
+}
+
+void test_config_multivar__add(void)
+{
+ git_config *cfg;
+ int n;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config/config11"));
+ cl_git_pass(git_config_set_multivar(cfg, _name, "nonexistant", "git://git.otherplace.org/libgit2"));
+
+ n = 0;
+ cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n));
+ cl_assert_equal_i(n, 3);
+
+ n = 0;
+ cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n));
+ cl_assert_equal_i(n, 1);
+
+ git_config_free(cfg);
+
+ /* We know it works in memory, let's see if the file is written correctly */
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config/config11"));
+
+ n = 0;
+ cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n));
+ cl_assert_equal_i(n, 3);
+
+ n = 0;
+ cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n));
+ cl_assert_equal_i(n, 1);
+
+ git_config_free(cfg);
+}
+
+void test_config_multivar__add_new(void)
+{
+ const char *var = "a.brand.new";
+ git_config *cfg;
+ int n;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config/config11"));
+
+ cl_git_pass(git_config_set_multivar(cfg, var, "", "variable"));
+ n = 0;
+ cl_git_pass(git_config_get_multivar_foreach(cfg, var, NULL, cb, &n));
+ cl_assert_equal_i(n, 1);
+
+ git_config_free(cfg);
+}
+
+void test_config_multivar__replace(void)
+{
+ git_config *cfg;
+ int n;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config/config11"));
+
+ n = 0;
+ cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n));
+ cl_assert(n == 2);
+
+ cl_git_pass(git_config_set_multivar(cfg, _name, "github", "git://git.otherplace.org/libgit2"));
+
+ n = 0;
+ cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n));
+ cl_assert(n == 2);
+
+ git_config_free(cfg);
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config/config11"));
+
+ n = 0;
+ cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n));
+ cl_assert(n == 2);
+
+ git_config_free(cfg);
+}
+
+void test_config_multivar__replace_multiple(void)
+{
+ git_config *cfg;
+ int n;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config/config11"));
+ cl_git_pass(git_config_set_multivar(cfg, _name, "git://", "git://git.otherplace.org/libgit2"));
+
+ n = 0;
+ cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n));
+ cl_assert_equal_i(n, 2);
+
+ git_config_free(cfg);
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config/config11"));
+
+ n = 0;
+ cl_git_pass(git_config_get_multivar_foreach(cfg, _name, "otherplace", cb, &n));
+ cl_assert_equal_i(n, 2);
+
+ git_config_free(cfg);
+}
+
+void test_config_multivar__delete(void)
+{
+ git_config *cfg;
+ int n;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config/config11"));
+
+ n = 0;
+ cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n));
+ cl_assert_equal_i(2, n);
+
+ cl_git_pass(git_config_delete_multivar(cfg, _name, "github"));
+
+ n = 0;
+ cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n));
+ cl_assert_equal_i(1, n);
+
+ git_config_free(cfg);
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config/config11"));
+
+ n = 0;
+ cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n));
+ cl_assert_equal_i(1, n);
+
+ git_config_free(cfg);
+}
+
+void test_config_multivar__delete_multiple(void)
+{
+ git_config *cfg;
+ int n;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config/config11"));
+
+ n = 0;
+ cl_git_pass(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n));
+ cl_assert(n == 2);
+
+ cl_git_pass(git_config_delete_multivar(cfg, _name, "git"));
+
+ n = 0;
+ cl_git_fail_with(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n), GIT_ENOTFOUND);
+
+ git_config_free(cfg);
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config/config11"));
+
+ n = 0;
+ cl_git_fail_with(git_config_get_multivar_foreach(cfg, _name, NULL, cb, &n), GIT_ENOTFOUND);
+
+ git_config_free(cfg);
+}
+
+void test_config_multivar__delete_notfound(void)
+{
+ git_config *cfg;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config/config11"));
+
+ cl_git_fail_with(git_config_delete_multivar(cfg, "remote.ab.noturl", "git"), GIT_ENOTFOUND);
+
+ git_config_free(cfg);
+}
diff --git a/tests/config/new.c b/tests/config/new.c
new file mode 100644
index 000000000..dd6dbca9e
--- /dev/null
+++ b/tests/config/new.c
@@ -0,0 +1,32 @@
+#include "clar_libgit2.h"
+
+#include "filebuf.h"
+#include "fileops.h"
+#include "posix.h"
+
+#define TEST_CONFIG "git-new-config"
+
+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"));
+ cl_git_pass(git_config_set_string(config, "core.editor", "ed"));
+
+ git_config_free(config);
+
+ cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG));
+
+ cl_git_pass(git_config_get_string(&out, config, "color.ui"));
+ cl_assert_equal_s(out, "auto");
+ cl_git_pass(git_config_get_string(&out, config, "core.editor"));
+ cl_assert_equal_s(out, "ed");
+
+ git_config_free(config);
+
+ p_unlink(TEST_CONFIG);
+}
diff --git a/tests/config/read.c b/tests/config/read.c
new file mode 100644
index 000000000..25672729f
--- /dev/null
+++ b/tests/config/read.c
@@ -0,0 +1,569 @@
+#include "clar_libgit2.h"
+
+void test_config_read__simple_read(void)
+{
+ git_config *cfg;
+ int32_t i;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config0")));
+
+ cl_git_pass(git_config_get_int32(&i, cfg, "core.repositoryformatversion"));
+ cl_assert(i == 0);
+ cl_git_pass(git_config_get_bool(&i, cfg, "core.filemode"));
+ cl_assert(i == 1);
+ cl_git_pass(git_config_get_bool(&i, cfg, "core.bare"));
+ cl_assert(i == 0);
+ cl_git_pass(git_config_get_bool(&i, cfg, "core.logallrefupdates"));
+ cl_assert(i == 1);
+
+ git_config_free(cfg);
+}
+
+void test_config_read__case_sensitive(void)
+{
+ git_config *cfg;
+ int i;
+ const char *str;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config1")));
+
+ cl_git_pass(git_config_get_string(&str, cfg, "this.that.other"));
+ cl_assert_equal_s(str, "true");
+ cl_git_pass(git_config_get_string(&str, cfg, "this.That.other"));
+ cl_assert_equal_s(str, "yes");
+
+ cl_git_pass(git_config_get_bool(&i, cfg, "this.that.other"));
+ cl_assert(i == 1);
+ cl_git_pass(git_config_get_bool(&i, cfg, "this.That.other"));
+ cl_assert(i == 1);
+
+ /* This one doesn't exist */
+ cl_must_fail(git_config_get_bool(&i, cfg, "this.thaT.other"));
+
+ git_config_free(cfg);
+}
+
+/*
+ * If \ is the last non-space character on the line, we read the next
+ * one, separating each line with SP.
+ */
+void test_config_read__multiline_value(void)
+{
+ git_config *cfg;
+ const char *str;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config2")));
+
+ cl_git_pass(git_config_get_string(&str, cfg, "this.That.and"));
+ cl_assert_equal_s(str, "one one one two two three three");
+
+ git_config_free(cfg);
+}
+
+/*
+ * This kind of subsection declaration is case-insensitive
+ */
+void test_config_read__subsection_header(void)
+{
+ git_config *cfg;
+ const char *str;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config3")));
+
+ cl_git_pass(git_config_get_string(&str, cfg, "section.subsection.var"));
+ cl_assert_equal_s(str, "hello");
+
+ /* The subsection is transformed to lower-case */
+ cl_must_fail(git_config_get_string(&str, cfg, "section.subSectIon.var"));
+
+ git_config_free(cfg);
+}
+
+void test_config_read__lone_variable(void)
+{
+ git_config *cfg;
+ const char *str;
+ int i;
+
+ 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_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);
+}
+
+void test_config_read__number_suffixes(void)
+{
+ git_config *cfg;
+ int64_t i;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config5")));
+
+ cl_git_pass(git_config_get_int64(&i, cfg, "number.simple"));
+ cl_assert(i == 1);
+
+ cl_git_pass(git_config_get_int64(&i, cfg, "number.k"));
+ cl_assert(i == 1 * 1024);
+
+ cl_git_pass(git_config_get_int64(&i, cfg, "number.kk"));
+ cl_assert(i == 1 * 1024);
+
+ cl_git_pass(git_config_get_int64(&i, cfg, "number.m"));
+ cl_assert(i == 1 * 1024 * 1024);
+
+ cl_git_pass(git_config_get_int64(&i, cfg, "number.mm"));
+ cl_assert(i == 1 * 1024 * 1024);
+
+ cl_git_pass(git_config_get_int64(&i, cfg, "number.g"));
+ cl_assert(i == 1 * 1024 * 1024 * 1024);
+
+ cl_git_pass(git_config_get_int64(&i, cfg, "number.gg"));
+ cl_assert(i == 1 * 1024 * 1024 * 1024);
+
+ git_config_free(cfg);
+}
+
+void test_config_read__blank_lines(void)
+{
+ git_config *cfg;
+ int i;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config6")));
+
+ cl_git_pass(git_config_get_bool(&i, cfg, "valid.subsection.something"));
+ cl_assert(i == 1);
+
+ cl_git_pass(git_config_get_bool(&i, cfg, "something.else.something"));
+ cl_assert(i == 0);
+
+ git_config_free(cfg);
+}
+
+void test_config_read__invalid_ext_headers(void)
+{
+ git_config *cfg;
+ cl_must_fail(git_config_open_ondisk(&cfg, cl_fixture("config/config7")));
+}
+
+void test_config_read__empty_files(void)
+{
+ git_config *cfg;
+ cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config8")));
+ git_config_free(cfg);
+}
+
+void test_config_read__symbol_headers(void)
+{
+ git_config *cfg;
+ cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config20")));
+ git_config_free(cfg);
+}
+
+void test_config_read__header_in_last_line(void)
+{
+ git_config *cfg;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config10")));
+ git_config_free(cfg);
+}
+
+void test_config_read__prefixes(void)
+{
+ git_config *cfg;
+ const char *str;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config9")));
+ cl_git_pass(git_config_get_string(&str, cfg, "remote.ab.url"));
+ cl_assert_equal_s(str, "http://example.com/git/ab");
+
+ cl_git_pass(git_config_get_string(&str, cfg, "remote.abba.url"));
+ cl_assert_equal_s(str, "http://example.com/git/abba");
+
+ git_config_free(cfg);
+}
+
+void test_config_read__escaping_quotes(void)
+{
+ git_config *cfg;
+ const char *str;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config13")));
+ cl_git_pass(git_config_get_string(&str, cfg, "core.editor"));
+ cl_assert_equal_s("\"C:/Program Files/Nonsense/bah.exe\" \"--some option\"", str);
+
+ git_config_free(cfg);
+}
+
+static int count_cfg_entries_and_compare_levels(
+ const git_config_entry *entry, void *payload)
+{
+ int *count = payload;
+
+ if (!strcmp(entry->value, "7") || !strcmp(entry->value, "17"))
+ cl_assert(entry->level == GIT_CONFIG_LEVEL_GLOBAL);
+ else
+ cl_assert(entry->level == GIT_CONFIG_LEVEL_SYSTEM);
+
+ (*count)++;
+ return 0;
+}
+
+static int cfg_callback_countdown(const git_config_entry *entry, void *payload)
+{
+ int *count = payload;
+ GIT_UNUSED(entry);
+ (*count)--;
+ if (*count == 0)
+ return -100;
+ return 0;
+}
+
+void test_config_read__foreach(void)
+{
+ git_config *cfg;
+ int count, ret;
+
+ cl_git_pass(git_config_new(&cfg));
+ cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"),
+ GIT_CONFIG_LEVEL_SYSTEM, 0));
+ cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"),
+ GIT_CONFIG_LEVEL_GLOBAL, 0));
+
+ count = 0;
+ cl_git_pass(git_config_foreach(cfg, count_cfg_entries_and_compare_levels, &count));
+ cl_assert_equal_i(7, count);
+
+ count = 3;
+ cl_git_fail(ret = git_config_foreach(cfg, cfg_callback_countdown, &count));
+ cl_assert_equal_i(-100, ret);
+
+ git_config_free(cfg);
+}
+
+void test_config_read__iterator(void)
+{
+ git_config *cfg;
+ git_config_iterator *iter;
+ git_config_entry *entry;
+ int count, ret;
+
+ cl_git_pass(git_config_new(&cfg));
+ cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"),
+ GIT_CONFIG_LEVEL_SYSTEM, 0));
+ cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"),
+ GIT_CONFIG_LEVEL_GLOBAL, 0));
+
+ count = 0;
+ cl_git_pass(git_config_iterator_new(&iter, cfg));
+
+ while ((ret = git_config_next(&entry, iter)) == 0) {
+ count++;
+ }
+
+ git_config_iterator_free(iter);
+ cl_assert_equal_i(GIT_ITEROVER, ret);
+ cl_assert_equal_i(7, count);
+
+ count = 3;
+ cl_git_pass(git_config_iterator_new(&iter, cfg));
+
+ git_config_iterator_free(iter);
+ git_config_free(cfg);
+}
+
+static int count_cfg_entries(const git_config_entry *entry, void *payload)
+{
+ int *count = payload;
+ GIT_UNUSED(entry);
+ (*count)++;
+ return 0;
+}
+
+void test_config_read__foreach_match(void)
+{
+ git_config *cfg;
+ int count;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config9")));
+
+ count = 0;
+ cl_git_pass(
+ git_config_foreach_match(cfg, "core.*", count_cfg_entries, &count));
+ cl_assert_equal_i(3, count);
+
+ count = 0;
+ cl_git_pass(
+ git_config_foreach_match(cfg, "remote\\.ab.*", count_cfg_entries, &count));
+ cl_assert_equal_i(2, count);
+
+ count = 0;
+ cl_git_pass(
+ git_config_foreach_match(cfg, ".*url$", count_cfg_entries, &count));
+ cl_assert_equal_i(2, count);
+
+ count = 0;
+ cl_git_pass(
+ git_config_foreach_match(cfg, ".*dummy.*", count_cfg_entries, &count));
+ cl_assert_equal_i(2, count);
+
+ count = 0;
+ cl_git_pass(
+ git_config_foreach_match(cfg, ".*nomatch.*", count_cfg_entries, &count));
+ cl_assert_equal_i(0, count);
+
+ git_config_free(cfg);
+}
+
+static void check_glob_iter(git_config *cfg, const char *regexp, int expected)
+{
+ git_config_iterator *iter;
+ git_config_entry *entry;
+ int count, error;
+
+ cl_git_pass(git_config_iterator_glob_new(&iter, cfg, regexp));
+
+ count = 0;
+ while ((error = git_config_next(&entry, iter)) == 0)
+ count++;
+
+ cl_assert_equal_i(GIT_ITEROVER, error);
+ cl_assert_equal_i(expected, count);
+ git_config_iterator_free(iter);
+}
+
+void test_config_read__iterator_glob(void)
+{
+ git_config *cfg;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config9")));
+
+ check_glob_iter(cfg, "core.*", 3);
+ check_glob_iter(cfg, "remote\\.ab.*", 2);
+ check_glob_iter(cfg, ".*url$", 2);
+ check_glob_iter(cfg, ".*dummy.*", 2);
+ check_glob_iter(cfg, ".*nomatch.*", 0);
+
+ git_config_free(cfg);
+}
+
+void test_config_read__whitespace_not_required_around_assignment(void)
+{
+ git_config *cfg;
+ const char *str;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config14")));
+
+ cl_git_pass(git_config_get_string(&str, cfg, "a.b"));
+ cl_assert_equal_s(str, "c");
+
+ cl_git_pass(git_config_get_string(&str, cfg, "d.e"));
+ cl_assert_equal_s(str, "f");
+
+ git_config_free(cfg);
+}
+
+void test_config_read__read_git_config_entry(void)
+{
+ git_config *cfg;
+ const git_config_entry *entry;
+
+ cl_git_pass(git_config_new(&cfg));
+ cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"),
+ GIT_CONFIG_LEVEL_SYSTEM, 0));
+
+ 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);
+
+ git_config_free(cfg);
+}
+
+/*
+ * At the beginning of the test:
+ * - config9 has: core.dummy2=42
+ * - config15 has: core.dummy2=7
+ * - config16 has: core.dummy2=28
+ */
+void test_config_read__local_config_overrides_global_config_overrides_system_config(void)
+{
+ git_config *cfg;
+ int32_t i;
+
+ cl_git_pass(git_config_new(&cfg));
+ cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"),
+ GIT_CONFIG_LEVEL_SYSTEM, 0));
+ cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"),
+ GIT_CONFIG_LEVEL_GLOBAL, 0));
+ cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config16"),
+ GIT_CONFIG_LEVEL_LOCAL, 0));
+
+ cl_git_pass(git_config_get_int32(&i, cfg, "core.dummy2"));
+ cl_assert_equal_i(28, i);
+
+ git_config_free(cfg);
+
+ cl_git_pass(git_config_new(&cfg));
+ cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"),
+ GIT_CONFIG_LEVEL_SYSTEM, 0));
+ cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"),
+ GIT_CONFIG_LEVEL_GLOBAL, 0));
+
+ cl_git_pass(git_config_get_int32(&i, cfg, "core.dummy2"));
+ cl_assert_equal_i(7, i);
+
+ git_config_free(cfg);
+}
+
+/*
+ * At the beginning of the test:
+ * - config9 has: core.global does not exist
+ * - config15 has: core.global=17
+ * - config16 has: core.global=29
+ *
+ * And also:
+ * - config9 has: core.system does not exist
+ * - config15 has: core.system does not exist
+ * - config16 has: core.system=11
+ */
+void test_config_read__fallback_from_local_to_global_and_from_global_to_system(void)
+{
+ git_config *cfg;
+ int32_t i;
+
+ cl_git_pass(git_config_new(&cfg));
+ cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"),
+ GIT_CONFIG_LEVEL_SYSTEM, 0));
+ cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config15"),
+ GIT_CONFIG_LEVEL_GLOBAL, 0));
+ cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config16"),
+ GIT_CONFIG_LEVEL_LOCAL, 0));
+
+ cl_git_pass(git_config_get_int32(&i, cfg, "core.global"));
+ cl_assert_equal_i(17, i);
+ cl_git_pass(git_config_get_int32(&i, cfg, "core.system"));
+ cl_assert_equal_i(11, i);
+
+ git_config_free(cfg);
+}
+
+/*
+ * At the beginning of the test, config18 has:
+ * int32global = 28
+ * int64global = 9223372036854775803
+ * boolglobal = true
+ * stringglobal = I'm a global config value!
+ *
+ * And config19 has:
+ * int32global = -1
+ * int64global = -2
+ * boolglobal = false
+ * stringglobal = don't find me!
+ *
+ */
+void test_config_read__simple_read_from_specific_level(void)
+{
+ git_config *cfg, *cfg_specific;
+ int i;
+ int64_t l, expected = +9223372036854775803;
+ const char *s;
+
+ cl_git_pass(git_config_new(&cfg));
+ cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config18"),
+ GIT_CONFIG_LEVEL_GLOBAL, 0));
+ cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config19"),
+ GIT_CONFIG_LEVEL_SYSTEM, 0));
+
+ cl_git_pass(git_config_open_level(&cfg_specific, cfg, GIT_CONFIG_LEVEL_GLOBAL));
+
+ cl_git_pass(git_config_get_int32(&i, cfg_specific, "core.int32global"));
+ cl_assert_equal_i(28, i);
+ cl_git_pass(git_config_get_int64(&l, cfg_specific, "core.int64global"));
+ cl_assert(l == expected);
+ cl_git_pass(git_config_get_bool(&i, cfg_specific, "core.boolglobal"));
+ cl_assert_equal_b(true, i);
+ cl_git_pass(git_config_get_string(&s, cfg_specific, "core.stringglobal"));
+ cl_assert_equal_s("I'm a global config value!", s);
+
+ git_config_free(cfg_specific);
+ git_config_free(cfg);
+}
+
+static void clean_test_config(void *unused)
+{
+ GIT_UNUSED(unused);
+ cl_fixture_cleanup("./testconfig");
+}
+
+void test_config_read__can_load_and_parse_an_empty_config_file(void)
+{
+ git_config *cfg;
+ int i;
+
+ cl_set_cleanup(&clean_test_config, NULL);
+ cl_git_mkfile("./testconfig", "");
+ cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig"));
+ cl_assert_equal_i(GIT_ENOTFOUND, git_config_get_int32(&i, cfg, "nope.neither"));
+
+ git_config_free(cfg);
+}
+
+void test_config_read__corrupt_header(void)
+{
+ git_config *cfg;
+
+ cl_set_cleanup(&clean_test_config, NULL);
+ cl_git_mkfile("./testconfig", "[sneaky ] \"quoted closing quote mark\\\"");
+ cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig"));
+
+ git_config_free(cfg);
+}
+
+void test_config_read__corrupt_header2(void)
+{
+ git_config *cfg;
+
+ cl_set_cleanup(&clean_test_config, NULL);
+ cl_git_mkfile("./testconfig", "[unclosed \"bracket\"\n lib = git2\n");
+ cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig"));
+
+ git_config_free(cfg);
+}
+
+void test_config_read__corrupt_header3(void)
+{
+ git_config *cfg;
+
+ cl_set_cleanup(&clean_test_config, NULL);
+ cl_git_mkfile("./testconfig", "[unclosed \"slash\\\"]\n lib = git2\n");
+ cl_git_fail(git_config_open_ondisk(&cfg, "./testconfig"));
+
+ git_config_free(cfg);
+}
+
+void test_config_read__override_variable(void)
+{
+ git_config *cfg;
+ const char *str;
+
+ cl_set_cleanup(&clean_test_config, NULL);
+ cl_git_mkfile("./testconfig", "[some] var = one\nvar = two");
+ cl_git_pass(git_config_open_ondisk(&cfg, "./testconfig"));
+
+ cl_git_pass(git_config_get_string(&str, cfg, "some.var"));
+ cl_assert_equal_s(str, "two");
+
+ git_config_free(cfg);
+}
diff --git a/tests/config/refresh.c b/tests/config/refresh.c
new file mode 100644
index 000000000..08cd45b95
--- /dev/null
+++ b/tests/config/refresh.c
@@ -0,0 +1,64 @@
+#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_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_fail_with(GIT_ENOTFOUND, git_config_get_int32(&v, cfg, "section.value"));
+
+ cl_git_pass(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/config/rename.c b/tests/config/rename.c
new file mode 100644
index 000000000..db07c798f
--- /dev/null
+++ b/tests/config/rename.c
@@ -0,0 +1,85 @@
+#include "clar_libgit2.h"
+#include "config.h"
+
+static git_repository *g_repo = NULL;
+static git_config *g_config = NULL;
+
+void test_config_rename__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo.git");
+ cl_git_pass(git_repository_config(&g_config, g_repo));
+}
+
+void test_config_rename__cleanup(void)
+{
+ git_config_free(g_config);
+ g_config = NULL;
+
+ cl_git_sandbox_cleanup();
+ g_repo = NULL;
+}
+
+void test_config_rename__can_rename(void)
+{
+ const git_config_entry *ce;
+
+ cl_git_pass(git_config_get_entry(
+ &ce, g_config, "branch.track-local.remote"));
+ cl_assert_equal_s(".", ce->value);
+
+ cl_git_fail(git_config_get_entry(
+ &ce, g_config, "branch.local-track.remote"));
+
+ cl_git_pass(git_config_rename_section(
+ g_repo, "branch.track-local", "branch.local-track"));
+
+ cl_git_pass(git_config_get_entry(
+ &ce, g_config, "branch.local-track.remote"));
+ cl_assert_equal_s(".", ce->value);
+
+ cl_git_fail(git_config_get_entry(
+ &ce, g_config, "branch.track-local.remote"));
+}
+
+void test_config_rename__prevent_overwrite(void)
+{
+ const git_config_entry *ce;
+
+ cl_git_pass(git_config_set_string(
+ g_config, "branch.local-track.remote", "yellow"));
+
+ cl_git_pass(git_config_get_entry(
+ &ce, g_config, "branch.local-track.remote"));
+ cl_assert_equal_s("yellow", ce->value);
+
+ cl_git_pass(git_config_rename_section(
+ g_repo, "branch.track-local", "branch.local-track"));
+
+ cl_git_pass(git_config_get_entry(
+ &ce, g_config, "branch.local-track.remote"));
+ cl_assert_equal_s(".", ce->value);
+
+ /* so, we don't currently prevent overwrite... */
+ /* {
+ const git_error *err;
+ cl_assert((err = giterr_last()) != NULL);
+ cl_assert(err->message != NULL);
+ } */
+}
+
+static void assert_invalid_config_section_name(
+ git_repository *repo, const char *name)
+{
+ cl_git_fail_with(
+ git_config_rename_section(repo, "branch.remoteless", name),
+ GIT_EINVALIDSPEC);
+}
+
+void test_config_rename__require_a_valid_new_name(void)
+{
+ assert_invalid_config_section_name(g_repo, "");
+ assert_invalid_config_section_name(g_repo, "bra\nch");
+ assert_invalid_config_section_name(g_repo, "branc#");
+ assert_invalid_config_section_name(g_repo, "bra\nch.duh");
+ assert_invalid_config_section_name(g_repo, "branc#.duh");
+}
diff --git a/tests/config/snapshot.c b/tests/config/snapshot.c
new file mode 100644
index 000000000..c9f15921a
--- /dev/null
+++ b/tests/config/snapshot.c
@@ -0,0 +1,64 @@
+#include "clar_libgit2.h"
+
+void test_config_snapshot__create_snapshot(void)
+{
+ int32_t tmp;
+ git_config *cfg, *snapshot;
+ const char *filename = "config-ext-change";
+
+ cl_git_mkfile(filename, "[old]\nvalue = 5\n");
+
+ cl_git_pass(git_config_open_ondisk(&cfg, filename));
+
+ cl_git_pass(git_config_get_int32(&tmp, cfg, "old.value"));
+ cl_assert_equal_i(5, tmp);
+
+ cl_git_pass(git_config_snapshot(&snapshot, cfg));
+
+ /* Change the value on the file itself (simulate external process) */
+ cl_git_mkfile(filename, "[old]\nvalue = 56\n");
+
+ cl_git_pass(git_config_get_int32(&tmp, cfg, "old.value"));
+ cl_assert_equal_i(56, tmp);
+
+ cl_git_pass(git_config_get_int32(&tmp, snapshot, "old.value"));
+ cl_assert_equal_i(5, tmp);
+
+ git_config_free(snapshot);
+ git_config_free(cfg);
+}
+
+static int count_me(const git_config_entry *entry, void *payload)
+{
+ int *n = (int *) payload;
+
+ GIT_UNUSED(entry);
+
+ (*n)++;
+
+ return 0;
+}
+
+void test_config_snapshot__multivar(void)
+{
+ int count = 0;
+ git_config *cfg, *snapshot;
+ const char *filename = "config-file";
+
+ cl_git_mkfile(filename, "[old]\nvalue = 5\nvalue = 6\n");
+
+ cl_git_pass(git_config_open_ondisk(&cfg, filename));
+ cl_git_pass(git_config_get_multivar_foreach(cfg, "old.value", NULL, count_me, &count));
+
+ cl_assert_equal_i(2, count);
+
+ cl_git_pass(git_config_snapshot(&snapshot, cfg));
+ git_config_free(cfg);
+
+ count = 0;
+ cl_git_pass(git_config_get_multivar_foreach(snapshot, "old.value", NULL, count_me, &count));
+
+ cl_assert_equal_i(2, count);
+
+ git_config_free(snapshot);
+}
diff --git a/tests/config/stress.c b/tests/config/stress.c
new file mode 100644
index 000000000..eeca54ff4
--- /dev/null
+++ b/tests/config/stress.c
@@ -0,0 +1,92 @@
+#include "clar_libgit2.h"
+
+#include "filebuf.h"
+#include "fileops.h"
+#include "posix.h"
+
+#define TEST_CONFIG "git-test-config"
+
+void test_config_stress__initialize(void)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+
+ cl_git_pass(git_filebuf_open(&file, TEST_CONFIG, 0, 0666));
+
+ git_filebuf_printf(&file, "[color]\n\tui = auto\n");
+ git_filebuf_printf(&file, "[core]\n\teditor = \n");
+
+ cl_git_pass(git_filebuf_commit(&file));
+}
+
+void test_config_stress__cleanup(void)
+{
+ p_unlink(TEST_CONFIG);
+}
+
+void test_config_stress__dont_break_on_invalid_input(void)
+{
+ const char *editor, *color;
+ git_config *config;
+
+ cl_assert(git_path_exists(TEST_CONFIG));
+ cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG));
+
+ cl_git_pass(git_config_get_string(&color, config, "color.ui"));
+ cl_git_pass(git_config_get_string(&editor, config, "core.editor"));
+
+ git_config_free(config);
+}
+
+void test_config_stress__comments(void)
+{
+ git_config *config;
+ const char *str;
+
+ cl_git_pass(git_config_open_ondisk(&config, cl_fixture("config/config12")));
+
+ cl_git_pass(git_config_get_string(&str, config, "some.section.other"));
+ cl_assert_equal_s("hello! \" ; ; ; ", str);
+
+ cl_git_pass(git_config_get_string(&str, config, "some.section.multi"));
+ cl_assert_equal_s("hi, this is a ; multiline comment # with ;\n special chars and other stuff !@#", str);
+
+ cl_git_pass(git_config_get_string(&str, config, "some.section.back"));
+ cl_assert_equal_s("this is \ba phrase", str);
+
+ git_config_free(config);
+}
+
+void test_config_stress__escape_subsection_names(void)
+{
+ git_config *config;
+ const char *str;
+
+ cl_assert(git_path_exists("git-test-config"));
+ cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG));
+
+ cl_git_pass(git_config_set_string(config, "some.sec\\tion.other", "foo"));
+ git_config_free(config);
+
+ cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG));
+
+ cl_git_pass(git_config_get_string(&str, config, "some.sec\\tion.other"));
+ cl_assert_equal_s("foo", str);
+ git_config_free(config);
+}
+
+void test_config_stress__trailing_backslash(void)
+{
+ git_config *config;
+ const char *str;
+ const char *path = "C:\\iam\\some\\windows\\path\\";
+
+ cl_assert(git_path_exists("git-test-config"));
+ cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG));
+ cl_git_pass(git_config_set_string(config, "windows.path", path));
+ git_config_free(config);
+
+ cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG));
+ cl_git_pass(git_config_get_string(&str, config, "windows.path"));
+ cl_assert_equal_s(path, str);
+ git_config_free(config);
+}
diff --git a/tests/config/validkeyname.c b/tests/config/validkeyname.c
new file mode 100644
index 000000000..0ef4a9ae3
--- /dev/null
+++ b/tests/config/validkeyname.c
@@ -0,0 +1,48 @@
+#include "clar_libgit2.h"
+
+#include "config.h"
+
+static git_config *cfg;
+static const char *value;
+
+void test_config_validkeyname__initialize(void)
+{
+ cl_fixture_sandbox("config/config10");
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config10"));
+}
+
+void test_config_validkeyname__cleanup(void)
+{
+ git_config_free(cfg);
+ cfg = NULL;
+
+ cl_fixture_cleanup("config10");
+}
+
+static void assert_invalid_config_key_name(const char *name)
+{
+ cl_git_fail_with(git_config_get_string(&value, cfg, name),
+ GIT_EINVALIDSPEC);
+ cl_git_fail_with(git_config_set_string(cfg, name, "42"),
+ GIT_EINVALIDSPEC);
+ cl_git_fail_with(git_config_delete_entry(cfg, name),
+ GIT_EINVALIDSPEC);
+ cl_git_fail_with(git_config_get_multivar_foreach(cfg, name, "*", NULL, NULL),
+ GIT_EINVALIDSPEC);
+ cl_git_fail_with(git_config_set_multivar(cfg, name, "*", "42"),
+ GIT_EINVALIDSPEC);
+}
+
+void test_config_validkeyname__accessing_requires_a_valid_name(void)
+{
+ assert_invalid_config_key_name("");
+ assert_invalid_config_key_name(".");
+ assert_invalid_config_key_name("..");
+ assert_invalid_config_key_name("core.");
+ assert_invalid_config_key_name("d#ff.dirstat.lines");
+ assert_invalid_config_key_name("diff.dirstat.lines#");
+ assert_invalid_config_key_name("dif\nf.dirstat.lines");
+ assert_invalid_config_key_name("dif.dir\nstat.lines");
+ assert_invalid_config_key_name("dif.dirstat.li\nes");
+}
diff --git a/tests/config/write.c b/tests/config/write.c
new file mode 100644
index 000000000..402be9317
--- /dev/null
+++ b/tests/config/write.c
@@ -0,0 +1,333 @@
+#include "clar_libgit2.h"
+
+void test_config_write__initialize(void)
+{
+ cl_fixture_sandbox("config/config9");
+ cl_fixture_sandbox("config/config15");
+ cl_fixture_sandbox("config/config17");
+}
+
+void test_config_write__cleanup(void)
+{
+ cl_fixture_cleanup("config9");
+ cl_fixture_cleanup("config15");
+ cl_fixture_cleanup("config17");
+}
+
+void test_config_write__replace_value(void)
+{
+ git_config *cfg;
+ int i;
+ int64_t l, expected = +9223372036854775803;
+
+ /* By freeing the config, we make sure we flush the values */
+ cl_git_pass(git_config_open_ondisk(&cfg, "config9"));
+ cl_git_pass(git_config_set_int32(cfg, "core.dummy", 5));
+ git_config_free(cfg);
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config9"));
+ cl_git_pass(git_config_get_int32(&i, cfg, "core.dummy"));
+ cl_assert(i == 5);
+ git_config_free(cfg);
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config9"));
+ cl_git_pass(git_config_set_int32(cfg, "core.dummy", 1));
+ git_config_free(cfg);
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config9"));
+ cl_git_pass(git_config_set_int64(cfg, "core.verylong", expected));
+ git_config_free(cfg);
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config9"));
+ cl_git_pass(git_config_get_int64(&l, cfg, "core.verylong"));
+ cl_assert(l == expected);
+ git_config_free(cfg);
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config9"));
+ cl_must_fail(git_config_get_int32(&i, cfg, "core.verylong"));
+ git_config_free(cfg);
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config9"));
+ cl_git_pass(git_config_set_int64(cfg, "core.verylong", 1));
+ git_config_free(cfg);
+}
+
+void test_config_write__delete_value(void)
+{
+ git_config *cfg;
+ int32_t i;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config9"));
+ cl_git_pass(git_config_set_int32(cfg, "core.dummy", 5));
+ git_config_free(cfg);
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config9"));
+ cl_git_pass(git_config_delete_entry(cfg, "core.dummy"));
+ git_config_free(cfg);
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config9"));
+ cl_assert(git_config_get_int32(&i, cfg, "core.dummy") == GIT_ENOTFOUND);
+ cl_git_pass(git_config_set_int32(cfg, "core.dummy", 1));
+ git_config_free(cfg);
+}
+
+/*
+ * At the beginning of the test:
+ * - config9 has: core.dummy2=42
+ * - config15 has: core.dummy2=7
+ */
+void test_config_write__delete_value_at_specific_level(void)
+{
+ git_config *cfg, *cfg_specific;
+ int32_t i;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config15"));
+ cl_git_pass(git_config_get_int32(&i, cfg, "core.dummy2"));
+ cl_assert(i == 7);
+ git_config_free(cfg);
+
+ cl_git_pass(git_config_new(&cfg));
+ cl_git_pass(git_config_add_file_ondisk(cfg, "config9",
+ GIT_CONFIG_LEVEL_LOCAL, 0));
+ cl_git_pass(git_config_add_file_ondisk(cfg, "config15",
+ GIT_CONFIG_LEVEL_GLOBAL, 0));
+
+ cl_git_pass(git_config_open_level(&cfg_specific, cfg, GIT_CONFIG_LEVEL_GLOBAL));
+
+ cl_git_pass(git_config_delete_entry(cfg_specific, "core.dummy2"));
+ git_config_free(cfg);
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config15"));
+ cl_assert(git_config_get_int32(&i, cfg, "core.dummy2") == GIT_ENOTFOUND);
+ cl_git_pass(git_config_set_int32(cfg, "core.dummy2", 7));
+
+ git_config_free(cfg_specific);
+ git_config_free(cfg);
+}
+
+void test_config_write__write_subsection(void)
+{
+ git_config *cfg;
+ const char *str;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config9"));
+ cl_git_pass(git_config_set_string(cfg, "my.own.var", "works"));
+ git_config_free(cfg);
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config9"));
+ cl_git_pass(git_config_get_string(&str, cfg, "my.own.var"));
+ cl_assert_equal_s("works", str);
+ git_config_free(cfg);
+}
+
+void test_config_write__delete_inexistent(void)
+{
+ git_config *cfg;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config9"));
+ cl_assert(git_config_delete_entry(cfg, "core.imaginary") == GIT_ENOTFOUND);
+ git_config_free(cfg);
+}
+
+void test_config_write__value_containing_quotes(void)
+{
+ git_config *cfg;
+ const char* str;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config9"));
+ cl_git_pass(git_config_set_string(cfg, "core.somevar", "this \"has\" quotes"));
+ cl_git_pass(git_config_get_string(&str, cfg, "core.somevar"));
+ cl_assert_equal_s(str, "this \"has\" quotes");
+ git_config_free(cfg);
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config9"));
+ cl_git_pass(git_config_get_string(&str, cfg, "core.somevar"));
+ cl_assert_equal_s(str, "this \"has\" quotes");
+ git_config_free(cfg);
+
+ /* The code path for values that already exist is different, check that one as well */
+ cl_git_pass(git_config_open_ondisk(&cfg, "config9"));
+ cl_git_pass(git_config_set_string(cfg, "core.somevar", "this also \"has\" quotes"));
+ cl_git_pass(git_config_get_string(&str, cfg, "core.somevar"));
+ cl_assert_equal_s(str, "this also \"has\" quotes");
+ git_config_free(cfg);
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config9"));
+ cl_git_pass(git_config_get_string(&str, cfg, "core.somevar"));
+ cl_assert_equal_s(str, "this also \"has\" quotes");
+ git_config_free(cfg);
+}
+
+void test_config_write__escape_value(void)
+{
+ git_config *cfg;
+ const char* str;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config9"));
+ cl_git_pass(git_config_set_string(cfg, "core.somevar", "this \"has\" quotes and \t"));
+ cl_git_pass(git_config_get_string(&str, cfg, "core.somevar"));
+ cl_assert_equal_s(str, "this \"has\" quotes and \t");
+ git_config_free(cfg);
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config9"));
+ cl_git_pass(git_config_get_string(&str, cfg, "core.somevar"));
+ cl_assert_equal_s(str, "this \"has\" quotes and \t");
+ git_config_free(cfg);
+}
+
+void test_config_write__add_value_at_specific_level(void)
+{
+ git_config *cfg, *cfg_specific;
+ int i;
+ int64_t l, expected = +9223372036854775803;
+ const char *s;
+
+ // open config15 as global level config file
+ cl_git_pass(git_config_new(&cfg));
+ cl_git_pass(git_config_add_file_ondisk(cfg, "config9",
+ GIT_CONFIG_LEVEL_LOCAL, 0));
+ cl_git_pass(git_config_add_file_ondisk(cfg, "config15",
+ GIT_CONFIG_LEVEL_GLOBAL, 0));
+
+ cl_git_pass(git_config_open_level(&cfg_specific, cfg, GIT_CONFIG_LEVEL_GLOBAL));
+
+ cl_git_pass(git_config_set_int32(cfg_specific, "core.int32global", 28));
+ cl_git_pass(git_config_set_int64(cfg_specific, "core.int64global", expected));
+ cl_git_pass(git_config_set_bool(cfg_specific, "core.boolglobal", true));
+ cl_git_pass(git_config_set_string(cfg_specific, "core.stringglobal", "I'm a global config value!"));
+ git_config_free(cfg_specific);
+ git_config_free(cfg);
+
+ // open config15 as local level config file
+ cl_git_pass(git_config_open_ondisk(&cfg, "config15"));
+
+ cl_git_pass(git_config_get_int32(&i, cfg, "core.int32global"));
+ cl_assert_equal_i(28, i);
+ cl_git_pass(git_config_get_int64(&l, cfg, "core.int64global"));
+ cl_assert(l == expected);
+ cl_git_pass(git_config_get_bool(&i, cfg, "core.boolglobal"));
+ cl_assert_equal_b(true, i);
+ cl_git_pass(git_config_get_string(&s, cfg, "core.stringglobal"));
+ cl_assert_equal_s("I'm a global config value!", s);
+
+ git_config_free(cfg);
+}
+
+void test_config_write__add_value_at_file_with_no_clrf_at_the_end(void)
+{
+ git_config *cfg;
+ int i;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config17"));
+ cl_git_pass(git_config_set_int32(cfg, "core.newline", 7));
+ git_config_free(cfg);
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config17"));
+ cl_git_pass(git_config_get_int32(&i, cfg, "core.newline"));
+ cl_assert_equal_i(7, i);
+
+ git_config_free(cfg);
+}
+
+void test_config_write__add_value_which_needs_quotes(void)
+{
+ git_config *cfg;
+ const char* str1;
+ const char* str2;
+ const char* str3;
+ const char* str4;
+ const char* str5;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config17"));
+ cl_git_pass(git_config_set_string(cfg, "core.startwithspace", " Something"));
+ cl_git_pass(git_config_set_string(cfg, "core.endwithspace", "Something "));
+ cl_git_pass(git_config_set_string(cfg, "core.containscommentchar1", "some#thing"));
+ cl_git_pass(git_config_set_string(cfg, "core.containscommentchar2", "some;thing"));
+ cl_git_pass(git_config_set_string(cfg, "core.startwhithsapceandcontainsdoublequote", " some\"thing"));
+ git_config_free(cfg);
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config17"));
+ cl_git_pass(git_config_get_string(&str1, cfg, "core.startwithspace"));
+ cl_assert_equal_s(" Something", str1);
+ cl_git_pass(git_config_get_string(&str2, cfg, "core.endwithspace"));
+ cl_assert_equal_s("Something ", str2);
+ cl_git_pass(git_config_get_string(&str3, cfg, "core.containscommentchar1"));
+ cl_assert_equal_s("some#thing", str3);
+ cl_git_pass(git_config_get_string(&str4, cfg, "core.containscommentchar2"));
+ cl_assert_equal_s("some;thing", str4);
+ cl_git_pass(git_config_get_string(&str5, cfg, "core.startwhithsapceandcontainsdoublequote"));
+ cl_assert_equal_s(" some\"thing", str5);
+ git_config_free(cfg);
+}
+
+void test_config_write__can_set_a_value_to_NULL(void)
+{
+ git_repository *repository;
+ git_config *config;
+
+ repository = cl_git_sandbox_init("testrepo.git");
+
+ cl_git_pass(git_repository_config(&config, repository));
+ cl_git_fail(git_config_set_string(config, "a.b.c", NULL));
+ git_config_free(config);
+
+ cl_git_sandbox_cleanup();
+}
+
+void test_config_write__can_set_an_empty_value(void)
+{
+ git_repository *repository;
+ git_config *config;
+ const char * str;
+
+ repository = cl_git_sandbox_init("testrepo.git");
+ cl_git_pass(git_repository_config(&config, repository));
+
+ cl_git_pass(git_config_set_string(config, "core.somevar", ""));
+ cl_git_pass(git_config_get_string(&str, config, "core.somevar"));
+ cl_assert_equal_s(str, "");
+
+ git_config_free(config);
+ cl_git_sandbox_cleanup();
+}
+
+void test_config_write__updating_a_locked_config_file_returns_ELOCKED(void)
+{
+ git_config *cfg;
+
+ cl_git_pass(git_config_open_ondisk(&cfg, "config9"));
+
+ cl_git_mkfile("config9.lock", "[core]\n");
+
+ cl_git_fail_with(git_config_set_string(cfg, "core.dump", "boom"), GIT_ELOCKED);
+
+ git_config_free(cfg);
+}
+
+void test_config_write__outside_change(void)
+{
+ int32_t tmp;
+ git_config *cfg;
+ const char *filename = "config-ext-change";
+
+ cl_git_mkfile(filename, "[old]\nvalue = 5\n");
+
+ cl_git_pass(git_config_open_ondisk(&cfg, filename));
+
+ cl_git_pass(git_config_get_int32(&tmp, cfg, "old.value"));
+
+ /* Change the value on the file itself (simulate external process) */
+ cl_git_mkfile(filename, "[old]\nvalue = 6\n");
+
+ cl_git_pass(git_config_set_int32(cfg, "new.value", 7));
+
+ cl_git_pass(git_config_get_int32(&tmp, cfg, "old.value"));
+ cl_assert_equal_i(6, tmp);
+
+ cl_git_pass(git_config_refresh(cfg));
+
+ cl_git_pass(git_config_get_int32(&tmp, cfg, "old.value"));
+ cl_assert_equal_i(6, tmp);
+
+ git_config_free(cfg);
+}
diff --git a/tests/core/bitvec.c b/tests/core/bitvec.c
new file mode 100644
index 000000000..48d7b99f0
--- /dev/null
+++ b/tests/core/bitvec.c
@@ -0,0 +1,64 @@
+#include "clar_libgit2.h"
+#include "bitvec.h"
+
+#if 0
+static void print_bitvec(git_bitvec *bv)
+{
+ int b;
+
+ if (!bv->length) {
+ for (b = 63; b >= 0; --b)
+ fprintf(stderr, "%d", (bv->u.bits & (1ul << b)) ? 1 : 0);
+ } else {
+ for (b = bv->length * 8; b >= 0; --b)
+ fprintf(stderr, "%d", (bv->u.ptr[b >> 3] & (b & 0x0ff)) ? 1 : 0);
+ }
+ fprintf(stderr, "\n");
+}
+#endif
+
+static void set_some_bits(git_bitvec *bv, size_t length)
+{
+ size_t i;
+
+ for (i = 0; i < length; ++i) {
+ if (i % 3 == 0 || i % 7 == 0)
+ git_bitvec_set(bv, i, true);
+ }
+}
+
+static void check_some_bits(git_bitvec *bv, size_t length)
+{
+ size_t i;
+
+ for (i = 0; i < length; ++i)
+ cl_assert_equal_b(i % 3 == 0 || i % 7 == 0, git_bitvec_get(bv, i));
+}
+
+void test_core_bitvec__0(void)
+{
+ git_bitvec bv;
+
+ cl_git_pass(git_bitvec_init(&bv, 32));
+ set_some_bits(&bv, 16);
+ check_some_bits(&bv, 16);
+ git_bitvec_clear(&bv);
+ set_some_bits(&bv, 32);
+ check_some_bits(&bv, 32);
+ git_bitvec_clear(&bv);
+ set_some_bits(&bv, 64);
+ check_some_bits(&bv, 64);
+ git_bitvec_free(&bv);
+
+ cl_git_pass(git_bitvec_init(&bv, 128));
+ set_some_bits(&bv, 32);
+ check_some_bits(&bv, 32);
+ set_some_bits(&bv, 128);
+ check_some_bits(&bv, 128);
+ git_bitvec_free(&bv);
+
+ cl_git_pass(git_bitvec_init(&bv, 4000));
+ set_some_bits(&bv, 4000);
+ check_some_bits(&bv, 4000);
+ git_bitvec_free(&bv);
+}
diff --git a/tests/core/buffer.c b/tests/core/buffer.c
new file mode 100644
index 000000000..da5ec605c
--- /dev/null
+++ b/tests/core/buffer.c
@@ -0,0 +1,1122 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "buf_text.h"
+#include "hashsig.h"
+#include "fileops.h"
+
+#define TESTSTR "Have you seen that? Have you seeeen that??"
+const char *test_string = TESTSTR;
+const char *test_string_x2 = TESTSTR TESTSTR;
+
+#define TESTSTR_4096 REP1024("1234")
+#define TESTSTR_8192 REP1024("12341234")
+const char *test_4096 = TESTSTR_4096;
+const char *test_8192 = TESTSTR_8192;
+
+/* test basic data concatenation */
+void test_core_buffer__0(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ cl_assert(buf.size == 0);
+
+ git_buf_puts(&buf, test_string);
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s(test_string, git_buf_cstr(&buf));
+
+ git_buf_puts(&buf, test_string);
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s(test_string_x2, git_buf_cstr(&buf));
+
+ git_buf_free(&buf);
+}
+
+/* test git_buf_printf */
+void test_core_buffer__1(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ git_buf_printf(&buf, "%s %s %d ", "shoop", "da", 23);
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s("shoop da 23 ", git_buf_cstr(&buf));
+
+ git_buf_printf(&buf, "%s %d", "woop", 42);
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s("shoop da 23 woop 42", git_buf_cstr(&buf));
+
+ git_buf_free(&buf);
+}
+
+/* more thorough test of concatenation options */
+void test_core_buffer__2(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+ int i;
+ char data[128];
+
+ cl_assert(buf.size == 0);
+
+ /* this must be safe to do */
+ git_buf_free(&buf);
+ cl_assert(buf.size == 0);
+ cl_assert(buf.asize == 0);
+
+ /* empty buffer should be empty string */
+ cl_assert_equal_s("", git_buf_cstr(&buf));
+ cl_assert(buf.size == 0);
+ /* cl_assert(buf.asize == 0); -- should not assume what git_buf does */
+
+ /* free should set us back to the beginning */
+ git_buf_free(&buf);
+ cl_assert(buf.size == 0);
+ cl_assert(buf.asize == 0);
+
+ /* add letter */
+ git_buf_putc(&buf, '+');
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s("+", git_buf_cstr(&buf));
+
+ /* add letter again */
+ git_buf_putc(&buf, '+');
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s("++", git_buf_cstr(&buf));
+
+ /* let's try that a few times */
+ for (i = 0; i < 16; ++i) {
+ git_buf_putc(&buf, '+');
+ cl_assert(git_buf_oom(&buf) == 0);
+ }
+ cl_assert_equal_s("++++++++++++++++++", git_buf_cstr(&buf));
+
+ git_buf_free(&buf);
+
+ /* add data */
+ git_buf_put(&buf, "xo", 2);
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s("xo", git_buf_cstr(&buf));
+
+ /* add letter again */
+ git_buf_put(&buf, "xo", 2);
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s("xoxo", git_buf_cstr(&buf));
+
+ /* let's try that a few times */
+ for (i = 0; i < 16; ++i) {
+ git_buf_put(&buf, "xo", 2);
+ cl_assert(git_buf_oom(&buf) == 0);
+ }
+ cl_assert_equal_s("xoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxoxo",
+ git_buf_cstr(&buf));
+
+ git_buf_free(&buf);
+
+ /* set to string */
+ git_buf_sets(&buf, test_string);
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s(test_string, git_buf_cstr(&buf));
+
+ /* append string */
+ git_buf_puts(&buf, test_string);
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s(test_string_x2, git_buf_cstr(&buf));
+
+ /* set to string again (should overwrite - not append) */
+ git_buf_sets(&buf, test_string);
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s(test_string, git_buf_cstr(&buf));
+
+ /* test clear */
+ git_buf_clear(&buf);
+ cl_assert_equal_s("", git_buf_cstr(&buf));
+
+ git_buf_free(&buf);
+
+ /* test extracting data into buffer */
+ git_buf_puts(&buf, REP4("0123456789"));
+ cl_assert(git_buf_oom(&buf) == 0);
+
+ git_buf_copy_cstr(data, sizeof(data), &buf);
+ cl_assert_equal_s(REP4("0123456789"), data);
+ git_buf_copy_cstr(data, 11, &buf);
+ cl_assert_equal_s("0123456789", data);
+ git_buf_copy_cstr(data, 3, &buf);
+ cl_assert_equal_s("01", data);
+ git_buf_copy_cstr(data, 1, &buf);
+ cl_assert_equal_s("", data);
+
+ git_buf_copy_cstr(data, sizeof(data), &buf);
+ cl_assert_equal_s(REP4("0123456789"), data);
+
+ git_buf_sets(&buf, REP256("x"));
+ git_buf_copy_cstr(data, sizeof(data), &buf);
+ /* since sizeof(data) == 128, only 127 bytes should be copied */
+ cl_assert_equal_s(REP4(REP16("x")) REP16("x") REP16("x")
+ REP16("x") "xxxxxxxxxxxxxxx", data);
+
+ git_buf_free(&buf);
+
+ git_buf_copy_cstr(data, sizeof(data), &buf);
+ cl_assert_equal_s("", data);
+}
+
+/* let's do some tests with larger buffers to push our limits */
+void test_core_buffer__3(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ /* set to string */
+ git_buf_set(&buf, test_4096, 4096);
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s(test_4096, git_buf_cstr(&buf));
+
+ /* append string */
+ git_buf_puts(&buf, test_4096);
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s(test_8192, git_buf_cstr(&buf));
+
+ /* set to string again (should overwrite - not append) */
+ git_buf_set(&buf, test_4096, 4096);
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s(test_4096, git_buf_cstr(&buf));
+
+ git_buf_free(&buf);
+}
+
+/* let's try some producer/consumer tests */
+void test_core_buffer__4(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+ int i;
+
+ for (i = 0; i < 10; ++i) {
+ git_buf_puts(&buf, "1234"); /* add 4 */
+ cl_assert(git_buf_oom(&buf) == 0);
+ git_buf_consume(&buf, buf.ptr + 2); /* eat the first two */
+ cl_assert(strlen(git_buf_cstr(&buf)) == (size_t)((i + 1) * 2));
+ }
+ /* we have appended 1234 10x and removed the first 20 letters */
+ cl_assert_equal_s("12341234123412341234", git_buf_cstr(&buf));
+
+ git_buf_consume(&buf, NULL);
+ cl_assert_equal_s("12341234123412341234", git_buf_cstr(&buf));
+
+ git_buf_consume(&buf, "invalid pointer");
+ cl_assert_equal_s("12341234123412341234", git_buf_cstr(&buf));
+
+ git_buf_consume(&buf, buf.ptr);
+ cl_assert_equal_s("12341234123412341234", git_buf_cstr(&buf));
+
+ git_buf_consume(&buf, buf.ptr + 1);
+ cl_assert_equal_s("2341234123412341234", git_buf_cstr(&buf));
+
+ git_buf_consume(&buf, buf.ptr + buf.size);
+ cl_assert_equal_s("", git_buf_cstr(&buf));
+
+ git_buf_free(&buf);
+}
+
+
+static void
+check_buf_append(
+ const char* data_a,
+ const char* data_b,
+ const char* expected_data,
+ size_t expected_size,
+ size_t expected_asize)
+{
+ git_buf tgt = GIT_BUF_INIT;
+
+ git_buf_sets(&tgt, data_a);
+ cl_assert(git_buf_oom(&tgt) == 0);
+ git_buf_puts(&tgt, data_b);
+ cl_assert(git_buf_oom(&tgt) == 0);
+ cl_assert_equal_s(expected_data, git_buf_cstr(&tgt));
+ cl_assert(tgt.size == expected_size);
+ if (expected_asize > 0)
+ cl_assert(tgt.asize == expected_asize);
+
+ git_buf_free(&tgt);
+}
+
+static void
+check_buf_append_abc(
+ const char* buf_a,
+ const char* buf_b,
+ const char* buf_c,
+ const char* expected_ab,
+ const char* expected_abc,
+ const char* expected_abca,
+ const char* expected_abcab,
+ const char* expected_abcabc)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ git_buf_sets(&buf, buf_a);
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s(buf_a, git_buf_cstr(&buf));
+
+ git_buf_puts(&buf, buf_b);
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s(expected_ab, git_buf_cstr(&buf));
+
+ git_buf_puts(&buf, buf_c);
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s(expected_abc, git_buf_cstr(&buf));
+
+ git_buf_puts(&buf, buf_a);
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s(expected_abca, git_buf_cstr(&buf));
+
+ git_buf_puts(&buf, buf_b);
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s(expected_abcab, git_buf_cstr(&buf));
+
+ git_buf_puts(&buf, buf_c);
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s(expected_abcabc, git_buf_cstr(&buf));
+
+ git_buf_free(&buf);
+}
+
+/* more variations on append tests */
+void test_core_buffer__5(void)
+{
+ check_buf_append("", "", "", 0, 8);
+ check_buf_append("a", "", "a", 1, 8);
+ check_buf_append("", "a", "a", 1, 8);
+ check_buf_append("", "a", "a", 1, 8);
+ check_buf_append("a", "", "a", 1, 8);
+ check_buf_append("a", "b", "ab", 2, 8);
+ check_buf_append("", "abcdefgh", "abcdefgh", 8, 16);
+ check_buf_append("abcdefgh", "", "abcdefgh", 8, 16);
+
+ /* buffer with starting asize will grow to:
+ * 1 -> 2, 2 -> 3, 3 -> 5, 4 -> 6, 5 -> 8, 6 -> 9,
+ * 7 -> 11, 8 -> 12, 9 -> 14, 10 -> 15, 11 -> 17, 12 -> 18,
+ * 13 -> 20, 14 -> 21, 15 -> 23, 16 -> 24, 17 -> 26, 18 -> 27,
+ * 19 -> 29, 20 -> 30, 21 -> 32, 22 -> 33, 23 -> 35, 24 -> 36,
+ * ...
+ * follow sequence until value > target size,
+ * then round up to nearest multiple of 8.
+ */
+
+ check_buf_append("abcdefgh", "/", "abcdefgh/", 9, 16);
+ check_buf_append("abcdefgh", "ijklmno", "abcdefghijklmno", 15, 16);
+ check_buf_append("abcdefgh", "ijklmnop", "abcdefghijklmnop", 16, 24);
+ check_buf_append("0123456789", "0123456789",
+ "01234567890123456789", 20, 24);
+ check_buf_append(REP16("x"), REP16("o"),
+ REP16("x") REP16("o"), 32, 40);
+
+ check_buf_append(test_4096, "", test_4096, 4096, 4104);
+ check_buf_append(test_4096, test_4096, test_8192, 8192, 9240);
+
+ /* check sequences of appends */
+ check_buf_append_abc("a", "b", "c",
+ "ab", "abc", "abca", "abcab", "abcabc");
+ check_buf_append_abc("a1", "b2", "c3",
+ "a1b2", "a1b2c3", "a1b2c3a1",
+ "a1b2c3a1b2", "a1b2c3a1b2c3");
+ check_buf_append_abc("a1/", "b2/", "c3/",
+ "a1/b2/", "a1/b2/c3/", "a1/b2/c3/a1/",
+ "a1/b2/c3/a1/b2/", "a1/b2/c3/a1/b2/c3/");
+}
+
+/* test swap */
+void test_core_buffer__6(void)
+{
+ git_buf a = GIT_BUF_INIT;
+ git_buf b = GIT_BUF_INIT;
+
+ git_buf_sets(&a, "foo");
+ cl_assert(git_buf_oom(&a) == 0);
+ git_buf_sets(&b, "bar");
+ cl_assert(git_buf_oom(&b) == 0);
+
+ cl_assert_equal_s("foo", git_buf_cstr(&a));
+ cl_assert_equal_s("bar", git_buf_cstr(&b));
+
+ git_buf_swap(&a, &b);
+
+ cl_assert_equal_s("bar", git_buf_cstr(&a));
+ cl_assert_equal_s("foo", git_buf_cstr(&b));
+
+ git_buf_free(&a);
+ git_buf_free(&b);
+}
+
+
+/* test detach/attach data */
+void test_core_buffer__7(void)
+{
+ const char *fun = "This is fun";
+ git_buf a = GIT_BUF_INIT;
+ char *b = NULL;
+
+ git_buf_sets(&a, "foo");
+ cl_assert(git_buf_oom(&a) == 0);
+ cl_assert_equal_s("foo", git_buf_cstr(&a));
+
+ b = git_buf_detach(&a);
+
+ cl_assert_equal_s("foo", b);
+ cl_assert_equal_s("", a.ptr);
+ git__free(b);
+
+ b = git_buf_detach(&a);
+
+ cl_assert_equal_s(NULL, b);
+ cl_assert_equal_s("", a.ptr);
+
+ git_buf_free(&a);
+
+ b = git__strdup(fun);
+ git_buf_attach(&a, b, 0);
+
+ cl_assert_equal_s(fun, a.ptr);
+ cl_assert(a.size == strlen(fun));
+ cl_assert(a.asize == strlen(fun) + 1);
+
+ git_buf_free(&a);
+
+ b = git__strdup(fun);
+ git_buf_attach(&a, b, strlen(fun) + 1);
+
+ cl_assert_equal_s(fun, a.ptr);
+ cl_assert(a.size == strlen(fun));
+ cl_assert(a.asize == strlen(fun) + 1);
+
+ git_buf_free(&a);
+}
+
+
+static void
+check_joinbuf_2(
+ const char *a,
+ const char *b,
+ const char *expected)
+{
+ char sep = '/';
+ git_buf buf = GIT_BUF_INIT;
+
+ git_buf_join(&buf, sep, a, b);
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s(expected, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+}
+
+static void
+check_joinbuf_overlapped(
+ const char *oldval,
+ int ofs_a,
+ const char *b,
+ const char *expected)
+{
+ char sep = '/';
+ git_buf buf = GIT_BUF_INIT;
+
+ git_buf_sets(&buf, oldval);
+ git_buf_join(&buf, sep, buf.ptr + ofs_a, b);
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s(expected, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+}
+
+static void
+check_joinbuf_n_2(
+ const char *a,
+ const char *b,
+ const char *expected)
+{
+ char sep = '/';
+ git_buf buf = GIT_BUF_INIT;
+
+ git_buf_sets(&buf, a);
+ cl_assert(git_buf_oom(&buf) == 0);
+
+ git_buf_join_n(&buf, sep, 1, b);
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s(expected, git_buf_cstr(&buf));
+
+ git_buf_free(&buf);
+}
+
+static void
+check_joinbuf_n_4(
+ const char *a,
+ const char *b,
+ const char *c,
+ const char *d,
+ const char *expected)
+{
+ char sep = ';';
+ git_buf buf = GIT_BUF_INIT;
+ git_buf_join_n(&buf, sep, 4, a, b, c, d);
+ cl_assert(git_buf_oom(&buf) == 0);
+ cl_assert_equal_s(expected, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+}
+
+/* test join */
+void test_core_buffer__8(void)
+{
+ git_buf a = GIT_BUF_INIT;
+
+ git_buf_join_n(&a, '/', 1, "foo");
+ cl_assert(git_buf_oom(&a) == 0);
+ cl_assert_equal_s("foo", git_buf_cstr(&a));
+
+ git_buf_join_n(&a, '/', 1, "bar");
+ cl_assert(git_buf_oom(&a) == 0);
+ cl_assert_equal_s("foo/bar", git_buf_cstr(&a));
+
+ git_buf_join_n(&a, '/', 1, "baz");
+ cl_assert(git_buf_oom(&a) == 0);
+ cl_assert_equal_s("foo/bar/baz", git_buf_cstr(&a));
+
+ git_buf_free(&a);
+
+ check_joinbuf_2(NULL, "", "");
+ check_joinbuf_2(NULL, "a", "a");
+ check_joinbuf_2(NULL, "/a", "/a");
+ check_joinbuf_2("", "", "");
+ check_joinbuf_2("", "a", "a");
+ check_joinbuf_2("", "/a", "/a");
+ check_joinbuf_2("a", "", "a/");
+ check_joinbuf_2("a", "/", "a/");
+ check_joinbuf_2("a", "b", "a/b");
+ check_joinbuf_2("/", "a", "/a");
+ check_joinbuf_2("/", "", "/");
+ check_joinbuf_2("/a", "/b", "/a/b");
+ check_joinbuf_2("/a", "/b/", "/a/b/");
+ check_joinbuf_2("/a/", "b/", "/a/b/");
+ check_joinbuf_2("/a/", "/b/", "/a/b/");
+ check_joinbuf_2("/a/", "//b/", "/a/b/");
+ check_joinbuf_2("/abcd", "/defg", "/abcd/defg");
+ check_joinbuf_2("/abcd", "/defg/", "/abcd/defg/");
+ check_joinbuf_2("/abcd/", "defg/", "/abcd/defg/");
+ check_joinbuf_2("/abcd/", "/defg/", "/abcd/defg/");
+
+ check_joinbuf_overlapped("abcd", 0, "efg", "abcd/efg");
+ check_joinbuf_overlapped("abcd", 1, "efg", "bcd/efg");
+ check_joinbuf_overlapped("abcd", 2, "efg", "cd/efg");
+ check_joinbuf_overlapped("abcd", 3, "efg", "d/efg");
+ check_joinbuf_overlapped("abcd", 4, "efg", "efg");
+ check_joinbuf_overlapped("abc/", 2, "efg", "c/efg");
+ check_joinbuf_overlapped("abc/", 3, "efg", "/efg");
+ check_joinbuf_overlapped("abc/", 4, "efg", "efg");
+ check_joinbuf_overlapped("abcd", 3, "", "d/");
+ check_joinbuf_overlapped("abcd", 4, "", "");
+ check_joinbuf_overlapped("abc/", 2, "", "c/");
+ check_joinbuf_overlapped("abc/", 3, "", "/");
+ check_joinbuf_overlapped("abc/", 4, "", "");
+
+ check_joinbuf_n_2("", "", "");
+ check_joinbuf_n_2("", "a", "a");
+ check_joinbuf_n_2("", "/a", "/a");
+ check_joinbuf_n_2("a", "", "a/");
+ check_joinbuf_n_2("a", "/", "a/");
+ check_joinbuf_n_2("a", "b", "a/b");
+ check_joinbuf_n_2("/", "a", "/a");
+ check_joinbuf_n_2("/", "", "/");
+ check_joinbuf_n_2("/a", "/b", "/a/b");
+ check_joinbuf_n_2("/a", "/b/", "/a/b/");
+ check_joinbuf_n_2("/a/", "b/", "/a/b/");
+ check_joinbuf_n_2("/a/", "/b/", "/a/b/");
+ check_joinbuf_n_2("/abcd", "/defg", "/abcd/defg");
+ check_joinbuf_n_2("/abcd", "/defg/", "/abcd/defg/");
+ check_joinbuf_n_2("/abcd/", "defg/", "/abcd/defg/");
+ check_joinbuf_n_2("/abcd/", "/defg/", "/abcd/defg/");
+
+ check_joinbuf_n_4("", "", "", "", "");
+ check_joinbuf_n_4("", "a", "", "", "a;");
+ check_joinbuf_n_4("a", "", "", "", "a;");
+ check_joinbuf_n_4("", "", "", "a", "a");
+ check_joinbuf_n_4("a", "b", "", ";c;d;", "a;b;c;d;");
+ check_joinbuf_n_4("a", "b", "", ";c;d", "a;b;c;d");
+ check_joinbuf_n_4("abcd", "efgh", "ijkl", "mnop", "abcd;efgh;ijkl;mnop");
+ check_joinbuf_n_4("abcd;", "efgh;", "ijkl;", "mnop;", "abcd;efgh;ijkl;mnop;");
+ check_joinbuf_n_4(";abcd;", ";efgh;", ";ijkl;", ";mnop;", ";abcd;efgh;ijkl;mnop;");
+}
+
+void test_core_buffer__9(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ /* just some exhaustive tests of various separator placement */
+ char *a[] = { "", "-", "a-", "-a", "-a-" };
+ char *b[] = { "", "-", "b-", "-b", "-b-" };
+ char sep[] = { 0, '-', '/' };
+ char *expect_null[] = { "", "-", "a-", "-a", "-a-",
+ "-", "--", "a--", "-a-", "-a--",
+ "b-", "-b-", "a-b-", "-ab-", "-a-b-",
+ "-b", "--b", "a--b", "-a-b", "-a--b",
+ "-b-", "--b-", "a--b-", "-a-b-", "-a--b-" };
+ char *expect_dash[] = { "", "-", "a-", "-a-", "-a-",
+ "-", "-", "a-", "-a-", "-a-",
+ "b-", "-b-", "a-b-", "-a-b-", "-a-b-",
+ "-b", "-b", "a-b", "-a-b", "-a-b",
+ "-b-", "-b-", "a-b-", "-a-b-", "-a-b-" };
+ char *expect_slas[] = { "", "-/", "a-/", "-a/", "-a-/",
+ "-", "-/-", "a-/-", "-a/-", "-a-/-",
+ "b-", "-/b-", "a-/b-", "-a/b-", "-a-/b-",
+ "-b", "-/-b", "a-/-b", "-a/-b", "-a-/-b",
+ "-b-", "-/-b-", "a-/-b-", "-a/-b-", "-a-/-b-" };
+ char **expect_values[] = { expect_null, expect_dash, expect_slas };
+ char separator, **expect;
+ unsigned int s, i, j;
+
+ for (s = 0; s < sizeof(sep) / sizeof(char); ++s) {
+ separator = sep[s];
+ expect = expect_values[s];
+
+ for (j = 0; j < sizeof(b) / sizeof(char*); ++j) {
+ for (i = 0; i < sizeof(a) / sizeof(char*); ++i) {
+ git_buf_join(&buf, separator, a[i], b[j]);
+ cl_assert_equal_s(*expect, buf.ptr);
+ expect++;
+ }
+ }
+ }
+
+ git_buf_free(&buf);
+}
+
+void test_core_buffer__10(void)
+{
+ git_buf a = GIT_BUF_INIT;
+
+ cl_git_pass(git_buf_join_n(&a, '/', 1, "test"));
+ cl_assert_equal_s(a.ptr, "test");
+ cl_git_pass(git_buf_join_n(&a, '/', 1, "string"));
+ cl_assert_equal_s(a.ptr, "test/string");
+ git_buf_clear(&a);
+ cl_git_pass(git_buf_join_n(&a, '/', 3, "test", "string", "join"));
+ cl_assert_equal_s(a.ptr, "test/string/join");
+ cl_git_pass(git_buf_join_n(&a, '/', 2, a.ptr, "more"));
+ cl_assert_equal_s(a.ptr, "test/string/join/test/string/join/more");
+
+ git_buf_free(&a);
+}
+
+void test_core_buffer__join3(void)
+{
+ git_buf a = GIT_BUF_INIT;
+
+ cl_git_pass(git_buf_join3(&a, '/', "test", "string", "join"));
+ cl_assert_equal_s("test/string/join", a.ptr);
+ cl_git_pass(git_buf_join3(&a, '/', "test/", "string", "join"));
+ cl_assert_equal_s("test/string/join", a.ptr);
+ cl_git_pass(git_buf_join3(&a, '/', "test/", "/string", "join"));
+ cl_assert_equal_s("test/string/join", a.ptr);
+ cl_git_pass(git_buf_join3(&a, '/', "test/", "/string/", "join"));
+ cl_assert_equal_s("test/string/join", a.ptr);
+ cl_git_pass(git_buf_join3(&a, '/', "test/", "/string/", "/join"));
+ cl_assert_equal_s("test/string/join", a.ptr);
+
+ cl_git_pass(git_buf_join3(&a, '/', "", "string", "join"));
+ cl_assert_equal_s("string/join", a.ptr);
+ cl_git_pass(git_buf_join3(&a, '/', "", "string/", "join"));
+ cl_assert_equal_s("string/join", a.ptr);
+ cl_git_pass(git_buf_join3(&a, '/', "", "string/", "/join"));
+ cl_assert_equal_s("string/join", a.ptr);
+
+ cl_git_pass(git_buf_join3(&a, '/', "string", "", "join"));
+ cl_assert_equal_s("string/join", a.ptr);
+ cl_git_pass(git_buf_join3(&a, '/', "string/", "", "join"));
+ cl_assert_equal_s("string/join", a.ptr);
+ cl_git_pass(git_buf_join3(&a, '/', "string/", "", "/join"));
+ cl_assert_equal_s("string/join", a.ptr);
+
+ git_buf_free(&a);
+}
+
+void test_core_buffer__11(void)
+{
+ git_buf a = GIT_BUF_INIT;
+ git_strarray t;
+ char *t1[] = { "nothing", "in", "common" };
+ char *t2[] = { "something", "something else", "some other" };
+ char *t3[] = { "something", "some fun", "no fun" };
+ char *t4[] = { "happy", "happier", "happiest" };
+ char *t5[] = { "happiest", "happier", "happy" };
+ char *t6[] = { "no", "nope", "" };
+ char *t7[] = { "", "doesn't matter" };
+
+ t.strings = t1;
+ t.count = 3;
+ cl_git_pass(git_buf_text_common_prefix(&a, &t));
+ cl_assert_equal_s(a.ptr, "");
+
+ t.strings = t2;
+ t.count = 3;
+ cl_git_pass(git_buf_text_common_prefix(&a, &t));
+ cl_assert_equal_s(a.ptr, "some");
+
+ t.strings = t3;
+ t.count = 3;
+ cl_git_pass(git_buf_text_common_prefix(&a, &t));
+ cl_assert_equal_s(a.ptr, "");
+
+ t.strings = t4;
+ t.count = 3;
+ cl_git_pass(git_buf_text_common_prefix(&a, &t));
+ cl_assert_equal_s(a.ptr, "happ");
+
+ t.strings = t5;
+ t.count = 3;
+ cl_git_pass(git_buf_text_common_prefix(&a, &t));
+ cl_assert_equal_s(a.ptr, "happ");
+
+ t.strings = t6;
+ t.count = 3;
+ cl_git_pass(git_buf_text_common_prefix(&a, &t));
+ cl_assert_equal_s(a.ptr, "");
+
+ t.strings = t7;
+ t.count = 3;
+ cl_git_pass(git_buf_text_common_prefix(&a, &t));
+ cl_assert_equal_s(a.ptr, "");
+
+ git_buf_free(&a);
+}
+
+void test_core_buffer__rfind_variants(void)
+{
+ git_buf a = GIT_BUF_INIT;
+ ssize_t len;
+
+ cl_git_pass(git_buf_sets(&a, "/this/is/it/"));
+
+ len = (ssize_t)git_buf_len(&a);
+
+ cl_assert(git_buf_rfind(&a, '/') == len - 1);
+ cl_assert(git_buf_rfind_next(&a, '/') == len - 4);
+
+ cl_assert(git_buf_rfind(&a, 'i') == len - 3);
+ cl_assert(git_buf_rfind_next(&a, 'i') == len - 3);
+
+ cl_assert(git_buf_rfind(&a, 'h') == 2);
+ cl_assert(git_buf_rfind_next(&a, 'h') == 2);
+
+ cl_assert(git_buf_rfind(&a, 'q') == -1);
+ cl_assert(git_buf_rfind_next(&a, 'q') == -1);
+
+ git_buf_free(&a);
+}
+
+void test_core_buffer__puts_escaped(void)
+{
+ git_buf a = GIT_BUF_INIT;
+
+ git_buf_clear(&a);
+ cl_git_pass(git_buf_text_puts_escaped(&a, "this is a test", "", ""));
+ cl_assert_equal_s("this is a test", a.ptr);
+
+ git_buf_clear(&a);
+ cl_git_pass(git_buf_text_puts_escaped(&a, "this is a test", "t", "\\"));
+ cl_assert_equal_s("\\this is a \\tes\\t", a.ptr);
+
+ git_buf_clear(&a);
+ cl_git_pass(git_buf_text_puts_escaped(&a, "this is a test", "i ", "__"));
+ cl_assert_equal_s("th__is__ __is__ a__ test", a.ptr);
+
+ git_buf_clear(&a);
+ cl_git_pass(git_buf_text_puts_escape_regex(&a, "^match\\s*[A-Z]+.*"));
+ cl_assert_equal_s("\\^match\\\\s\\*\\[A-Z\\]\\+\\.\\*", a.ptr);
+
+ git_buf_free(&a);
+}
+
+static void assert_unescape(char *expected, char *to_unescape) {
+ git_buf buf = GIT_BUF_INIT;
+
+ cl_git_pass(git_buf_sets(&buf, to_unescape));
+ git_buf_text_unescape(&buf);
+ cl_assert_equal_s(expected, buf.ptr);
+ cl_assert_equal_sz(strlen(expected), buf.size);
+
+ git_buf_free(&buf);
+}
+
+void test_core_buffer__unescape(void)
+{
+ assert_unescape("Escaped\\", "Es\\ca\\ped\\");
+ assert_unescape("Es\\caped\\", "Es\\\\ca\\ped\\\\");
+ assert_unescape("\\", "\\");
+ assert_unescape("\\", "\\\\");
+ assert_unescape("", "");
+}
+
+void test_core_buffer__base64(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ /* t h i s
+ * 0x 74 68 69 73
+ * 0b 01110100 01101000 01101001 01110011
+ * 0b 011101 000110 100001 101001 011100 110000
+ * 0x 1d 06 21 29 1c 30
+ * d G h p c w
+ */
+ cl_git_pass(git_buf_put_base64(&buf, "this", 4));
+ cl_assert_equal_s("dGhpcw==", buf.ptr);
+
+ git_buf_clear(&buf);
+ cl_git_pass(git_buf_put_base64(&buf, "this!", 5));
+ cl_assert_equal_s("dGhpcyE=", buf.ptr);
+
+ git_buf_clear(&buf);
+ cl_git_pass(git_buf_put_base64(&buf, "this!\n", 6));
+ cl_assert_equal_s("dGhpcyEK", buf.ptr);
+
+ git_buf_free(&buf);
+}
+
+void test_core_buffer__base85(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ cl_git_pass(git_buf_put_base85(&buf, "this", 4));
+ cl_assert_equal_s("bZBXF", buf.ptr);
+ git_buf_clear(&buf);
+
+ cl_git_pass(git_buf_put_base85(&buf, "two rnds", 8));
+ cl_assert_equal_s("ba!tca&BaE", buf.ptr);
+ git_buf_clear(&buf);
+
+ cl_git_pass(git_buf_put_base85(&buf, "this is base 85 encoded",
+ strlen("this is base 85 encoded")));
+ cl_assert_equal_s("bZBXFAZc?TVqtS-AUHK3Wo~0{WMyOk", buf.ptr);
+ git_buf_clear(&buf);
+
+ git_buf_free(&buf);
+}
+
+void test_core_buffer__classify_with_utf8(void)
+{
+ char *data0 = "Simple text\n";
+ size_t data0len = 12;
+ char *data1 = "Is that UTF-8 data I see…\nYep!\n";
+ size_t data1len = 31;
+ char *data2 = "Internal NUL!!!\000\n\nI see you!\n";
+ size_t data2len = 29;
+ char *data3 = "\xef\xbb\xbfThis is UTF-8 with a BOM.\n";
+ size_t data3len = 20;
+ git_buf b;
+
+ b.ptr = data0; b.size = b.asize = data0len;
+ cl_assert(!git_buf_text_is_binary(&b));
+ cl_assert(!git_buf_text_contains_nul(&b));
+
+ b.ptr = data1; b.size = b.asize = data1len;
+ cl_assert(git_buf_text_is_binary(&b));
+ cl_assert(!git_buf_text_contains_nul(&b));
+
+ b.ptr = data2; b.size = b.asize = data2len;
+ cl_assert(git_buf_text_is_binary(&b));
+ cl_assert(git_buf_text_contains_nul(&b));
+
+ b.ptr = data3; b.size = b.asize = data3len;
+ cl_assert(!git_buf_text_is_binary(&b));
+ cl_assert(!git_buf_text_contains_nul(&b));
+}
+
+#define SIMILARITY_TEST_DATA_1 \
+ "000\n001\n002\n003\n004\n005\n006\n007\n008\n009\n" \
+ "010\n011\n012\n013\n014\n015\n016\n017\n018\n019\n" \
+ "020\n021\n022\n023\n024\n025\n026\n027\n028\n029\n" \
+ "030\n031\n032\n033\n034\n035\n036\n037\n038\n039\n" \
+ "040\n041\n042\n043\n044\n045\n046\n047\n048\n049\n"
+
+void test_core_buffer__similarity_metric(void)
+{
+ git_hashsig *a, *b;
+ git_buf buf = GIT_BUF_INIT;
+ int sim;
+
+ /* in the first case, we compare data to itself and expect 100% match */
+
+ cl_git_pass(git_buf_sets(&buf, SIMILARITY_TEST_DATA_1));
+ cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL));
+ cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL));
+
+ cl_assert_equal_i(100, git_hashsig_compare(a, b));
+
+ git_hashsig_free(a);
+ git_hashsig_free(b);
+
+ /* if we change just a single byte, how much does that change magnify? */
+
+ cl_git_pass(git_buf_sets(&buf, SIMILARITY_TEST_DATA_1));
+ cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL));
+ cl_git_pass(git_buf_sets(&buf,
+ "000\n001\n002\n003\n004\n005\n006\n007\n008\n009\n" \
+ "010\n011\n012\n013\n014\n015\n016\n017\n018\n019\n" \
+ "x020x\n021\n022\n023\n024\n025\n026\n027\n028\n029\n" \
+ "030\n031\n032\n033\n034\n035\n036\n037\n038\n039\n" \
+ "040\n041\n042\n043\n044\n045\n046\n047\n048\n049\n"
+ ));
+ cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL));
+
+ sim = git_hashsig_compare(a, b);
+
+ cl_assert_in_range(95, sim, 100); /* expect >95% similarity */
+
+ git_hashsig_free(a);
+ git_hashsig_free(b);
+
+ /* let's try comparing data to a superset of itself */
+
+ cl_git_pass(git_buf_sets(&buf, SIMILARITY_TEST_DATA_1));
+ cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL));
+ cl_git_pass(git_buf_sets(&buf, SIMILARITY_TEST_DATA_1
+ "050\n051\n052\n053\n054\n055\n056\n057\n058\n059\n"));
+ cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL));
+
+ sim = git_hashsig_compare(a, b);
+ /* 20% lines added ~= 10% lines changed */
+
+ cl_assert_in_range(85, sim, 95); /* expect similarity around 90% */
+
+ git_hashsig_free(a);
+ git_hashsig_free(b);
+
+ /* what if we keep about half the original data and add half new */
+
+ cl_git_pass(git_buf_sets(&buf, SIMILARITY_TEST_DATA_1));
+ cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL));
+ cl_git_pass(git_buf_sets(&buf,
+ "000\n001\n002\n003\n004\n005\n006\n007\n008\n009\n" \
+ "010\n011\n012\n013\n014\n015\n016\n017\n018\n019\n" \
+ "020x\n021\n022\n023\n024\n" \
+ "x25\nx26\nx27\nx28\nx29\n" \
+ "x30\nx31\nx32\nx33\nx34\nx35\nx36\nx37\nx38\nx39\n" \
+ "x40\nx41\nx42\nx43\nx44\nx45\nx46\nx47\nx48\nx49\n"
+ ));
+ cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, GIT_HASHSIG_NORMAL));
+
+ sim = git_hashsig_compare(a, b);
+ /* 50% lines changed */
+
+ cl_assert_in_range(40, sim, 60); /* expect in the 40-60% similarity range */
+
+ git_hashsig_free(a);
+ git_hashsig_free(b);
+
+ /* lastly, let's check that we can hash file content as well */
+
+ cl_git_pass(git_buf_sets(&buf, SIMILARITY_TEST_DATA_1));
+ cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL));
+
+ cl_git_pass(git_futils_mkdir("scratch", NULL, 0755, GIT_MKDIR_PATH));
+ cl_git_mkfile("scratch/testdata", SIMILARITY_TEST_DATA_1);
+ cl_git_pass(git_hashsig_create_fromfile(
+ &b, "scratch/testdata", GIT_HASHSIG_NORMAL));
+
+ cl_assert_equal_i(100, git_hashsig_compare(a, b));
+
+ git_hashsig_free(a);
+ git_hashsig_free(b);
+
+ git_buf_free(&buf);
+ git_futils_rmdir_r("scratch", NULL, GIT_RMDIR_REMOVE_FILES);
+}
+
+
+void test_core_buffer__similarity_metric_whitespace(void)
+{
+ git_hashsig *a, *b;
+ git_buf buf = GIT_BUF_INIT;
+ int sim, i, j;
+ git_hashsig_option_t opt;
+ const char *tabbed =
+ " for (s = 0; s < sizeof(sep) / sizeof(char); ++s) {\n"
+ " separator = sep[s];\n"
+ " expect = expect_values[s];\n"
+ "\n"
+ " for (j = 0; j < sizeof(b) / sizeof(char*); ++j) {\n"
+ " for (i = 0; i < sizeof(a) / sizeof(char*); ++i) {\n"
+ " git_buf_join(&buf, separator, a[i], b[j]);\n"
+ " cl_assert_equal_s(*expect, buf.ptr);\n"
+ " expect++;\n"
+ " }\n"
+ " }\n"
+ " }\n";
+ const char *spaced =
+ " for (s = 0; s < sizeof(sep) / sizeof(char); ++s) {\n"
+ " separator = sep[s];\n"
+ " expect = expect_values[s];\n"
+ "\n"
+ " for (j = 0; j < sizeof(b) / sizeof(char*); ++j) {\n"
+ " for (i = 0; i < sizeof(a) / sizeof(char*); ++i) {\n"
+ " git_buf_join(&buf, separator, a[i], b[j]);\n"
+ " cl_assert_equal_s(*expect, buf.ptr);\n"
+ " expect++;\n"
+ " }\n"
+ " }\n"
+ " }\n";
+ const char *crlf_spaced2 =
+ " for (s = 0; s < sizeof(sep) / sizeof(char); ++s) {\r\n"
+ " separator = sep[s];\r\n"
+ " expect = expect_values[s];\r\n"
+ "\r\n"
+ " for (j = 0; j < sizeof(b) / sizeof(char*); ++j) {\r\n"
+ " for (i = 0; i < sizeof(a) / sizeof(char*); ++i) {\r\n"
+ " git_buf_join(&buf, separator, a[i], b[j]);\r\n"
+ " cl_assert_equal_s(*expect, buf.ptr);\r\n"
+ " expect++;\r\n"
+ " }\r\n"
+ " }\r\n"
+ " }\r\n";
+ const char *text[3] = { tabbed, spaced, crlf_spaced2 };
+
+ /* let's try variations of our own code with whitespace changes */
+
+ for (opt = GIT_HASHSIG_NORMAL; opt <= GIT_HASHSIG_SMART_WHITESPACE; ++opt) {
+ for (i = 0; i < 3; ++i) {
+ for (j = 0; j < 3; ++j) {
+ cl_git_pass(git_buf_sets(&buf, text[i]));
+ cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, opt));
+
+ cl_git_pass(git_buf_sets(&buf, text[j]));
+ cl_git_pass(git_hashsig_create(&b, buf.ptr, buf.size, opt));
+
+ sim = git_hashsig_compare(a, b);
+
+ if (opt == GIT_HASHSIG_NORMAL) {
+ if (i == j)
+ cl_assert_equal_i(100, sim);
+ else
+ cl_assert_in_range(0, sim, 30); /* pretty different */
+ } else {
+ cl_assert_equal_i(100, sim);
+ }
+
+ git_hashsig_free(a);
+ git_hashsig_free(b);
+ }
+ }
+ }
+
+ git_buf_free(&buf);
+}
+
+#include "../filter/crlf.h"
+
+#define check_buf(expected,buf) do { \
+ cl_assert_equal_s(expected, buf.ptr); \
+ cl_assert_equal_sz(strlen(expected), buf.size); } while (0)
+
+void test_core_buffer__lf_and_crlf_conversions(void)
+{
+ git_buf src = GIT_BUF_INIT, tgt = GIT_BUF_INIT;
+
+ /* LF source */
+
+ git_buf_sets(&src, "lf\nlf\nlf\nlf\n");
+
+ cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
+ check_buf("lf\r\nlf\r\nlf\r\nlf\r\n", tgt);
+
+ cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
+ check_buf(src.ptr, tgt);
+
+ git_buf_sets(&src, "\nlf\nlf\nlf\nlf\nlf");
+
+ cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
+ check_buf("\r\nlf\r\nlf\r\nlf\r\nlf\r\nlf", tgt);
+
+ cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
+ check_buf(src.ptr, tgt);
+
+ /* CRLF source */
+
+ git_buf_sets(&src, "crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n");
+
+ cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
+ check_buf("crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n", tgt);
+ check_buf(src.ptr, tgt);
+
+ cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
+ check_buf("crlf\ncrlf\ncrlf\ncrlf\n", tgt);
+
+ git_buf_sets(&src, "\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf");
+
+ cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
+ check_buf("\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf", tgt);
+ check_buf(src.ptr, tgt);
+
+ cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
+ check_buf("\ncrlf\ncrlf\ncrlf\ncrlf\ncrlf", tgt);
+
+ /* CRLF in LF text */
+
+ git_buf_sets(&src, "\nlf\nlf\ncrlf\r\nlf\nlf\ncrlf\r\n");
+
+ cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
+ check_buf("\r\nlf\r\nlf\r\ncrlf\r\nlf\r\nlf\r\ncrlf\r\n", tgt);
+ cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
+ check_buf("\nlf\nlf\ncrlf\nlf\nlf\ncrlf\n", tgt);
+
+ /* LF in CRLF text */
+
+ git_buf_sets(&src, "\ncrlf\r\ncrlf\r\nlf\ncrlf\r\ncrlf");
+
+ cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
+ check_buf("\r\ncrlf\r\ncrlf\r\nlf\r\ncrlf\r\ncrlf", tgt);
+ cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
+ check_buf("\ncrlf\ncrlf\nlf\ncrlf\ncrlf", tgt);
+
+ /* bare CR test */
+
+ git_buf_sets(&src, "\rcrlf\r\nlf\nlf\ncr\rcrlf\r\nlf\ncr\r");
+
+ cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
+ check_buf("\rcrlf\r\nlf\r\nlf\r\ncr\rcrlf\r\nlf\r\ncr\r", tgt);
+ cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
+ check_buf("\rcrlf\nlf\nlf\ncr\rcrlf\nlf\ncr\r", tgt);
+
+ git_buf_sets(&src, "\rcr\r");
+ cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
+ check_buf(src.ptr, tgt);
+ cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
+ check_buf("\rcr\r", tgt);
+
+ git_buf_free(&src);
+ git_buf_free(&tgt);
+
+ /* blob correspondence tests */
+
+ git_buf_sets(&src, ALL_CRLF_TEXT_RAW);
+ cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
+ check_buf(ALL_CRLF_TEXT_AS_CRLF, tgt);
+ cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
+ check_buf(ALL_CRLF_TEXT_AS_LF, tgt);
+ git_buf_free(&src);
+ git_buf_free(&tgt);
+
+ git_buf_sets(&src, ALL_LF_TEXT_RAW);
+ cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
+ check_buf(ALL_LF_TEXT_AS_CRLF, tgt);
+ cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
+ check_buf(ALL_LF_TEXT_AS_LF, tgt);
+ git_buf_free(&src);
+ git_buf_free(&tgt);
+
+ git_buf_sets(&src, MORE_CRLF_TEXT_RAW);
+ cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
+ check_buf(MORE_CRLF_TEXT_AS_CRLF, tgt);
+ cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
+ check_buf(MORE_CRLF_TEXT_AS_LF, tgt);
+ git_buf_free(&src);
+ git_buf_free(&tgt);
+
+ git_buf_sets(&src, MORE_LF_TEXT_RAW);
+ cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
+ check_buf(MORE_LF_TEXT_AS_CRLF, tgt);
+ cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
+ check_buf(MORE_LF_TEXT_AS_LF, tgt);
+ git_buf_free(&src);
+ git_buf_free(&tgt);
+}
diff --git a/tests/core/copy.c b/tests/core/copy.c
new file mode 100644
index 000000000..04b2dfab5
--- /dev/null
+++ b/tests/core/copy.c
@@ -0,0 +1,152 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+#include "path.h"
+#include "posix.h"
+
+void test_core_copy__file(void)
+{
+ struct stat st;
+ const char *content = "This is some stuff to copy\n";
+
+ cl_git_mkfile("copy_me", content);
+
+ cl_git_pass(git_futils_cp("copy_me", "copy_me_two", 0664));
+
+ cl_git_pass(git_path_lstat("copy_me_two", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert(strlen(content) == (size_t)st.st_size);
+
+ cl_git_pass(p_unlink("copy_me_two"));
+ cl_git_pass(p_unlink("copy_me"));
+}
+
+void test_core_copy__file_in_dir(void)
+{
+ struct stat st;
+ const char *content = "This is some other stuff to copy\n";
+
+ cl_git_pass(git_futils_mkdir("an_dir/in_a_dir", NULL, 0775, GIT_MKDIR_PATH));
+ cl_git_mkfile("an_dir/in_a_dir/copy_me", content);
+ cl_assert(git_path_isdir("an_dir"));
+
+ cl_git_pass(git_futils_mkpath2file
+ ("an_dir/second_dir/and_more/copy_me_two", 0775));
+
+ cl_git_pass(git_futils_cp
+ ("an_dir/in_a_dir/copy_me",
+ "an_dir/second_dir/and_more/copy_me_two",
+ 0664));
+
+ cl_git_pass(git_path_lstat("an_dir/second_dir/and_more/copy_me_two", &st));
+ 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_RMDIR_REMOVE_FILES));
+ cl_assert(!git_path_isdir("an_dir"));
+}
+
+void assert_hard_link(const char *path)
+{
+ /* we assert this by checking that there's more than one link to the file */
+ struct stat st;
+
+ cl_assert(git_path_isfile(path));
+ cl_git_pass(p_stat(path, &st));
+ cl_assert(st.st_nlink > 1);
+}
+
+void test_core_copy__tree(void)
+{
+ struct stat st;
+ const char *content = "File content\n";
+
+ cl_git_pass(git_futils_mkdir("src/b", NULL, 0775, GIT_MKDIR_PATH));
+ cl_git_pass(git_futils_mkdir("src/c/d", NULL, 0775, GIT_MKDIR_PATH));
+ cl_git_pass(git_futils_mkdir("src/c/e", NULL, 0775, GIT_MKDIR_PATH));
+
+ cl_git_mkfile("src/f1", content);
+ cl_git_mkfile("src/b/f2", content);
+ cl_git_mkfile("src/c/f3", content);
+ cl_git_mkfile("src/c/d/f4", content);
+ cl_git_mkfile("src/c/d/.f5", content);
+
+#ifndef GIT_WIN32
+ cl_assert(p_symlink("../../b/f2", "src/c/d/l1") == 0);
+#endif
+
+ cl_assert(git_path_isdir("src"));
+ cl_assert(git_path_isdir("src/b"));
+ cl_assert(git_path_isdir("src/c/d"));
+ cl_assert(git_path_isfile("src/c/d/f4"));
+
+ /* copy with no empty dirs, yes links, no dotfiles, no overwrite */
+
+ cl_git_pass(
+ git_futils_cp_r("src", "t1", GIT_CPDIR_COPY_SYMLINKS, 0) );
+
+ cl_assert(git_path_isdir("t1"));
+ cl_assert(git_path_isdir("t1/b"));
+ cl_assert(git_path_isdir("t1/c"));
+ cl_assert(git_path_isdir("t1/c/d"));
+ cl_assert(!git_path_isdir("t1/c/e"));
+
+ cl_assert(git_path_isfile("t1/f1"));
+ cl_assert(git_path_isfile("t1/b/f2"));
+ cl_assert(git_path_isfile("t1/c/f3"));
+ cl_assert(git_path_isfile("t1/c/d/f4"));
+ cl_assert(!git_path_isfile("t1/c/d/.f5"));
+
+ cl_git_pass(git_path_lstat("t1/c/f3", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert(strlen(content) == (size_t)st.st_size);
+
+#ifndef GIT_WIN32
+ cl_git_pass(git_path_lstat("t1/c/d/l1", &st));
+ cl_assert(S_ISLNK(st.st_mode));
+#endif
+
+ 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 */
+
+ cl_git_pass(
+ git_futils_cp_r("src", "t2", GIT_CPDIR_CREATE_EMPTY_DIRS | GIT_CPDIR_COPY_DOTFILES, 0) );
+
+ cl_assert(git_path_isdir("t2"));
+ cl_assert(git_path_isdir("t2/b"));
+ cl_assert(git_path_isdir("t2/c"));
+ cl_assert(git_path_isdir("t2/c/d"));
+ cl_assert(git_path_isdir("t2/c/e"));
+
+ cl_assert(git_path_isfile("t2/f1"));
+ cl_assert(git_path_isfile("t2/b/f2"));
+ cl_assert(git_path_isfile("t2/c/f3"));
+ cl_assert(git_path_isfile("t2/c/d/f4"));
+ cl_assert(git_path_isfile("t2/c/d/.f5"));
+
+#ifndef GIT_WIN32
+ cl_git_fail(git_path_lstat("t2/c/d/l1", &st));
+#endif
+
+ cl_git_pass(git_futils_rmdir_r("t2", NULL, GIT_RMDIR_REMOVE_FILES));
+ cl_assert(!git_path_isdir("t2"));
+
+#ifndef GIT_WIN32
+ cl_git_pass(git_futils_cp_r("src", "t3", GIT_CPDIR_CREATE_EMPTY_DIRS | GIT_CPDIR_LINK_FILES, 0));
+ cl_assert(git_path_isdir("t3"));
+
+ cl_assert(git_path_isdir("t3"));
+ cl_assert(git_path_isdir("t3/b"));
+ cl_assert(git_path_isdir("t3/c"));
+ cl_assert(git_path_isdir("t3/c/d"));
+ cl_assert(git_path_isdir("t3/c/e"));
+
+ assert_hard_link("t3/f1");
+ assert_hard_link("t3/b/f2");
+ assert_hard_link("t3/c/f3");
+ assert_hard_link("t3/c/d/f4");
+#endif
+
+ cl_git_pass(git_futils_rmdir_r("src", NULL, GIT_RMDIR_REMOVE_FILES));
+}
diff --git a/tests/core/dirent.c b/tests/core/dirent.c
new file mode 100644
index 000000000..f17260362
--- /dev/null
+++ b/tests/core/dirent.c
@@ -0,0 +1,236 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+
+typedef struct name_data {
+ int count; /* return count */
+ char *name; /* filename */
+} name_data;
+
+typedef struct walk_data {
+ char *sub; /* sub-directory name */
+ name_data *names; /* name state data */
+ git_buf path;
+} walk_data;
+
+
+static char *top_dir = "dir-walk";
+static walk_data *state_loc;
+
+static void setup(walk_data *d)
+{
+ name_data *n;
+
+ cl_must_pass(p_mkdir(top_dir, 0777));
+
+ cl_must_pass(p_chdir(top_dir));
+
+ if (strcmp(d->sub, ".") != 0)
+ cl_must_pass(p_mkdir(d->sub, 0777));
+
+ cl_git_pass(git_buf_sets(&d->path, d->sub));
+
+ state_loc = d;
+
+ for (n = d->names; n->name; n++) {
+ git_file fd = p_creat(n->name, 0666);
+ cl_assert(fd >= 0);
+ p_close(fd);
+ n->count = 0;
+ }
+}
+
+static void dirent_cleanup__cb(void *_d)
+{
+ walk_data *d = _d;
+ name_data *n;
+
+ for (n = d->names; n->name; n++) {
+ cl_must_pass(p_unlink(n->name));
+ }
+
+ if (strcmp(d->sub, ".") != 0)
+ cl_must_pass(p_rmdir(d->sub));
+
+ cl_must_pass(p_chdir(".."));
+
+ cl_must_pass(p_rmdir(top_dir));
+
+ git_buf_free(&d->path);
+}
+
+static void check_counts(walk_data *d)
+{
+ name_data *n;
+
+ for (n = d->names; n->name; n++) {
+ cl_assert(n->count == 1);
+ }
+}
+
+static int one_entry(void *state, git_buf *path)
+{
+ walk_data *d = (walk_data *) state;
+ name_data *n;
+
+ if (state != state_loc)
+ return GIT_ERROR;
+
+ if (path != &d->path)
+ return GIT_ERROR;
+
+ for (n = d->names; n->name; n++) {
+ if (!strcmp(n->name, path->ptr)) {
+ n->count++;
+ return 0;
+ }
+ }
+
+ return GIT_ERROR;
+}
+
+
+static name_data dot_names[] = {
+ { 0, "./a" },
+ { 0, "./asdf" },
+ { 0, "./pack-foo.pack" },
+ { 0, NULL }
+};
+static walk_data dot = {
+ ".",
+ dot_names,
+ GIT_BUF_INIT
+};
+
+/* make sure that the '.' folder is not traversed */
+void test_core_dirent__dont_traverse_dot(void)
+{
+ cl_set_cleanup(&dirent_cleanup__cb, &dot);
+ setup(&dot);
+
+ cl_git_pass(git_path_direach(&dot.path, 0, one_entry, &dot));
+
+ check_counts(&dot);
+}
+
+
+static name_data sub_names[] = {
+ { 0, "sub/a" },
+ { 0, "sub/asdf" },
+ { 0, "sub/pack-foo.pack" },
+ { 0, NULL }
+};
+static walk_data sub = {
+ "sub",
+ sub_names,
+ GIT_BUF_INIT
+};
+
+/* traverse a subfolder */
+void test_core_dirent__traverse_subfolder(void)
+{
+ cl_set_cleanup(&dirent_cleanup__cb, &sub);
+ setup(&sub);
+
+ cl_git_pass(git_path_direach(&sub.path, 0, one_entry, &sub));
+
+ check_counts(&sub);
+}
+
+
+static walk_data sub_slash = {
+ "sub/",
+ sub_names,
+ GIT_BUF_INIT
+};
+
+/* traverse a slash-terminated subfolder */
+void test_core_dirent__traverse_slash_terminated_folder(void)
+{
+ cl_set_cleanup(&dirent_cleanup__cb, &sub_slash);
+ setup(&sub_slash);
+
+ cl_git_pass(git_path_direach(&sub_slash.path, 0, one_entry, &sub_slash));
+
+ check_counts(&sub_slash);
+}
+
+
+static name_data empty_names[] = {
+ { 0, NULL }
+};
+static walk_data empty = {
+ "empty",
+ empty_names,
+ GIT_BUF_INIT
+};
+
+/* make sure that empty folders are not traversed */
+void test_core_dirent__dont_traverse_empty_folders(void)
+{
+ cl_set_cleanup(&dirent_cleanup__cb, &empty);
+ setup(&empty);
+
+ cl_git_pass(git_path_direach(&empty.path, 0, one_entry, &empty));
+
+ check_counts(&empty);
+
+ /* make sure callback not called */
+ cl_assert(git_path_is_empty_dir(empty.path.ptr));
+}
+
+static name_data odd_names[] = {
+ { 0, "odd/.a" },
+ { 0, "odd/..c" },
+ /* the following don't work on cygwin/win32 */
+ /* { 0, "odd/.b." }, */
+ /* { 0, "odd/..d.." }, */
+ { 0, NULL }
+};
+static walk_data odd = {
+ "odd",
+ odd_names,
+ GIT_BUF_INIT
+};
+
+/* make sure that strange looking filenames ('..c') are traversed */
+void test_core_dirent__traverse_weird_filenames(void)
+{
+ cl_set_cleanup(&dirent_cleanup__cb, &odd);
+ setup(&odd);
+
+ cl_git_pass(git_path_direach(&odd.path, 0, one_entry, &odd));
+
+ check_counts(&odd);
+}
+
+/* test filename length limits */
+void test_core_dirent__length_limits(void)
+{
+ char *big_filename = (char *)git__malloc(FILENAME_MAX + 1);
+ memset(big_filename, 'a', FILENAME_MAX + 1);
+ big_filename[FILENAME_MAX] = 0;
+
+ cl_must_fail(p_creat(big_filename, 0666));
+
+ git__free(big_filename);
+}
+
+void test_core_dirent__empty_dir(void)
+{
+ cl_must_pass(p_mkdir("empty_dir", 0777));
+ cl_assert(git_path_is_empty_dir("empty_dir"));
+
+ cl_git_mkfile("empty_dir/content", "whatever\n");
+ cl_assert(!git_path_is_empty_dir("empty_dir"));
+ cl_assert(!git_path_is_empty_dir("empty_dir/content"));
+
+ cl_must_pass(p_unlink("empty_dir/content"));
+
+ cl_must_pass(p_mkdir("empty_dir/content", 0777));
+ cl_assert(!git_path_is_empty_dir("empty_dir"));
+ cl_assert(git_path_is_empty_dir("empty_dir/content"));
+
+ cl_must_pass(p_rmdir("empty_dir/content"));
+
+ cl_must_pass(p_rmdir("empty_dir"));
+}
diff --git a/tests/core/env.c b/tests/core/env.c
new file mode 100644
index 000000000..293b786db
--- /dev/null
+++ b/tests/core/env.c
@@ -0,0 +1,304 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+#include "sysdir.h"
+#include "path.h"
+
+#ifdef GIT_WIN32
+#define NUM_VARS 5
+static const char *env_vars[NUM_VARS] = {
+ "HOME", "HOMEDRIVE", "HOMEPATH", "USERPROFILE", "PROGRAMFILES"
+};
+#else
+#define NUM_VARS 1
+static const char *env_vars[NUM_VARS] = { "HOME" };
+#endif
+
+static char *env_save[NUM_VARS];
+
+static char *home_values[] = {
+ "fake_home",
+ "f\xc3\xa1ke_h\xc3\xb5me", /* all in latin-1 supplement */
+ "f\xc4\x80ke_\xc4\xa4ome", /* latin extended */
+ "f\xce\xb1\xce\xba\xce\xb5_h\xce\xbfm\xce\xad", /* having fun with greek */
+ "fa\xe0" "\xb8" "\x87" "e_\xe0" "\xb8" "\x99" "ome", /* thai characters */
+ "f\xe1\x9c\x80ke_\xe1\x9c\x91ome", /* tagalog characters */
+ "\xe1\xb8\x9f\xe1\xba\xa2" "ke_ho" "\xe1" "\xb9" "\x81" "e", /* latin extended additional */
+ "\xf0\x9f\x98\x98\xf0\x9f\x98\x82", /* emoticons */
+ NULL
+};
+
+void test_core_env__initialize(void)
+{
+ int i;
+ for (i = 0; i < NUM_VARS; ++i) {
+ const char *original = cl_getenv(env_vars[i]);
+#ifdef GIT_WIN32
+ env_save[i] = (char *)original;
+#else
+ env_save[i] = original ? git__strdup(original) : NULL;
+#endif
+ }
+}
+
+static void set_global_search_path_from_env(void)
+{
+ cl_git_pass(git_sysdir_set(GIT_SYSDIR_GLOBAL, NULL));
+}
+
+static void set_system_search_path_from_env(void)
+{
+ cl_git_pass(git_sysdir_set(GIT_SYSDIR_SYSTEM, NULL));
+}
+
+void test_core_env__cleanup(void)
+{
+ int i;
+ char **val;
+
+ for (i = 0; i < NUM_VARS; ++i) {
+ cl_setenv(env_vars[i], env_save[i]);
+ git__free(env_save[i]);
+ env_save[i] = NULL;
+ }
+
+ /* these will probably have already been cleaned up, but if a test
+ * fails, then it's probably good to try and clear out these dirs
+ */
+ for (val = home_values; *val != NULL; val++) {
+ if (**val != '\0')
+ (void)p_rmdir(*val);
+ }
+
+ cl_sandbox_set_search_path_defaults();
+}
+
+static void setenv_and_check(const char *name, const char *value)
+{
+ char *check;
+
+ cl_git_pass(cl_setenv(name, value));
+
+ check = cl_getenv(name);
+ cl_assert_equal_s(value, check);
+#ifdef GIT_WIN32
+ git__free(check);
+#endif
+}
+
+void test_core_env__0(void)
+{
+ git_buf path = GIT_BUF_INIT, found = GIT_BUF_INIT;
+ char testfile[16], tidx = '0';
+ char **val;
+ const char *testname = "testfile";
+ size_t testlen = strlen(testname);
+
+ strncpy(testfile, testname, sizeof(testfile));
+ cl_assert_equal_s(testname, testfile);
+
+ for (val = home_values; *val != NULL; val++) {
+
+ /* if we can't make the directory, let's just assume
+ * we are on a filesystem that doesn't support the
+ * characters in question and skip this test...
+ */
+ if (p_mkdir(*val, 0777) != 0) {
+ *val = ""; /* mark as not created */
+ continue;
+ }
+
+ cl_git_pass(git_path_prettify(&path, *val, NULL));
+
+ /* vary testfile name in each directory so accidentally leaving
+ * an environment variable set from a previous iteration won't
+ * accidentally make this test pass...
+ */
+ testfile[testlen] = tidx++;
+ cl_git_pass(git_buf_joinpath(&path, path.ptr, testfile));
+ cl_git_mkfile(path.ptr, "find me");
+ git_buf_rtruncate_at_char(&path, '/');
+
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_sysdir_find_global_file(&found, testfile));
+
+ setenv_and_check("HOME", path.ptr);
+ set_global_search_path_from_env();
+
+ cl_git_pass(git_sysdir_find_global_file(&found, testfile));
+
+ cl_setenv("HOME", env_save[0]);
+ set_global_search_path_from_env();
+
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_sysdir_find_global_file(&found, testfile));
+
+#ifdef GIT_WIN32
+ setenv_and_check("HOMEDRIVE", NULL);
+ setenv_and_check("HOMEPATH", NULL);
+ setenv_and_check("USERPROFILE", path.ptr);
+ set_global_search_path_from_env();
+
+ cl_git_pass(git_sysdir_find_global_file(&found, testfile));
+
+ {
+ int root = git_path_root(path.ptr);
+ char old;
+
+ if (root >= 0) {
+ setenv_and_check("USERPROFILE", NULL);
+ set_global_search_path_from_env();
+
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_sysdir_find_global_file(&found, testfile));
+
+ old = path.ptr[root];
+ path.ptr[root] = '\0';
+ setenv_and_check("HOMEDRIVE", path.ptr);
+ path.ptr[root] = old;
+ setenv_and_check("HOMEPATH", &path.ptr[root]);
+ set_global_search_path_from_env();
+
+ cl_git_pass(git_sysdir_find_global_file(&found, testfile));
+ }
+ }
+#endif
+
+ (void)p_rmdir(*val);
+ }
+
+ git_buf_free(&path);
+ git_buf_free(&found);
+}
+
+
+void test_core_env__1(void)
+{
+ git_buf path = GIT_BUF_INIT;
+
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_sysdir_find_global_file(&path, "nonexistentfile"));
+
+ cl_git_pass(cl_setenv("HOME", "doesnotexist"));
+#ifdef GIT_WIN32
+ cl_git_pass(cl_setenv("HOMEPATH", "doesnotexist"));
+ cl_git_pass(cl_setenv("USERPROFILE", "doesnotexist"));
+#endif
+ set_global_search_path_from_env();
+
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_sysdir_find_global_file(&path, "nonexistentfile"));
+
+ cl_git_pass(cl_setenv("HOME", NULL));
+#ifdef GIT_WIN32
+ cl_git_pass(cl_setenv("HOMEPATH", NULL));
+ cl_git_pass(cl_setenv("USERPROFILE", NULL));
+#endif
+ set_global_search_path_from_env();
+ set_system_search_path_from_env();
+
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_sysdir_find_global_file(&path, "nonexistentfile"));
+
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_sysdir_find_system_file(&path, "nonexistentfile"));
+
+#ifdef GIT_WIN32
+ cl_git_pass(cl_setenv("PROGRAMFILES", NULL));
+ set_system_search_path_from_env();
+
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_sysdir_find_system_file(&path, "nonexistentfile"));
+#endif
+
+ git_buf_free(&path);
+}
+
+static void check_global_searchpath(
+ const char *path, int position, const char *file, git_buf *temp)
+{
+ git_buf out = GIT_BUF_INIT;
+
+ /* build and set new path */
+ if (position < 0)
+ cl_git_pass(git_buf_join(temp, GIT_PATH_LIST_SEPARATOR, path, "$PATH"));
+ else if (position > 0)
+ cl_git_pass(git_buf_join(temp, GIT_PATH_LIST_SEPARATOR, "$PATH", path));
+ else
+ cl_git_pass(git_buf_sets(temp, path));
+
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, temp->ptr));
+
+ /* get path and make sure $PATH expansion worked */
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &out));
+
+ if (position < 0)
+ cl_assert(git__prefixcmp(out.ptr, path) == 0);
+ else if (position > 0)
+ cl_assert(git__suffixcmp(out.ptr, path) == 0);
+ else
+ cl_assert_equal_s(out.ptr, path);
+
+ /* find file using new path */
+ cl_git_pass(git_sysdir_find_global_file(temp, file));
+
+ /* reset path and confirm file not found */
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, NULL));
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_sysdir_find_global_file(temp, file));
+
+ git_buf_free(&out);
+}
+
+void test_core_env__2(void)
+{
+ git_buf path = GIT_BUF_INIT, found = GIT_BUF_INIT;
+ char testfile[16], tidx = '0';
+ char **val;
+ const char *testname = "alternate";
+ size_t testlen = strlen(testname);
+
+ strncpy(testfile, testname, sizeof(testfile));
+ cl_assert_equal_s(testname, testfile);
+
+ for (val = home_values; *val != NULL; val++) {
+
+ /* if we can't make the directory, let's just assume
+ * we are on a filesystem that doesn't support the
+ * characters in question and skip this test...
+ */
+ if (p_mkdir(*val, 0777) != 0 && errno != EEXIST) {
+ *val = ""; /* mark as not created */
+ continue;
+ }
+
+ cl_git_pass(git_path_prettify(&path, *val, NULL));
+
+ /* vary testfile name so any sloppiness is resetting variables or
+ * deleting files won't accidentally make a test pass.
+ */
+ testfile[testlen] = tidx++;
+ cl_git_pass(git_buf_joinpath(&path, path.ptr, testfile));
+ cl_git_mkfile(path.ptr, "find me");
+ git_buf_rtruncate_at_char(&path, '/');
+
+ /* default should be NOTFOUND */
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_sysdir_find_global_file(&found, testfile));
+
+ /* try plain, append $PATH, and prepend $PATH */
+ check_global_searchpath(path.ptr, 0, testfile, &found);
+ check_global_searchpath(path.ptr, -1, testfile, &found);
+ check_global_searchpath(path.ptr, 1, testfile, &found);
+
+ /* cleanup */
+ cl_git_pass(git_buf_joinpath(&path, path.ptr, testfile));
+ (void)p_unlink(path.ptr);
+ (void)p_rmdir(*val);
+ }
+
+ git_buf_free(&path);
+ git_buf_free(&found);
+}
diff --git a/tests/core/errors.c b/tests/core/errors.c
new file mode 100644
index 000000000..366d8f16a
--- /dev/null
+++ b/tests/core/errors.c
@@ -0,0 +1,111 @@
+#include "clar_libgit2.h"
+
+void test_core_errors__public_api(void)
+{
+ char *str_in_error;
+
+ giterr_clear();
+ cl_assert(giterr_last() == NULL);
+
+ giterr_set_oom();
+
+ cl_assert(giterr_last() != NULL);
+ cl_assert(giterr_last()->klass == GITERR_NOMEMORY);
+ str_in_error = strstr(giterr_last()->message, "memory");
+ cl_assert(str_in_error != NULL);
+
+ giterr_clear();
+
+ giterr_set_str(GITERR_REPOSITORY, "This is a test");
+
+ cl_assert(giterr_last() != NULL);
+ str_in_error = strstr(giterr_last()->message, "This is a test");
+ cl_assert(str_in_error != NULL);
+
+ giterr_clear();
+ cl_assert(giterr_last() == NULL);
+}
+
+#include "common.h"
+#include "util.h"
+#include "posix.h"
+
+void test_core_errors__new_school(void)
+{
+ char *str_in_error;
+
+ giterr_clear();
+ cl_assert(giterr_last() == NULL);
+
+ giterr_set_oom(); /* internal fn */
+
+ cl_assert(giterr_last() != NULL);
+ cl_assert(giterr_last()->klass == GITERR_NOMEMORY);
+ str_in_error = strstr(giterr_last()->message, "memory");
+ cl_assert(str_in_error != NULL);
+
+ giterr_clear();
+
+ giterr_set(GITERR_REPOSITORY, "This is a test"); /* internal fn */
+
+ cl_assert(giterr_last() != NULL);
+ str_in_error = strstr(giterr_last()->message, "This is a test");
+ cl_assert(str_in_error != NULL);
+
+ giterr_clear();
+ cl_assert(giterr_last() == NULL);
+
+ do {
+ struct stat st;
+ memset(&st, 0, sizeof(st));
+ cl_assert(p_lstat("this_file_does_not_exist", &st) < 0);
+ GIT_UNUSED(st);
+ } while (false);
+ giterr_set(GITERR_OS, "stat failed"); /* internal fn */
+
+ cl_assert(giterr_last() != NULL);
+ str_in_error = strstr(giterr_last()->message, "stat failed");
+ cl_assert(str_in_error != NULL);
+ cl_assert(git__prefixcmp(str_in_error, "stat failed: ") == 0);
+ cl_assert(strlen(str_in_error) > strlen("stat failed: "));
+
+#ifdef GIT_WIN32
+ giterr_clear();
+
+ /* The MSDN docs use this to generate a sample error */
+ cl_assert(GetProcessId(NULL) == 0);
+ giterr_set(GITERR_OS, "GetProcessId failed"); /* internal fn */
+
+ cl_assert(giterr_last() != NULL);
+ str_in_error = strstr(giterr_last()->message, "GetProcessId failed");
+ cl_assert(str_in_error != NULL);
+ cl_assert(git__prefixcmp(str_in_error, "GetProcessId failed: ") == 0);
+ cl_assert(strlen(str_in_error) > strlen("GetProcessId failed: "));
+#endif
+
+ giterr_clear();
+}
+
+void test_core_errors__restore(void)
+{
+ git_error_state err_state = {0};
+
+ giterr_clear();
+ cl_assert(giterr_last() == NULL);
+
+ cl_assert_equal_i(0, giterr_capture(&err_state, 0));
+
+ memset(&err_state, 0x0, sizeof(git_error_state));
+
+ giterr_set(42, "Foo: %s", "bar");
+ cl_assert_equal_i(-1, giterr_capture(&err_state, -1));
+
+ cl_assert(giterr_last() == NULL);
+
+ giterr_set(99, "Bar: %s", "foo");
+
+ giterr_restore(&err_state);
+
+ cl_assert_equal_i(42, giterr_last()->klass);
+ cl_assert_equal_s("Foo: bar", giterr_last()->message);
+}
diff --git a/tests/core/features.c b/tests/core/features.c
new file mode 100644
index 000000000..3ce02f4d6
--- /dev/null
+++ b/tests/core/features.c
@@ -0,0 +1,31 @@
+#include "clar_libgit2.h"
+
+void test_core_features__0(void)
+{
+ int major, minor, rev, caps;
+
+ git_libgit2_version(&major, &minor, &rev);
+ cl_assert_equal_i(LIBGIT2_VER_MAJOR, major);
+ cl_assert_equal_i(LIBGIT2_VER_MINOR, minor);
+ cl_assert_equal_i(LIBGIT2_VER_REVISION, rev);
+
+ caps = git_libgit2_features();
+
+#ifdef GIT_THREADS
+ cl_assert((caps & GIT_FEATURE_THREADS) != 0);
+#else
+ cl_assert((caps & GIT_FEATURE_THREADS) == 0);
+#endif
+
+#if defined(GIT_SSL) || defined(GIT_WINHTTP)
+ cl_assert((caps & GIT_FEATURE_HTTPS) != 0);
+#else
+ cl_assert((caps & GIT_FEATURE_HTTPS) == 0);
+#endif
+
+#if defined(GIT_SSH)
+ cl_assert((caps & GIT_FEATURE_SSH) != 0);
+#else
+ cl_assert((caps & GIT_FEATURE_SSH) == 0);
+#endif
+}
diff --git a/tests/core/filebuf.c b/tests/core/filebuf.c
new file mode 100644
index 000000000..5a3e7510f
--- /dev/null
+++ b/tests/core/filebuf.c
@@ -0,0 +1,126 @@
+#include "clar_libgit2.h"
+#include "filebuf.h"
+
+/* make sure git_filebuf_open doesn't delete an existing lock */
+void test_core_filebuf__0(void)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ int fd;
+ char test[] = "test", testlock[] = "test.lock";
+
+ fd = p_creat(testlock, 0744); //-V536
+
+ cl_must_pass(fd);
+ cl_must_pass(p_close(fd));
+
+ cl_git_fail(git_filebuf_open(&file, test, 0, 0666));
+ cl_assert(git_path_exists(testlock));
+
+ cl_must_pass(p_unlink(testlock));
+}
+
+
+/* make sure GIT_FILEBUF_APPEND works as expected */
+void test_core_filebuf__1(void)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ char test[] = "test";
+
+ cl_git_mkfile(test, "libgit2 rocks\n");
+
+ cl_git_pass(git_filebuf_open(&file, test, GIT_FILEBUF_APPEND, 0666));
+ cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks"));
+ cl_git_pass(git_filebuf_commit(&file));
+
+ cl_assert_equal_file("libgit2 rocks\nlibgit2 rocks\n", 0, test);
+
+ cl_must_pass(p_unlink(test));
+}
+
+
+/* make sure git_filebuf_write writes large buffer correctly */
+void test_core_filebuf__2(void)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ char test[] = "test";
+ unsigned char buf[4096 * 4]; /* 2 * WRITE_BUFFER_SIZE */
+
+ memset(buf, 0xfe, sizeof(buf));
+
+ cl_git_pass(git_filebuf_open(&file, test, 0, 0666));
+ cl_git_pass(git_filebuf_write(&file, buf, sizeof(buf)));
+ cl_git_pass(git_filebuf_commit(&file));
+
+ cl_assert_equal_file((char *)buf, sizeof(buf), test);
+
+ cl_must_pass(p_unlink(test));
+}
+
+/* make sure git_filebuf_cleanup clears the buffer */
+void test_core_filebuf__4(void)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ char test[] = "test";
+
+ cl_assert(file.buffer == NULL);
+
+ cl_git_pass(git_filebuf_open(&file, test, 0, 0666));
+ cl_assert(file.buffer != NULL);
+
+ git_filebuf_cleanup(&file);
+ cl_assert(file.buffer == NULL);
+}
+
+
+/* make sure git_filebuf_commit clears the buffer */
+void test_core_filebuf__5(void)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ char test[] = "test";
+
+ cl_assert(file.buffer == NULL);
+
+ cl_git_pass(git_filebuf_open(&file, test, 0, 0666));
+ cl_assert(file.buffer != NULL);
+ cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks"));
+ cl_assert(file.buffer != NULL);
+
+ cl_git_pass(git_filebuf_commit(&file));
+ cl_assert(file.buffer == NULL);
+
+ cl_must_pass(p_unlink(test));
+}
+
+
+/* make sure git_filebuf_commit takes umask into account */
+void test_core_filebuf__umask(void)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ char test[] = "test";
+ struct stat statbuf;
+ mode_t mask, os_mask;
+
+#ifdef GIT_WIN32
+ os_mask = 0600;
+#else
+ os_mask = 0777;
+#endif
+
+ p_umask(mask = p_umask(0));
+
+ cl_assert(file.buffer == NULL);
+
+ cl_git_pass(git_filebuf_open(&file, test, 0, 0666));
+ cl_assert(file.buffer != NULL);
+ cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks"));
+ cl_assert(file.buffer != NULL);
+
+ cl_git_pass(git_filebuf_commit(&file));
+ cl_assert(file.buffer == NULL);
+
+ cl_must_pass(p_stat("test", &statbuf));
+ cl_assert_equal_i(statbuf.st_mode & os_mask, (0666 & ~mask) & os_mask);
+
+ cl_must_pass(p_unlink(test));
+}
+
diff --git a/tests/core/hex.c b/tests/core/hex.c
new file mode 100644
index 000000000..930af1670
--- /dev/null
+++ b/tests/core/hex.c
@@ -0,0 +1,22 @@
+#include "clar_libgit2.h"
+#include "util.h"
+
+void test_core_hex__fromhex(void)
+{
+ /* Passing cases */
+ cl_assert(git__fromhex('0') == 0x0);
+ cl_assert(git__fromhex('1') == 0x1);
+ cl_assert(git__fromhex('3') == 0x3);
+ cl_assert(git__fromhex('9') == 0x9);
+ cl_assert(git__fromhex('A') == 0xa);
+ cl_assert(git__fromhex('C') == 0xc);
+ cl_assert(git__fromhex('F') == 0xf);
+ cl_assert(git__fromhex('a') == 0xa);
+ cl_assert(git__fromhex('c') == 0xc);
+ cl_assert(git__fromhex('f') == 0xf);
+
+ /* Failing cases */
+ cl_assert(git__fromhex('g') == -1);
+ cl_assert(git__fromhex('z') == -1);
+ cl_assert(git__fromhex('X') == -1);
+}
diff --git a/tests/core/iconv.c b/tests/core/iconv.c
new file mode 100644
index 000000000..cb85f458a
--- /dev/null
+++ b/tests/core/iconv.c
@@ -0,0 +1,78 @@
+#include "clar_libgit2.h"
+#include "path.h"
+
+#ifdef GIT_USE_ICONV
+static git_path_iconv_t ic;
+static char *nfc = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D";
+static char *nfd = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D";
+#endif
+
+void test_core_iconv__initialize(void)
+{
+#ifdef GIT_USE_ICONV
+ cl_git_pass(git_path_iconv_init_precompose(&ic));
+#endif
+}
+
+void test_core_iconv__cleanup(void)
+{
+#ifdef GIT_USE_ICONV
+ git_path_iconv_clear(&ic);
+#endif
+}
+
+void test_core_iconv__unchanged(void)
+{
+#ifdef GIT_USE_ICONV
+ char *data = "Ascii data", *original = data;
+ size_t datalen = strlen(data);
+
+ cl_git_pass(git_path_iconv(&ic, &data, &datalen));
+ GIT_UNUSED(datalen);
+
+ /* There are no high bits set, so this should leave data untouched */
+ cl_assert(data == original);
+#endif
+}
+
+void test_core_iconv__decomposed_to_precomposed(void)
+{
+#ifdef GIT_USE_ICONV
+ char *data = nfd;
+ size_t datalen, nfdlen = strlen(nfd);
+
+ datalen = nfdlen;
+ cl_git_pass(git_path_iconv(&ic, &data, &datalen));
+ GIT_UNUSED(datalen);
+
+ /* The decomposed nfd string should be transformed to the nfc form
+ * (on platforms where iconv is enabled, of course).
+ */
+ cl_assert_equal_s(nfc, data);
+
+ /* should be able to do it multiple times with the same git_path_iconv_t */
+ data = nfd; datalen = nfdlen;
+ cl_git_pass(git_path_iconv(&ic, &data, &datalen));
+ cl_assert_equal_s(nfc, data);
+
+ data = nfd; datalen = nfdlen;
+ cl_git_pass(git_path_iconv(&ic, &data, &datalen));
+ cl_assert_equal_s(nfc, data);
+#endif
+}
+
+void test_core_iconv__precomposed_is_unmodified(void)
+{
+#ifdef GIT_USE_ICONV
+ char *data = nfc;
+ size_t datalen = strlen(nfc);
+
+ cl_git_pass(git_path_iconv(&ic, &data, &datalen));
+ GIT_UNUSED(datalen);
+
+ /* data is already in precomposed form, so even though some bytes have
+ * the high-bit set, the iconv transform should result in no change.
+ */
+ cl_assert_equal_s(nfc, data);
+#endif
+}
diff --git a/tests/core/link.c b/tests/core/link.c
new file mode 100644
index 000000000..1794a3893
--- /dev/null
+++ b/tests/core/link.c
@@ -0,0 +1,602 @@
+#include "clar_libgit2.h"
+#include "posix.h"
+#include "buffer.h"
+#include "path.h"
+
+#ifdef GIT_WIN32
+# include "win32/reparse.h"
+#endif
+
+void test_core_link__cleanup(void)
+{
+#ifdef GIT_WIN32
+ RemoveDirectory("lstat_junction");
+ RemoveDirectory("lstat_dangling");
+ RemoveDirectory("lstat_dangling_dir");
+ RemoveDirectory("lstat_dangling_junction");
+
+ RemoveDirectory("stat_junction");
+ RemoveDirectory("stat_dangling");
+ RemoveDirectory("stat_dangling_dir");
+ RemoveDirectory("stat_dangling_junction");
+#endif
+}
+
+#ifdef GIT_WIN32
+static bool is_administrator(void)
+{
+ static SID_IDENTIFIER_AUTHORITY authority = { SECURITY_NT_AUTHORITY };
+ PSID admin_sid;
+ BOOL is_admin;
+
+ cl_win32_pass(AllocateAndInitializeSid(&authority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &admin_sid));
+ cl_win32_pass(CheckTokenMembership(NULL, admin_sid, &is_admin));
+ FreeSid(admin_sid);
+
+ return is_admin ? true : false;
+}
+#endif
+
+static void do_symlink(const char *old, const char *new, int is_dir)
+{
+#ifndef GIT_WIN32
+ GIT_UNUSED(is_dir);
+
+ cl_must_pass(symlink(old, new));
+#else
+ typedef DWORD (WINAPI *create_symlink_func)(LPCTSTR, LPCTSTR, DWORD);
+ HMODULE module;
+ create_symlink_func pCreateSymbolicLink;
+
+ if (!is_administrator())
+ clar__skip();
+
+ cl_assert(module = GetModuleHandle("kernel32"));
+ cl_assert(pCreateSymbolicLink = (create_symlink_func)GetProcAddress(module, "CreateSymbolicLinkA"));
+
+ cl_win32_pass(pCreateSymbolicLink(new, old, is_dir));
+#endif
+}
+
+static void do_hardlink(const char *old, const char *new)
+{
+#ifndef GIT_WIN32
+ cl_must_pass(link(old, new));
+#else
+ typedef DWORD (WINAPI *create_hardlink_func)(LPCTSTR, LPCTSTR, LPSECURITY_ATTRIBUTES);
+ HMODULE module;
+ create_hardlink_func pCreateHardLink;
+
+ if (!is_administrator())
+ clar__skip();
+
+ cl_assert(module = GetModuleHandle("kernel32"));
+ cl_assert(pCreateHardLink = (create_hardlink_func)GetProcAddress(module, "CreateHardLinkA"));
+
+ cl_win32_pass(pCreateHardLink(new, old, 0));
+#endif
+}
+
+#ifdef GIT_WIN32
+
+static void do_junction(const char *old, const char *new)
+{
+ GIT_REPARSE_DATA_BUFFER *reparse_buf;
+ HANDLE handle;
+ git_buf unparsed_buf = GIT_BUF_INIT;
+ wchar_t *subst_utf16, *print_utf16;
+ DWORD ioctl_ret;
+ int subst_utf16_len, subst_byte_len, print_utf16_len, print_byte_len, ret;
+ USHORT reparse_buflen;
+ size_t i;
+
+ /* Junction targets must be the unparsed name, starting with \??\, using
+ * backslashes instead of forward, and end in a trailing backslash.
+ * eg: \??\C:\Foo\
+ */
+ git_buf_puts(&unparsed_buf, "\\??\\");
+
+ for (i = 0; i < strlen(old); i++)
+ git_buf_putc(&unparsed_buf, old[i] == '/' ? '\\' : old[i]);
+
+ git_buf_putc(&unparsed_buf, '\\');
+
+ subst_utf16_len = git__utf8_to_16(NULL, 0, git_buf_cstr(&unparsed_buf));
+ subst_byte_len = subst_utf16_len * sizeof(WCHAR);
+
+ print_utf16_len = subst_utf16_len - 4;
+ print_byte_len = subst_byte_len - (4 * sizeof(WCHAR));
+
+ /* The junction must be an empty directory before the junction attribute
+ * can be added.
+ */
+ cl_win32_pass(CreateDirectoryA(new, NULL));
+
+ handle = CreateFileA(new, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
+ FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
+ cl_win32_pass(handle != INVALID_HANDLE_VALUE);
+
+ reparse_buflen = (USHORT)(REPARSE_DATA_HEADER_SIZE +
+ REPARSE_DATA_MOUNTPOINT_HEADER_SIZE +
+ subst_byte_len + sizeof(WCHAR) +
+ print_byte_len + sizeof(WCHAR));
+
+ reparse_buf = LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, reparse_buflen);
+ cl_assert(reparse_buf);
+
+ subst_utf16 = reparse_buf->MountPointReparseBuffer.PathBuffer;
+ print_utf16 = subst_utf16 + subst_utf16_len + 1;
+
+ ret = git__utf8_to_16(subst_utf16, subst_utf16_len + 1,
+ git_buf_cstr(&unparsed_buf));
+ cl_assert_equal_i(subst_utf16_len, ret);
+
+ ret = git__utf8_to_16(print_utf16,
+ print_utf16_len + 1, git_buf_cstr(&unparsed_buf) + 4);
+ cl_assert_equal_i(print_utf16_len, ret);
+
+ reparse_buf->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
+ reparse_buf->MountPointReparseBuffer.SubstituteNameOffset = 0;
+ reparse_buf->MountPointReparseBuffer.SubstituteNameLength = subst_byte_len;
+ reparse_buf->MountPointReparseBuffer.PrintNameOffset = (USHORT)(subst_byte_len + sizeof(WCHAR));
+ reparse_buf->MountPointReparseBuffer.PrintNameLength = print_byte_len;
+ reparse_buf->ReparseDataLength = reparse_buflen - REPARSE_DATA_HEADER_SIZE;
+
+ cl_win32_pass(DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT,
+ reparse_buf, reparse_buflen, NULL, 0, &ioctl_ret, NULL));
+
+ CloseHandle(handle);
+ LocalFree(reparse_buf);
+}
+
+static void do_custom_reparse(const char *path)
+{
+ REPARSE_GUID_DATA_BUFFER *reparse_buf;
+ HANDLE handle;
+ DWORD ioctl_ret;
+
+ const char *reparse_data = "Reparse points are silly.";
+ size_t reparse_buflen = REPARSE_GUID_DATA_BUFFER_HEADER_SIZE +
+ strlen(reparse_data) + 1;
+
+ reparse_buf = LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, reparse_buflen);
+ cl_assert(reparse_buf);
+
+ reparse_buf->ReparseTag = 42;
+ reparse_buf->ReparseDataLength = (WORD)(strlen(reparse_data) + 1);
+
+ reparse_buf->ReparseGuid.Data1 = 0xdeadbeef;
+ reparse_buf->ReparseGuid.Data2 = 0xdead;
+ reparse_buf->ReparseGuid.Data3 = 0xbeef;
+ reparse_buf->ReparseGuid.Data4[0] = 42;
+ reparse_buf->ReparseGuid.Data4[1] = 42;
+ reparse_buf->ReparseGuid.Data4[2] = 42;
+ reparse_buf->ReparseGuid.Data4[3] = 42;
+ reparse_buf->ReparseGuid.Data4[4] = 42;
+ reparse_buf->ReparseGuid.Data4[5] = 42;
+ reparse_buf->ReparseGuid.Data4[6] = 42;
+ reparse_buf->ReparseGuid.Data4[7] = 42;
+ reparse_buf->ReparseGuid.Data4[8] = 42;
+
+ memcpy(reparse_buf->GenericReparseBuffer.DataBuffer,
+ reparse_data, strlen(reparse_data) + 1);
+
+ handle = CreateFileA(path, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
+ FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
+ cl_win32_pass(handle != INVALID_HANDLE_VALUE);
+
+ cl_win32_pass(DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT,
+ reparse_buf,
+ reparse_buf->ReparseDataLength + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE,
+ NULL, 0, &ioctl_ret, NULL));
+
+ CloseHandle(handle);
+ LocalFree(reparse_buf);
+}
+
+#endif
+
+git_buf *unslashify(git_buf *buf)
+{
+#ifdef GIT_WIN32
+ size_t i;
+
+ for (i = 0; i < buf->size; i++)
+ if (buf->ptr[i] == '/')
+ buf->ptr[i] = '\\';
+#endif
+
+ return buf;
+}
+
+void test_core_link__stat_regular_file(void)
+{
+ struct stat st;
+
+ cl_git_rewritefile("stat_regfile", "This is a regular file!\n");
+
+ cl_must_pass(p_stat("stat_regfile", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(24, st.st_size);
+}
+
+void test_core_link__lstat_regular_file(void)
+{
+ struct stat st;
+
+ cl_git_rewritefile("lstat_regfile", "This is a regular file!\n");
+
+ cl_must_pass(p_stat("lstat_regfile", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(24, st.st_size);
+}
+
+void test_core_link__stat_symlink(void)
+{
+ struct stat st;
+
+ cl_git_rewritefile("stat_target", "This is the target of a symbolic link.\n");
+ do_symlink("stat_target", "stat_symlink", 0);
+
+ cl_must_pass(p_stat("stat_target", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(39, st.st_size);
+
+ cl_must_pass(p_stat("stat_symlink", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(39, st.st_size);
+}
+
+void test_core_link__stat_symlink_directory(void)
+{
+ struct stat st;
+
+ p_mkdir("stat_dirtarget", 0777);
+ do_symlink("stat_dirtarget", "stat_dirlink", 1);
+
+ cl_must_pass(p_stat("stat_dirtarget", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+
+ cl_must_pass(p_stat("stat_dirlink", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+}
+
+void test_core_link__stat_symlink_chain(void)
+{
+ struct stat st;
+
+ cl_git_rewritefile("stat_final_target", "Final target of some symbolic links...\n");
+ do_symlink("stat_final_target", "stat_chain_3", 0);
+ do_symlink("stat_chain_3", "stat_chain_2", 0);
+ do_symlink("stat_chain_2", "stat_chain_1", 0);
+
+ cl_must_pass(p_stat("stat_chain_1", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(39, st.st_size);
+}
+
+void test_core_link__stat_dangling_symlink(void)
+{
+ struct stat st;
+
+ do_symlink("stat_nonexistent", "stat_dangling", 0);
+
+ cl_must_fail(p_stat("stat_nonexistent", &st));
+ cl_must_fail(p_stat("stat_dangling", &st));
+}
+
+void test_core_link__stat_dangling_symlink_directory(void)
+{
+ struct stat st;
+
+ do_symlink("stat_nonexistent", "stat_dangling_dir", 1);
+
+ cl_must_fail(p_stat("stat_nonexistent_dir", &st));
+ cl_must_fail(p_stat("stat_dangling", &st));
+}
+
+void test_core_link__lstat_symlink(void)
+{
+ git_buf target_path = GIT_BUF_INIT;
+ struct stat st;
+
+ /* Windows always writes the canonical path as the link target, so
+ * write the full path on all platforms.
+ */
+ git_buf_join(&target_path, '/', clar_sandbox_path(), "lstat_target");
+
+ cl_git_rewritefile("lstat_target", "This is the target of a symbolic link.\n");
+ do_symlink(git_buf_cstr(&target_path), "lstat_symlink", 0);
+
+ cl_must_pass(p_lstat("lstat_target", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(39, st.st_size);
+
+ cl_must_pass(p_lstat("lstat_symlink", &st));
+ cl_assert(S_ISLNK(st.st_mode));
+ cl_assert_equal_i(git_buf_len(&target_path), st.st_size);
+
+ git_buf_free(&target_path);
+}
+
+void test_core_link__lstat_symlink_directory(void)
+{
+ git_buf target_path = GIT_BUF_INIT;
+ struct stat st;
+
+ git_buf_join(&target_path, '/', clar_sandbox_path(), "lstat_dirtarget");
+
+ p_mkdir("lstat_dirtarget", 0777);
+ do_symlink(git_buf_cstr(&target_path), "lstat_dirlink", 1);
+
+ cl_must_pass(p_lstat("lstat_dirtarget", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+
+ cl_must_pass(p_lstat("lstat_dirlink", &st));
+ cl_assert(S_ISLNK(st.st_mode));
+ cl_assert_equal_i(git_buf_len(&target_path), st.st_size);
+
+ git_buf_free(&target_path);
+}
+
+void test_core_link__lstat_dangling_symlink(void)
+{
+ struct stat st;
+
+ do_symlink("lstat_nonexistent", "lstat_dangling", 0);
+
+ cl_must_fail(p_lstat("lstat_nonexistent", &st));
+
+ cl_must_pass(p_lstat("lstat_dangling", &st));
+ cl_assert(S_ISLNK(st.st_mode));
+ cl_assert_equal_i(strlen("lstat_nonexistent"), st.st_size);
+}
+
+void test_core_link__lstat_dangling_symlink_directory(void)
+{
+ struct stat st;
+
+ do_symlink("lstat_nonexistent", "lstat_dangling_dir", 1);
+
+ cl_must_fail(p_lstat("lstat_nonexistent", &st));
+
+ cl_must_pass(p_lstat("lstat_dangling_dir", &st));
+ cl_assert(S_ISLNK(st.st_mode));
+ cl_assert_equal_i(strlen("lstat_nonexistent"), st.st_size);
+}
+
+void test_core_link__stat_junction(void)
+{
+#ifdef GIT_WIN32
+ git_buf target_path = GIT_BUF_INIT;
+ struct stat st;
+
+ git_buf_join(&target_path, '/', clar_sandbox_path(), "stat_junctarget");
+
+ p_mkdir("stat_junctarget", 0777);
+ do_junction(git_buf_cstr(&target_path), "stat_junction");
+
+ cl_must_pass(p_stat("stat_junctarget", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+
+ cl_must_pass(p_stat("stat_junction", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+
+ git_buf_free(&target_path);
+#endif
+}
+
+void test_core_link__stat_dangling_junction(void)
+{
+#ifdef GIT_WIN32
+ git_buf target_path = GIT_BUF_INIT;
+ struct stat st;
+
+ git_buf_join(&target_path, '/', clar_sandbox_path(), "stat_nonexistent_junctarget");
+
+ p_mkdir("stat_nonexistent_junctarget", 0777);
+ do_junction(git_buf_cstr(&target_path), "stat_dangling_junction");
+
+ RemoveDirectory("stat_nonexistent_junctarget");
+
+ cl_must_fail(p_stat("stat_nonexistent_junctarget", &st));
+ cl_must_fail(p_stat("stat_dangling_junction", &st));
+
+ git_buf_free(&target_path);
+#endif
+}
+
+void test_core_link__lstat_junction(void)
+{
+#ifdef GIT_WIN32
+ git_buf target_path = GIT_BUF_INIT;
+ struct stat st;
+
+ git_buf_join(&target_path, '/', clar_sandbox_path(), "lstat_junctarget");
+
+ p_mkdir("lstat_junctarget", 0777);
+ do_junction(git_buf_cstr(&target_path), "lstat_junction");
+
+ cl_must_pass(p_lstat("lstat_junctarget", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+
+ cl_must_pass(p_lstat("lstat_junction", &st));
+ cl_assert(S_ISLNK(st.st_mode));
+
+ git_buf_free(&target_path);
+#endif
+}
+
+void test_core_link__lstat_dangling_junction(void)
+{
+#ifdef GIT_WIN32
+ git_buf target_path = GIT_BUF_INIT;
+ struct stat st;
+
+ git_buf_join(&target_path, '/', clar_sandbox_path(), "lstat_nonexistent_junctarget");
+
+ p_mkdir("lstat_nonexistent_junctarget", 0777);
+ do_junction(git_buf_cstr(&target_path), "lstat_dangling_junction");
+
+ RemoveDirectory("lstat_nonexistent_junctarget");
+
+ cl_must_fail(p_lstat("lstat_nonexistent_junctarget", &st));
+
+ cl_must_pass(p_lstat("lstat_dangling_junction", &st));
+ cl_assert(S_ISLNK(st.st_mode));
+ cl_assert_equal_i(git_buf_len(&target_path), st.st_size);
+
+ git_buf_free(&target_path);
+#endif
+}
+
+void test_core_link__stat_hardlink(void)
+{
+ struct stat st;
+
+ cl_git_rewritefile("stat_hardlink1", "This file has many names!\n");
+ do_hardlink("stat_hardlink1", "stat_hardlink2");
+
+ cl_must_pass(p_stat("stat_hardlink1", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(26, st.st_size);
+
+ cl_must_pass(p_stat("stat_hardlink2", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(26, st.st_size);
+}
+
+void test_core_link__lstat_hardlink(void)
+{
+ struct stat st;
+
+ cl_git_rewritefile("lstat_hardlink1", "This file has many names!\n");
+ do_hardlink("lstat_hardlink1", "lstat_hardlink2");
+
+ cl_must_pass(p_lstat("lstat_hardlink1", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(26, st.st_size);
+
+ cl_must_pass(p_lstat("lstat_hardlink2", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(26, st.st_size);
+}
+
+void test_core_link__stat_reparse_point(void)
+{
+#ifdef GIT_WIN32
+ struct stat st;
+
+ /* Generic reparse points should be treated as regular files, only
+ * symlinks and junctions should be treated as links.
+ */
+
+ cl_git_rewritefile("stat_reparse", "This is a reparse point!\n");
+ do_custom_reparse("stat_reparse");
+
+ cl_must_pass(p_lstat("stat_reparse", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(25, st.st_size);
+#endif
+}
+
+void test_core_link__lstat_reparse_point(void)
+{
+#ifdef GIT_WIN32
+ struct stat st;
+
+ cl_git_rewritefile("lstat_reparse", "This is a reparse point!\n");
+ do_custom_reparse("lstat_reparse");
+
+ cl_must_pass(p_lstat("lstat_reparse", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_equal_i(25, st.st_size);
+#endif
+}
+
+void test_core_link__readlink_nonexistent_file(void)
+{
+ char buf[2048];
+
+ cl_must_fail(p_readlink("readlink_nonexistent", buf, 2048));
+ cl_assert_equal_i(ENOENT, errno);
+}
+
+void test_core_link__readlink_normal_file(void)
+{
+ char buf[2048];
+
+ cl_git_rewritefile("readlink_regfile", "This is a regular file!\n");
+ cl_must_fail(p_readlink("readlink_regfile", buf, 2048));
+ cl_assert_equal_i(EINVAL, errno);
+}
+
+void test_core_link__readlink_symlink(void)
+{
+ git_buf target_path = GIT_BUF_INIT;
+ int len;
+ char buf[2048];
+
+ git_buf_join(&target_path, '/', clar_sandbox_path(), "readlink_target");
+
+ cl_git_rewritefile("readlink_target", "This is the target of a symlink\n");
+ do_symlink(git_buf_cstr(&target_path), "readlink_link", 0);
+
+ len = p_readlink("readlink_link", buf, 2048);
+ cl_must_pass(len);
+
+ buf[len] = 0;
+
+ cl_assert_equal_s(git_buf_cstr(unslashify(&target_path)), buf);
+
+ git_buf_free(&target_path);
+}
+
+void test_core_link__readlink_dangling(void)
+{
+ git_buf target_path = GIT_BUF_INIT;
+ int len;
+ char buf[2048];
+
+ git_buf_join(&target_path, '/', clar_sandbox_path(), "readlink_nonexistent");
+
+ do_symlink(git_buf_cstr(&target_path), "readlink_dangling", 0);
+
+ len = p_readlink("readlink_dangling", buf, 2048);
+ cl_must_pass(len);
+
+ buf[len] = 0;
+
+ cl_assert_equal_s(git_buf_cstr(unslashify(&target_path)), buf);
+
+ git_buf_free(&target_path);
+}
+
+void test_core_link__readlink_multiple(void)
+{
+ git_buf target_path = GIT_BUF_INIT,
+ path3 = GIT_BUF_INIT, path2 = GIT_BUF_INIT, path1 = GIT_BUF_INIT;
+ int len;
+ char buf[2048];
+
+ git_buf_join(&target_path, '/', clar_sandbox_path(), "readlink_final");
+ git_buf_join(&path3, '/', clar_sandbox_path(), "readlink_3");
+ git_buf_join(&path2, '/', clar_sandbox_path(), "readlink_2");
+ git_buf_join(&path1, '/', clar_sandbox_path(), "readlink_1");
+
+ do_symlink(git_buf_cstr(&target_path), git_buf_cstr(&path3), 0);
+ do_symlink(git_buf_cstr(&path3), git_buf_cstr(&path2), 0);
+ do_symlink(git_buf_cstr(&path2), git_buf_cstr(&path1), 0);
+
+ len = p_readlink("readlink_1", buf, 2048);
+ cl_must_pass(len);
+
+ buf[len] = 0;
+
+ cl_assert_equal_s(git_buf_cstr(unslashify(&path2)), buf);
+
+ git_buf_free(&path1);
+ git_buf_free(&path2);
+ git_buf_free(&path3);
+ git_buf_free(&target_path);
+}
diff --git a/tests/core/mkdir.c b/tests/core/mkdir.c
new file mode 100644
index 000000000..a8c5b10ae
--- /dev/null
+++ b/tests/core/mkdir.c
@@ -0,0 +1,188 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+#include "path.h"
+#include "posix.h"
+
+static void cleanup_basic_dirs(void *ref)
+{
+ GIT_UNUSED(ref);
+ 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)
+{
+ cl_set_cleanup(cleanup_basic_dirs, NULL);
+
+ /* make a directory */
+ cl_assert(!git_path_isdir("d0"));
+ cl_git_pass(git_futils_mkdir("d0", NULL, 0755, 0));
+ cl_assert(git_path_isdir("d0"));
+
+ /* make a path */
+ cl_assert(!git_path_isdir("d1"));
+ cl_git_pass(git_futils_mkdir("d1/d1.1/d1.2", NULL, 0755, GIT_MKDIR_PATH));
+ cl_assert(git_path_isdir("d1"));
+ cl_assert(git_path_isdir("d1/d1.1"));
+ cl_assert(git_path_isdir("d1/d1.1/d1.2"));
+
+ /* make a dir exclusively */
+ cl_assert(!git_path_isdir("d2"));
+ cl_git_pass(git_futils_mkdir("d2", NULL, 0755, GIT_MKDIR_EXCL));
+ cl_assert(git_path_isdir("d2"));
+
+ /* make exclusive failure */
+ cl_git_fail(git_futils_mkdir("d2", NULL, 0755, GIT_MKDIR_EXCL));
+
+ /* make a path exclusively */
+ cl_assert(!git_path_isdir("d3"));
+ cl_git_pass(git_futils_mkdir("d3/d3.1/d3.2", NULL, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL));
+ cl_assert(git_path_isdir("d3"));
+ cl_assert(git_path_isdir("d3/d3.1/d3.2"));
+
+ /* make exclusive path failure */
+ cl_git_fail(git_futils_mkdir("d3/d3.1/d3.2", NULL, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL));
+ /* ??? Should EXCL only apply to the last item in the path? */
+
+ /* path with trailing slash? */
+ cl_assert(!git_path_isdir("d4"));
+ cl_git_pass(git_futils_mkdir("d4/d4.1/", NULL, 0755, GIT_MKDIR_PATH));
+ cl_assert(git_path_isdir("d4/d4.1"));
+}
+
+static void cleanup_basedir(void *ref)
+{
+ GIT_UNUSED(ref);
+ git_futils_rmdir_r("base", NULL, GIT_RMDIR_EMPTY_HIERARCHY);
+}
+
+void test_core_mkdir__with_base(void)
+{
+#define BASEDIR "base/dir/here"
+
+ cl_set_cleanup(cleanup_basedir, NULL);
+
+ cl_git_pass(git_futils_mkdir(BASEDIR, NULL, 0755, GIT_MKDIR_PATH));
+
+ cl_git_pass(git_futils_mkdir("a", BASEDIR, 0755, 0));
+ cl_assert(git_path_isdir(BASEDIR "/a"));
+
+ cl_git_pass(git_futils_mkdir("b/b1/b2", BASEDIR, 0755, GIT_MKDIR_PATH));
+ cl_assert(git_path_isdir(BASEDIR "/b/b1/b2"));
+
+ /* exclusive with existing base */
+ cl_git_pass(git_futils_mkdir("c/c1/c2", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL));
+
+ /* fail: exclusive with duplicated suffix */
+ cl_git_fail(git_futils_mkdir("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL));
+
+ /* fail: exclusive with any duplicated component */
+ cl_git_fail(git_futils_mkdir("c/cz/cz", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL));
+
+ /* success: exclusive without path */
+ cl_git_pass(git_futils_mkdir("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_EXCL));
+
+ /* path with shorter base and existing dirs */
+ cl_git_pass(git_futils_mkdir("dir/here/d/", "base", 0755, GIT_MKDIR_PATH));
+ cl_assert(git_path_isdir("base/dir/here/d"));
+
+ /* fail: path with shorter base and existing dirs */
+ cl_git_fail(git_futils_mkdir("dir/here/e/", "base", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL));
+
+ /* fail: base with missing components */
+ cl_git_fail(git_futils_mkdir("f/", "base/missing", 0755, GIT_MKDIR_PATH));
+
+ /* success: shift missing component to path */
+ cl_git_pass(git_futils_mkdir("missing/f/", "base/", 0755, GIT_MKDIR_PATH));
+}
+
+static void cleanup_chmod_root(void *ref)
+{
+ mode_t *mode = ref;
+
+ if (*mode != 0) {
+ (void)p_umask(*mode);
+ git__free(mode);
+ }
+
+ git_futils_rmdir_r("r", NULL, GIT_RMDIR_EMPTY_HIERARCHY);
+}
+
+#define check_mode(X,A) check_mode_at_line((X), (A), __FILE__, __LINE__)
+
+static void check_mode_at_line(
+ mode_t expected, mode_t actual, const char *file, int line)
+{
+ /* FAT filesystems don't support exec bit, nor group/world bits */
+ if (!cl_is_chmod_supported()) {
+ expected &= 0600;
+ actual &= 0600;
+ }
+
+ clar__assert_equal(
+ file, line, "expected_mode != actual_mode", 1,
+ "%07o", (int)expected, (int)(actual & 0777));
+}
+
+void test_core_mkdir__chmods(void)
+{
+ struct stat st;
+ mode_t *old = git__malloc(sizeof(mode_t));
+ *old = p_umask(022);
+
+ cl_set_cleanup(cleanup_chmod_root, old);
+
+ cl_git_pass(git_futils_mkdir("r", NULL, 0777, 0));
+
+ cl_git_pass(git_futils_mkdir("mode/is/important", "r", 0777, GIT_MKDIR_PATH));
+
+ cl_git_pass(git_path_lstat("r/mode", &st));
+ check_mode(0755, st.st_mode);
+ cl_git_pass(git_path_lstat("r/mode/is", &st));
+ check_mode(0755, st.st_mode);
+ cl_git_pass(git_path_lstat("r/mode/is/important", &st));
+ check_mode(0755, st.st_mode);
+
+ cl_git_pass(git_futils_mkdir("mode2/is2/important2", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD));
+
+ cl_git_pass(git_path_lstat("r/mode2", &st));
+ check_mode(0755, st.st_mode);
+ cl_git_pass(git_path_lstat("r/mode2/is2", &st));
+ check_mode(0755, st.st_mode);
+ cl_git_pass(git_path_lstat("r/mode2/is2/important2", &st));
+ check_mode(0777, st.st_mode);
+
+ cl_git_pass(git_futils_mkdir("mode3/is3/important3", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH));
+
+ cl_git_pass(git_path_lstat("r/mode3", &st));
+ check_mode(0777, st.st_mode);
+ cl_git_pass(git_path_lstat("r/mode3/is3", &st));
+ check_mode(0777, st.st_mode);
+ cl_git_pass(git_path_lstat("r/mode3/is3/important3", &st));
+ check_mode(0777, st.st_mode);
+
+ /* test that we chmod existing dir */
+
+ cl_git_pass(git_futils_mkdir("mode/is/important", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD));
+
+ cl_git_pass(git_path_lstat("r/mode", &st));
+ check_mode(0755, st.st_mode);
+ cl_git_pass(git_path_lstat("r/mode/is", &st));
+ check_mode(0755, st.st_mode);
+ cl_git_pass(git_path_lstat("r/mode/is/important", &st));
+ check_mode(0777, st.st_mode);
+
+ /* test that we chmod even existing dirs if CHMOD_PATH is set */
+
+ cl_git_pass(git_futils_mkdir("mode2/is2/important2.1", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH));
+
+ cl_git_pass(git_path_lstat("r/mode2", &st));
+ check_mode(0777, st.st_mode);
+ cl_git_pass(git_path_lstat("r/mode2/is2", &st));
+ check_mode(0777, st.st_mode);
+ cl_git_pass(git_path_lstat("r/mode2/is2/important2.1", &st));
+ check_mode(0777, st.st_mode);
+}
diff --git a/tests/core/oid.c b/tests/core/oid.c
new file mode 100644
index 000000000..7ee6fb67d
--- /dev/null
+++ b/tests/core/oid.c
@@ -0,0 +1,70 @@
+#include "clar_libgit2.h"
+
+static git_oid id;
+static git_oid idp;
+static git_oid idm;
+const char *str_oid = "ae90f12eea699729ed24555e40b9fd669da12a12";
+const char *str_oid_p = "ae90f12eea699729ed";
+const char *str_oid_m = "ae90f12eea699729ed24555e40b9fd669da12a12THIS IS EXTRA TEXT THAT SHOULD GET IGNORED";
+
+void test_core_oid__initialize(void)
+{
+ cl_git_pass(git_oid_fromstr(&id, str_oid));
+ cl_git_pass(git_oid_fromstrp(&idp, str_oid_p));
+ cl_git_fail(git_oid_fromstrp(&idm, str_oid_m));
+}
+
+void test_core_oid__streq(void)
+{
+ cl_assert_equal_i(0, git_oid_streq(&id, str_oid));
+ cl_assert_equal_i(-1, git_oid_streq(&id, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
+
+ cl_assert_equal_i(-1, git_oid_streq(&id, "deadbeef"));
+ cl_assert_equal_i(-1, git_oid_streq(&id, "I'm not an oid.... :)"));
+
+ cl_assert_equal_i(0, git_oid_streq(&idp, "ae90f12eea699729ed0000000000000000000000"));
+ cl_assert_equal_i(0, git_oid_streq(&idp, "ae90f12eea699729ed"));
+ cl_assert_equal_i(-1, git_oid_streq(&idp, "ae90f12eea699729ed1"));
+ cl_assert_equal_i(-1, git_oid_streq(&idp, "ae90f12eea699729ec"));
+ cl_assert_equal_i(-1, git_oid_streq(&idp, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
+
+ cl_assert_equal_i(-1, git_oid_streq(&idp, "deadbeef"));
+ cl_assert_equal_i(-1, git_oid_streq(&idp, "I'm not an oid.... :)"));
+}
+
+void test_core_oid__strcmp(void)
+{
+ cl_assert_equal_i(0, git_oid_strcmp(&id, str_oid));
+ cl_assert(git_oid_strcmp(&id, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") < 0);
+
+ cl_assert(git_oid_strcmp(&id, "deadbeef") < 0);
+ cl_assert_equal_i(-1, git_oid_strcmp(&id, "I'm not an oid.... :)"));
+
+ cl_assert_equal_i(0, git_oid_strcmp(&idp, "ae90f12eea699729ed0000000000000000000000"));
+ cl_assert_equal_i(0, git_oid_strcmp(&idp, "ae90f12eea699729ed"));
+ cl_assert(git_oid_strcmp(&idp, "ae90f12eea699729ed1") < 0);
+ cl_assert(git_oid_strcmp(&idp, "ae90f12eea699729ec") > 0);
+ cl_assert(git_oid_strcmp(&idp, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef") < 0);
+
+ cl_assert(git_oid_strcmp(&idp, "deadbeef") < 0);
+ cl_assert_equal_i(-1, git_oid_strcmp(&idp, "I'm not an oid.... :)"));
+}
+
+void test_core_oid__ncmp(void)
+{
+ cl_assert(!git_oid_ncmp(&id, &idp, 0));
+ cl_assert(!git_oid_ncmp(&id, &idp, 1));
+ cl_assert(!git_oid_ncmp(&id, &idp, 2));
+ cl_assert(!git_oid_ncmp(&id, &idp, 17));
+ cl_assert(!git_oid_ncmp(&id, &idp, 18));
+ cl_assert(git_oid_ncmp(&id, &idp, 19));
+ cl_assert(git_oid_ncmp(&id, &idp, 40));
+ cl_assert(git_oid_ncmp(&id, &idp, 41));
+ cl_assert(git_oid_ncmp(&id, &idp, 42));
+
+ cl_assert(!git_oid_ncmp(&id, &id, 0));
+ cl_assert(!git_oid_ncmp(&id, &id, 1));
+ cl_assert(!git_oid_ncmp(&id, &id, 39));
+ cl_assert(!git_oid_ncmp(&id, &id, 40));
+ cl_assert(!git_oid_ncmp(&id, &id, 41));
+}
diff --git a/tests/core/oidmap.c b/tests/core/oidmap.c
new file mode 100644
index 000000000..ec4b5e775
--- /dev/null
+++ b/tests/core/oidmap.c
@@ -0,0 +1,110 @@
+#include "clar_libgit2.h"
+#include "oidmap.h"
+
+GIT__USE_OIDMAP;
+
+typedef struct {
+ git_oid oid;
+ size_t extra;
+} oidmap_item;
+
+#define NITEMS 0x0fff
+
+void test_core_oidmap__basic(void)
+{
+ git_oidmap *map;
+ oidmap_item items[NITEMS];
+ uint32_t i, j;
+
+ for (i = 0; i < NITEMS; ++i) {
+ items[i].extra = i;
+ for (j = 0; j < GIT_OID_RAWSZ / 4; ++j) {
+ items[i].oid.id[j * 4 ] = (unsigned char)i;
+ items[i].oid.id[j * 4 + 1] = (unsigned char)(i >> 8);
+ items[i].oid.id[j * 4 + 2] = (unsigned char)(i >> 16);
+ items[i].oid.id[j * 4 + 3] = (unsigned char)(i >> 24);
+ }
+ }
+
+ map = git_oidmap_alloc();
+ cl_assert(map != NULL);
+
+ for (i = 0; i < NITEMS; ++i) {
+ khiter_t pos;
+ int ret;
+
+ pos = kh_get(oid, map, &items[i].oid);
+ cl_assert(pos == kh_end(map));
+
+ pos = kh_put(oid, map, &items[i].oid, &ret);
+ cl_assert(ret != 0);
+
+ kh_val(map, pos) = &items[i];
+ }
+
+
+ for (i = 0; i < NITEMS; ++i) {
+ khiter_t pos;
+
+ pos = kh_get(oid, map, &items[i].oid);
+ cl_assert(pos != kh_end(map));
+
+ cl_assert_equal_p(kh_val(map, pos), &items[i]);
+ }
+
+ git_oidmap_free(map);
+}
+
+void test_core_oidmap__hash_collision(void)
+{
+ git_oidmap *map;
+ oidmap_item items[NITEMS];
+ uint32_t i, j;
+
+ for (i = 0; i < NITEMS; ++i) {
+ uint32_t segment = i / 8;
+ int modi = i - (segment * 8);
+
+ items[i].extra = i;
+
+ for (j = 0; j < GIT_OID_RAWSZ / 4; ++j) {
+ items[i].oid.id[j * 4 ] = (unsigned char)modi;
+ items[i].oid.id[j * 4 + 1] = (unsigned char)(modi >> 8);
+ items[i].oid.id[j * 4 + 2] = (unsigned char)(modi >> 16);
+ items[i].oid.id[j * 4 + 3] = (unsigned char)(modi >> 24);
+ }
+
+ items[i].oid.id[ 8] = (unsigned char)i;
+ items[i].oid.id[ 9] = (unsigned char)(i >> 8);
+ items[i].oid.id[10] = (unsigned char)(i >> 16);
+ items[i].oid.id[11] = (unsigned char)(i >> 24);
+ }
+
+ map = git_oidmap_alloc();
+ cl_assert(map != NULL);
+
+ for (i = 0; i < NITEMS; ++i) {
+ khiter_t pos;
+ int ret;
+
+ pos = kh_get(oid, map, &items[i].oid);
+ cl_assert(pos == kh_end(map));
+
+ pos = kh_put(oid, map, &items[i].oid, &ret);
+ cl_assert(ret != 0);
+
+ kh_val(map, pos) = &items[i];
+ }
+
+
+ for (i = 0; i < NITEMS; ++i) {
+ khiter_t pos;
+
+ pos = kh_get(oid, map, &items[i].oid);
+ cl_assert(pos != kh_end(map));
+
+ cl_assert_equal_p(kh_val(map, pos), &items[i]);
+ }
+
+ git_oidmap_free(map);
+}
diff --git a/tests/core/opts.c b/tests/core/opts.c
new file mode 100644
index 000000000..3173c648b
--- /dev/null
+++ b/tests/core/opts.c
@@ -0,0 +1,19 @@
+#include "clar_libgit2.h"
+#include "cache.h"
+
+void test_core_opts__readwrite(void)
+{
+ size_t old_val = 0;
+ size_t new_val = 0;
+
+ git_libgit2_opts(GIT_OPT_GET_MWINDOW_SIZE, &old_val);
+ git_libgit2_opts(GIT_OPT_SET_MWINDOW_SIZE, (size_t)1234);
+ git_libgit2_opts(GIT_OPT_GET_MWINDOW_SIZE, &new_val);
+
+ cl_assert(new_val == 1234);
+
+ git_libgit2_opts(GIT_OPT_SET_MWINDOW_SIZE, old_val);
+ git_libgit2_opts(GIT_OPT_GET_MWINDOW_SIZE, &new_val);
+
+ cl_assert(new_val == old_val);
+}
diff --git a/tests/core/path.c b/tests/core/path.c
new file mode 100644
index 000000000..471491b87
--- /dev/null
+++ b/tests/core/path.c
@@ -0,0 +1,631 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+
+static void
+check_dirname(const char *A, const char *B)
+{
+ git_buf dir = GIT_BUF_INIT;
+ char *dir2;
+
+ cl_assert(git_path_dirname_r(&dir, A) >= 0);
+ cl_assert_equal_s(B, dir.ptr);
+ git_buf_free(&dir);
+
+ cl_assert((dir2 = git_path_dirname(A)) != NULL);
+ cl_assert_equal_s(B, dir2);
+ git__free(dir2);
+}
+
+static void
+check_basename(const char *A, const char *B)
+{
+ git_buf base = GIT_BUF_INIT;
+ char *base2;
+
+ cl_assert(git_path_basename_r(&base, A) >= 0);
+ cl_assert_equal_s(B, base.ptr);
+ git_buf_free(&base);
+
+ cl_assert((base2 = git_path_basename(A)) != NULL);
+ cl_assert_equal_s(B, base2);
+ git__free(base2);
+}
+
+static void
+check_topdir(const char *A, const char *B)
+{
+ const char *dir;
+
+ cl_assert((dir = git_path_topdir(A)) != NULL);
+ cl_assert_equal_s(B, dir);
+}
+
+static void
+check_joinpath(const char *path_a, const char *path_b, const char *expected_path)
+{
+ git_buf joined_path = GIT_BUF_INIT;
+
+ cl_git_pass(git_buf_joinpath(&joined_path, path_a, path_b));
+ cl_assert_equal_s(expected_path, joined_path.ptr);
+
+ git_buf_free(&joined_path);
+}
+
+static void
+check_joinpath_n(
+ const char *path_a,
+ const char *path_b,
+ const char *path_c,
+ const char *path_d,
+ const char *expected_path)
+{
+ git_buf joined_path = GIT_BUF_INIT;
+
+ cl_git_pass(git_buf_join_n(&joined_path, '/', 4,
+ path_a, path_b, path_c, path_d));
+ cl_assert_equal_s(expected_path, joined_path.ptr);
+
+ git_buf_free(&joined_path);
+}
+
+
+/* get the dirname of a path */
+void test_core_path__00_dirname(void)
+{
+ check_dirname(NULL, ".");
+ check_dirname("", ".");
+ check_dirname("a", ".");
+ check_dirname("/", "/");
+ check_dirname("/usr", "/");
+ check_dirname("/usr/", "/");
+ check_dirname("/usr/lib", "/usr");
+ check_dirname("/usr/lib/", "/usr");
+ check_dirname("/usr/lib//", "/usr");
+ check_dirname("usr/lib", "usr");
+ check_dirname("usr/lib/", "usr");
+ check_dirname("usr/lib//", "usr");
+ check_dirname(".git/", ".");
+
+ check_dirname(REP16("/abc"), REP15("/abc"));
+
+#ifdef GIT_WIN32
+ check_dirname("C:/path/", "C:/");
+ check_dirname("C:/path", "C:/");
+ check_dirname("//computername/path/", "//computername/");
+ check_dirname("//computername/path", "//computername/");
+ check_dirname("//computername/sub/path/", "//computername/sub");
+ check_dirname("//computername/sub/path", "//computername/sub");
+#endif
+}
+
+/* get the base name of a path */
+void test_core_path__01_basename(void)
+{
+ check_basename(NULL, ".");
+ check_basename("", ".");
+ check_basename("a", "a");
+ check_basename("/", "/");
+ check_basename("/usr", "usr");
+ check_basename("/usr/", "usr");
+ check_basename("/usr/lib", "lib");
+ check_basename("/usr/lib//", "lib");
+ check_basename("usr/lib", "lib");
+
+ check_basename(REP16("/abc"), "abc");
+ check_basename(REP1024("/abc"), "abc");
+}
+
+/* get the latest component in a path */
+void test_core_path__02_topdir(void)
+{
+ check_topdir(".git/", ".git/");
+ check_topdir("/.git/", ".git/");
+ check_topdir("usr/local/.git/", ".git/");
+ check_topdir("./.git/", ".git/");
+ check_topdir("/usr/.git/", ".git/");
+ check_topdir("/", "/");
+ check_topdir("a/", "a/");
+
+ cl_assert(git_path_topdir("/usr/.git") == NULL);
+ cl_assert(git_path_topdir(".") == NULL);
+ cl_assert(git_path_topdir("") == NULL);
+ cl_assert(git_path_topdir("a") == NULL);
+}
+
+/* properly join path components */
+void test_core_path__05_joins(void)
+{
+ check_joinpath("", "", "");
+ check_joinpath("", "a", "a");
+ check_joinpath("", "/a", "/a");
+ check_joinpath("a", "", "a/");
+ check_joinpath("a", "/", "a/");
+ check_joinpath("a", "b", "a/b");
+ check_joinpath("/", "a", "/a");
+ check_joinpath("/", "", "/");
+ check_joinpath("/a", "/b", "/a/b");
+ check_joinpath("/a", "/b/", "/a/b/");
+ check_joinpath("/a/", "b/", "/a/b/");
+ check_joinpath("/a/", "/b/", "/a/b/");
+
+ check_joinpath("/abcd", "/defg", "/abcd/defg");
+ check_joinpath("/abcd", "/defg/", "/abcd/defg/");
+ check_joinpath("/abcd/", "defg/", "/abcd/defg/");
+ check_joinpath("/abcd/", "/defg/", "/abcd/defg/");
+
+ check_joinpath("/abcdefgh", "/12345678", "/abcdefgh/12345678");
+ check_joinpath("/abcdefgh", "/12345678/", "/abcdefgh/12345678/");
+ check_joinpath("/abcdefgh/", "12345678/", "/abcdefgh/12345678/");
+
+ check_joinpath(REP1024("aaaa"), "", REP1024("aaaa") "/");
+ check_joinpath(REP1024("aaaa/"), "", REP1024("aaaa/"));
+ check_joinpath(REP1024("/aaaa"), "", REP1024("/aaaa") "/");
+
+ check_joinpath(REP1024("aaaa"), REP1024("bbbb"),
+ REP1024("aaaa") "/" REP1024("bbbb"));
+ check_joinpath(REP1024("/aaaa"), REP1024("/bbbb"),
+ REP1024("/aaaa") REP1024("/bbbb"));
+}
+
+/* properly join path components for more than one path */
+void test_core_path__06_long_joins(void)
+{
+ check_joinpath_n("", "", "", "", "");
+ check_joinpath_n("", "a", "", "", "a/");
+ check_joinpath_n("a", "", "", "", "a/");
+ check_joinpath_n("", "", "", "a", "a");
+ check_joinpath_n("a", "b", "", "/c/d/", "a/b/c/d/");
+ check_joinpath_n("a", "b", "", "/c/d", "a/b/c/d");
+ check_joinpath_n("abcd", "efgh", "ijkl", "mnop", "abcd/efgh/ijkl/mnop");
+ check_joinpath_n("abcd/", "efgh/", "ijkl/", "mnop/", "abcd/efgh/ijkl/mnop/");
+ check_joinpath_n("/abcd/", "/efgh/", "/ijkl/", "/mnop/", "/abcd/efgh/ijkl/mnop/");
+
+ check_joinpath_n(REP1024("a"), REP1024("b"), REP1024("c"), REP1024("d"),
+ REP1024("a") "/" REP1024("b") "/"
+ REP1024("c") "/" REP1024("d"));
+ check_joinpath_n(REP1024("/a"), REP1024("/b"), REP1024("/c"), REP1024("/d"),
+ REP1024("/a") REP1024("/b")
+ REP1024("/c") REP1024("/d"));
+}
+
+
+static void
+check_path_to_dir(
+ const char* path,
+ const char* expected)
+{
+ git_buf tgt = GIT_BUF_INIT;
+
+ git_buf_sets(&tgt, path);
+ cl_git_pass(git_path_to_dir(&tgt));
+ cl_assert_equal_s(expected, tgt.ptr);
+
+ git_buf_free(&tgt);
+}
+
+static void
+check_string_to_dir(
+ const char* path,
+ size_t maxlen,
+ const char* expected)
+{
+ size_t len = strlen(path);
+ char *buf = git__malloc(len + 2);
+ cl_assert(buf);
+
+ strncpy(buf, path, len + 2);
+
+ git_path_string_to_dir(buf, maxlen);
+
+ cl_assert_equal_s(expected, buf);
+
+ git__free(buf);
+}
+
+/* convert paths to dirs */
+void test_core_path__07_path_to_dir(void)
+{
+ check_path_to_dir("", "");
+ check_path_to_dir(".", "./");
+ check_path_to_dir("./", "./");
+ check_path_to_dir("a/", "a/");
+ check_path_to_dir("ab", "ab/");
+ /* make sure we try just under and just over an expansion that will
+ * require a realloc
+ */
+ check_path_to_dir("abcdef", "abcdef/");
+ check_path_to_dir("abcdefg", "abcdefg/");
+ check_path_to_dir("abcdefgh", "abcdefgh/");
+ check_path_to_dir("abcdefghi", "abcdefghi/");
+ check_path_to_dir(REP1024("abcd") "/", REP1024("abcd") "/");
+ check_path_to_dir(REP1024("abcd"), REP1024("abcd") "/");
+
+ check_string_to_dir("", 1, "");
+ check_string_to_dir(".", 1, ".");
+ check_string_to_dir(".", 2, "./");
+ check_string_to_dir(".", 3, "./");
+ check_string_to_dir("abcd", 3, "abcd");
+ check_string_to_dir("abcd", 4, "abcd");
+ check_string_to_dir("abcd", 5, "abcd/");
+ check_string_to_dir("abcd", 6, "abcd/");
+}
+
+/* join path to itself */
+void test_core_path__08_self_join(void)
+{
+ git_buf path = GIT_BUF_INIT;
+ size_t asize = 0;
+
+ asize = path.asize;
+ cl_git_pass(git_buf_sets(&path, "/foo"));
+ cl_assert_equal_s(path.ptr, "/foo");
+ cl_assert(asize < path.asize);
+
+ asize = path.asize;
+ cl_git_pass(git_buf_joinpath(&path, path.ptr, "this is a new string"));
+ cl_assert_equal_s(path.ptr, "/foo/this is a new string");
+ cl_assert(asize < path.asize);
+
+ asize = path.asize;
+ cl_git_pass(git_buf_joinpath(&path, path.ptr, "/grow the buffer, grow the buffer, grow the buffer"));
+ cl_assert_equal_s(path.ptr, "/foo/this is a new string/grow the buffer, grow the buffer, grow the buffer");
+ cl_assert(asize < path.asize);
+
+ git_buf_free(&path);
+ cl_git_pass(git_buf_sets(&path, "/foo/bar"));
+
+ cl_git_pass(git_buf_joinpath(&path, path.ptr + 4, "baz"));
+ cl_assert_equal_s(path.ptr, "/bar/baz");
+
+ asize = path.asize;
+ cl_git_pass(git_buf_joinpath(&path, path.ptr + 4, "somethinglongenoughtorealloc"));
+ cl_assert_equal_s(path.ptr, "/baz/somethinglongenoughtorealloc");
+ cl_assert(asize < path.asize);
+
+ git_buf_free(&path);
+}
+
+static void check_percent_decoding(const char *expected_result, const char *input)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ cl_git_pass(git__percent_decode(&buf, input));
+ cl_assert_equal_s(expected_result, git_buf_cstr(&buf));
+
+ git_buf_free(&buf);
+}
+
+void test_core_path__09_percent_decode(void)
+{
+ check_percent_decoding("abcd", "abcd");
+ check_percent_decoding("a2%", "a2%");
+ check_percent_decoding("a2%3", "a2%3");
+ check_percent_decoding("a2%%3", "a2%%3");
+ check_percent_decoding("a2%3z", "a2%3z");
+ check_percent_decoding("a,", "a%2c");
+ check_percent_decoding("a21", "a2%31");
+ check_percent_decoding("a2%1", "a2%%31");
+ check_percent_decoding("a bc ", "a%20bc%20");
+ check_percent_decoding("Vicent Mart" "\355", "Vicent%20Mart%ED");
+}
+
+static void check_fromurl(const char *expected_result, const char *input, int should_fail)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ assert(should_fail || expected_result);
+
+ if (!should_fail) {
+ cl_git_pass(git_path_fromurl(&buf, input));
+ cl_assert_equal_s(expected_result, git_buf_cstr(&buf));
+ } else
+ cl_git_fail(git_path_fromurl(&buf, input));
+
+ git_buf_free(&buf);
+}
+
+#ifdef GIT_WIN32
+#define ABS_PATH_MARKER ""
+#else
+#define ABS_PATH_MARKER "/"
+#endif
+
+void test_core_path__10_fromurl(void)
+{
+ /* Failing cases */
+ check_fromurl(NULL, "a", 1);
+ check_fromurl(NULL, "http:///c:/Temp%20folder/note.txt", 1);
+ check_fromurl(NULL, "file://c:/Temp%20folder/note.txt", 1);
+ check_fromurl(NULL, "file:////c:/Temp%20folder/note.txt", 1);
+ check_fromurl(NULL, "file:///", 1);
+ check_fromurl(NULL, "file:////", 1);
+ check_fromurl(NULL, "file://servername/c:/Temp%20folder/note.txt", 1);
+
+ /* Passing cases */
+ check_fromurl(ABS_PATH_MARKER "c:/Temp folder/note.txt", "file:///c:/Temp%20folder/note.txt", 0);
+ check_fromurl(ABS_PATH_MARKER "c:/Temp folder/note.txt", "file://localhost/c:/Temp%20folder/note.txt", 0);
+ check_fromurl(ABS_PATH_MARKER "c:/Temp+folder/note.txt", "file:///c:/Temp+folder/note.txt", 0);
+ check_fromurl(ABS_PATH_MARKER "a", "file:///a", 0);
+}
+
+typedef struct {
+ int expect_idx;
+ int cancel_after;
+ char **expect;
+} check_walkup_info;
+
+#define CANCEL_VALUE 1234
+
+static int check_one_walkup_step(void *ref, git_buf *path)
+{
+ check_walkup_info *info = (check_walkup_info *)ref;
+
+ if (!info->cancel_after) {
+ cl_assert_equal_s(info->expect[info->expect_idx], "[CANCEL]");
+ return CANCEL_VALUE;
+ }
+ info->cancel_after--;
+
+ cl_assert(info->expect[info->expect_idx] != NULL);
+ cl_assert_equal_s(info->expect[info->expect_idx], path->ptr);
+ info->expect_idx++;
+
+ return 0;
+}
+
+void test_core_path__11_walkup(void)
+{
+ git_buf p = GIT_BUF_INIT;
+ char *expect[] = {
+ "/a/b/c/d/e/", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL,
+ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL,
+ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL,
+ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", "/a/", "/", NULL,
+ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL,
+ "/a/b/c/d/e", "/a/b/c/d/", "/a/b/c/", "/a/b/", NULL,
+ "this is a path", NULL,
+ "///a///b///c///d///e///", "///a///b///c///d///", "///a///b///c///", "///a///b///", "///a///", "///", NULL,
+ NULL
+ };
+ char *root[] = { NULL, NULL, "/", "", "/a/b", "/a/b/", NULL, NULL, NULL };
+ int i, j;
+ check_walkup_info info;
+
+ info.expect = expect;
+ info.cancel_after = -1;
+
+ for (i = 0, j = 0; expect[i] != NULL; i++, j++) {
+
+ git_buf_sets(&p, expect[i]);
+
+ info.expect_idx = i;
+ cl_git_pass(
+ git_path_walk_up(&p, root[j], check_one_walkup_step, &info)
+ );
+
+ cl_assert_equal_s(p.ptr, expect[i]);
+
+ /* skip to next run of expectations */
+ while (expect[i] != NULL) i++;
+ }
+
+ git_buf_free(&p);
+}
+
+void test_core_path__11a_walkup_cancel(void)
+{
+ git_buf p = GIT_BUF_INIT;
+ int cancel[] = { 3, 2, 1, 0 };
+ char *expect[] = {
+ "/a/b/c/d/e/", "/a/b/c/d/", "/a/b/c/", "[CANCEL]", NULL,
+ "/a/b/c/d/e", "/a/b/c/d/", "[CANCEL]", NULL,
+ "/a/b/c/d/e", "[CANCEL]", NULL,
+ "[CANCEL]", NULL,
+ NULL
+ };
+ char *root[] = { NULL, NULL, "/", "", NULL };
+ int i, j;
+ check_walkup_info info;
+
+ info.expect = expect;
+
+ for (i = 0, j = 0; expect[i] != NULL; i++, j++) {
+
+ git_buf_sets(&p, expect[i]);
+
+ info.cancel_after = cancel[j];
+ info.expect_idx = i;
+
+ cl_assert_equal_i(
+ CANCEL_VALUE,
+ git_path_walk_up(&p, root[j], check_one_walkup_step, &info)
+ );
+
+ /* skip to next run of expectations */
+ while (expect[i] != NULL) i++;
+ }
+
+ git_buf_free(&p);
+}
+
+void test_core_path__12_offset_to_path_root(void)
+{
+ cl_assert(git_path_root("non/rooted/path") == -1);
+ cl_assert(git_path_root("/rooted/path") == 0);
+
+#ifdef GIT_WIN32
+ /* Windows specific tests */
+ cl_assert(git_path_root("C:non/rooted/path") == -1);
+ cl_assert(git_path_root("C:/rooted/path") == 2);
+ cl_assert(git_path_root("//computername/sharefolder/resource") == 14);
+ cl_assert(git_path_root("//computername/sharefolder") == 14);
+ cl_assert(git_path_root("//computername") == -1);
+#endif
+}
+
+#define NON_EXISTING_FILEPATH "i_hope_i_do_not_exist"
+
+void test_core_path__13_cannot_prettify_a_non_existing_file(void)
+{
+ git_buf p = GIT_BUF_INIT;
+
+ cl_must_pass(git_path_exists(NON_EXISTING_FILEPATH) == false);
+ cl_assert_equal_i(GIT_ENOTFOUND, git_path_prettify(&p, NON_EXISTING_FILEPATH, NULL));
+ cl_assert_equal_i(GIT_ENOTFOUND, git_path_prettify(&p, NON_EXISTING_FILEPATH "/so-do-i", NULL));
+
+ git_buf_free(&p);
+}
+
+void test_core_path__14_apply_relative(void)
+{
+ git_buf p = GIT_BUF_INIT;
+
+ cl_git_pass(git_buf_sets(&p, "/this/is/a/base"));
+
+ cl_git_pass(git_path_apply_relative(&p, "../test"));
+ cl_assert_equal_s("/this/is/a/test", p.ptr);
+
+ cl_git_pass(git_path_apply_relative(&p, "../../the/./end"));
+ cl_assert_equal_s("/this/is/the/end", p.ptr);
+
+ cl_git_pass(git_path_apply_relative(&p, "./of/this/../the/string"));
+ cl_assert_equal_s("/this/is/the/end/of/the/string", p.ptr);
+
+ cl_git_pass(git_path_apply_relative(&p, "../../../../../.."));
+ cl_assert_equal_s("/this/", p.ptr);
+
+ cl_git_pass(git_path_apply_relative(&p, "../"));
+ cl_assert_equal_s("/", p.ptr);
+
+ cl_git_fail(git_path_apply_relative(&p, "../../.."));
+
+
+ cl_git_pass(git_buf_sets(&p, "d:/another/test"));
+
+ cl_git_pass(git_path_apply_relative(&p, "../.."));
+ cl_assert_equal_s("d:/", p.ptr);
+
+ cl_git_pass(git_path_apply_relative(&p, "from/here/to/../and/./back/."));
+ cl_assert_equal_s("d:/from/here/and/back/", p.ptr);
+
+
+ cl_git_pass(git_buf_sets(&p, "https://my.url.com/test.git"));
+
+ cl_git_pass(git_path_apply_relative(&p, "../another.git"));
+ cl_assert_equal_s("https://my.url.com/another.git", p.ptr);
+
+ cl_git_pass(git_path_apply_relative(&p, "../full/path/url.patch"));
+ cl_assert_equal_s("https://my.url.com/full/path/url.patch", p.ptr);
+
+ cl_git_pass(git_path_apply_relative(&p, ".."));
+ cl_assert_equal_s("https://my.url.com/full/path/", p.ptr);
+
+ cl_git_pass(git_path_apply_relative(&p, "../../../"));
+ cl_assert_equal_s("https://", p.ptr);
+
+
+ cl_git_pass(git_buf_sets(&p, "../../this/is/relative"));
+
+ cl_git_pass(git_path_apply_relative(&p, "../../preserves/the/prefix"));
+ cl_assert_equal_s("../../this/preserves/the/prefix", p.ptr);
+
+ cl_git_pass(git_path_apply_relative(&p, "../../../../that"));
+ cl_assert_equal_s("../../that", p.ptr);
+
+ cl_git_pass(git_path_apply_relative(&p, "../there"));
+ cl_assert_equal_s("../../there", p.ptr);
+ git_buf_free(&p);
+}
+
+static void assert_resolve_relative(
+ git_buf *buf, const char *expected, const char *path)
+{
+ cl_git_pass(git_buf_sets(buf, path));
+ cl_git_pass(git_path_resolve_relative(buf, 0));
+ cl_assert_equal_s(expected, buf->ptr);
+}
+
+void test_core_path__15_resolve_relative(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ assert_resolve_relative(&buf, "", "");
+ assert_resolve_relative(&buf, "", ".");
+ assert_resolve_relative(&buf, "", "./");
+ assert_resolve_relative(&buf, "..", "..");
+ assert_resolve_relative(&buf, "../", "../");
+ assert_resolve_relative(&buf, "..", "./..");
+ assert_resolve_relative(&buf, "../", "./../");
+ assert_resolve_relative(&buf, "../", "../.");
+ assert_resolve_relative(&buf, "../", ".././");
+ assert_resolve_relative(&buf, "../..", "../..");
+ assert_resolve_relative(&buf, "../../", "../../");
+
+ assert_resolve_relative(&buf, "/", "/");
+ assert_resolve_relative(&buf, "/", "/.");
+
+ assert_resolve_relative(&buf, "", "a/..");
+ assert_resolve_relative(&buf, "", "a/../");
+ assert_resolve_relative(&buf, "", "a/../.");
+
+ assert_resolve_relative(&buf, "/a", "/a");
+ assert_resolve_relative(&buf, "/a/", "/a/.");
+ assert_resolve_relative(&buf, "/", "/a/../");
+ assert_resolve_relative(&buf, "/", "/a/../.");
+ assert_resolve_relative(&buf, "/", "/a/.././");
+
+ assert_resolve_relative(&buf, "a", "a");
+ assert_resolve_relative(&buf, "a/", "a/");
+ assert_resolve_relative(&buf, "a/", "a/.");
+ assert_resolve_relative(&buf, "a/", "a/./");
+
+ assert_resolve_relative(&buf, "a/b", "a//b");
+ assert_resolve_relative(&buf, "a/b/c", "a/b/c");
+ assert_resolve_relative(&buf, "b/c", "./b/c");
+ assert_resolve_relative(&buf, "a/c", "a/./c");
+ assert_resolve_relative(&buf, "a/b/", "a/b/.");
+
+ assert_resolve_relative(&buf, "/a/b/c", "///a/b/c");
+ assert_resolve_relative(&buf, "/", "////");
+ assert_resolve_relative(&buf, "/a", "///a");
+ assert_resolve_relative(&buf, "/", "///.");
+ assert_resolve_relative(&buf, "/", "///a/..");
+
+ assert_resolve_relative(&buf, "../../path", "../../test//../././path");
+ assert_resolve_relative(&buf, "../d", "a/b/../../../c/../d");
+
+ cl_git_pass(git_buf_sets(&buf, "/.."));
+ cl_git_fail(git_path_resolve_relative(&buf, 0));
+
+ cl_git_pass(git_buf_sets(&buf, "/./.."));
+ cl_git_fail(git_path_resolve_relative(&buf, 0));
+
+ cl_git_pass(git_buf_sets(&buf, "/.//.."));
+ cl_git_fail(git_path_resolve_relative(&buf, 0));
+
+ cl_git_pass(git_buf_sets(&buf, "/../."));
+ cl_git_fail(git_path_resolve_relative(&buf, 0));
+
+ cl_git_pass(git_buf_sets(&buf, "/../.././../a"));
+ cl_git_fail(git_path_resolve_relative(&buf, 0));
+
+ cl_git_pass(git_buf_sets(&buf, "////.."));
+ cl_git_fail(git_path_resolve_relative(&buf, 0));
+
+ /* things that start with Windows network paths */
+#ifdef GIT_WIN32
+ assert_resolve_relative(&buf, "//a/b/c", "//a/b/c");
+ assert_resolve_relative(&buf, "//a/", "//a/b/..");
+ assert_resolve_relative(&buf, "//a/b/c", "//a/Q/../b/x/y/../../c");
+
+ cl_git_pass(git_buf_sets(&buf, "//a/b/../.."));
+ cl_git_fail(git_path_resolve_relative(&buf, 0));
+#else
+ assert_resolve_relative(&buf, "/a/b/c", "//a/b/c");
+ assert_resolve_relative(&buf, "/a/", "//a/b/..");
+ assert_resolve_relative(&buf, "/a/b/c", "//a/Q/../b/x/y/../../c");
+ assert_resolve_relative(&buf, "/", "//a/b/../..");
+#endif
+
+ git_buf_free(&buf);
+}
diff --git a/tests/core/pool.c b/tests/core/pool.c
new file mode 100644
index 000000000..351d0c20f
--- /dev/null
+++ b/tests/core/pool.c
@@ -0,0 +1,146 @@
+#include "clar_libgit2.h"
+#include "pool.h"
+#include "git2/oid.h"
+
+void test_core_pool__0(void)
+{
+ int i;
+ git_pool p;
+ void *ptr;
+
+ cl_git_pass(git_pool_init(&p, 1, 4000));
+
+ for (i = 1; i < 10000; i *= 2) {
+ ptr = git_pool_malloc(&p, i);
+ cl_assert(ptr != NULL);
+ cl_assert(git_pool__ptr_in_pool(&p, ptr));
+ cl_assert(!git_pool__ptr_in_pool(&p, &i));
+ }
+
+ /* 1+2+4+8+16+32+64+128+256+512+1024 -> original block */
+ /* 2048 -> 1 block */
+ /* 4096 -> 1 block */
+ /* 8192 -> 1 block */
+
+ cl_assert(git_pool__open_pages(&p) + git_pool__full_pages(&p) == 4);
+
+ git_pool_clear(&p);
+}
+
+void test_core_pool__1(void)
+{
+ int i;
+ git_pool p;
+
+ cl_git_pass(git_pool_init(&p, 1, 4000));
+
+ for (i = 2010; i > 0; i--)
+ cl_assert(git_pool_malloc(&p, i) != NULL);
+
+ /* with fixed page size, allocation must end up with these values */
+ cl_assert(git_pool__open_pages(&p) == 1);
+ cl_assert(git_pool__full_pages(&p) == 505);
+
+ git_pool_clear(&p);
+
+ cl_git_pass(git_pool_init(&p, 1, 4100));
+
+ for (i = 2010; i > 0; i--)
+ cl_assert(git_pool_malloc(&p, i) != NULL);
+
+ /* with fixed page size, allocation must end up with these values */
+ cl_assert(git_pool__open_pages(&p) == 1);
+ cl_assert(git_pool__full_pages(&p) == 492);
+
+ git_pool_clear(&p);
+}
+
+static char to_hex[] = "0123456789abcdef";
+
+void test_core_pool__2(void)
+{
+ git_pool p;
+ char oid_hex[GIT_OID_HEXSZ];
+ git_oid *oid;
+ int i, j;
+
+ memset(oid_hex, '0', sizeof(oid_hex));
+
+ cl_git_pass(git_pool_init(&p, sizeof(git_oid), 100));
+
+ for (i = 1000; i < 10000; i++) {
+ oid = git_pool_malloc(&p, 1);
+ cl_assert(oid != NULL);
+
+ for (j = 0; j < 8; j++)
+ oid_hex[j] = to_hex[(i >> (4 * j)) & 0x0f];
+ cl_git_pass(git_oid_fromstr(oid, oid_hex));
+ }
+
+ /* with fixed page size, allocation must end up with these values */
+ cl_assert(git_pool__open_pages(&p) == 0);
+ cl_assert(git_pool__full_pages(&p) == 90);
+
+ git_pool_clear(&p);
+}
+
+void test_core_pool__free_list(void)
+{
+ int i;
+ git_pool p;
+ void *ptr, *ptrs[50];
+
+ cl_git_pass(git_pool_init(&p, 100, 100));
+
+ for (i = 0; i < 10; ++i) {
+ ptr = git_pool_malloc(&p, 1);
+ cl_assert(ptr != NULL);
+ }
+ cl_assert_equal_i(10, (int)p.items);
+
+ for (i = 0; i < 50; ++i) {
+ ptrs[i] = git_pool_malloc(&p, 1);
+ cl_assert(ptrs[i] != NULL);
+ }
+ cl_assert_equal_i(60, (int)p.items);
+
+ git_pool_free(&p, ptr);
+ cl_assert_equal_i(60, (int)p.items);
+
+ git_pool_free_array(&p, 50, ptrs);
+ cl_assert_equal_i(60, (int)p.items);
+
+ for (i = 0; i < 50; ++i) {
+ ptrs[i] = git_pool_malloc(&p, 1);
+ cl_assert(ptrs[i] != NULL);
+ }
+ cl_assert_equal_i(60, (int)p.items);
+
+ for (i = 0; i < 111; ++i) {
+ ptr = git_pool_malloc(&p, 1);
+ cl_assert(ptr != NULL);
+ }
+ cl_assert_equal_i(170, (int)p.items);
+
+ git_pool_free_array(&p, 50, ptrs);
+ cl_assert_equal_i(170, (int)p.items);
+
+ for (i = 0; i < 50; ++i) {
+ ptrs[i] = git_pool_malloc(&p, 1);
+ cl_assert(ptrs[i] != NULL);
+ }
+ cl_assert_equal_i(170, (int)p.items);
+
+ git_pool_clear(&p);
+}
+
+void test_core_pool__strndup_limit(void)
+{
+ git_pool p;
+
+ cl_git_pass(git_pool_init(&p, 1, 100));
+ /* ensure 64 bit doesn't overflow */
+ cl_assert(git_pool_strndup(&p, "foo", (size_t)-1) == NULL);
+ git_pool_clear(&p);
+}
+
diff --git a/tests/core/posix.c b/tests/core/posix.c
new file mode 100644
index 000000000..1cef937cd
--- /dev/null
+++ b/tests/core/posix.c
@@ -0,0 +1,99 @@
+#ifndef _WIN32
+# include <arpa/inet.h>
+# include <sys/socket.h>
+# include <netinet/in.h>
+#else
+# include <ws2tcpip.h>
+# ifdef _MSC_VER
+# pragma comment(lib, "ws2_32")
+# endif
+#endif
+
+#include "clar_libgit2.h"
+#include "posix.h"
+
+void test_core_posix__initialize(void)
+{
+#ifdef GIT_WIN32
+ /* on win32, the WSA context needs to be initialized
+ * before any socket calls can be performed */
+ WSADATA wsd;
+
+ cl_git_pass(WSAStartup(MAKEWORD(2,2), &wsd));
+ cl_assert(LOBYTE(wsd.wVersion) == 2 && HIBYTE(wsd.wVersion) == 2);
+#endif
+}
+
+static bool supports_ipv6(void)
+{
+#ifdef GIT_WIN32
+ /* IPv6 is supported on Vista and newer */
+ return git_has_win32_version(6, 0, 0);
+#else
+ return 1;
+#endif
+}
+
+void test_core_posix__inet_pton(void)
+{
+ struct in_addr addr;
+ struct in6_addr addr6;
+ size_t i;
+
+ struct in_addr_data {
+ const char *p;
+ const uint8_t n[4];
+ };
+
+ struct in6_addr_data {
+ const char *p;
+ const uint8_t n[16];
+ };
+
+ static struct in_addr_data in_addr_data[] = {
+ { "0.0.0.0", { 0, 0, 0, 0 } },
+ { "10.42.101.8", { 10, 42, 101, 8 } },
+ { "127.0.0.1", { 127, 0, 0, 1 } },
+ { "140.177.10.12", { 140, 177, 10, 12 } },
+ { "204.232.175.90", { 204, 232, 175, 90 } },
+ { "255.255.255.255", { 255, 255, 255, 255 } },
+ };
+
+ static struct in6_addr_data in6_addr_data[] = {
+ { "::", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
+ { "::1", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } },
+ { "0:0:0:0:0:0:0:1", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } },
+ { "2001:db8:8714:3a90::12", { 0x20, 0x01, 0x0d, 0xb8, 0x87, 0x14, 0x3a, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12 } },
+ { "fe80::f8ba:c2d6:86be:3645", { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xba, 0xc2, 0xd6, 0x86, 0xbe, 0x36, 0x45 } },
+ { "::ffff:204.152.189.116", { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xcc, 0x98, 0xbd, 0x74 } },
+ };
+
+ /* Test some ipv4 addresses */
+ for (i = 0; i < 6; i++) {
+ cl_assert(p_inet_pton(AF_INET, in_addr_data[i].p, &addr) == 1);
+ cl_assert(memcmp(&addr, in_addr_data[i].n, sizeof(struct in_addr)) == 0);
+ }
+
+ /* Test some ipv6 addresses */
+ if (supports_ipv6())
+ {
+ for (i = 0; i < 6; i++) {
+ cl_assert(p_inet_pton(AF_INET6, in6_addr_data[i].p, &addr6) == 1);
+ cl_assert(memcmp(&addr6, in6_addr_data[i].n, sizeof(struct in6_addr)) == 0);
+ }
+ }
+
+ /* Test some invalid strings */
+ cl_assert(p_inet_pton(AF_INET, "", &addr) == 0);
+ cl_assert(p_inet_pton(AF_INET, "foo", &addr) == 0);
+ cl_assert(p_inet_pton(AF_INET, " 127.0.0.1", &addr) == 0);
+ cl_assert(p_inet_pton(AF_INET, "bar", &addr) == 0);
+ cl_assert(p_inet_pton(AF_INET, "10.foo.bar.1", &addr) == 0);
+
+ /* Test unsupported address families */
+ cl_git_fail(p_inet_pton(12, "52.472", NULL)); /* AF_DECnet */
+ cl_assert_equal_i(EAFNOSUPPORT, errno);
+
+ cl_git_fail(p_inet_pton(5, "315.124", NULL)); /* AF_CHAOS */
+ cl_assert_equal_i(EAFNOSUPPORT, errno);
+}
diff --git a/tests/core/pqueue.c b/tests/core/pqueue.c
new file mode 100644
index 000000000..bcd4eea9f
--- /dev/null
+++ b/tests/core/pqueue.c
@@ -0,0 +1,128 @@
+#include "clar_libgit2.h"
+#include "pqueue.h"
+
+static int cmp_ints(const void *v1, const void *v2)
+{
+ int i1 = *(int *)v1, i2 = *(int *)v2;
+ return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
+}
+
+void test_core_pqueue__items_are_put_in_order(void)
+{
+ git_pqueue pq;
+ int i, vals[20];
+
+ cl_git_pass(git_pqueue_init(&pq, 0, 20, cmp_ints));
+
+ for (i = 0; i < 20; ++i) {
+ if (i < 10)
+ vals[i] = 10 - i; /* 10 down to 1 */
+ else
+ vals[i] = i + 1; /* 11 up to 20 */
+
+ cl_git_pass(git_pqueue_insert(&pq, &vals[i]));
+ }
+
+ cl_assert_equal_i(20, git_pqueue_size(&pq));
+
+ for (i = 1; i <= 20; ++i) {
+ void *p = git_pqueue_pop(&pq);
+ cl_assert(p);
+ cl_assert_equal_i(i, *(int *)p);
+ }
+
+ cl_assert_equal_i(0, git_pqueue_size(&pq));
+
+ git_pqueue_free(&pq);
+}
+
+void test_core_pqueue__interleave_inserts_and_pops(void)
+{
+ git_pqueue pq;
+ int chunk, v, i, vals[200];
+
+ cl_git_pass(git_pqueue_init(&pq, 0, 20, cmp_ints));
+
+ for (v = 0, chunk = 20; chunk <= 200; chunk += 20) {
+ /* push the next 20 */
+ for (; v < chunk; ++v) {
+ vals[v] = (v & 1) ? 200 - v : v;
+ cl_git_pass(git_pqueue_insert(&pq, &vals[v]));
+ }
+
+ /* pop the lowest 10 */
+ for (i = 0; i < 10; ++i)
+ (void)git_pqueue_pop(&pq);
+ }
+
+ cl_assert_equal_i(100, git_pqueue_size(&pq));
+
+ /* at this point, we've popped 0-99 */
+
+ for (v = 100; v < 200; ++v) {
+ void *p = git_pqueue_pop(&pq);
+ cl_assert(p);
+ cl_assert_equal_i(v, *(int *)p);
+ }
+
+ cl_assert_equal_i(0, git_pqueue_size(&pq));
+
+ git_pqueue_free(&pq);
+}
+
+void test_core_pqueue__max_heap_size(void)
+{
+ git_pqueue pq;
+ int i, vals[100];
+
+ cl_git_pass(git_pqueue_init(&pq, GIT_PQUEUE_FIXED_SIZE, 50, cmp_ints));
+
+ for (i = 0; i < 100; ++i) {
+ vals[i] = (i & 1) ? 100 - i : i;
+ cl_git_pass(git_pqueue_insert(&pq, &vals[i]));
+ }
+
+ cl_assert_equal_i(50, git_pqueue_size(&pq));
+
+ for (i = 50; i < 100; ++i) {
+ void *p = git_pqueue_pop(&pq);
+ cl_assert(p);
+ cl_assert_equal_i(i, *(int *)p);
+ }
+
+ cl_assert_equal_i(0, git_pqueue_size(&pq));
+
+ git_pqueue_free(&pq);
+
+}
+
+static int cmp_ints_like_commit_time(const void *a, const void *b)
+{
+ return *((const int *)a) < *((const int *)b);
+}
+
+void test_core_pqueue__interleaved_pushes_and_pops(void)
+{
+ git_pqueue pq;
+ int i, j, *val;
+ static int commands[] =
+ { 6, 9, 8, 0, 5, 0, 7, 0, 4, 3, 0, 0, 0, 4, 0, 2, 0, 1, 0, 0, -1 };
+ static int expected[] =
+ { 9, 8, 7, 6, 5, 4, 4, 3, 2, 1, -1 };
+
+ cl_git_pass(git_pqueue_init(&pq, 0, 10, cmp_ints_like_commit_time));
+
+ for (i = 0, j = 0; commands[i] >= 0; ++i) {
+ if (!commands[i]) {
+ cl_assert((val = git_pqueue_pop(&pq)) != NULL);
+ cl_assert_equal_i(expected[j], *val);
+ ++j;
+ } else {
+ cl_git_pass(git_pqueue_insert(&pq, &commands[i]));
+ }
+ }
+
+ cl_assert_equal_i(0, git_pqueue_size(&pq));
+ git_pqueue_free(&pq);
+}
+
diff --git a/tests/core/rmdir.c b/tests/core/rmdir.c
new file mode 100644
index 000000000..f0b0bfa42
--- /dev/null
+++ b/tests/core/rmdir.c
@@ -0,0 +1,98 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+
+static const char *empty_tmp_dir = "test_gitfo_rmdir_recurs_test";
+
+void test_core_rmdir__initialize(void)
+{
+ git_buf path = GIT_BUF_INIT;
+
+ cl_must_pass(p_mkdir(empty_tmp_dir, 0777));
+
+ cl_git_pass(git_buf_joinpath(&path, empty_tmp_dir, "/one"));
+ cl_must_pass(p_mkdir(path.ptr, 0777));
+
+ cl_git_pass(git_buf_joinpath(&path, empty_tmp_dir, "/one/two_one"));
+ cl_must_pass(p_mkdir(path.ptr, 0777));
+
+ cl_git_pass(git_buf_joinpath(&path, empty_tmp_dir, "/one/two_two"));
+ cl_must_pass(p_mkdir(path.ptr, 0777));
+
+ cl_git_pass(git_buf_joinpath(&path, empty_tmp_dir, "/one/two_two/three"));
+ cl_must_pass(p_mkdir(path.ptr, 0777));
+
+ cl_git_pass(git_buf_joinpath(&path, empty_tmp_dir, "/two"));
+ cl_must_pass(p_mkdir(path.ptr, 0777));
+
+ git_buf_free(&path);
+}
+
+/* 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_RMDIR_EMPTY_HIERARCHY));
+}
+
+/* make sure non-empty dir cannot be deleted recusively */
+void test_core_rmdir__fail_to_delete_non_empty_dir(void)
+{
+ git_buf file = GIT_BUF_INIT;
+
+ cl_git_pass(git_buf_joinpath(&file, empty_tmp_dir, "/two/file.txt"));
+
+ cl_git_mkfile(git_buf_cstr(&file), "dummy");
+
+ 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_RMDIR_EMPTY_HIERARCHY));
+
+ git_buf_free(&file);
+}
+
+void test_core_rmdir__can_skip_non_empty_dir(void)
+{
+ git_buf file = GIT_BUF_INIT;
+
+ cl_git_pass(git_buf_joinpath(&file, empty_tmp_dir, "/two/file.txt"));
+
+ cl_git_mkfile(git_buf_cstr(&file), "dummy");
+
+ 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_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/core/sortedcache.c b/tests/core/sortedcache.c
new file mode 100644
index 000000000..c1869bee0
--- /dev/null
+++ b/tests/core/sortedcache.c
@@ -0,0 +1,363 @@
+#include "clar_libgit2.h"
+#include "sortedcache.h"
+
+static int name_only_cmp(const void *a, const void *b)
+{
+ return strcmp(a, b);
+}
+
+void test_core_sortedcache__name_only(void)
+{
+ git_sortedcache *sc;
+ void *item;
+ size_t pos;
+
+ cl_git_pass(git_sortedcache_new(
+ &sc, 0, NULL, NULL, name_only_cmp, NULL));
+
+ cl_git_pass(git_sortedcache_wlock(sc));
+ cl_git_pass(git_sortedcache_upsert(&item, sc, "aaa"));
+ cl_git_pass(git_sortedcache_upsert(&item, sc, "bbb"));
+ cl_git_pass(git_sortedcache_upsert(&item, sc, "zzz"));
+ cl_git_pass(git_sortedcache_upsert(&item, sc, "mmm"));
+ cl_git_pass(git_sortedcache_upsert(&item, sc, "iii"));
+ git_sortedcache_wunlock(sc);
+
+ cl_assert_equal_sz(5, git_sortedcache_entrycount(sc));
+
+ cl_assert((item = git_sortedcache_lookup(sc, "aaa")) != NULL);
+ cl_assert_equal_s("aaa", item);
+ cl_assert((item = git_sortedcache_lookup(sc, "mmm")) != NULL);
+ cl_assert_equal_s("mmm", item);
+ cl_assert((item = git_sortedcache_lookup(sc, "zzz")) != NULL);
+ cl_assert_equal_s("zzz", item);
+ cl_assert(git_sortedcache_lookup(sc, "qqq") == NULL);
+
+ cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL);
+ cl_assert_equal_s("aaa", item);
+ cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL);
+ cl_assert_equal_s("bbb", item);
+ cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL);
+ cl_assert_equal_s("iii", item);
+ cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL);
+ cl_assert_equal_s("mmm", item);
+ cl_assert((item = git_sortedcache_entry(sc, 4)) != NULL);
+ cl_assert_equal_s("zzz", item);
+ cl_assert(git_sortedcache_entry(sc, 5) == NULL);
+
+ cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "aaa"));
+ cl_assert_equal_sz(0, pos);
+ cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "iii"));
+ cl_assert_equal_sz(2, pos);
+ cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "zzz"));
+ cl_assert_equal_sz(4, pos);
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "abc"));
+
+ git_sortedcache_clear(sc, true);
+
+ cl_assert_equal_sz(0, git_sortedcache_entrycount(sc));
+ cl_assert(git_sortedcache_entry(sc, 0) == NULL);
+ cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL);
+ cl_assert(git_sortedcache_entry(sc, 0) == NULL);
+
+ git_sortedcache_free(sc);
+}
+
+typedef struct {
+ int value;
+ char smaller_value;
+ char path[GIT_FLEX_ARRAY];
+} sortedcache_test_struct;
+
+static int sortedcache_test_struct_cmp(const void *a_, const void *b_)
+{
+ const sortedcache_test_struct *a = a_, *b = b_;
+ return strcmp(a->path, b->path);
+}
+
+static void sortedcache_test_struct_free(void *payload, void *item_)
+{
+ sortedcache_test_struct *item = item_;
+ int *count = payload;
+ (*count)++;
+ item->smaller_value = 0;
+}
+
+void test_core_sortedcache__in_memory(void)
+{
+ git_sortedcache *sc;
+ sortedcache_test_struct *item;
+ int free_count = 0;
+
+ cl_git_pass(git_sortedcache_new(
+ &sc, offsetof(sortedcache_test_struct, path),
+ sortedcache_test_struct_free, &free_count,
+ sortedcache_test_struct_cmp, NULL));
+
+ cl_git_pass(git_sortedcache_wlock(sc));
+ cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "aaa"));
+ item->value = 10;
+ item->smaller_value = 1;
+ cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "bbb"));
+ item->value = 20;
+ item->smaller_value = 2;
+ cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "zzz"));
+ item->value = 30;
+ item->smaller_value = 26;
+ cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "mmm"));
+ item->value = 40;
+ item->smaller_value = 14;
+ cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "iii"));
+ item->value = 50;
+ item->smaller_value = 9;
+ git_sortedcache_wunlock(sc);
+
+ cl_assert_equal_sz(5, git_sortedcache_entrycount(sc));
+
+ cl_git_pass(git_sortedcache_rlock(sc));
+
+ cl_assert((item = git_sortedcache_lookup(sc, "aaa")) != NULL);
+ cl_assert_equal_s("aaa", item->path);
+ cl_assert_equal_i(10, item->value);
+ cl_assert((item = git_sortedcache_lookup(sc, "mmm")) != NULL);
+ cl_assert_equal_s("mmm", item->path);
+ cl_assert_equal_i(40, item->value);
+ cl_assert((item = git_sortedcache_lookup(sc, "zzz")) != NULL);
+ cl_assert_equal_s("zzz", item->path);
+ cl_assert_equal_i(30, item->value);
+ cl_assert(git_sortedcache_lookup(sc, "abc") == NULL);
+
+ /* not on Windows:
+ * cl_git_pass(git_sortedcache_rlock(sc)); -- grab more than one
+ */
+
+ cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL);
+ cl_assert_equal_s("aaa", item->path);
+ cl_assert_equal_i(10, item->value);
+ cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL);
+ cl_assert_equal_s("bbb", item->path);
+ cl_assert_equal_i(20, item->value);
+ cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL);
+ cl_assert_equal_s("iii", item->path);
+ cl_assert_equal_i(50, item->value);
+ cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL);
+ cl_assert_equal_s("mmm", item->path);
+ cl_assert_equal_i(40, item->value);
+ cl_assert((item = git_sortedcache_entry(sc, 4)) != NULL);
+ cl_assert_equal_s("zzz", item->path);
+ cl_assert_equal_i(30, item->value);
+ cl_assert(git_sortedcache_entry(sc, 5) == NULL);
+
+ git_sortedcache_runlock(sc);
+ /* git_sortedcache_runlock(sc); */
+
+ cl_assert_equal_i(0, free_count);
+
+ git_sortedcache_clear(sc, true);
+
+ cl_assert_equal_i(5, free_count);
+
+ cl_assert_equal_sz(0, git_sortedcache_entrycount(sc));
+ cl_assert(git_sortedcache_entry(sc, 0) == NULL);
+ cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL);
+ cl_assert(git_sortedcache_entry(sc, 0) == NULL);
+
+ free_count = 0;
+
+ cl_git_pass(git_sortedcache_wlock(sc));
+ cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "testing"));
+ item->value = 10;
+ item->smaller_value = 3;
+ cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "again"));
+ item->value = 20;
+ item->smaller_value = 1;
+ cl_git_pass(git_sortedcache_upsert((void **)&item, sc, "final"));
+ item->value = 30;
+ item->smaller_value = 2;
+ git_sortedcache_wunlock(sc);
+
+ cl_assert_equal_sz(3, git_sortedcache_entrycount(sc));
+
+ cl_assert((item = git_sortedcache_lookup(sc, "testing")) != NULL);
+ cl_assert_equal_s("testing", item->path);
+ cl_assert_equal_i(10, item->value);
+ cl_assert((item = git_sortedcache_lookup(sc, "again")) != NULL);
+ cl_assert_equal_s("again", item->path);
+ cl_assert_equal_i(20, item->value);
+ cl_assert((item = git_sortedcache_lookup(sc, "final")) != NULL);
+ cl_assert_equal_s("final", item->path);
+ cl_assert_equal_i(30, item->value);
+ cl_assert(git_sortedcache_lookup(sc, "zzz") == NULL);
+
+ cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL);
+ cl_assert_equal_s("again", item->path);
+ cl_assert_equal_i(20, item->value);
+ cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL);
+ cl_assert_equal_s("final", item->path);
+ cl_assert_equal_i(30, item->value);
+ cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL);
+ cl_assert_equal_s("testing", item->path);
+ cl_assert_equal_i(10, item->value);
+ cl_assert(git_sortedcache_entry(sc, 3) == NULL);
+
+ {
+ size_t pos;
+
+ cl_git_pass(git_sortedcache_wlock(sc));
+
+ cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "again"));
+ cl_assert_equal_sz(0, pos);
+ cl_git_pass(git_sortedcache_remove(sc, pos));
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "again"));
+
+ cl_assert_equal_sz(2, git_sortedcache_entrycount(sc));
+
+ cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "testing"));
+ cl_assert_equal_sz(1, pos);
+ cl_git_pass(git_sortedcache_remove(sc, pos));
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "testing"));
+
+ cl_assert_equal_sz(1, git_sortedcache_entrycount(sc));
+
+ cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "final"));
+ cl_assert_equal_sz(0, pos);
+ cl_git_pass(git_sortedcache_remove(sc, pos));
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "final"));
+
+ cl_assert_equal_sz(0, git_sortedcache_entrycount(sc));
+
+ git_sortedcache_wunlock(sc);
+ }
+
+ git_sortedcache_free(sc);
+
+ cl_assert_equal_i(3, free_count);
+}
+
+static void sortedcache_test_reload(git_sortedcache *sc)
+{
+ int count = 0;
+ git_buf buf = GIT_BUF_INIT;
+ char *scan, *after;
+ sortedcache_test_struct *item;
+
+ cl_assert(git_sortedcache_lockandload(sc, &buf) > 0);
+
+ git_sortedcache_clear(sc, false); /* clear once we already have lock */
+
+ for (scan = buf.ptr; *scan; scan = after + 1) {
+ int val = strtol(scan, &after, 0);
+ cl_assert(after > scan);
+ scan = after;
+
+ for (scan = after; git__isspace(*scan); ++scan) /* find start */;
+ for (after = scan; *after && *after != '\n'; ++after) /* find eol */;
+ *after = '\0';
+
+ cl_git_pass(git_sortedcache_upsert((void **)&item, sc, scan));
+
+ item->value = val;
+ item->smaller_value = (char)(count++);
+ }
+
+ git_sortedcache_wunlock(sc);
+
+ git_buf_free(&buf);
+}
+
+void test_core_sortedcache__on_disk(void)
+{
+ git_sortedcache *sc;
+ sortedcache_test_struct *item;
+ int free_count = 0;
+ size_t pos;
+
+ cl_git_mkfile("cacheitems.txt", "10 abc\n20 bcd\n30 cde\n");
+
+ cl_git_pass(git_sortedcache_new(
+ &sc, offsetof(sortedcache_test_struct, path),
+ sortedcache_test_struct_free, &free_count,
+ sortedcache_test_struct_cmp, "cacheitems.txt"));
+
+ /* should need to reload the first time */
+
+ sortedcache_test_reload(sc);
+
+ /* test what we loaded */
+
+ cl_assert_equal_sz(3, git_sortedcache_entrycount(sc));
+
+ cl_assert((item = git_sortedcache_lookup(sc, "abc")) != NULL);
+ cl_assert_equal_s("abc", item->path);
+ cl_assert_equal_i(10, item->value);
+ cl_assert((item = git_sortedcache_lookup(sc, "cde")) != NULL);
+ cl_assert_equal_s("cde", item->path);
+ cl_assert_equal_i(30, item->value);
+ cl_assert(git_sortedcache_lookup(sc, "aaa") == NULL);
+
+ cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL);
+ cl_assert_equal_s("abc", item->path);
+ cl_assert_equal_i(10, item->value);
+ cl_assert((item = git_sortedcache_entry(sc, 1)) != NULL);
+ cl_assert_equal_s("bcd", item->path);
+ cl_assert_equal_i(20, item->value);
+ cl_assert(git_sortedcache_entry(sc, 3) == NULL);
+
+ /* should not need to reload this time */
+
+ cl_assert_equal_i(0, git_sortedcache_lockandload(sc, NULL));
+
+ /* rewrite ondisk file and reload */
+
+ cl_assert_equal_i(0, free_count);
+
+ cl_git_rewritefile(
+ "cacheitems.txt", "100 abc\n200 zzz\n500 aaa\n10 final\n");
+ sortedcache_test_reload(sc);
+
+ cl_assert_equal_i(3, free_count);
+
+ /* test what we loaded */
+
+ cl_assert_equal_sz(4, git_sortedcache_entrycount(sc));
+
+ cl_assert((item = git_sortedcache_lookup(sc, "abc")) != NULL);
+ cl_assert_equal_s("abc", item->path);
+ cl_assert_equal_i(100, item->value);
+ cl_assert((item = git_sortedcache_lookup(sc, "final")) != NULL);
+ cl_assert_equal_s("final", item->path);
+ cl_assert_equal_i(10, item->value);
+ cl_assert(git_sortedcache_lookup(sc, "cde") == NULL);
+
+ cl_assert((item = git_sortedcache_entry(sc, 0)) != NULL);
+ cl_assert_equal_s("aaa", item->path);
+ cl_assert_equal_i(500, item->value);
+ cl_assert((item = git_sortedcache_entry(sc, 2)) != NULL);
+ cl_assert_equal_s("final", item->path);
+ cl_assert_equal_i(10, item->value);
+ cl_assert((item = git_sortedcache_entry(sc, 3)) != NULL);
+ cl_assert_equal_s("zzz", item->path);
+ cl_assert_equal_i(200, item->value);
+
+ cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "aaa"));
+ cl_assert_equal_sz(0, pos);
+ cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "abc"));
+ cl_assert_equal_sz(1, pos);
+ cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "final"));
+ cl_assert_equal_sz(2, pos);
+ cl_git_pass(git_sortedcache_lookup_index(&pos, sc, "zzz"));
+ cl_assert_equal_sz(3, pos);
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "missing"));
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_sortedcache_lookup_index(&pos, sc, "cde"));
+
+ git_sortedcache_free(sc);
+
+ cl_assert_equal_i(7, free_count);
+}
+
diff --git a/tests/core/stat.c b/tests/core/stat.c
new file mode 100644
index 000000000..2e4abfb79
--- /dev/null
+++ b/tests/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/core/string.c b/tests/core/string.c
new file mode 100644
index 000000000..ec9575685
--- /dev/null
+++ b/tests/core/string.c
@@ -0,0 +1,41 @@
+#include "clar_libgit2.h"
+
+/* compare prefixes */
+void test_core_string__0(void)
+{
+ cl_assert(git__prefixcmp("", "") == 0);
+ cl_assert(git__prefixcmp("a", "") == 0);
+ cl_assert(git__prefixcmp("", "a") < 0);
+ cl_assert(git__prefixcmp("a", "b") < 0);
+ cl_assert(git__prefixcmp("b", "a") > 0);
+ cl_assert(git__prefixcmp("ab", "a") == 0);
+ cl_assert(git__prefixcmp("ab", "ac") < 0);
+ cl_assert(git__prefixcmp("ab", "aa") > 0);
+}
+
+/* compare suffixes */
+void test_core_string__1(void)
+{
+ cl_assert(git__suffixcmp("", "") == 0);
+ cl_assert(git__suffixcmp("a", "") == 0);
+ cl_assert(git__suffixcmp("", "a") < 0);
+ cl_assert(git__suffixcmp("a", "b") < 0);
+ cl_assert(git__suffixcmp("b", "a") > 0);
+ cl_assert(git__suffixcmp("ba", "a") == 0);
+ cl_assert(git__suffixcmp("zaa", "ac") < 0);
+ cl_assert(git__suffixcmp("zaz", "ac") > 0);
+}
+
+/* compare icase sorting with case equality */
+void test_core_string__2(void)
+{
+ cl_assert(git__strcasesort_cmp("", "") == 0);
+ cl_assert(git__strcasesort_cmp("foo", "foo") == 0);
+ cl_assert(git__strcasesort_cmp("foo", "bar") > 0);
+ cl_assert(git__strcasesort_cmp("bar", "foo") < 0);
+ cl_assert(git__strcasesort_cmp("foo", "FOO") > 0);
+ cl_assert(git__strcasesort_cmp("FOO", "foo") < 0);
+ cl_assert(git__strcasesort_cmp("foo", "BAR") > 0);
+ cl_assert(git__strcasesort_cmp("BAR", "foo") < 0);
+ cl_assert(git__strcasesort_cmp("fooBar", "foobar") < 0);
+}
diff --git a/tests/core/strmap.c b/tests/core/strmap.c
new file mode 100644
index 000000000..a120f1feb
--- /dev/null
+++ b/tests/core/strmap.c
@@ -0,0 +1,100 @@
+#include "clar_libgit2.h"
+#include "strmap.h"
+
+GIT__USE_STRMAP;
+
+git_strmap *g_table;
+
+void test_core_strmap__initialize(void)
+{
+ cl_git_pass(git_strmap_alloc(&g_table));
+ cl_assert(g_table != NULL);
+}
+
+void test_core_strmap__cleanup(void)
+{
+ git_strmap_free(g_table);
+}
+
+void test_core_strmap__0(void)
+{
+ cl_assert(git_strmap_num_entries(g_table) == 0);
+}
+
+static void insert_strings(git_strmap *table, int count)
+{
+ int i, j, over, err;
+ char *str;
+
+ for (i = 0; i < count; ++i) {
+ str = malloc(10);
+ for (j = 0; j < 10; ++j)
+ str[j] = 'a' + (i % 26);
+ str[9] = '\0';
+
+ /* if > 26, then encode larger value in first letters */
+ for (j = 0, over = i / 26; over > 0; j++, over = over / 26)
+ str[j] = 'A' + (over % 26);
+
+ git_strmap_insert(table, str, str, err);
+ cl_assert(err >= 0);
+ }
+
+ cl_assert((int)git_strmap_num_entries(table) == count);
+}
+
+void test_core_strmap__1(void)
+{
+ int i;
+ char *str;
+
+ insert_strings(g_table, 20);
+
+ cl_assert(git_strmap_exists(g_table, "aaaaaaaaa"));
+ cl_assert(git_strmap_exists(g_table, "ggggggggg"));
+ cl_assert(!git_strmap_exists(g_table, "aaaaaaaab"));
+ cl_assert(!git_strmap_exists(g_table, "abcdefghi"));
+
+ i = 0;
+ git_strmap_foreach_value(g_table, str, { i++; free(str); });
+ cl_assert(i == 20);
+}
+
+void test_core_strmap__2(void)
+{
+ khiter_t pos;
+ int i;
+ char *str;
+
+ insert_strings(g_table, 20);
+
+ cl_assert(git_strmap_exists(g_table, "aaaaaaaaa"));
+ cl_assert(git_strmap_exists(g_table, "ggggggggg"));
+ cl_assert(!git_strmap_exists(g_table, "aaaaaaaab"));
+ cl_assert(!git_strmap_exists(g_table, "abcdefghi"));
+
+ cl_assert(git_strmap_exists(g_table, "bbbbbbbbb"));
+ pos = git_strmap_lookup_index(g_table, "bbbbbbbbb");
+ cl_assert(git_strmap_valid_index(g_table, pos));
+ cl_assert_equal_s(git_strmap_value_at(g_table, pos), "bbbbbbbbb");
+ free(git_strmap_value_at(g_table, pos));
+ git_strmap_delete_at(g_table, pos);
+
+ cl_assert(!git_strmap_exists(g_table, "bbbbbbbbb"));
+
+ i = 0;
+ git_strmap_foreach_value(g_table, str, { i++; free(str); });
+ cl_assert(i == 19);
+}
+
+void test_core_strmap__3(void)
+{
+ int i;
+ char *str;
+
+ insert_strings(g_table, 10000);
+
+ i = 0;
+ git_strmap_foreach_value(g_table, str, { i++; free(str); });
+ cl_assert(i == 10000);
+}
diff --git a/tests/core/strtol.c b/tests/core/strtol.c
new file mode 100644
index 000000000..8765e042b
--- /dev/null
+++ b/tests/core/strtol.c
@@ -0,0 +1,37 @@
+#include "clar_libgit2.h"
+
+void test_core_strtol__int32(void)
+{
+ int32_t i;
+
+ cl_git_pass(git__strtol32(&i, "123", NULL, 10));
+ cl_assert(i == 123);
+ cl_git_pass(git__strtol32(&i, " +123 ", NULL, 10));
+ cl_assert(i == 123);
+ cl_git_pass(git__strtol32(&i, " +2147483647 ", NULL, 10));
+ cl_assert(i == 2147483647);
+ cl_git_pass(git__strtol32(&i, " -2147483648 ", NULL, 10));
+ cl_assert(i == -2147483648LL);
+
+ cl_git_fail(git__strtol32(&i, " 2147483657 ", NULL, 10));
+ cl_git_fail(git__strtol32(&i, " -2147483657 ", NULL, 10));
+}
+
+void test_core_strtol__int64(void)
+{
+ int64_t i;
+
+ cl_git_pass(git__strtol64(&i, "123", NULL, 10));
+ cl_assert(i == 123);
+ cl_git_pass(git__strtol64(&i, " +123 ", NULL, 10));
+ cl_assert(i == 123);
+ cl_git_pass(git__strtol64(&i, " +2147483647 ", NULL, 10));
+ cl_assert(i == 2147483647);
+ cl_git_pass(git__strtol64(&i, " -2147483648 ", NULL, 10));
+ cl_assert(i == -2147483648LL);
+ cl_git_pass(git__strtol64(&i, " 2147483657 ", NULL, 10));
+ cl_assert(i == 2147483657LL);
+ cl_git_pass(git__strtol64(&i, " -2147483657 ", NULL, 10));
+ cl_assert(i == -2147483657LL);
+}
+
diff --git a/tests/core/vector.c b/tests/core/vector.c
new file mode 100644
index 000000000..66f90b82b
--- /dev/null
+++ b/tests/core/vector.c
@@ -0,0 +1,276 @@
+#include "clar_libgit2.h"
+#include "vector.h"
+
+/* initial size of 1 would cause writing past array bounds */
+void test_core_vector__0(void)
+{
+ git_vector x;
+ int i;
+ git_vector_init(&x, 1, NULL);
+ for (i = 0; i < 10; ++i) {
+ git_vector_insert(&x, (void*) 0xabc);
+ }
+ git_vector_free(&x);
+}
+
+
+/* don't read past array bounds on remove() */
+void test_core_vector__1(void)
+{
+ git_vector x;
+ // make initial capacity exact for our insertions.
+ git_vector_init(&x, 3, NULL);
+ git_vector_insert(&x, (void*) 0xabc);
+ git_vector_insert(&x, (void*) 0xdef);
+ git_vector_insert(&x, (void*) 0x123);
+
+ git_vector_remove(&x, 0); // used to read past array bounds.
+ git_vector_free(&x);
+}
+
+
+static int test_cmp(const void *a, const void *b)
+{
+ return *(const int *)a - *(const int *)b;
+}
+
+/* remove duplicates */
+void test_core_vector__2(void)
+{
+ git_vector x;
+ int *ptrs[2];
+
+ ptrs[0] = git__malloc(sizeof(int));
+ ptrs[1] = git__malloc(sizeof(int));
+
+ *ptrs[0] = 2;
+ *ptrs[1] = 1;
+
+ cl_git_pass(git_vector_init(&x, 5, test_cmp));
+ cl_git_pass(git_vector_insert(&x, ptrs[0]));
+ cl_git_pass(git_vector_insert(&x, ptrs[1]));
+ cl_git_pass(git_vector_insert(&x, ptrs[1]));
+ cl_git_pass(git_vector_insert(&x, ptrs[0]));
+ cl_git_pass(git_vector_insert(&x, ptrs[1]));
+ cl_assert(x.length == 5);
+
+ git_vector_uniq(&x, NULL);
+ cl_assert(x.length == 2);
+
+ git_vector_free(&x);
+
+ git__free(ptrs[0]);
+ git__free(ptrs[1]);
+}
+
+
+static int compare_them(const void *a, const void *b)
+{
+ return (int)((long)a - (long)b);
+}
+
+/* insert_sorted */
+void test_core_vector__3(void)
+{
+ git_vector x;
+ long i;
+ git_vector_init(&x, 1, &compare_them);
+
+ for (i = 0; i < 10; i += 2) {
+ git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
+ }
+
+ for (i = 9; i > 0; i -= 2) {
+ git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
+ }
+
+ cl_assert(x.length == 10);
+ for (i = 0; i < 10; ++i) {
+ cl_assert(git_vector_get(&x, i) == (void*)(i + 1));
+ }
+
+ git_vector_free(&x);
+}
+
+/* insert_sorted with duplicates */
+void test_core_vector__4(void)
+{
+ git_vector x;
+ long i;
+ git_vector_init(&x, 1, &compare_them);
+
+ for (i = 0; i < 10; i += 2) {
+ git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
+ }
+
+ for (i = 9; i > 0; i -= 2) {
+ git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
+ }
+
+ for (i = 0; i < 10; i += 2) {
+ git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
+ }
+
+ for (i = 9; i > 0; i -= 2) {
+ git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
+ }
+
+ cl_assert(x.length == 20);
+ for (i = 0; i < 20; ++i) {
+ cl_assert(git_vector_get(&x, i) == (void*)(i / 2 + 1));
+ }
+
+ git_vector_free(&x);
+}
+
+typedef struct {
+ int content;
+ int count;
+} my_struct;
+
+static int _struct_count = 0;
+
+static int compare_structs(const void *a, const void *b)
+{
+ return ((const my_struct *)a)->content -
+ ((const my_struct *)b)->content;
+}
+
+static int merge_structs(void **old_raw, void *new)
+{
+ my_struct *old = *(my_struct **)old_raw;
+ cl_assert(((my_struct *)old)->content == ((my_struct *)new)->content);
+ ((my_struct *)old)->count += 1;
+ git__free(new);
+ _struct_count--;
+ return GIT_EEXISTS;
+}
+
+static my_struct *alloc_struct(int value)
+{
+ my_struct *st = git__malloc(sizeof(my_struct));
+ st->content = value;
+ st->count = 0;
+ _struct_count++;
+ return st;
+}
+
+/* insert_sorted with duplicates and special handling */
+void test_core_vector__5(void)
+{
+ git_vector x;
+ int i;
+
+ git_vector_init(&x, 1, &compare_structs);
+
+ for (i = 0; i < 10; i += 2)
+ git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs);
+
+ for (i = 9; i > 0; i -= 2)
+ git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs);
+
+ cl_assert(x.length == 10);
+ cl_assert(_struct_count == 10);
+
+ for (i = 0; i < 10; i += 2)
+ git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs);
+
+ for (i = 9; i > 0; i -= 2)
+ git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs);
+
+ cl_assert(x.length == 10);
+ cl_assert(_struct_count == 10);
+
+ for (i = 0; i < 10; ++i) {
+ cl_assert(((my_struct *)git_vector_get(&x, i))->content == i);
+ git__free(git_vector_get(&x, i));
+ _struct_count--;
+ }
+
+ git_vector_free(&x);
+}
+
+static int remove_ones(const git_vector *v, size_t idx, void *p)
+{
+ GIT_UNUSED(p);
+ 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, NULL);
+ 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, NULL);
+ 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, NULL);
+ 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, NULL);
+ 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, NULL);
+ 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, NULL);
+ cl_assert(x.length == 4);
+
+ git_vector_free(&x);
+}
diff --git a/tests/core/zstream.c b/tests/core/zstream.c
new file mode 100644
index 000000000..7ba9424ba
--- /dev/null
+++ b/tests/core/zstream.c
@@ -0,0 +1,143 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "zstream.h"
+
+static const char *data = "This is a test test test of This is a test";
+
+#define INFLATE_EXTRA 2
+
+static void assert_zlib_equal_(
+ const void *expected, size_t e_len,
+ const void *compressed, size_t c_len,
+ const char *msg, const char *file, int line)
+{
+ z_stream stream;
+ char *expanded = git__calloc(1, e_len + INFLATE_EXTRA);
+ cl_assert(expanded);
+
+ memset(&stream, 0, sizeof(stream));
+ stream.next_out = (Bytef *)expanded;
+ stream.avail_out = (uInt)(e_len + INFLATE_EXTRA);
+ stream.next_in = (Bytef *)compressed;
+ stream.avail_in = (uInt)c_len;
+
+ cl_assert(inflateInit(&stream) == Z_OK);
+ cl_assert(inflate(&stream, Z_FINISH));
+ inflateEnd(&stream);
+
+ clar__assert_equal(
+ file, line, msg, 1,
+ "%d", (int)stream.total_out, (int)e_len);
+ clar__assert_equal(
+ file, line, "Buffer len was not exact match", 1,
+ "%d", (int)stream.avail_out, (int)INFLATE_EXTRA);
+
+ clar__assert(
+ memcmp(expanded, expected, e_len) == 0,
+ file, line, "uncompressed data did not match", NULL, 1);
+
+ git__free(expanded);
+}
+
+#define assert_zlib_equal(E,EL,C,CL) \
+ assert_zlib_equal_(E, EL, C, CL, #EL " != " #CL, __FILE__, (int)__LINE__)
+
+void test_core_zstream__basic(void)
+{
+ git_zstream z = GIT_ZSTREAM_INIT;
+ char out[128];
+ size_t outlen = sizeof(out);
+
+ cl_git_pass(git_zstream_init(&z));
+ cl_git_pass(git_zstream_set_input(&z, data, strlen(data) + 1));
+ cl_git_pass(git_zstream_get_output(out, &outlen, &z));
+ cl_assert(git_zstream_done(&z));
+ cl_assert(outlen > 0);
+ git_zstream_free(&z);
+
+ assert_zlib_equal(data, strlen(data) + 1, out, outlen);
+}
+
+void test_core_zstream__buffer(void)
+{
+ git_buf out = GIT_BUF_INIT;
+ cl_git_pass(git_zstream_deflatebuf(&out, data, strlen(data) + 1));
+ assert_zlib_equal(data, strlen(data) + 1, out.ptr, out.size);
+ git_buf_free(&out);
+}
+
+#define BIG_STRING_PART "Big Data IS Big - Long Data IS Long - We need a buffer larger than 1024 x 1024 to make sure we trigger chunked compression - Big Big Data IS Bigger than Big - Long Long Data IS Longer than Long"
+
+static void compress_input_various_ways(git_buf *input)
+{
+ git_buf out1 = GIT_BUF_INIT, out2 = GIT_BUF_INIT;
+ size_t i, fixed_size = max(input->size / 2, 256);
+ char *fixed = git__malloc(fixed_size);
+ cl_assert(fixed);
+
+ /* compress with deflatebuf */
+
+ cl_git_pass(git_zstream_deflatebuf(&out1, input->ptr, input->size));
+ assert_zlib_equal(input->ptr, input->size, out1.ptr, out1.size);
+
+ /* compress with various fixed size buffer (accumulating the output) */
+
+ for (i = 0; i < 3; ++i) {
+ git_zstream zs = GIT_ZSTREAM_INIT;
+ size_t use_fixed_size;
+
+ switch (i) {
+ case 0: use_fixed_size = 256; break;
+ case 1: use_fixed_size = fixed_size / 2; break;
+ case 2: use_fixed_size = fixed_size; break;
+ }
+ cl_assert(use_fixed_size <= fixed_size);
+
+ cl_git_pass(git_zstream_init(&zs));
+ cl_git_pass(git_zstream_set_input(&zs, input->ptr, input->size));
+
+ while (!git_zstream_done(&zs)) {
+ size_t written = use_fixed_size;
+ cl_git_pass(git_zstream_get_output(fixed, &written, &zs));
+ cl_git_pass(git_buf_put(&out2, fixed, written));
+ }
+
+ git_zstream_free(&zs);
+ assert_zlib_equal(input->ptr, input->size, out2.ptr, out2.size);
+
+ /* did both approaches give the same data? */
+ cl_assert_equal_sz(out1.size, out2.size);
+ cl_assert(!memcmp(out1.ptr, out2.ptr, out1.size));
+
+ git_buf_free(&out2);
+ }
+
+ git_buf_free(&out1);
+ git__free(fixed);
+}
+
+void test_core_zstream__big_data(void)
+{
+ git_buf in = GIT_BUF_INIT;
+ size_t scan, target;
+
+ for (target = 1024; target <= 1024 * 1024 * 4; target *= 8) {
+
+ /* make a big string that's easy to compress */
+ git_buf_clear(&in);
+ while (in.size < target)
+ cl_git_pass(
+ git_buf_put(&in, BIG_STRING_PART, strlen(BIG_STRING_PART)));
+
+ compress_input_various_ways(&in);
+
+ /* make a big string that's hard to compress */
+ srand(0xabad1dea);
+ for (scan = 0; scan < in.size; ++scan)
+ in.ptr[scan] = (char)rand();
+
+ compress_input_various_ways(&in);
+ }
+
+ git_buf_free(&in);
+}
diff --git a/tests/date/date.c b/tests/date/date.c
new file mode 100644
index 000000000..88881d1e1
--- /dev/null
+++ b/tests/date/date.c
@@ -0,0 +1,15 @@
+#include "clar_libgit2.h"
+
+#include "util.h"
+
+void test_date_date__overflow(void)
+{
+#ifdef __LP64__
+ git_time_t d2038, d2039;
+
+ /* This is expected to fail on a 32-bit machine. */
+ cl_git_pass(git__date_parse(&d2038, "2038-1-1"));
+ cl_git_pass(git__date_parse(&d2039, "2039-1-1"));
+ cl_assert(d2038 < d2039);
+#endif
+}
diff --git a/tests/date/rfc2822.c b/tests/date/rfc2822.c
new file mode 100644
index 000000000..eda475ac9
--- /dev/null
+++ b/tests/date/rfc2822.c
@@ -0,0 +1,40 @@
+#include "clar_libgit2.h"
+
+#include "util.h"
+
+void test_date_rfc2822__format_rfc2822_no_offset(void)
+{
+ git_time t = {1397031663, 0};
+ char buf[GIT_DATE_RFC2822_SZ];
+
+ cl_git_pass(git__date_rfc2822_fmt(buf, sizeof(buf), &t));
+ cl_assert(strcmp(buf, "Wed, 9 Apr 2014 08:21:03 +0000") == 0);
+}
+
+void test_date_rfc2822__format_rfc2822_positive_offset(void)
+{
+ git_time t = {1397031663, 120};
+ char buf[GIT_DATE_RFC2822_SZ];
+
+ cl_git_pass(git__date_rfc2822_fmt(buf, sizeof(buf), &t));
+ cl_assert(strcmp(buf, "Wed, 9 Apr 2014 10:21:03 +0200") == 0);
+}
+
+void test_date_rfc2822__format_rfc2822_negative_offset(void)
+{
+ git_time t = {1397031663, -120};
+ char buf[GIT_DATE_RFC2822_SZ];
+
+ cl_git_pass(git__date_rfc2822_fmt(buf, sizeof(buf), &t));
+ cl_assert(strcmp(buf, "Wed, 9 Apr 2014 06:21:03 -0200") == 0);
+}
+
+void test_date_rfc2822__format_rfc2822_buffer_too_small(void)
+{
+ // "Wed, 10 Apr 2014 08:21:03 +0000"
+ git_time t = {1397031663 + 86400, 0};
+ char buf[GIT_DATE_RFC2822_SZ-1];
+
+ cl_git_fail(git__date_rfc2822_fmt(buf, sizeof(buf), &t));
+}
+
diff --git a/tests/diff/binary.c b/tests/diff/binary.c
new file mode 100644
index 000000000..cb574a588
--- /dev/null
+++ b/tests/diff/binary.c
@@ -0,0 +1,263 @@
+#include "clar_libgit2.h"
+
+#include "buffer.h"
+#include "filebuf.h"
+
+static git_repository *repo;
+
+void test_diff_binary__initialize(void)
+{
+}
+
+void test_diff_binary__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_patch(
+ const char *one,
+ const char *two,
+ const git_diff_options *opts,
+ const char *expected)
+{
+ git_oid id_one, id_two;
+ git_index *index = NULL;
+ git_commit *commit_one, *commit_two = NULL;
+ git_tree *tree_one, *tree_two;
+ git_diff *diff;
+ git_patch *patch;
+ git_buf actual = GIT_BUF_INIT;
+
+ cl_git_pass(git_oid_fromstr(&id_one, one));
+ cl_git_pass(git_commit_lookup(&commit_one, repo, &id_one));
+ cl_git_pass(git_commit_tree(&tree_one, commit_one));
+
+ if (two) {
+ cl_git_pass(git_oid_fromstr(&id_two, two));
+ cl_git_pass(git_commit_lookup(&commit_two, repo, &id_two));
+ cl_git_pass(git_commit_tree(&tree_two, commit_two));
+ } else {
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_git_pass(git_index_write_tree(&id_two, index));
+ cl_git_pass(git_tree_lookup(&tree_two, repo, &id_two));
+ }
+
+ cl_git_pass(git_diff_tree_to_tree(&diff, repo, tree_one, tree_two, opts));
+
+ cl_git_pass(git_patch_from_diff(&patch, diff, 0));
+ cl_git_pass(git_patch_to_buf(&actual, patch));
+
+ cl_assert_equal_s(expected, actual.ptr);
+
+ git_buf_free(&actual);
+ git_patch_free(patch);
+ git_diff_free(diff);
+ git_tree_free(tree_one);
+ git_tree_free(tree_two);
+ git_commit_free(commit_one);
+ git_commit_free(commit_two);
+ git_index_free(index);
+}
+
+void test_diff_binary__add_normal(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ const char *expected =
+ "diff --git a/binary.bin b/binary.bin\n" \
+ "new file mode 100644\n" \
+ "index 0000000..bd474b2\n" \
+ "Binary files /dev/null and b/binary.bin differ\n";
+
+ repo = cl_git_sandbox_init("diff_format_email");
+ test_patch(
+ "873806f6f27e631eb0b23e4b56bea2bfac14a373",
+ "897d3af16ca9e420cd071b1c4541bd2b91d04c8c",
+ &opts,
+ expected);
+}
+
+void test_diff_binary__add(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ const char *expected =
+ "diff --git a/binary.bin b/binary.bin\n" \
+ "new file mode 100644\n" \
+ "index 0000000000000000000000000000000000000000..bd474b2519cc15eab801ff851cc7d50f0dee49a1\n" \
+ "GIT binary patch\n" \
+ "literal 3\n" \
+ "Kc${Nk-~s>u4FC%O\n"
+ "\n" \
+ "literal 0\n" \
+ "Hc$@<O00001\n";
+
+ opts.flags = GIT_DIFF_SHOW_BINARY;
+ opts.id_abbrev = GIT_OID_HEXSZ;
+
+ repo = cl_git_sandbox_init("diff_format_email");
+ test_patch(
+ "873806f6f27e631eb0b23e4b56bea2bfac14a373",
+ "897d3af16ca9e420cd071b1c4541bd2b91d04c8c",
+ &opts,
+ expected);
+}
+
+void test_diff_binary__modify_normal(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ const char *expected =
+ "diff --git a/binary.bin b/binary.bin\n" \
+ "index bd474b2..9ac35ff 100644\n" \
+ "Binary files a/binary.bin and b/binary.bin differ\n";
+
+ repo = cl_git_sandbox_init("diff_format_email");
+ test_patch(
+ "897d3af16ca9e420cd071b1c4541bd2b91d04c8c",
+ "8d7523f6fcb2404257889abe0d96f093d9f524f9",
+ &opts,
+ expected);
+}
+
+void test_diff_binary__modify(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ const char *expected =
+ "diff --git a/binary.bin b/binary.bin\n" \
+ "index bd474b2519cc15eab801ff851cc7d50f0dee49a1..9ac35ff15cd8864aeafd889e4826a3150f0b06c4 100644\n" \
+ "GIT binary patch\n" \
+ "literal 5\n" \
+ "Mc${NkU}WL~000&M4gdfE\n" \
+ "\n" \
+ "literal 3\n" \
+ "Kc${Nk-~s>u4FC%O\n";
+
+ opts.flags = GIT_DIFF_SHOW_BINARY;
+
+ repo = cl_git_sandbox_init("diff_format_email");
+ test_patch(
+ "897d3af16ca9e420cd071b1c4541bd2b91d04c8c",
+ "8d7523f6fcb2404257889abe0d96f093d9f524f9",
+ &opts,
+ expected);
+}
+
+void test_diff_binary__delete_normal(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ const char *expected =
+ "diff --git a/binary.bin b/binary.bin\n" \
+ "deleted file mode 100644\n" \
+ "index bd474b2..0000000\n" \
+ "Binary files a/binary.bin and /dev/null differ\n";
+
+ repo = cl_git_sandbox_init("diff_format_email");
+ test_patch(
+ "897d3af16ca9e420cd071b1c4541bd2b91d04c8c",
+ "873806f6f27e631eb0b23e4b56bea2bfac14a373",
+ &opts,
+ expected);
+}
+
+void test_diff_binary__delete(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ const char *expected =
+ "diff --git a/binary.bin b/binary.bin\n" \
+ "deleted file mode 100644\n" \
+ "index bd474b2519cc15eab801ff851cc7d50f0dee49a1..0000000000000000000000000000000000000000\n" \
+ "GIT binary patch\n" \
+ "literal 0\n" \
+ "Hc$@<O00001\n" \
+ "\n" \
+ "literal 3\n" \
+ "Kc${Nk-~s>u4FC%O\n";
+
+ opts.flags = GIT_DIFF_SHOW_BINARY;
+ opts.id_abbrev = GIT_OID_HEXSZ;
+
+ repo = cl_git_sandbox_init("diff_format_email");
+ test_patch(
+ "897d3af16ca9e420cd071b1c4541bd2b91d04c8c",
+ "873806f6f27e631eb0b23e4b56bea2bfac14a373",
+ &opts,
+ expected);
+}
+
+void test_diff_binary__delta(void)
+{
+ git_index *index;
+ git_buf contents = GIT_BUF_INIT;
+ size_t i;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ const char *expected =
+ "diff --git a/songof7cities.txt b/songof7cities.txt\n" \
+ "index 4210ffd5c390b21dd5483375e75288dea9ede512..cc84ec183351c9944ed90a619ca08911924055b5 100644\n" \
+ "GIT binary patch\n" \
+ "delta 198\n" \
+ "zc$}LmI8{(0BqLQJI6p64AwNwaIJGP_Pa)Ye#M3o+qJ$<Jl;sX*mF<MGCYv&*L7AHu\n" \
+ "zGA1*^gt?gYVN82wTbPO_W)+x<&1+cP;HrPHR>PQ;Y(X&QMK*C5^Br3bjG4d=XI^5@\n" \
+ "JfH567LIG)KJdFSV\n" \
+ "\n" \
+ "delta 198\n" \
+ "zc$}LmI8{(0BqLQJI6p64AwNwaIJGP_Pr*5}Br~;mqJ$<Jl;sX*mF<MGCYv&*L7AHu\n" \
+ "zGA1*^gt?gYVN82wTbPO_W)+x<&1+cP;HrPHR>PQ;Y(X&QMK*C5^Br3bjG4d=XI^5@\n" \
+ "JfH567LIF3FM2!Fd\n";
+
+ opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY;
+ opts.id_abbrev = GIT_OID_HEXSZ;
+
+ repo = cl_git_sandbox_init("renames");
+ cl_git_pass(git_repository_index(&index, repo));
+
+ cl_git_pass(git_futils_readbuffer(&contents, "renames/songof7cities.txt"));
+
+ for (i = 0; i < contents.size - 6; i++) {
+ if (strncmp(&contents.ptr[i], "Cities", 6) == 0)
+ memcpy(&contents.ptr[i], "cITIES", 6);
+ }
+
+ cl_git_rewritefile("renames/songof7cities.txt", contents.ptr);
+ cl_git_pass(git_index_add_bypath(index, "songof7cities.txt"));
+ cl_git_pass(git_index_write(index));
+
+ test_patch(
+ "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13",
+ NULL,
+ &opts,
+ expected);
+
+ git_index_free(index);
+ git_buf_free(&contents);
+}
+
+void test_diff_binary__delta_append(void)
+{
+ git_index *index;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ const char *expected =
+ "diff --git a/untimely.txt b/untimely.txt\n" \
+ "index 9a69d960ae94b060f56c2a8702545e2bb1abb935..1111d4f11f4b35bf6759e0fb714fe09731ef0840 100644\n" \
+ "GIT binary patch\n" \
+ "delta 32\n" \
+ "nc%1vf+QYWt3zLL@hC)e3Vu?a>QDRl4f_G*?PG(-ZA}<#J$+QbW\n" \
+ "\n" \
+ "delta 7\n" \
+ "Oc%18D`@*{63ljhg(E~C7\n";
+
+ opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY;
+ opts.id_abbrev = GIT_OID_HEXSZ;
+
+ repo = cl_git_sandbox_init("renames");
+ cl_git_pass(git_repository_index(&index, repo));
+
+ cl_git_append2file("renames/untimely.txt", "Oh that crazy Kipling!\r\n");
+ cl_git_pass(git_index_add_bypath(index, "untimely.txt"));
+ cl_git_pass(git_index_write(index));
+
+ test_patch(
+ "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13",
+ NULL,
+ &opts,
+ expected);
+
+ git_index_free(index);
+}
diff --git a/tests/diff/blob.c b/tests/diff/blob.c
new file mode 100644
index 000000000..527007965
--- /dev/null
+++ b/tests/diff/blob.c
@@ -0,0 +1,1008 @@
+#include "clar_libgit2.h"
+#include "diff_helpers.h"
+
+static git_repository *g_repo = NULL;
+static diff_expects expected;
+static git_diff_options opts;
+static git_blob *d, *alien;
+
+static void quick_diff_blob_to_str(
+ const git_blob *blob, const char *blob_path,
+ const char *str, size_t len, const char *str_path)
+{
+ memset(&expected, 0, sizeof(expected));
+
+ if (str && !len)
+ len = strlen(str);
+
+ cl_git_pass(git_diff_blob_to_buffer(
+ blob, blob_path, str, len, str_path,
+ &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+}
+
+void test_diff_blob__initialize(void)
+{
+ git_oid oid;
+
+ g_repo = cl_git_sandbox_init("attr");
+
+ cl_git_pass(git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION));
+ opts.context_lines = 1;
+
+ memset(&expected, 0, sizeof(expected));
+
+ /* tests/resources/attr/root_test4.txt */
+ cl_git_pass(git_oid_fromstrn(&oid, "a0f7217a", 8));
+ cl_git_pass(git_blob_lookup_prefix(&d, g_repo, &oid, 4));
+
+ /* alien.png */
+ cl_git_pass(git_oid_fromstrn(&oid, "edf3dcee", 8));
+ cl_git_pass(git_blob_lookup_prefix(&alien, g_repo, &oid, 4));
+}
+
+void test_diff_blob__cleanup(void)
+{
+ git_blob_free(d);
+ d = NULL;
+
+ git_blob_free(alien);
+ alien = NULL;
+
+ cl_git_sandbox_cleanup();
+}
+
+static void assert_one_modified(
+ int hunks, int lines, int ctxt, int adds, int dels, diff_expects *exp)
+{
+ cl_assert_equal_i(1, exp->files);
+ cl_assert_equal_i(1, exp->file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(0, exp->files_binary);
+
+ cl_assert_equal_i(hunks, exp->hunks);
+ cl_assert_equal_i(lines, exp->lines);
+ cl_assert_equal_i(ctxt, exp->line_ctxt);
+ cl_assert_equal_i(adds, exp->line_adds);
+ cl_assert_equal_i(dels, exp->line_dels);
+}
+
+void test_diff_blob__can_compare_text_blobs(void)
+{
+ git_blob *a, *b, *c;
+ git_oid a_oid, b_oid, c_oid;
+
+ /* tests/resources/attr/root_test1 */
+ cl_git_pass(git_oid_fromstrn(&a_oid, "45141a79", 8));
+ cl_git_pass(git_blob_lookup_prefix(&a, g_repo, &a_oid, 4));
+
+ /* tests/resources/attr/root_test2 */
+ cl_git_pass(git_oid_fromstrn(&b_oid, "4d713dc4", 8));
+ cl_git_pass(git_blob_lookup_prefix(&b, g_repo, &b_oid, 4));
+
+ /* tests/resources/attr/root_test3 */
+ cl_git_pass(git_oid_fromstrn(&c_oid, "c96bbb2c2557a832", 16));
+ cl_git_pass(git_blob_lookup_prefix(&c, g_repo, &c_oid, 8));
+
+ /* Doing the equivalent of a `git diff -U1` on these files */
+
+ /* diff on tests/resources/attr/root_test1 */
+ memset(&expected, 0, sizeof(expected));
+ cl_git_pass(git_diff_blobs(
+ a, NULL, b, NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+ assert_one_modified(1, 6, 1, 5, 0, &expected);
+
+ /* same diff but use direct buffers */
+ memset(&expected, 0, sizeof(expected));
+ cl_git_pass(git_diff_buffers(
+ git_blob_rawcontent(a), (size_t)git_blob_rawsize(a), NULL,
+ git_blob_rawcontent(b), (size_t)git_blob_rawsize(b), NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+ assert_one_modified(1, 6, 1, 5, 0, &expected);
+
+ /* diff on tests/resources/attr/root_test2 */
+ memset(&expected, 0, sizeof(expected));
+ cl_git_pass(git_diff_blobs(
+ b, NULL, c, NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+ assert_one_modified(1, 15, 3, 9, 3, &expected);
+
+ /* diff on tests/resources/attr/root_test3 */
+ memset(&expected, 0, sizeof(expected));
+ cl_git_pass(git_diff_blobs(
+ a, NULL, c, NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+ assert_one_modified(1, 13, 0, 12, 1, &expected);
+
+ memset(&expected, 0, sizeof(expected));
+ cl_git_pass(git_diff_blobs(
+ c, NULL, d, NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+ assert_one_modified(2, 14, 4, 6, 4, &expected);
+
+ git_blob_free(a);
+ git_blob_free(b);
+ git_blob_free(c);
+}
+
+static void assert_patch_matches_blobs(
+ git_patch *p, git_blob *a, git_blob *b,
+ int hunks, int l0, int l1, int ctxt, int adds, int dels)
+{
+ const git_diff_delta *delta;
+ size_t tc, ta, td;
+
+ cl_assert(p != NULL);
+
+ delta = git_patch_get_delta(p);
+ cl_assert(delta != NULL);
+
+ cl_assert_equal_i(GIT_DELTA_MODIFIED, delta->status);
+ cl_assert(git_oid_equal(git_blob_id(a), &delta->old_file.id));
+ cl_assert_equal_sz(git_blob_rawsize(a), delta->old_file.size);
+ cl_assert(git_oid_equal(git_blob_id(b), &delta->new_file.id));
+ cl_assert_equal_sz(git_blob_rawsize(b), delta->new_file.size);
+
+ cl_assert_equal_i(hunks, (int)git_patch_num_hunks(p));
+
+ if (hunks > 0)
+ cl_assert_equal_i(l0, git_patch_num_lines_in_hunk(p, 0));
+ if (hunks > 1)
+ cl_assert_equal_i(l1, git_patch_num_lines_in_hunk(p, 1));
+
+ cl_git_pass(git_patch_line_stats(&tc, &ta, &td, p));
+ cl_assert_equal_i(ctxt, (int)tc);
+ cl_assert_equal_i(adds, (int)ta);
+ cl_assert_equal_i(dels, (int)td);
+}
+
+void test_diff_blob__can_compare_text_blobs_with_patch(void)
+{
+ git_blob *a, *b, *c;
+ git_oid a_oid, b_oid, c_oid;
+ git_patch *p;
+
+ /* tests/resources/attr/root_test1 */
+ cl_git_pass(git_oid_fromstrn(&a_oid, "45141a79", 8));
+ cl_git_pass(git_blob_lookup_prefix(&a, g_repo, &a_oid, 4));
+
+ /* tests/resources/attr/root_test2 */
+ cl_git_pass(git_oid_fromstrn(&b_oid, "4d713dc4", 8));
+ cl_git_pass(git_blob_lookup_prefix(&b, g_repo, &b_oid, 4));
+
+ /* tests/resources/attr/root_test3 */
+ cl_git_pass(git_oid_fromstrn(&c_oid, "c96bbb2c2557a832", 16));
+ cl_git_pass(git_blob_lookup_prefix(&c, g_repo, &c_oid, 8));
+
+ /* Doing the equivalent of a `git diff -U1` on these files */
+
+ /* diff on tests/resources/attr/root_test1 */
+ cl_git_pass(git_patch_from_blobs(&p, a, NULL, b, NULL, &opts));
+ assert_patch_matches_blobs(p, a, b, 1, 6, 0, 1, 5, 0);
+ git_patch_free(p);
+
+ /* diff on tests/resources/attr/root_test2 */
+ cl_git_pass(git_patch_from_blobs(&p, b, NULL, c, NULL, &opts));
+ assert_patch_matches_blobs(p, b, c, 1, 15, 0, 3, 9, 3);
+ git_patch_free(p);
+
+ /* diff on tests/resources/attr/root_test3 */
+ cl_git_pass(git_patch_from_blobs(&p, a, NULL, c, NULL, &opts));
+ assert_patch_matches_blobs(p, a, c, 1, 13, 0, 0, 12, 1);
+ git_patch_free(p);
+
+ /* one more */
+ cl_git_pass(git_patch_from_blobs(&p, c, NULL, d, NULL, &opts));
+ assert_patch_matches_blobs(p, c, d, 2, 5, 9, 4, 6, 4);
+ git_patch_free(p);
+
+ git_blob_free(a);
+ git_blob_free(b);
+ git_blob_free(c);
+}
+
+void test_diff_blob__can_compare_against_null_blobs(void)
+{
+ git_blob *e = NULL;
+
+ cl_git_pass(git_diff_blobs(
+ d, NULL, e, NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+
+ cl_assert_equal_i(1, expected.files);
+ 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);
+ cl_assert_equal_i(14, expected.lines);
+ cl_assert_equal_i(14, expected.line_dels);
+
+ opts.flags |= GIT_DIFF_REVERSE;
+ memset(&expected, 0, sizeof(expected));
+
+ cl_git_pass(git_diff_blobs(
+ d, NULL, e, NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+
+ cl_assert_equal_i(1, expected.files);
+ 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);
+ cl_assert_equal_i(14, expected.lines);
+ cl_assert_equal_i(14, expected.line_adds);
+
+ opts.flags ^= GIT_DIFF_REVERSE;
+ memset(&expected, 0, sizeof(expected));
+
+ cl_git_pass(git_diff_blobs(
+ alien, NULL, NULL, NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+
+ cl_assert_equal_i(1, expected.files);
+ 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);
+
+ memset(&expected, 0, sizeof(expected));
+
+ cl_git_pass(git_diff_blobs(
+ NULL, NULL, alien, NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+
+ cl_assert_equal_i(1, expected.files);
+ 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);
+}
+
+void test_diff_blob__can_compare_against_null_blobs_with_patch(void)
+{
+ git_blob *e = NULL;
+ git_patch *p;
+ const git_diff_delta *delta;
+ const git_diff_line *line;
+ int l, max_l;
+
+ cl_git_pass(git_patch_from_blobs(&p, d, NULL, e, NULL, &opts));
+
+ cl_assert(p != NULL);
+
+ delta = git_patch_get_delta(p);
+ cl_assert(delta != NULL);
+ cl_assert_equal_i(GIT_DELTA_DELETED, delta->status);
+ cl_assert(git_oid_equal(git_blob_id(d), &delta->old_file.id));
+ cl_assert_equal_sz(git_blob_rawsize(d), delta->old_file.size);
+ cl_assert(git_oid_iszero(&delta->new_file.id));
+ cl_assert_equal_sz(0, delta->new_file.size);
+
+ cl_assert_equal_i(1, (int)git_patch_num_hunks(p));
+ cl_assert_equal_i(14, git_patch_num_lines_in_hunk(p, 0));
+
+ max_l = git_patch_num_lines_in_hunk(p, 0);
+ for (l = 0; l < max_l; ++l) {
+ cl_git_pass(git_patch_get_line_in_hunk(&line, p, 0, l));
+ cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)line->origin);
+ }
+
+ git_patch_free(p);
+
+ opts.flags |= GIT_DIFF_REVERSE;
+
+ cl_git_pass(git_patch_from_blobs(&p, d, NULL, e, NULL, &opts));
+
+ cl_assert(p != NULL);
+
+ delta = git_patch_get_delta(p);
+ cl_assert(delta != NULL);
+ cl_assert_equal_i(GIT_DELTA_ADDED, delta->status);
+ cl_assert(git_oid_iszero(&delta->old_file.id));
+ cl_assert_equal_sz(0, delta->old_file.size);
+ cl_assert(git_oid_equal(git_blob_id(d), &delta->new_file.id));
+ cl_assert_equal_sz(git_blob_rawsize(d), delta->new_file.size);
+
+ cl_assert_equal_i(1, (int)git_patch_num_hunks(p));
+ cl_assert_equal_i(14, git_patch_num_lines_in_hunk(p, 0));
+
+ max_l = git_patch_num_lines_in_hunk(p, 0);
+ for (l = 0; l < max_l; ++l) {
+ cl_git_pass(git_patch_get_line_in_hunk(&line, p, 0, l));
+ cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)line->origin);
+ }
+
+ git_patch_free(p);
+
+ opts.flags ^= GIT_DIFF_REVERSE;
+
+ cl_git_pass(git_patch_from_blobs(&p, alien, NULL, NULL, NULL, &opts));
+
+ cl_assert(p != NULL);
+
+ delta = git_patch_get_delta(p);
+ cl_assert(delta != NULL);
+ cl_assert_equal_i(GIT_DELTA_DELETED, delta->status);
+ cl_assert((delta->flags & GIT_DIFF_FLAG_BINARY) != 0);
+
+ cl_assert_equal_i(0, (int)git_patch_num_hunks(p));
+
+ git_patch_free(p);
+
+ cl_git_pass(git_patch_from_blobs(&p, NULL, NULL, alien, NULL, &opts));
+
+ cl_assert(p != NULL);
+
+ delta = git_patch_get_delta(p);
+ cl_assert(delta != NULL);
+ cl_assert_equal_i(GIT_DELTA_ADDED, delta->status);
+ cl_assert((delta->flags & GIT_DIFF_FLAG_BINARY) != 0);
+
+ cl_assert_equal_i(0, (int)git_patch_num_hunks(p));
+
+ git_patch_free(p);
+}
+
+static void assert_identical_blobs_comparison(diff_expects *expected)
+{
+ 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)
+{
+ opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
+
+ cl_git_pass(git_diff_blobs(
+ d, NULL, d, NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+
+ assert_identical_blobs_comparison(&expected);
+ cl_assert_equal_i(0, expected.files_binary);
+
+ memset(&expected, 0, sizeof(expected));
+ cl_git_pass(git_diff_blobs(
+ NULL, NULL, NULL, NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+
+ assert_identical_blobs_comparison(&expected);
+ cl_assert_equal_i(0, expected.files_binary);
+
+ memset(&expected, 0, sizeof(expected));
+ cl_git_pass(git_diff_blobs(
+ alien, NULL, alien, NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+
+ assert_identical_blobs_comparison(&expected);
+ cl_assert(expected.files_binary > 0);
+}
+
+void test_diff_blob__can_compare_identical_blobs_with_patch(void)
+{
+ git_patch *p;
+ const git_diff_delta *delta;
+
+ cl_git_pass(git_patch_from_blobs(&p, d, NULL, d, NULL, &opts));
+ cl_assert(p != NULL);
+
+ delta = git_patch_get_delta(p);
+ cl_assert(delta != NULL);
+ cl_assert_equal_i(GIT_DELTA_UNMODIFIED, delta->status);
+ cl_assert_equal_sz(delta->old_file.size, git_blob_rawsize(d));
+ cl_assert(git_oid_equal(git_blob_id(d), &delta->old_file.id));
+ cl_assert_equal_sz(delta->new_file.size, git_blob_rawsize(d));
+ cl_assert(git_oid_equal(git_blob_id(d), &delta->new_file.id));
+
+ cl_assert_equal_i(0, (int)git_patch_num_hunks(p));
+ git_patch_free(p);
+
+ cl_git_pass(git_patch_from_blobs(&p, NULL, NULL, NULL, NULL, &opts));
+ cl_assert(p != NULL);
+
+ delta = git_patch_get_delta(p);
+ cl_assert(delta != NULL);
+ cl_assert_equal_i(GIT_DELTA_UNMODIFIED, delta->status);
+ cl_assert_equal_sz(0, delta->old_file.size);
+ cl_assert(git_oid_iszero(&delta->old_file.id));
+ cl_assert_equal_sz(0, delta->new_file.size);
+ cl_assert(git_oid_iszero(&delta->new_file.id));
+
+ cl_assert_equal_i(0, (int)git_patch_num_hunks(p));
+ git_patch_free(p);
+
+ cl_git_pass(git_patch_from_blobs(&p, alien, NULL, alien, NULL, &opts));
+ cl_assert(p != NULL);
+ cl_assert_equal_i(GIT_DELTA_UNMODIFIED, git_patch_get_delta(p)->status);
+ cl_assert_equal_i(0, (int)git_patch_num_hunks(p));
+ git_patch_free(p);
+}
+
+static void assert_binary_blobs_comparison(diff_expects *expected)
+{
+ cl_assert(expected->files_binary > 0);
+
+ 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)
+{
+ git_blob *heart;
+ git_oid h_oid;
+
+ /* heart.png */
+ cl_git_pass(git_oid_fromstrn(&h_oid, "de863bff", 8));
+ cl_git_pass(git_blob_lookup_prefix(&heart, g_repo, &h_oid, 4));
+
+ cl_git_pass(git_diff_blobs(
+ alien, NULL, heart, NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+
+ assert_binary_blobs_comparison(&expected);
+
+ memset(&expected, 0, sizeof(expected));
+
+ cl_git_pass(git_diff_blobs(
+ heart, NULL, alien, NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+
+ assert_binary_blobs_comparison(&expected);
+
+ git_blob_free(heart);
+}
+
+void test_diff_blob__can_compare_a_binary_blob_and_a_text_blob(void)
+{
+ cl_git_pass(git_diff_blobs(
+ alien, NULL, d, NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+
+ assert_binary_blobs_comparison(&expected);
+
+ memset(&expected, 0, sizeof(expected));
+
+ cl_git_pass(git_diff_blobs(
+ d, NULL, alien, NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+
+ assert_binary_blobs_comparison(&expected);
+}
+
+/*
+ * $ git diff fe773770 a0f7217
+ * diff --git a/fe773770 b/a0f7217
+ * index fe77377..a0f7217 100644
+ * --- a/fe773770
+ * +++ b/a0f7217
+ * @@ -1,6 +1,6 @@
+ * Here is some stuff at the start
+ *
+ * -This should go in one hunk
+ * +This should go in one hunk (first)
+ *
+ * Some additional lines
+ *
+ * @@ -8,7 +8,7 @@ Down here below the other lines
+ *
+ * With even more at the end
+ *
+ * -Followed by a second hunk of stuff
+ * +Followed by a second hunk of stuff (second)
+ *
+ * That happens down here
+ */
+void test_diff_blob__comparing_two_text_blobs_honors_interhunkcontext(void)
+{
+ git_blob *old_d;
+ git_oid old_d_oid;
+
+ opts.context_lines = 3;
+
+ /* tests/resources/attr/root_test1 from commit f5b0af1 */
+ cl_git_pass(git_oid_fromstrn(&old_d_oid, "fe773770", 8));
+ cl_git_pass(git_blob_lookup_prefix(&old_d, g_repo, &old_d_oid, 4));
+
+ /* Test with default inter-hunk-context (not set) => default is 0 */
+ cl_git_pass(git_diff_blobs(
+ old_d, NULL, d, NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+
+ cl_assert_equal_i(2, expected.hunks);
+
+ /* Test with inter-hunk-context explicitly set to 0 */
+ opts.interhunk_lines = 0;
+ memset(&expected, 0, sizeof(expected));
+ cl_git_pass(git_diff_blobs(
+ old_d, NULL, d, NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+
+ cl_assert_equal_i(2, expected.hunks);
+
+ /* Test with inter-hunk-context explicitly set to 1 */
+ opts.interhunk_lines = 1;
+ memset(&expected, 0, sizeof(expected));
+ cl_git_pass(git_diff_blobs(
+ old_d, NULL, d, NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+
+ cl_assert_equal_i(1, expected.hunks);
+
+ git_blob_free(old_d);
+}
+
+void test_diff_blob__checks_options_version_too_low(void)
+{
+ const git_error *err;
+
+ opts.version = 0;
+ cl_git_fail(git_diff_blobs(
+ d, NULL, alien, NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+ err = giterr_last();
+ cl_assert_equal_i(GITERR_INVALID, err->klass);
+}
+
+void test_diff_blob__checks_options_version_too_high(void)
+{
+ const git_error *err;
+
+ opts.version = 1024;
+ cl_git_fail(git_diff_blobs(
+ d, NULL, alien, NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+ err = giterr_last();
+ cl_assert_equal_i(GITERR_INVALID, err->klass);
+}
+
+void test_diff_blob__can_correctly_detect_a_binary_blob_as_binary(void)
+{
+ /* alien.png */
+ cl_assert_equal_i(true, git_blob_is_binary(alien));
+}
+
+void test_diff_blob__can_correctly_detect_a_textual_blob_as_non_binary(void)
+{
+ /* tests/resources/attr/root_test4.txt */
+ cl_assert_equal_i(false, git_blob_is_binary(d));
+}
+
+/*
+ * git_diff_blob_to_buffer tests
+ */
+
+static void assert_changed_single_one_line_file(
+ diff_expects *expected, git_delta_t mod)
+{
+ cl_assert_equal_i(1, expected->files);
+ cl_assert_equal_i(1, expected->file_status[mod]);
+ cl_assert_equal_i(1, expected->hunks);
+ cl_assert_equal_i(1, expected->lines);
+
+ if (mod == GIT_DELTA_ADDED)
+ cl_assert_equal_i(1, expected->line_adds);
+ else if (mod == GIT_DELTA_DELETED)
+ cl_assert_equal_i(1, expected->line_dels);
+}
+
+void test_diff_blob__can_compare_blob_to_buffer(void)
+{
+ git_blob *a;
+ git_oid a_oid;
+ const char *a_content = "Hello from the root\n";
+ const char *b_content = "Hello from the root\n\nSome additional lines\n\nDown here below\n\n";
+
+ /* tests/resources/attr/root_test1 */
+ cl_git_pass(git_oid_fromstrn(&a_oid, "45141a79", 8));
+ cl_git_pass(git_blob_lookup_prefix(&a, g_repo, &a_oid, 4));
+
+ /* diff from blob a to content of b */
+ quick_diff_blob_to_str(a, NULL, b_content, 0, NULL);
+ assert_one_modified(1, 6, 1, 5, 0, &expected);
+
+ /* diff from blob a to content of a */
+ opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
+ quick_diff_blob_to_str(a, NULL, a_content, 0, NULL);
+ assert_identical_blobs_comparison(&expected);
+
+ /* diff from NULL blob to content of a */
+ memset(&expected, 0, sizeof(expected));
+ quick_diff_blob_to_str(NULL, NULL, a_content, 0, NULL);
+ assert_changed_single_one_line_file(&expected, GIT_DELTA_ADDED);
+
+ /* diff from blob a to NULL buffer */
+ memset(&expected, 0, sizeof(expected));
+ quick_diff_blob_to_str(a, NULL, NULL, 0, NULL);
+ assert_changed_single_one_line_file(&expected, GIT_DELTA_DELETED);
+
+ /* diff with reverse */
+ opts.flags ^= GIT_DIFF_REVERSE;
+
+ memset(&expected, 0, sizeof(expected));
+ quick_diff_blob_to_str(a, NULL, NULL, 0, NULL);
+ assert_changed_single_one_line_file(&expected, GIT_DELTA_ADDED);
+
+ git_blob_free(a);
+}
+
+void test_diff_blob__can_compare_blob_to_buffer_with_patch(void)
+{
+ git_patch *p;
+ git_blob *a;
+ git_oid a_oid;
+ const char *a_content = "Hello from the root\n";
+ const char *b_content = "Hello from the root\n\nSome additional lines\n\nDown here below\n\n";
+ size_t tc, ta, td;
+
+ /* tests/resources/attr/root_test1 */
+ cl_git_pass(git_oid_fromstrn(&a_oid, "45141a79", 8));
+ cl_git_pass(git_blob_lookup_prefix(&a, g_repo, &a_oid, 4));
+
+ /* diff from blob a to content of b */
+ cl_git_pass(git_patch_from_blob_and_buffer(
+ &p, a, NULL, b_content, strlen(b_content), NULL, &opts));
+
+ cl_assert(p != NULL);
+ cl_assert_equal_i(GIT_DELTA_MODIFIED, git_patch_get_delta(p)->status);
+ cl_assert_equal_i(1, (int)git_patch_num_hunks(p));
+ cl_assert_equal_i(6, git_patch_num_lines_in_hunk(p, 0));
+
+ cl_git_pass(git_patch_line_stats(&tc, &ta, &td, p));
+ cl_assert_equal_i(1, (int)tc);
+ cl_assert_equal_i(5, (int)ta);
+ cl_assert_equal_i(0, (int)td);
+
+ git_patch_free(p);
+
+ /* diff from blob a to content of a */
+ opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
+ cl_git_pass(git_patch_from_blob_and_buffer(
+ &p, a, NULL, a_content, strlen(a_content), NULL, &opts));
+ cl_assert(p != NULL);
+ cl_assert_equal_i(GIT_DELTA_UNMODIFIED, git_patch_get_delta(p)->status);
+ cl_assert_equal_i(0, (int)git_patch_num_hunks(p));
+ git_patch_free(p);
+
+ /* diff from NULL blob to content of a */
+ cl_git_pass(git_patch_from_blob_and_buffer(
+ &p, NULL, NULL, a_content, strlen(a_content), NULL, &opts));
+ cl_assert(p != NULL);
+ cl_assert_equal_i(GIT_DELTA_ADDED, git_patch_get_delta(p)->status);
+ cl_assert_equal_i(1, (int)git_patch_num_hunks(p));
+ cl_assert_equal_i(1, git_patch_num_lines_in_hunk(p, 0));
+ git_patch_free(p);
+
+ /* diff from blob a to NULL buffer */
+ cl_git_pass(git_patch_from_blob_and_buffer(
+ &p, a, NULL, NULL, 0, NULL, &opts));
+ cl_assert(p != NULL);
+ cl_assert_equal_i(GIT_DELTA_DELETED, git_patch_get_delta(p)->status);
+ cl_assert_equal_i(1, (int)git_patch_num_hunks(p));
+ cl_assert_equal_i(1, git_patch_num_lines_in_hunk(p, 0));
+ git_patch_free(p);
+
+ /* diff with reverse */
+ opts.flags ^= GIT_DIFF_REVERSE;
+
+ cl_git_pass(git_patch_from_blob_and_buffer(
+ &p, a, NULL, NULL, 0, NULL, &opts));
+ cl_assert(p != NULL);
+ cl_assert_equal_i(GIT_DELTA_ADDED, git_patch_get_delta(p)->status);
+ cl_assert_equal_i(1, (int)git_patch_num_hunks(p));
+ cl_assert_equal_i(1, git_patch_num_lines_in_hunk(p, 0));
+ git_patch_free(p);
+
+ git_blob_free(a);
+}
+
+static void assert_one_modified_with_lines(diff_expects *expected, int 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->files_binary);
+ cl_assert_equal_i(lines, expected->lines);
+}
+
+void test_diff_blob__binary_data_comparisons(void)
+{
+ git_blob *bin, *nonbin;
+ git_oid oid;
+ const char *nonbin_content = "Hello from the root\n";
+ size_t nonbin_len = 20;
+ const char *bin_content = "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\n0123456789\n";
+ size_t bin_len = 33;
+
+ opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
+
+ cl_git_pass(git_oid_fromstrn(&oid, "45141a79", 8));
+ cl_git_pass(git_blob_lookup_prefix(&nonbin, g_repo, &oid, 4));
+
+ cl_git_pass(git_oid_fromstrn(&oid, "b435cd56", 8));
+ cl_git_pass(git_blob_lookup_prefix(&bin, g_repo, &oid, 4));
+
+ /* non-binary to reference content */
+
+ quick_diff_blob_to_str(nonbin, NULL, nonbin_content, nonbin_len, NULL);
+ assert_identical_blobs_comparison(&expected);
+ cl_assert_equal_i(0, expected.files_binary);
+
+ /* binary to reference content */
+
+ quick_diff_blob_to_str(bin, NULL, bin_content, bin_len, NULL);
+ assert_identical_blobs_comparison(&expected);
+
+ cl_assert_equal_i(1, expected.files_binary);
+
+ /* non-binary to binary content */
+
+ quick_diff_blob_to_str(nonbin, NULL, bin_content, bin_len, NULL);
+ assert_binary_blobs_comparison(&expected);
+
+ /* binary to non-binary content */
+
+ quick_diff_blob_to_str(bin, NULL, nonbin_content, nonbin_len, NULL);
+ assert_binary_blobs_comparison(&expected);
+
+ /* non-binary to binary blob */
+
+ memset(&expected, 0, sizeof(expected));
+ cl_git_pass(git_diff_blobs(
+ bin, NULL, nonbin, NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+ assert_binary_blobs_comparison(&expected);
+
+ /*
+ * repeat with FORCE_TEXT
+ */
+
+ opts.flags |= GIT_DIFF_FORCE_TEXT;
+
+ quick_diff_blob_to_str(bin, NULL, bin_content, bin_len, NULL);
+ assert_identical_blobs_comparison(&expected);
+
+ quick_diff_blob_to_str(nonbin, NULL, bin_content, bin_len, NULL);
+ assert_one_modified_with_lines(&expected, 4);
+
+ quick_diff_blob_to_str(bin, NULL, nonbin_content, nonbin_len, NULL);
+ assert_one_modified_with_lines(&expected, 4);
+
+ memset(&expected, 0, sizeof(expected));
+ cl_git_pass(git_diff_blobs(
+ bin, NULL, nonbin, NULL, &opts,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+ assert_one_modified_with_lines(&expected, 4);
+
+ /* cleanup */
+ git_blob_free(bin);
+ git_blob_free(nonbin);
+}
+
+void test_diff_blob__using_path_and_attributes(void)
+{
+ git_config *cfg;
+ git_blob *bin, *nonbin;
+ git_oid oid;
+ const char *nonbin_content = "Hello from the root\n";
+ const char *bin_content =
+ "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\n0123456789\n";
+ size_t bin_len = 33;
+ const char *changed;
+ git_patch *p;
+ git_buf buf = GIT_BUF_INIT;
+
+ /* set up custom diff drivers and 'diff' attribute mappings for them */
+
+ cl_git_pass(git_repository_config(&cfg, g_repo));
+ cl_git_pass(git_config_set_bool(cfg, "diff.iam_binary.binary", 1));
+ cl_git_pass(git_config_set_bool(cfg, "diff.iam_text.binary", 0));
+ cl_git_pass(git_config_set_string(
+ cfg, "diff.iam_alphactx.xfuncname", "^[A-Za-z].*$"));
+ cl_git_pass(git_config_set_bool(cfg, "diff.iam_textalpha.binary", 0));
+ cl_git_pass(git_config_set_string(
+ cfg, "diff.iam_textalpha.xfuncname", "^[A-Za-z].*$"));
+ cl_git_pass(git_config_set_string(
+ cfg, "diff.iam_numctx.funcname", "^[0-9][0-9]*"));
+ cl_git_pass(git_config_set_bool(cfg, "diff.iam_textnum.binary", 0));
+ cl_git_pass(git_config_set_string(
+ cfg, "diff.iam_textnum.funcname", "^[0-9][0-9]*"));
+ git_config_free(cfg);
+
+ cl_git_append2file(
+ "attr/.gitattributes",
+ "\n\n# test_diff_blob__using_path_and_attributes extra\n\n"
+ "*.binary diff=iam_binary\n"
+ "*.textary diff=iam_text\n"
+ "*.alphary diff=iam_alphactx\n"
+ "*.textalphary diff=iam_textalpha\n"
+ "*.textnumary diff=iam_textnum\n"
+ "*.numary diff=iam_numctx\n\n");
+
+ opts.context_lines = 0;
+ opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
+
+ cl_git_pass(git_oid_fromstrn(&oid, "45141a79", 8));
+ cl_git_pass(git_blob_lookup_prefix(&nonbin, g_repo, &oid, 4));
+ /* 20b: "Hello from the root\n" */
+
+ cl_git_pass(git_oid_fromstrn(&oid, "b435cd56", 8));
+ cl_git_pass(git_blob_lookup_prefix(&bin, g_repo, &oid, 4));
+ /* 33b: "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\n0123456789\n" */
+
+ /* non-binary to reference content */
+
+ quick_diff_blob_to_str(nonbin, NULL, nonbin_content, 0, NULL);
+ assert_identical_blobs_comparison(&expected);
+ cl_assert_equal_i(0, expected.files_binary);
+
+ /* binary to reference content */
+
+ quick_diff_blob_to_str(bin, NULL, bin_content, bin_len, NULL);
+ assert_identical_blobs_comparison(&expected);
+ cl_assert_equal_i(1, expected.files_binary);
+
+ /* add some text */
+
+ changed = "Hello from the root\nMore lines\nAnd more\nGo here\n";
+
+ quick_diff_blob_to_str(nonbin, NULL, changed, 0, NULL);
+ assert_one_modified(1, 3, 0, 3, 0, &expected);
+
+ quick_diff_blob_to_str(nonbin, "foo/bar.binary", changed, 0, NULL);
+ cl_assert_equal_i(1, expected.files);
+ cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(1, expected.files_binary);
+ cl_assert_equal_i(0, expected.hunks);
+ cl_assert_equal_i(0, expected.lines);
+
+ quick_diff_blob_to_str(nonbin, "foo/bar.textary", changed, 0, NULL);
+ assert_one_modified(1, 3, 0, 3, 0, &expected);
+
+ quick_diff_blob_to_str(nonbin, "foo/bar.alphary", changed, 0, NULL);
+ assert_one_modified(1, 3, 0, 3, 0, &expected);
+
+ cl_git_pass(git_patch_from_blob_and_buffer(
+ &p, nonbin, "zzz.normal", changed, strlen(changed), NULL, &opts));
+ cl_git_pass(git_patch_to_buf(&buf, p));
+ cl_assert_equal_s(
+ "diff --git a/zzz.normal b/zzz.normal\n"
+ "index 45141a7..75b0dbb 100644\n"
+ "--- a/zzz.normal\n"
+ "+++ b/zzz.normal\n"
+ "@@ -1,0 +2,3 @@ Hello from the root\n"
+ "+More lines\n"
+ "+And more\n"
+ "+Go here\n", buf.ptr);
+ git_buf_clear(&buf);
+ git_patch_free(p);
+
+ cl_git_pass(git_patch_from_blob_and_buffer(
+ &p, nonbin, "zzz.binary", changed, strlen(changed), NULL, &opts));
+ cl_git_pass(git_patch_to_buf(&buf, p));
+ cl_assert_equal_s(
+ "diff --git a/zzz.binary b/zzz.binary\n"
+ "index 45141a7..75b0dbb 100644\n"
+ "Binary files a/zzz.binary and b/zzz.binary differ\n", buf.ptr);
+ git_buf_clear(&buf);
+ git_patch_free(p);
+
+ cl_git_pass(git_patch_from_blob_and_buffer(
+ &p, nonbin, "zzz.alphary", changed, strlen(changed), NULL, &opts));
+ cl_git_pass(git_patch_to_buf(&buf, p));
+ cl_assert_equal_s(
+ "diff --git a/zzz.alphary b/zzz.alphary\n"
+ "index 45141a7..75b0dbb 100644\n"
+ "--- a/zzz.alphary\n"
+ "+++ b/zzz.alphary\n"
+ "@@ -1,0 +2,3 @@ Hello from the root\n"
+ "+More lines\n"
+ "+And more\n"
+ "+Go here\n", buf.ptr);
+ git_buf_clear(&buf);
+ git_patch_free(p);
+
+ cl_git_pass(git_patch_from_blob_and_buffer(
+ &p, nonbin, "zzz.numary", changed, strlen(changed), NULL, &opts));
+ cl_git_pass(git_patch_to_buf(&buf, p));
+ cl_assert_equal_s(
+ "diff --git a/zzz.numary b/zzz.numary\n"
+ "index 45141a7..75b0dbb 100644\n"
+ "--- a/zzz.numary\n"
+ "+++ b/zzz.numary\n"
+ "@@ -1,0 +2,3 @@\n"
+ "+More lines\n"
+ "+And more\n"
+ "+Go here\n", buf.ptr);
+ git_buf_clear(&buf);
+ git_patch_free(p);
+
+ /* "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\n0123456789\n"
+ * 33 bytes
+ */
+
+ changed = "0123456789\n\x01\x02\x03\x04\x05\x06\x07\x08\x09\x00\nreplace a line\n";
+
+ cl_git_pass(git_patch_from_blob_and_buffer(
+ &p, bin, "zzz.normal", changed, 37, NULL, &opts));
+ cl_git_pass(git_patch_to_buf(&buf, p));
+ cl_assert_equal_s(
+ "diff --git a/zzz.normal b/zzz.normal\n"
+ "index b435cd5..1604519 100644\n"
+ "Binary files a/zzz.normal and b/zzz.normal differ\n", buf.ptr);
+ git_buf_clear(&buf);
+ git_patch_free(p);
+
+ cl_git_pass(git_patch_from_blob_and_buffer(
+ &p, bin, "zzz.textary", changed, 37, NULL, &opts));
+ cl_git_pass(git_patch_to_buf(&buf, p));
+ cl_assert_equal_s(
+ "diff --git a/zzz.textary b/zzz.textary\n"
+ "index b435cd5..1604519 100644\n"
+ "--- a/zzz.textary\n"
+ "+++ b/zzz.textary\n"
+ "@@ -3 +3 @@\n"
+ "-0123456789\n"
+ "+replace a line\n", buf.ptr);
+ git_buf_clear(&buf);
+ git_patch_free(p);
+
+ cl_git_pass(git_patch_from_blob_and_buffer(
+ &p, bin, "zzz.textalphary", changed, 37, NULL, &opts));
+ cl_git_pass(git_patch_to_buf(&buf, p));
+ cl_assert_equal_s(
+ "diff --git a/zzz.textalphary b/zzz.textalphary\n"
+ "index b435cd5..1604519 100644\n"
+ "--- a/zzz.textalphary\n"
+ "+++ b/zzz.textalphary\n"
+ "@@ -3 +3 @@\n"
+ "-0123456789\n"
+ "+replace a line\n", buf.ptr);
+ git_buf_clear(&buf);
+ git_patch_free(p);
+
+ cl_git_pass(git_patch_from_blob_and_buffer(
+ &p, bin, "zzz.textnumary", changed, 37, NULL, &opts));
+ cl_git_pass(git_patch_to_buf(&buf, p));
+ cl_assert_equal_s(
+ "diff --git a/zzz.textnumary b/zzz.textnumary\n"
+ "index b435cd5..1604519 100644\n"
+ "--- a/zzz.textnumary\n"
+ "+++ b/zzz.textnumary\n"
+ "@@ -3 +3 @@ 0123456789\n"
+ "-0123456789\n"
+ "+replace a line\n", buf.ptr);
+ git_buf_clear(&buf);
+ git_patch_free(p);
+
+ git_buf_free(&buf);
+ git_blob_free(nonbin);
+ git_blob_free(bin);
+}
+
+void test_diff_blob__can_compare_buffer_to_buffer(void)
+{
+ const char *a = "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n";
+ const char *b = "a\nB\nc\nd\nE\nF\nh\nj\nk\n";
+
+ opts.interhunk_lines = 0;
+ opts.context_lines = 0;
+
+ memset(&expected, 0, sizeof(expected));
+
+ cl_git_pass(git_diff_buffers(
+ a, strlen(a), NULL, b, strlen(b), NULL,
+ &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+ assert_one_modified(4, 9, 0, 4, 5, &expected);
+
+ opts.flags ^= GIT_DIFF_REVERSE;
+
+ memset(&expected, 0, sizeof(expected));
+
+ cl_git_pass(git_diff_buffers(
+ a, strlen(a), NULL, b, strlen(b), NULL,
+ &opts, diff_file_cb, diff_hunk_cb, diff_line_cb, &expected));
+ assert_one_modified(4, 9, 0, 5, 4, &expected);
+}
diff --git a/tests/diff/diff_helpers.c b/tests/diff/diff_helpers.c
new file mode 100644
index 000000000..279cb20c5
--- /dev/null
+++ b/tests/diff/diff_helpers.c
@@ -0,0 +1,231 @@
+#include "clar_libgit2.h"
+#include "diff_helpers.h"
+#include "git2/sys/diff.h"
+
+git_tree *resolve_commit_oid_to_tree(
+ git_repository *repo,
+ const char *partial_oid)
+{
+ size_t len = strlen(partial_oid);
+ git_oid oid;
+ git_object *obj = NULL;
+ git_tree *tree = NULL;
+
+ if (git_oid_fromstrn(&oid, partial_oid, len) == 0)
+ git_object_lookup_prefix(&obj, repo, &oid, len, GIT_OBJ_ANY);
+ cl_assert(obj);
+ if (git_object_type(obj) == GIT_OBJ_TREE)
+ return (git_tree *)obj;
+ cl_assert(git_object_type(obj) == GIT_OBJ_COMMIT);
+ cl_git_pass(git_commit_tree(&tree, (git_commit *)obj));
+ git_object_free(obj);
+ return tree;
+}
+
+static char diff_pick_suffix(int mode)
+{
+ if (S_ISDIR(mode))
+ return '/';
+ else if (GIT_PERMS_IS_EXEC(mode))
+ return '*';
+ else
+ return ' ';
+}
+
+static void fprintf_delta(FILE *fp, const git_diff_delta *delta, float progress)
+{
+ char code = git_diff_status_char(delta->status);
+ char old_suffix = diff_pick_suffix(delta->old_file.mode);
+ char new_suffix = diff_pick_suffix(delta->new_file.mode);
+
+ fprintf(fp, "%c\t%s", code, delta->old_file.path);
+
+ if ((delta->old_file.path != delta->new_file.path &&
+ strcmp(delta->old_file.path, delta->new_file.path) != 0) ||
+ (delta->old_file.mode != delta->new_file.mode &&
+ delta->old_file.mode != 0 && delta->new_file.mode != 0))
+ fprintf(fp, "%c %s%c", old_suffix, delta->new_file.path, new_suffix);
+ else if (old_suffix != ' ')
+ fprintf(fp, "%c", old_suffix);
+
+ fprintf(fp, "\t[%.2f]\n", progress);
+}
+
+int diff_file_cb(
+ const git_diff_delta *delta,
+ float progress,
+ void *payload)
+{
+ diff_expects *e = payload;
+
+ if (e->debug)
+ fprintf_delta(stderr, delta, progress);
+
+ if (e->names)
+ cl_assert_equal_s(e->names[e->files], delta->old_file.path);
+ if (e->statuses)
+ cl_assert_equal_i(e->statuses[e->files], (int)delta->status);
+
+ e->files++;
+
+ if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0)
+ e->files_binary++;
+
+ cl_assert(delta->status <= GIT_DELTA_TYPECHANGE);
+
+ e->file_status[delta->status] += 1;
+
+ return 0;
+}
+
+int diff_print_file_cb(
+ const git_diff_delta *delta,
+ float progress,
+ void *payload)
+{
+ if (!payload) {
+ fprintf_delta(stderr, delta, progress);
+ return 0;
+ }
+
+ if (!((diff_expects *)payload)->debug)
+ fprintf_delta(stderr, delta, progress);
+
+ return diff_file_cb(delta, progress, payload);
+}
+
+int diff_hunk_cb(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk,
+ void *payload)
+{
+ diff_expects *e = payload;
+ const char *scan = hunk->header, *scan_end = scan + hunk->header_len;
+
+ GIT_UNUSED(delta);
+
+ /* confirm no NUL bytes in header text */
+ while (scan < scan_end)
+ cl_assert('\0' != *scan++);
+
+ e->hunks++;
+ e->hunk_old_lines += hunk->old_lines;
+ e->hunk_new_lines += hunk->new_lines;
+ return 0;
+}
+
+int diff_line_cb(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk,
+ const git_diff_line *line,
+ void *payload)
+{
+ diff_expects *e = payload;
+
+ GIT_UNUSED(delta);
+ GIT_UNUSED(hunk);
+
+ e->lines++;
+ switch (line->origin) {
+ case GIT_DIFF_LINE_CONTEXT:
+ case GIT_DIFF_LINE_CONTEXT_EOFNL: /* techically not a line */
+ e->line_ctxt++;
+ break;
+ case GIT_DIFF_LINE_ADDITION:
+ case GIT_DIFF_LINE_ADD_EOFNL: /* technically not a line add */
+ e->line_adds++;
+ break;
+ case GIT_DIFF_LINE_DELETION:
+ case GIT_DIFF_LINE_DEL_EOFNL: /* technically not a line delete */
+ e->line_dels++;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+int diff_foreach_via_iterator(
+ git_diff *diff,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb line_cb,
+ void *data)
+{
+ size_t d, num_d = git_diff_num_deltas(diff);
+
+ for (d = 0; d < num_d; ++d) {
+ git_patch *patch;
+ const git_diff_delta *delta;
+ size_t h, num_h;
+
+ cl_git_pass(git_patch_from_diff(&patch, diff, d));
+ cl_assert((delta = git_patch_get_delta(patch)) != NULL);
+
+ /* call file_cb for this file */
+ if (file_cb != NULL && file_cb(delta, (float)d / num_d, data) != 0) {
+ git_patch_free(patch);
+ goto abort;
+ }
+
+ /* if there are no changes, then the patch will be NULL */
+ if (!patch) {
+ cl_assert(delta->status == GIT_DELTA_UNMODIFIED ||
+ (delta->flags & GIT_DIFF_FLAG_BINARY) != 0);
+ continue;
+ }
+
+ if (!hunk_cb && !line_cb) {
+ git_patch_free(patch);
+ continue;
+ }
+
+ num_h = git_patch_num_hunks(patch);
+
+ for (h = 0; h < num_h; h++) {
+ const git_diff_hunk *hunk;
+ size_t l, num_l;
+
+ cl_git_pass(git_patch_get_hunk(&hunk, &num_l, patch, h));
+
+ if (hunk_cb && hunk_cb(delta, hunk, data) != 0) {
+ git_patch_free(patch);
+ goto abort;
+ }
+
+ for (l = 0; l < num_l; ++l) {
+ const git_diff_line *line;
+
+ cl_git_pass(git_patch_get_line_in_hunk(&line, patch, h, l));
+
+ if (line_cb &&
+ line_cb(delta, hunk, line, data) != 0) {
+ git_patch_free(patch);
+ goto abort;
+ }
+ }
+ }
+
+ git_patch_free(patch);
+ }
+
+ return 0;
+
+abort:
+ giterr_clear();
+ return GIT_EUSER;
+}
+
+void diff_print(FILE *fp, git_diff *diff)
+{
+ cl_git_pass(
+ git_diff_print(diff, GIT_DIFF_FORMAT_PATCH,
+ git_diff_print_callback__to_file_handle, fp ? fp : stderr));
+}
+
+void diff_print_raw(FILE *fp, git_diff *diff)
+{
+ cl_git_pass(
+ git_diff_print(diff, GIT_DIFF_FORMAT_RAW,
+ git_diff_print_callback__to_file_handle, fp ? fp : stderr));
+}
diff --git a/tests/diff/diff_helpers.h b/tests/diff/diff_helpers.h
new file mode 100644
index 000000000..bf21f4b1f
--- /dev/null
+++ b/tests/diff/diff_helpers.h
@@ -0,0 +1,64 @@
+#include "fileops.h"
+#include "git2/diff.h"
+
+extern git_tree *resolve_commit_oid_to_tree(
+ git_repository *repo, const char *partial_oid);
+
+typedef struct {
+ int files;
+ int files_binary;
+
+ int file_status[10]; /* indexed by git_delta_t value */
+
+ int hunks;
+ int hunk_new_lines;
+ int hunk_old_lines;
+
+ int lines;
+ int line_ctxt;
+ int line_adds;
+ int line_dels;
+
+ /* optional arrays of expected specific values */
+ const char **names;
+ int *statuses;
+
+ int debug;
+
+} diff_expects;
+
+typedef struct {
+ const char *path;
+ const char *matched_pathspec;
+} notify_expected;
+
+extern int diff_file_cb(
+ const git_diff_delta *delta,
+ float progress,
+ void *cb_data);
+
+extern int diff_print_file_cb(
+ const git_diff_delta *delta,
+ float progress,
+ void *cb_data);
+
+extern int diff_hunk_cb(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk,
+ void *cb_data);
+
+extern int diff_line_cb(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk,
+ const git_diff_line *line,
+ void *cb_data);
+
+extern int diff_foreach_via_iterator(
+ git_diff *diff,
+ git_diff_file_cb file_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb line_cb,
+ void *data);
+
+extern void diff_print(FILE *fp, git_diff *diff);
+extern void diff_print_raw(FILE *fp, git_diff *diff);
diff --git a/tests/diff/diffiter.c b/tests/diff/diffiter.c
new file mode 100644
index 000000000..c976e30e2
--- /dev/null
+++ b/tests/diff/diffiter.c
@@ -0,0 +1,453 @@
+#include "clar_libgit2.h"
+#include "diff_helpers.h"
+
+void test_diff_diffiter__initialize(void)
+{
+}
+
+void test_diff_diffiter__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_diff_diffiter__create(void)
+{
+ git_repository *repo = cl_git_sandbox_init("attr");
+ git_diff *diff;
+ size_t d, num_d;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL));
+
+ num_d = git_diff_num_deltas(diff);
+ for (d = 0; d < num_d; ++d) {
+ const git_diff_delta *delta = git_diff_get_delta(diff, d);
+ cl_assert(delta != NULL);
+ }
+
+ cl_assert(!git_diff_get_delta(diff, num_d));
+
+ git_diff_free(diff);
+}
+
+void test_diff_diffiter__iterate_files_1(void)
+{
+ git_repository *repo = cl_git_sandbox_init("attr");
+ git_diff *diff;
+ size_t d, num_d;
+ diff_expects exp = { 0 };
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL));
+
+ num_d = git_diff_num_deltas(diff);
+
+ for (d = 0; d < num_d; ++d) {
+ const git_diff_delta *delta = git_diff_get_delta(diff, d);
+ cl_assert(delta != NULL);
+
+ diff_file_cb(delta, (float)d / (float)num_d, &exp);
+ }
+ cl_assert_equal_sz(6, exp.files);
+
+ git_diff_free(diff);
+}
+
+void test_diff_diffiter__iterate_files_2(void)
+{
+ git_repository *repo = cl_git_sandbox_init("status");
+ git_diff *diff;
+ size_t d, num_d;
+ int count = 0;
+
+ cl_git_pass(git_diff_index_to_workdir(&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) {
+ const git_diff_delta *delta = git_diff_get_delta(diff, d);
+ cl_assert(delta != NULL);
+ count++;
+ }
+ cl_assert_equal_i(8, count);
+
+ git_diff_free(diff);
+}
+
+void test_diff_diffiter__iterate_files_and_hunks(void)
+{
+ git_repository *repo = cl_git_sandbox_init("status");
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ size_t d, num_d;
+ int file_count = 0, hunk_count = 0;
+
+ opts.context_lines = 3;
+ opts.interhunk_lines = 1;
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts));
+
+ num_d = git_diff_num_deltas(diff);
+
+ for (d = 0; d < num_d; ++d) {
+ git_patch *patch;
+ size_t h, num_h;
+
+ cl_git_pass(git_patch_from_diff(&patch, diff, d));
+ cl_assert(patch);
+
+ file_count++;
+
+ num_h = git_patch_num_hunks(patch);
+
+ for (h = 0; h < num_h; h++) {
+ const git_diff_hunk *hunk;
+
+ cl_git_pass(git_patch_get_hunk(&hunk, NULL, patch, h));
+ cl_assert(hunk);
+
+ hunk_count++;
+ }
+
+ git_patch_free(patch);
+ }
+
+ cl_assert_equal_i(13, file_count);
+ cl_assert_equal_i(8, hunk_count);
+
+ git_diff_free(diff);
+}
+
+void test_diff_diffiter__max_size_threshold(void)
+{
+ git_repository *repo = cl_git_sandbox_init("status");
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ int file_count = 0, binary_count = 0, hunk_count = 0;
+ size_t d, num_d;
+
+ opts.context_lines = 3;
+ opts.interhunk_lines = 1;
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts));
+ num_d = git_diff_num_deltas(diff);
+
+ for (d = 0; d < num_d; ++d) {
+ git_patch *patch;
+ const git_diff_delta *delta;
+
+ cl_git_pass(git_patch_from_diff(&patch, diff, d));
+ cl_assert(patch);
+ delta = git_patch_get_delta(patch);
+ cl_assert(delta);
+
+ file_count++;
+ hunk_count += (int)git_patch_num_hunks(patch);
+
+ assert((delta->flags & (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY)) != 0);
+ binary_count += ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0);
+
+ git_patch_free(patch);
+ }
+
+ cl_assert_equal_i(13, file_count);
+ cl_assert_equal_i(0, binary_count);
+ cl_assert_equal_i(8, hunk_count);
+
+ git_diff_free(diff);
+
+ /* try again with low file size threshold */
+
+ file_count = binary_count = hunk_count = 0;
+
+ opts.context_lines = 3;
+ opts.interhunk_lines = 1;
+ 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_index_to_workdir(&diff, repo, NULL, &opts));
+ num_d = git_diff_num_deltas(diff);
+
+ for (d = 0; d < num_d; ++d) {
+ git_patch *patch;
+ const git_diff_delta *delta;
+
+ cl_git_pass(git_patch_from_diff(&patch, diff, d));
+ delta = git_patch_get_delta(patch);
+
+ file_count++;
+ hunk_count += (int)git_patch_num_hunks(patch);
+
+ assert((delta->flags & (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY)) != 0);
+ binary_count += ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0);
+
+ git_patch_free(patch);
+ }
+
+ cl_assert_equal_i(13, file_count);
+ /* Three files are over the 50 byte threshold:
+ * - staged_changes_file_deleted
+ * - staged_changes_modified_file
+ * - staged_new_file_modified_file
+ */
+ cl_assert_equal_i(3, binary_count);
+ cl_assert_equal_i(5, hunk_count);
+
+ git_diff_free(diff);
+}
+
+
+void test_diff_diffiter__iterate_all(void)
+{
+ git_repository *repo = cl_git_sandbox_init("status");
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ diff_expects exp = {0};
+ size_t d, num_d;
+
+ opts.context_lines = 3;
+ opts.interhunk_lines = 1;
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts));
+
+ num_d = git_diff_num_deltas(diff);
+ for (d = 0; d < num_d; ++d) {
+ git_patch *patch;
+ size_t h, num_h;
+
+ cl_git_pass(git_patch_from_diff(&patch, diff, d));
+ cl_assert(patch);
+ exp.files++;
+
+ num_h = git_patch_num_hunks(patch);
+ for (h = 0; h < num_h; h++) {
+ const git_diff_hunk *range;
+ size_t l, num_l;
+
+ cl_git_pass(git_patch_get_hunk(&range, &num_l, patch, h));
+ cl_assert(range);
+ exp.hunks++;
+
+ for (l = 0; l < num_l; ++l) {
+ const git_diff_line *line;
+
+ cl_git_pass(git_patch_get_line_in_hunk(&line, patch, h, l));
+ cl_assert(line && line->content);
+ exp.lines++;
+ }
+ }
+
+ git_patch_free(patch);
+ }
+
+ cl_assert_equal_i(13, exp.files);
+ cl_assert_equal_i(8, exp.hunks);
+ cl_assert_equal_i(14, exp.lines);
+
+ git_diff_free(diff);
+}
+
+static void iterate_over_patch(git_patch *patch, diff_expects *exp)
+{
+ size_t h, num_h = git_patch_num_hunks(patch), num_l;
+
+ exp->files++;
+ exp->hunks += (int)num_h;
+
+ /* let's iterate in reverse, just because we can! */
+ for (h = 1, num_l = 0; h <= num_h; ++h)
+ num_l += git_patch_num_lines_in_hunk(patch, num_h - h);
+
+ exp->lines += (int)num_l;
+}
+
+#define PATCH_CACHE 5
+
+void test_diff_diffiter__iterate_randomly_while_saving_state(void)
+{
+ git_repository *repo = cl_git_sandbox_init("status");
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ diff_expects exp = {0};
+ git_patch *patches[PATCH_CACHE];
+ size_t p, d, num_d;
+
+ memset(patches, 0, sizeof(patches));
+
+ opts.context_lines = 3;
+ opts.interhunk_lines = 1;
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts));
+
+ num_d = git_diff_num_deltas(diff);
+
+ /* To make sure that references counts work for diff and patch objects,
+ * this generates patches and randomly caches them. Only when the patch
+ * is removed from the cache are hunks and lines counted. At the end,
+ * there are still patches in the cache, so free the diff and try to
+ * process remaining patches after the diff is freed.
+ */
+
+ srand(121212);
+ p = rand() % PATCH_CACHE;
+
+ for (d = 0; d < num_d; ++d) {
+ /* take old patch */
+ git_patch *patch = patches[p];
+ patches[p] = NULL;
+
+ /* cache new patch */
+ cl_git_pass(git_patch_from_diff(&patches[p], diff, d));
+ cl_assert(patches[p] != NULL);
+
+ /* process old patch if non-NULL */
+ if (patch != NULL) {
+ iterate_over_patch(patch, &exp);
+ git_patch_free(patch);
+ }
+
+ p = rand() % PATCH_CACHE;
+ }
+
+ /* free diff list now - refcounts should keep things safe */
+ git_diff_free(diff);
+
+ /* process remaining unprocessed patches */
+ for (p = 0; p < PATCH_CACHE; p++) {
+ git_patch *patch = patches[p];
+
+ if (patch != NULL) {
+ iterate_over_patch(patch, &exp);
+ git_patch_free(patch);
+ }
+ }
+
+ /* hopefully it all still added up right */
+ cl_assert_equal_i(13, exp.files);
+ 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 *diff;
+ size_t d, num_d;
+
+ cl_git_pass(git_diff_index_to_workdir(&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_patch *patch;
+ git_buf buf = GIT_BUF_INIT;
+
+ cl_git_pass(git_patch_from_diff(&patch, diff, d));
+ cl_assert(patch != NULL);
+
+ cl_git_pass(git_patch_to_buf(&buf, patch));
+
+ cl_assert_equal_s(expected_patch_text[d], buf.ptr);
+
+ git_buf_free(&buf);
+ git_patch_free(patch);
+ }
+
+ git_diff_free(diff);
+}
+
+void test_diff_diffiter__checks_options_version(void)
+{
+ git_repository *repo = cl_git_sandbox_init("status");
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ const git_error *err;
+
+ opts.version = 0;
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
+
+ cl_git_fail(git_diff_index_to_workdir(&diff, repo, NULL, &opts));
+ err = giterr_last();
+ cl_assert_equal_i(GITERR_INVALID, err->klass);
+
+ giterr_clear();
+ opts.version = 1024;
+ cl_git_fail(git_diff_index_to_workdir(&diff, repo, NULL, &opts));
+ err = giterr_last();
+ cl_assert_equal_i(GITERR_INVALID, err->klass);
+}
+
diff --git a/tests/diff/drivers.c b/tests/diff/drivers.c
new file mode 100644
index 000000000..8b12368ea
--- /dev/null
+++ b/tests/diff/drivers.c
@@ -0,0 +1,252 @@
+#include "clar_libgit2.h"
+#include "diff_helpers.h"
+#include "repository.h"
+#include "diff_driver.h"
+
+static git_repository *g_repo = NULL;
+
+void test_diff_drivers__initialize(void)
+{
+}
+
+void test_diff_drivers__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+ g_repo = NULL;
+}
+
+static void overwrite_filemode(const char *expected, git_buf *actual)
+{
+ size_t offset;
+ char *found;
+
+ found = strstr(expected, "100644");
+ if (!found)
+ return;
+
+ offset = ((const char *)found) - expected;
+ if (actual->size < offset + 6)
+ return;
+
+ if (memcmp(&actual->ptr[offset], "100644", 6) != 0)
+ memcpy(&actual->ptr[offset], "100644", 6);
+}
+
+void test_diff_drivers__patterns(void)
+{
+ git_config *cfg;
+ const char *one_sha = "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13";
+ git_tree *one;
+ git_diff *diff;
+ git_patch *patch;
+ git_buf actual = GIT_BUF_INIT;
+ const char *expected0 = "diff --git a/untimely.txt b/untimely.txt\nindex 9a69d96..57fd0cf 100644\n--- a/untimely.txt\n+++ b/untimely.txt\n@@ -22,3 +22,5 @@ Comes through the blood of the vanguards who\n dreamed--too soon--it had sounded.\r\n \r\n -- Rudyard Kipling\r\n+\r\n+Some new stuff\r\n";
+ const char *expected1 = "diff --git a/untimely.txt b/untimely.txt\nindex 9a69d96..57fd0cf 100644\nBinary files a/untimely.txt and b/untimely.txt differ\n";
+ const char *expected2 = "diff --git a/untimely.txt b/untimely.txt\nindex 9a69d96..57fd0cf 100644\n--- a/untimely.txt\n+++ b/untimely.txt\n@@ -22,3 +22,5 @@ Heaven delivers on earth the Hour that cannot be\n dreamed--too soon--it had sounded.\r\n \r\n -- Rudyard Kipling\r\n+\r\n+Some new stuff\r\n";
+
+ g_repo = cl_git_sandbox_init("renames");
+
+ one = resolve_commit_oid_to_tree(g_repo, one_sha);
+
+ /* no diff */
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL));
+ cl_assert_equal_i(0, (int)git_diff_num_deltas(diff));
+ git_diff_free(diff);
+
+ /* default diff */
+
+ cl_git_append2file("renames/untimely.txt", "\r\nSome new stuff\r\n");
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL));
+ cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
+
+ cl_git_pass(git_patch_from_diff(&patch, diff, 0));
+ cl_git_pass(git_patch_to_buf(&actual, patch));
+ cl_assert_equal_s(expected0, actual.ptr);
+
+ git_buf_free(&actual);
+ git_patch_free(patch);
+ git_diff_free(diff);
+
+ /* attribute diff set to false */
+
+ cl_git_rewritefile("renames/.gitattributes", "untimely.txt -diff\n");
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL));
+ cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
+
+ cl_git_pass(git_patch_from_diff(&patch, diff, 0));
+ cl_git_pass(git_patch_to_buf(&actual, patch));
+ cl_assert_equal_s(expected1, actual.ptr);
+
+ git_buf_free(&actual);
+ git_patch_free(patch);
+ git_diff_free(diff);
+
+ /* attribute diff set to unconfigured value (should use default) */
+
+ cl_git_rewritefile("renames/.gitattributes", "untimely.txt diff=kipling0\n");
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL));
+ cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
+
+ cl_git_pass(git_patch_from_diff(&patch, diff, 0));
+ cl_git_pass(git_patch_to_buf(&actual, patch));
+ cl_assert_equal_s(expected0, actual.ptr);
+
+ git_buf_free(&actual);
+ git_patch_free(patch);
+ git_diff_free(diff);
+
+ /* let's define that driver */
+
+ cl_git_pass(git_repository_config(&cfg, g_repo));
+ cl_git_pass(git_config_set_bool(cfg, "diff.kipling0.binary", 1));
+ git_config_free(cfg);
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL));
+ cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
+
+ cl_git_pass(git_patch_from_diff(&patch, diff, 0));
+ cl_git_pass(git_patch_to_buf(&actual, patch));
+ cl_assert_equal_s(expected1, actual.ptr);
+
+ git_buf_free(&actual);
+ git_patch_free(patch);
+ git_diff_free(diff);
+
+ /* let's use a real driver with some regular expressions */
+
+ git_diff_driver_registry_free(g_repo->diff_drivers);
+ g_repo->diff_drivers = NULL;
+
+ cl_git_pass(git_repository_config(&cfg, g_repo));
+ cl_git_pass(git_config_set_bool(cfg, "diff.kipling0.binary", 0));
+ cl_git_pass(git_config_set_string(cfg, "diff.kipling0.xfuncname", "^H.*$"));
+ git_config_free(cfg);
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, one, NULL));
+ cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
+
+ cl_git_pass(git_patch_from_diff(&patch, diff, 0));
+ cl_git_pass(git_patch_to_buf(&actual, patch));
+ cl_assert_equal_s(expected2, actual.ptr);
+
+ git_buf_free(&actual);
+ git_patch_free(patch);
+ git_diff_free(diff);
+
+ git_tree_free(one);
+}
+
+void test_diff_drivers__long_lines(void)
+{
+ const char *base = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non nisi ligula. Ut viverra enim sed lobortis suscipit.\nPhasellus eget erat odio. Praesent at est iaculis, ultricies augue vel, dignissim risus. Suspendisse at nisi quis turpis fringilla rutrum id sit amet nulla.\nNam eget dolor fermentum, aliquet nisl at, convallis tellus. Pellentesque rhoncus erat enim, id porttitor elit euismod quis.\nMauris sollicitudin magna odio, non egestas libero vehicula ut. Etiam et quam velit. Fusce eget libero rhoncus, ultricies felis sit amet, egestas purus.\nAliquam in semper tellus. Pellentesque adipiscing rutrum velit, quis malesuada lacus consequat eget.\n";
+ git_index *idx;
+ git_diff *diff;
+ git_patch *patch;
+ git_buf actual = GIT_BUF_INIT;
+ const char *expected = "diff --git a/longlines.txt b/longlines.txt\nindex c1ce6ef..0134431 100644\n--- a/longlines.txt\n+++ b/longlines.txt\n@@ -3,3 +3,5 @@ Phasellus eget erat odio. Praesent at est iaculis, ultricies augue vel, dignissi\n Nam eget dolor fermentum, aliquet nisl at, convallis tellus. Pellentesque rhoncus erat enim, id porttitor elit euismod quis.\n Mauris sollicitudin magna odio, non egestas libero vehicula ut. Etiam et quam velit. Fusce eget libero rhoncus, ultricies felis sit amet, egestas purus.\n Aliquam in semper tellus. Pellentesque adipiscing rutrum velit, quis malesuada lacus consequat eget.\n+newline\n+newline\n";
+
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+
+ cl_git_mkfile("empty_standard_repo/longlines.txt", base);
+ cl_git_pass(git_repository_index(&idx, g_repo));
+ cl_git_pass(git_index_add_bypath(idx, "longlines.txt"));
+ cl_git_pass(git_index_write(idx));
+ git_index_free(idx);
+
+ cl_git_append2file("empty_standard_repo/longlines.txt", "newline\nnewline\n");
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL));
+ cl_assert_equal_sz(1, git_diff_num_deltas(diff));
+ cl_git_pass(git_patch_from_diff(&patch, diff, 0));
+ cl_git_pass(git_patch_to_buf(&actual, patch));
+
+ /* if chmod not supported, overwrite mode bits since anything is possible */
+ overwrite_filemode(expected, &actual);
+
+ cl_assert_equal_s(expected, actual.ptr);
+
+ git_buf_free(&actual);
+ git_patch_free(patch);
+ git_diff_free(diff);
+}
+
+void test_diff_drivers__builtins(void)
+{
+ git_diff *diff;
+ git_patch *patch;
+ git_buf file = GIT_BUF_INIT, actual = GIT_BUF_INIT, expected = GIT_BUF_INIT;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_vector files = GIT_VECTOR_INIT;
+ size_t i;
+ char *path, *extension;
+
+ g_repo = cl_git_sandbox_init("userdiff");
+
+ cl_git_pass(git_path_dirload("userdiff/files", 9, 0, 0, &files));
+
+ opts.interhunk_lines = 1;
+ opts.context_lines = 1;
+ opts.pathspec.count = 1;
+
+ git_vector_foreach(&files, i, path) {
+ if (git__prefixcmp(path, "files/file."))
+ continue;
+ extension = path + strlen("files/file.");
+ opts.pathspec.strings = &path;
+
+ /* do diff with no special driver */
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ cl_assert_equal_sz(1, git_diff_num_deltas(diff));
+ cl_git_pass(git_patch_from_diff(&patch, diff, 0));
+ cl_git_pass(git_patch_to_buf(&actual, patch));
+
+ git_buf_sets(&expected, "userdiff/expected/nodriver/diff.");
+ git_buf_puts(&expected, extension);
+ cl_git_pass(git_futils_readbuffer(&expected, expected.ptr));
+
+ overwrite_filemode(expected.ptr, &actual);
+
+ cl_assert_equal_s(expected.ptr, actual.ptr);
+
+ git_buf_clear(&actual);
+ git_patch_free(patch);
+ git_diff_free(diff);
+
+ /* do diff with driver */
+
+ {
+ FILE *fp = fopen("userdiff/.gitattributes", "w");
+ fprintf(fp, "*.%s diff=%s\n", extension, extension);
+ fclose(fp);
+ }
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ cl_assert_equal_sz(1, git_diff_num_deltas(diff));
+ cl_git_pass(git_patch_from_diff(&patch, diff, 0));
+ cl_git_pass(git_patch_to_buf(&actual, patch));
+
+ git_buf_sets(&expected, "userdiff/expected/driver/diff.");
+ git_buf_puts(&expected, extension);
+ cl_git_pass(git_futils_readbuffer(&expected, expected.ptr));
+
+ overwrite_filemode(expected.ptr, &actual);
+
+ cl_assert_equal_s(expected.ptr, actual.ptr);
+
+ git_buf_clear(&actual);
+ git_patch_free(patch);
+ git_diff_free(diff);
+
+ git__free(path);
+ }
+
+ git_buf_free(&file);
+ git_buf_free(&actual);
+ git_buf_free(&expected);
+ git_vector_free(&files);
+}
diff --git a/tests/diff/format_email.c b/tests/diff/format_email.c
new file mode 100644
index 000000000..18ad99bd5
--- /dev/null
+++ b/tests/diff/format_email.c
@@ -0,0 +1,467 @@
+#include "clar.h"
+#include "clar_libgit2.h"
+
+#include "buffer.h"
+#include "commit.h"
+#include "diff.h"
+
+static git_repository *repo;
+
+void test_diff_format_email__initialize(void)
+{
+ repo = cl_git_sandbox_init("diff_format_email");
+}
+
+void test_diff_format_email__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+static void assert_email_match(
+ const char *expected,
+ const char *oidstr,
+ git_diff_format_email_options *opts)
+{
+ git_oid oid;
+ git_commit *commit = NULL;
+ git_diff *diff = NULL;
+ git_buf buf = GIT_BUF_INIT;
+
+ git_oid_fromstr(&oid, oidstr);
+
+ cl_git_pass(git_commit_lookup(&commit, repo, &oid));
+
+ opts->id = git_commit_id(commit);
+ opts->author = git_commit_author(commit);
+ if (!opts->summary)
+ opts->summary = git_commit_summary(commit);
+
+ cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+ cl_git_pass(git_diff_format_email(&buf, diff, opts));
+
+ cl_assert_equal_s(expected, git_buf_cstr(&buf));
+ git_buf_clear(&buf);
+
+ cl_git_pass(git_diff_commit_as_email(
+ &buf, repo, commit, 1, 1, opts->flags, NULL));
+ cl_assert_equal_s(expected, git_buf_cstr(&buf));
+
+ git_diff_free(diff);
+ git_commit_free(commit);
+ git_buf_free(&buf);
+}
+
+void test_diff_format_email__simple(void)
+{
+ git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
+ const char *email =
+ "From 9264b96c6d104d0e07ae33d3007b6a48246c6f92 Mon Sep 17 00:00:00 2001\n" \
+ "From: Jacques Germishuys <jacquesg@striata.com>\n" \
+ "Date: Wed, 9 Apr 2014 20:57:01 +0200\n" \
+ "Subject: [PATCH] Modify some content\n" \
+ "\n" \
+ "---\n" \
+ " file1.txt | 8 +++++---\n" \
+ " 1 file changed, 5 insertions(+), 3 deletions(-)\n" \
+ "\n" \
+ "diff --git a/file1.txt b/file1.txt\n" \
+ "index 94aaae8..af8f41d 100644\n" \
+ "--- a/file1.txt\n" \
+ "+++ b/file1.txt\n" \
+ "@@ -1,15 +1,17 @@\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ "+_file1.txt_\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ "+\n" \
+ "+\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ "-file1.txt\n" \
+ "-file1.txt\n" \
+ "-file1.txt\n" \
+ "+_file1.txt_\n" \
+ "+_file1.txt_\n" \
+ " file1.txt\n" \
+ "--\n" \
+ "libgit2 " LIBGIT2_VERSION "\n" \
+ "\n";
+
+ assert_email_match(
+ email, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts);
+}
+
+void test_diff_format_email__multiple(void)
+{
+ git_oid oid;
+ git_commit *commit = NULL;
+ git_diff *diff = NULL;
+ git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
+ git_buf buf = GIT_BUF_INIT;
+
+ const char *email =
+ "From 10808fe9c9be5a190c0ba68d1a002233fb363508 Mon Sep 17 00:00:00 2001\n" \
+ "From: Jacques Germishuys <jacquesg@striata.com>\n" \
+ "Date: Thu, 10 Apr 2014 19:37:05 +0200\n" \
+ "Subject: [PATCH 1/2] Added file2.txt file3.txt\n" \
+ "\n" \
+ "---\n" \
+ " file2.txt | 5 +++++\n" \
+ " file3.txt | 5 +++++\n" \
+ " 2 files changed, 10 insertions(+), 0 deletions(-)\n" \
+ " create mode 100644 file2.txt\n" \
+ " create mode 100644 file3.txt\n" \
+ "\n" \
+ "diff --git a/file2.txt b/file2.txt\n" \
+ "new file mode 100644\n" \
+ "index 0000000..e909123\n" \
+ "--- /dev/null\n" \
+ "+++ b/file2.txt\n" \
+ "@@ -0,0 +1,5 @@\n" \
+ "+file2\n" \
+ "+file2\n" \
+ "+file2\n" \
+ "+file2\n" \
+ "+file2\n" \
+ "diff --git a/file3.txt b/file3.txt\n" \
+ "new file mode 100644\n" \
+ "index 0000000..9435022\n" \
+ "--- /dev/null\n" \
+ "+++ b/file3.txt\n" \
+ "@@ -0,0 +1,5 @@\n" \
+ "+file3\n" \
+ "+file3\n" \
+ "+file3\n" \
+ "+file3\n" \
+ "+file3\n" \
+ "--\n" \
+ "libgit2 " LIBGIT2_VERSION "\n" \
+ "\n" \
+ "From 873806f6f27e631eb0b23e4b56bea2bfac14a373 Mon Sep 17 00:00:00 2001\n" \
+ "From: Jacques Germishuys <jacquesg@striata.com>\n" \
+ "Date: Thu, 10 Apr 2014 19:37:36 +0200\n" \
+ "Subject: [PATCH 2/2] Modified file2.txt, file3.txt\n" \
+ "\n" \
+ "---\n" \
+ " file2.txt | 2 +-\n" \
+ " file3.txt | 2 +-\n" \
+ " 2 files changed, 2 insertions(+), 2 deletions(-)\n" \
+ "\n" \
+ "diff --git a/file2.txt b/file2.txt\n" \
+ "index e909123..7aff11d 100644\n" \
+ "--- a/file2.txt\n" \
+ "+++ b/file2.txt\n" \
+ "@@ -1,5 +1,5 @@\n" \
+ " file2\n" \
+ " file2\n" \
+ " file2\n" \
+ "-file2\n" \
+ "+file2!\n" \
+ " file2\n" \
+ "diff --git a/file3.txt b/file3.txt\n" \
+ "index 9435022..9a2d780 100644\n" \
+ "--- a/file3.txt\n" \
+ "+++ b/file3.txt\n" \
+ "@@ -1,5 +1,5 @@\n" \
+ " file3\n" \
+ "-file3\n" \
+ "+file3!\n" \
+ " file3\n" \
+ " file3\n" \
+ " file3\n" \
+ "--\n" \
+ "libgit2 " LIBGIT2_VERSION "\n" \
+ "\n";
+
+
+ git_oid_fromstr(&oid, "10808fe9c9be5a190c0ba68d1a002233fb363508");
+ cl_git_pass(git_commit_lookup(&commit, repo, &oid));
+
+ opts.id = git_commit_id(commit);
+ opts.author = git_commit_author(commit);
+ opts.summary = git_commit_summary(commit);
+ opts.patch_no = 1;
+ opts.total_patches = 2;
+
+ cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+ cl_git_pass(git_diff_format_email(&buf, diff, &opts));
+
+ git_diff_free(diff);
+ git_commit_free(commit);
+ diff = NULL;
+ commit = NULL;
+
+ git_oid_fromstr(&oid, "873806f6f27e631eb0b23e4b56bea2bfac14a373");
+ cl_git_pass(git_commit_lookup(&commit, repo, &oid));
+
+ opts.id = git_commit_id(commit);
+ opts.author = git_commit_author(commit);
+ opts.summary = git_commit_summary(commit);
+ opts.patch_no = 2;
+ opts.total_patches = 2;
+
+ cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+ cl_git_pass(git_diff_format_email(&buf, diff, &opts));
+
+ cl_assert_equal_s(email, git_buf_cstr(&buf));
+
+ git_diff_free(diff);
+ git_commit_free(commit);
+ git_buf_free(&buf);
+}
+
+void test_diff_format_email__exclude_marker(void)
+{
+ git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
+ const char *email =
+ "From 9264b96c6d104d0e07ae33d3007b6a48246c6f92 Mon Sep 17 00:00:00 2001\n" \
+ "From: Jacques Germishuys <jacquesg@striata.com>\n" \
+ "Date: Wed, 9 Apr 2014 20:57:01 +0200\n" \
+ "Subject: Modify some content\n" \
+ "\n" \
+ "---\n" \
+ " file1.txt | 8 +++++---\n" \
+ " 1 file changed, 5 insertions(+), 3 deletions(-)\n" \
+ "\n" \
+ "diff --git a/file1.txt b/file1.txt\n" \
+ "index 94aaae8..af8f41d 100644\n" \
+ "--- a/file1.txt\n" \
+ "+++ b/file1.txt\n" \
+ "@@ -1,15 +1,17 @@\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ "+_file1.txt_\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ "+\n" \
+ "+\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ "-file1.txt\n" \
+ "-file1.txt\n" \
+ "-file1.txt\n" \
+ "+_file1.txt_\n" \
+ "+_file1.txt_\n" \
+ " file1.txt\n" \
+ "--\n" \
+ "libgit2 " LIBGIT2_VERSION "\n" \
+ "\n";
+
+ opts.flags |= GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER;
+
+ assert_email_match(
+ email, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts);
+}
+
+void test_diff_format_email__invalid_no(void)
+{
+ git_oid oid;
+ git_commit *commit = NULL;
+ git_diff *diff = NULL;
+ git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
+ git_buf buf = GIT_BUF_INIT;
+
+ git_oid_fromstr(&oid, "9264b96c6d104d0e07ae33d3007b6a48246c6f92");
+
+ cl_git_pass(git_commit_lookup(&commit, repo, &oid));
+
+ opts.id = git_commit_id(commit);
+ opts.author = git_commit_author(commit);
+ opts.summary = git_commit_summary(commit);
+ opts.patch_no = 2;
+ opts.total_patches = 1;
+
+ cl_git_pass(git_diff__commit(&diff, repo, commit, NULL));
+ cl_git_fail(git_diff_format_email(&buf, diff, &opts));
+ cl_git_fail(git_diff_commit_as_email(&buf, repo, commit, 2, 1, 0, NULL));
+ cl_git_fail(git_diff_commit_as_email(&buf, repo, commit, 0, 0, 0, NULL));
+
+ git_diff_free(diff);
+ git_commit_free(commit);
+ git_buf_free(&buf);
+}
+
+void test_diff_format_email__mode_change(void)
+{
+ git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
+ const char *email =
+ "From 7ade76dd34bba4733cf9878079f9fd4a456a9189 Mon Sep 17 00:00:00 2001\n" \
+ "From: Jacques Germishuys <jacquesg@striata.com>\n" \
+ "Date: Thu, 10 Apr 2014 10:05:03 +0200\n" \
+ "Subject: [PATCH] Update permissions\n" \
+ "\n" \
+ "---\n" \
+ " file1.txt.renamed | 0\n" \
+ " 1 file changed, 0 insertions(+), 0 deletions(-)\n" \
+ " mode change 100644 => 100755 file1.txt.renamed\n" \
+ "\n" \
+ "diff --git a/file1.txt.renamed b/file1.txt.renamed\n" \
+ "old mode 100644\n" \
+ "new mode 100755\n" \
+ "index a97157a..a97157a\n" \
+ "--- a/file1.txt.renamed\n" \
+ "+++ b/file1.txt.renamed\n" \
+ "--\n" \
+ "libgit2 " LIBGIT2_VERSION "\n" \
+ "\n";
+
+ assert_email_match(
+ email, "7ade76dd34bba4733cf9878079f9fd4a456a9189", &opts);
+}
+
+void test_diff_format_email__rename_add_remove(void)
+{
+ git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
+ const char *email =
+ "From 6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d Mon Sep 17 00:00:00 2001\n" \
+ "From: Jacques Germishuys <jacquesg@striata.com>\n" \
+ "Date: Wed, 9 Apr 2014 21:15:56 +0200\n" \
+ "Subject: [PATCH] Renamed file1.txt -> file1.txt.renamed\n" \
+ "\n" \
+ "---\n" \
+ " file1.txt | 17 -----------------\n" \
+ " file1.txt.renamed | 17 +++++++++++++++++\n" \
+ " 2 files changed, 17 insertions(+), 17 deletions(-)\n" \
+ " delete mode 100644 file1.txt\n" \
+ " create mode 100644 file1.txt.renamed\n" \
+ "\n" \
+ "diff --git a/file1.txt b/file1.txt\n" \
+ "deleted file mode 100644\n" \
+ "index af8f41d..0000000\n" \
+ "--- a/file1.txt\n" \
+ "+++ /dev/null\n" \
+ "@@ -1,17 +0,0 @@\n" \
+ "-file1.txt\n" \
+ "-file1.txt\n" \
+ "-_file1.txt_\n" \
+ "-file1.txt\n" \
+ "-file1.txt\n" \
+ "-file1.txt\n" \
+ "-file1.txt\n" \
+ "-\n" \
+ "-\n" \
+ "-file1.txt\n" \
+ "-file1.txt\n" \
+ "-file1.txt\n" \
+ "-file1.txt\n" \
+ "-file1.txt\n" \
+ "-_file1.txt_\n" \
+ "-_file1.txt_\n" \
+ "-file1.txt\n" \
+ "diff --git a/file1.txt.renamed b/file1.txt.renamed\n" \
+ "new file mode 100644\n" \
+ "index 0000000..a97157a\n" \
+ "--- /dev/null\n" \
+ "+++ b/file1.txt.renamed\n" \
+ "@@ -0,0 +1,17 @@\n" \
+ "+file1.txt\n" \
+ "+file1.txt\n" \
+ "+_file1.txt_\n" \
+ "+file1.txt\n" \
+ "+file1.txt\n" \
+ "+file1.txt_renamed\n" \
+ "+file1.txt\n" \
+ "+\n" \
+ "+\n" \
+ "+file1.txt\n" \
+ "+file1.txt\n" \
+ "+file1.txt_renamed\n" \
+ "+file1.txt\n" \
+ "+file1.txt\n" \
+ "+_file1.txt_\n" \
+ "+_file1.txt_\n" \
+ "+file1.txt\n" \
+ "--\n" \
+ "libgit2 " LIBGIT2_VERSION "\n" \
+ "\n";
+
+ assert_email_match(
+ email, "6e05acc5a5dab507d91a0a0cc0fb05a3dd98892d", &opts);
+}
+
+void test_diff_format_email__multiline_summary(void)
+{
+ git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
+ const char *email =
+ "From 9264b96c6d104d0e07ae33d3007b6a48246c6f92 Mon Sep 17 00:00:00 2001\n" \
+ "From: Jacques Germishuys <jacquesg@striata.com>\n" \
+ "Date: Wed, 9 Apr 2014 20:57:01 +0200\n" \
+ "Subject: [PATCH] Modify some content\n" \
+ "\n" \
+ "---\n" \
+ " file1.txt | 8 +++++---\n" \
+ " 1 file changed, 5 insertions(+), 3 deletions(-)\n" \
+ "\n" \
+ "diff --git a/file1.txt b/file1.txt\n" \
+ "index 94aaae8..af8f41d 100644\n" \
+ "--- a/file1.txt\n" \
+ "+++ b/file1.txt\n" \
+ "@@ -1,15 +1,17 @@\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ "+_file1.txt_\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ "+\n" \
+ "+\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ " file1.txt\n" \
+ "-file1.txt\n" \
+ "-file1.txt\n" \
+ "-file1.txt\n" \
+ "+_file1.txt_\n" \
+ "+_file1.txt_\n" \
+ " file1.txt\n" \
+ "--\n" \
+ "libgit2 " LIBGIT2_VERSION "\n" \
+ "\n";
+
+ opts.summary = "Modify some content\nSome extra stuff here";
+
+ assert_email_match(
+ email, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", &opts);
+}
+
+void test_diff_format_email__binary(void)
+{
+ git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
+ const char *email =
+ "From 8d7523f6fcb2404257889abe0d96f093d9f524f9 Mon Sep 17 00:00:00 2001\n" \
+ "From: Jacques Germishuys <jacquesg@striata.com>\n" \
+ "Date: Sun, 13 Apr 2014 18:10:18 +0200\n" \
+ "Subject: [PATCH] Modified binary file\n" \
+ "\n" \
+ "---\n" \
+ " binary.bin | Bin 3 -> 0 bytes\n" \
+ " 1 file changed, 0 insertions(+), 0 deletions(-)\n" \
+ "\n" \
+ "diff --git a/binary.bin b/binary.bin\n" \
+ "index bd474b2..9ac35ff 100644\n" \
+ "Binary files a/binary.bin and b/binary.bin differ\n" \
+ "--\n" \
+ "libgit2 " LIBGIT2_VERSION "\n" \
+ "\n";
+ /* TODO: Actually 0 bytes here should be 5!. Seems like we don't load the new content for binary files? */
+
+ opts.summary = "Modified binary file";
+
+ assert_email_match(
+ email, "8d7523f6fcb2404257889abe0d96f093d9f524f9", &opts);
+}
+
diff --git a/tests/diff/index.c b/tests/diff/index.c
new file mode 100644
index 000000000..21afe8da2
--- /dev/null
+++ b/tests/diff/index.c
@@ -0,0 +1,165 @@
+#include "clar_libgit2.h"
+#include "diff_helpers.h"
+
+static git_repository *g_repo = NULL;
+
+void test_diff_index__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("status");
+}
+
+void test_diff_index__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_diff_index__0(void)
+{
+ /* grabbed a couple of commit oids from the history of the attr repo */
+ const char *a_commit = "26a125ee1bf"; /* the current HEAD */
+ const char *b_commit = "0017bd4ab1ec3"; /* the start */
+ git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit);
+ git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit);
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ diff_expects exp;
+
+ cl_assert(a);
+ cl_assert(b);
+
+ opts.context_lines = 1;
+ opts.interhunk_lines = 1;
+
+ memset(&exp, 0, sizeof(exp));
+
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, NULL, &opts));
+
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ /* to generate these values:
+ * - cd to tests/resources/status,
+ * - mv .gitted .git
+ * - git diff --name-status --cached 26a125ee1bf
+ * - git diff -U1 --cached 26a125ee1bf
+ * - mv .git .gitted
+ */
+ cl_assert_equal_i(8, exp.files);
+ 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);
+
+ cl_assert_equal_i(11, exp.lines);
+ cl_assert_equal_i(3, exp.line_ctxt);
+ cl_assert_equal_i(6, exp.line_adds);
+ cl_assert_equal_i(2, exp.line_dels);
+
+ git_diff_free(diff);
+ diff = NULL;
+ memset(&exp, 0, sizeof(exp));
+
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, b, NULL, &opts));
+
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ /* to generate these values:
+ * - cd to tests/resources/status,
+ * - mv .gitted .git
+ * - git diff --name-status --cached 0017bd4ab1ec3
+ * - git diff -U1 --cached 0017bd4ab1ec3
+ * - mv .git .gitted
+ */
+ cl_assert_equal_i(12, exp.files);
+ 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);
+
+ cl_assert_equal_i(16, exp.lines);
+ cl_assert_equal_i(3, exp.line_ctxt);
+ cl_assert_equal_i(11, exp.line_adds);
+ cl_assert_equal_i(2, exp.line_dels);
+
+ git_diff_free(diff);
+ diff = NULL;
+
+ git_tree_free(a);
+ git_tree_free(b);
+}
+
+static int diff_stop_after_2_files(
+ const git_diff_delta *delta,
+ float progress,
+ void *payload)
+{
+ diff_expects *e = payload;
+
+ GIT_UNUSED(progress);
+ GIT_UNUSED(delta);
+
+ e->files++;
+
+ return (e->files == 2);
+}
+
+void test_diff_index__1(void)
+{
+ /* grabbed a couple of commit oids from the history of the attr repo */
+ const char *a_commit = "26a125ee1bf"; /* the current HEAD */
+ const char *b_commit = "0017bd4ab1ec3"; /* the start */
+ git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit);
+ git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit);
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ diff_expects exp;
+
+ cl_assert(a);
+ cl_assert(b);
+
+ opts.context_lines = 1;
+ opts.interhunk_lines = 1;
+
+ memset(&exp, 0, sizeof(exp));
+
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, NULL, &opts));
+
+ cl_assert_equal_i(
+ 1, git_diff_foreach(diff, diff_stop_after_2_files, NULL, NULL, &exp) );
+
+ cl_assert_equal_i(2, exp.files);
+
+ git_diff_free(diff);
+ diff = NULL;
+
+ git_tree_free(a);
+ git_tree_free(b);
+}
+
+void test_diff_index__checks_options_version(void)
+{
+ const char *a_commit = "26a125ee1bf";
+ git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit);
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ const git_error *err;
+
+ opts.version = 0;
+ cl_git_fail(git_diff_tree_to_index(&diff, g_repo, a, NULL, &opts));
+ err = giterr_last();
+ cl_assert_equal_i(GITERR_INVALID, err->klass);
+ cl_assert_equal_p(diff, NULL);
+
+ giterr_clear();
+ opts.version = 1024;
+ cl_git_fail(git_diff_tree_to_index(&diff, g_repo, a, NULL, &opts));
+ err = giterr_last();
+ cl_assert_equal_i(GITERR_INVALID, err->klass);
+ cl_assert_equal_p(diff, NULL);
+
+ git_tree_free(a);
+}
+
diff --git a/tests/diff/iterator.c b/tests/diff/iterator.c
new file mode 100644
index 000000000..a2df1c7a7
--- /dev/null
+++ b/tests/diff/iterator.c
@@ -0,0 +1,980 @@
+#include "clar_libgit2.h"
+#include "diff_helpers.h"
+#include "iterator.h"
+#include "tree.h"
+
+void test_diff_iterator__initialize(void)
+{
+ /* since we are doing tests with different sandboxes, defer setup
+ * to the actual tests. cleanup will still be done in the global
+ * cleanup function so that assertion failures don't result in a
+ * missed cleanup.
+ */
+}
+
+void test_diff_iterator__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+
+/* -- TREE ITERATOR TESTS -- */
+
+static void tree_iterator_test(
+ const char *sandbox,
+ const char *treeish,
+ const char *start,
+ const char *end,
+ int expected_count,
+ const char **expected_values)
+{
+ git_tree *t;
+ git_iterator *i;
+ const git_index_entry *entry;
+ int error, count = 0, count_post_reset = 0;
+ git_repository *repo = cl_git_sandbox_init(sandbox);
+
+ cl_assert(t = resolve_commit_oid_to_tree(repo, treeish));
+ cl_git_pass(git_iterator_for_tree(
+ &i, t, GIT_ITERATOR_DONT_IGNORE_CASE, start, end));
+
+ /* test loop */
+ while (!(error = git_iterator_advance(&entry, i))) {
+ cl_assert(entry);
+ if (expected_values != NULL)
+ cl_assert_equal_s(expected_values[count], entry->path);
+ count++;
+ }
+ cl_assert_equal_i(GIT_ITEROVER, error);
+ cl_assert(!entry);
+ cl_assert_equal_i(expected_count, count);
+
+ /* test reset */
+ cl_git_pass(git_iterator_reset(i, NULL, NULL));
+
+ while (!(error = git_iterator_advance(&entry, i))) {
+ cl_assert(entry);
+ if (expected_values != NULL)
+ cl_assert_equal_s(expected_values[count_post_reset], entry->path);
+ count_post_reset++;
+ }
+ cl_assert_equal_i(GIT_ITEROVER, error);
+ cl_assert(!entry);
+ cl_assert_equal_i(count, count_post_reset);
+
+ git_iterator_free(i);
+ git_tree_free(t);
+}
+
+/* results of: git ls-tree -r --name-only 605812a */
+const char *expected_tree_0[] = {
+ ".gitattributes",
+ "attr0",
+ "attr1",
+ "attr2",
+ "attr3",
+ "binfile",
+ "macro_test",
+ "root_test1",
+ "root_test2",
+ "root_test3",
+ "root_test4.txt",
+ "subdir/.gitattributes",
+ "subdir/abc",
+ "subdir/subdir_test1",
+ "subdir/subdir_test2.txt",
+ "subdir2/subdir2_test1",
+ NULL
+};
+
+void test_diff_iterator__tree_0(void)
+{
+ tree_iterator_test("attr", "605812a", NULL, NULL, 16, expected_tree_0);
+}
+
+/* results of: git ls-tree -r --name-only 6bab5c79 */
+const char *expected_tree_1[] = {
+ ".gitattributes",
+ "attr0",
+ "attr1",
+ "attr2",
+ "attr3",
+ "root_test1",
+ "root_test2",
+ "root_test3",
+ "root_test4.txt",
+ "subdir/.gitattributes",
+ "subdir/subdir_test1",
+ "subdir/subdir_test2.txt",
+ "subdir2/subdir2_test1",
+ NULL
+};
+
+void test_diff_iterator__tree_1(void)
+{
+ tree_iterator_test("attr", "6bab5c79cd5", NULL, NULL, 13, expected_tree_1);
+}
+
+/* results of: git ls-tree -r --name-only 26a125ee1 */
+const char *expected_tree_2[] = {
+ "current_file",
+ "file_deleted",
+ "modified_file",
+ "staged_changes",
+ "staged_changes_file_deleted",
+ "staged_changes_modified_file",
+ "staged_delete_file_deleted",
+ "staged_delete_modified_file",
+ "subdir.txt",
+ "subdir/current_file",
+ "subdir/deleted_file",
+ "subdir/modified_file",
+ NULL
+};
+
+void test_diff_iterator__tree_2(void)
+{
+ tree_iterator_test("status", "26a125ee1", NULL, NULL, 12, expected_tree_2);
+}
+
+/* $ git ls-tree -r --name-only 0017bd4ab1e */
+const char *expected_tree_3[] = {
+ "current_file",
+ "file_deleted",
+ "modified_file",
+ "staged_changes",
+ "staged_changes_file_deleted",
+ "staged_changes_modified_file",
+ "staged_delete_file_deleted",
+ "staged_delete_modified_file"
+};
+
+void test_diff_iterator__tree_3(void)
+{
+ tree_iterator_test("status", "0017bd4ab1e", NULL, NULL, 8, expected_tree_3);
+}
+
+/* $ git ls-tree -r --name-only 24fa9a9fc4e202313e24b648087495441dab432b */
+const char *expected_tree_4[] = {
+ "attr0",
+ "attr1",
+ "attr2",
+ "attr3",
+ "binfile",
+ "gitattributes",
+ "macro_bad",
+ "macro_test",
+ "root_test1",
+ "root_test2",
+ "root_test3",
+ "root_test4.txt",
+ "sub/abc",
+ "sub/file",
+ "sub/sub/file",
+ "sub/sub/subsub.txt",
+ "sub/subdir_test1",
+ "sub/subdir_test2.txt",
+ "subdir/.gitattributes",
+ "subdir/abc",
+ "subdir/subdir_test1",
+ "subdir/subdir_test2.txt",
+ "subdir2/subdir2_test1",
+ NULL
+};
+
+void test_diff_iterator__tree_4(void)
+{
+ tree_iterator_test(
+ "attr", "24fa9a9fc4e202313e24b648087495441dab432b", NULL, NULL,
+ 23, expected_tree_4);
+}
+
+void test_diff_iterator__tree_4_ranged(void)
+{
+ tree_iterator_test(
+ "attr", "24fa9a9fc4e202313e24b648087495441dab432b",
+ "sub", "sub",
+ 11, &expected_tree_4[12]);
+}
+
+const char *expected_tree_ranged_0[] = {
+ "gitattributes",
+ "macro_bad",
+ "macro_test",
+ "root_test1",
+ "root_test2",
+ "root_test3",
+ "root_test4.txt",
+ NULL
+};
+
+void test_diff_iterator__tree_ranged_0(void)
+{
+ tree_iterator_test(
+ "attr", "24fa9a9fc4e202313e24b648087495441dab432b",
+ "git", "root",
+ 7, expected_tree_ranged_0);
+}
+
+const char *expected_tree_ranged_1[] = {
+ "sub/subdir_test2.txt",
+ NULL
+};
+
+void test_diff_iterator__tree_ranged_1(void)
+{
+ tree_iterator_test(
+ "attr", "24fa9a9fc4e202313e24b648087495441dab432b",
+ "sub/subdir_test2.txt", "sub/subdir_test2.txt",
+ 1, expected_tree_ranged_1);
+}
+
+void test_diff_iterator__tree_range_empty_0(void)
+{
+ tree_iterator_test(
+ "attr", "24fa9a9fc4e202313e24b648087495441dab432b",
+ "empty", "empty", 0, NULL);
+}
+
+void test_diff_iterator__tree_range_empty_1(void)
+{
+ tree_iterator_test(
+ "attr", "24fa9a9fc4e202313e24b648087495441dab432b",
+ "z_empty_after", NULL, 0, NULL);
+}
+
+void test_diff_iterator__tree_range_empty_2(void)
+{
+ tree_iterator_test(
+ "attr", "24fa9a9fc4e202313e24b648087495441dab432b",
+ NULL, ".aaa_empty_before", 0, NULL);
+}
+
+static void check_tree_entry(
+ git_iterator *i,
+ const char *oid,
+ const char *oid_p,
+ const char *oid_pp,
+ const char *oid_ppp)
+{
+ const git_index_entry *ie;
+ const git_tree_entry *te;
+ const git_tree *tree;
+ git_buf path = GIT_BUF_INIT;
+
+ cl_git_pass(git_iterator_current_tree_entry(&te, i));
+ cl_assert(te);
+ cl_assert(git_oid_streq(&te->oid, oid) == 0);
+
+ cl_git_pass(git_iterator_current(&ie, i));
+ cl_git_pass(git_buf_sets(&path, ie->path));
+
+ if (oid_p) {
+ git_buf_rtruncate_at_char(&path, '/');
+ cl_git_pass(git_iterator_current_parent_tree(&tree, i, path.ptr));
+ cl_assert(tree);
+ cl_assert(git_oid_streq(git_tree_id(tree), oid_p) == 0);
+ }
+
+ if (oid_pp) {
+ git_buf_rtruncate_at_char(&path, '/');
+ cl_git_pass(git_iterator_current_parent_tree(&tree, i, path.ptr));
+ cl_assert(tree);
+ cl_assert(git_oid_streq(git_tree_id(tree), oid_pp) == 0);
+ }
+
+ if (oid_ppp) {
+ git_buf_rtruncate_at_char(&path, '/');
+ cl_git_pass(git_iterator_current_parent_tree(&tree, i, path.ptr));
+ cl_assert(tree);
+ cl_assert(git_oid_streq(git_tree_id(tree), oid_ppp) == 0);
+ }
+
+ git_buf_free(&path);
+}
+
+void test_diff_iterator__tree_special_functions(void)
+{
+ git_tree *t;
+ git_iterator *i;
+ const git_index_entry *entry;
+ git_repository *repo = cl_git_sandbox_init("attr");
+ int error, cases = 0;
+ const char *rootoid = "ce39a97a7fb1fa90bcf5e711249c1e507476ae0e";
+
+ t = resolve_commit_oid_to_tree(
+ repo, "24fa9a9fc4e202313e24b648087495441dab432b");
+ cl_assert(t != NULL);
+
+ cl_git_pass(git_iterator_for_tree(
+ &i, t, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL));
+
+ while (!(error = git_iterator_advance(&entry, i))) {
+ cl_assert(entry);
+
+ if (strcmp(entry->path, "sub/file") == 0) {
+ cases++;
+ check_tree_entry(
+ i, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057",
+ "ecb97df2a174987475ac816e3847fc8e9f6c596b",
+ rootoid, NULL);
+ }
+ else if (strcmp(entry->path, "sub/sub/subsub.txt") == 0) {
+ cases++;
+ check_tree_entry(
+ i, "9e5bdc47d6a80f2be0ea3049ad74231b94609242",
+ "4e49ba8c5b6c32ff28cd9dcb60be34df50fcc485",
+ "ecb97df2a174987475ac816e3847fc8e9f6c596b", rootoid);
+ }
+ else if (strcmp(entry->path, "subdir/.gitattributes") == 0) {
+ cases++;
+ check_tree_entry(
+ i, "99eae476896f4907224978b88e5ecaa6c5bb67a9",
+ "9fb40b6675dde60b5697afceae91b66d908c02d9",
+ rootoid, NULL);
+ }
+ else if (strcmp(entry->path, "subdir2/subdir2_test1") == 0) {
+ cases++;
+ check_tree_entry(
+ i, "dccada462d3df8ac6de596fb8c896aba9344f941",
+ "2929de282ce999e95183aedac6451d3384559c4b",
+ rootoid, NULL);
+ }
+ }
+ cl_assert_equal_i(GIT_ITEROVER, error);
+ cl_assert(!entry);
+ cl_assert_equal_i(4, cases);
+
+ git_iterator_free(i);
+ git_tree_free(t);
+}
+
+/* -- INDEX ITERATOR TESTS -- */
+
+static void index_iterator_test(
+ const char *sandbox,
+ const char *start,
+ const char *end,
+ git_iterator_flag_t flags,
+ int expected_count,
+ const char **expected_names,
+ const char **expected_oids)
+{
+ git_index *index;
+ git_iterator *i;
+ const git_index_entry *entry;
+ int error, count = 0, caps;
+ git_repository *repo = cl_git_sandbox_init(sandbox);
+
+ cl_git_pass(git_repository_index(&index, repo));
+ caps = git_index_caps(index);
+
+ cl_git_pass(git_iterator_for_index(&i, index, flags, start, end));
+
+ while (!(error = git_iterator_advance(&entry, i))) {
+ cl_assert(entry);
+
+ if (expected_names != NULL)
+ cl_assert_equal_s(expected_names[count], entry->path);
+
+ if (expected_oids != NULL) {
+ git_oid oid;
+ cl_git_pass(git_oid_fromstr(&oid, expected_oids[count]));
+ cl_assert_equal_i(git_oid_cmp(&oid, &entry->id), 0);
+ }
+
+ count++;
+ }
+
+ cl_assert_equal_i(GIT_ITEROVER, error);
+ cl_assert(!entry);
+ cl_assert_equal_i(expected_count, count);
+
+ git_iterator_free(i);
+
+ cl_assert(caps == git_index_caps(index));
+ git_index_free(index);
+}
+
+static const char *expected_index_0[] = {
+ "attr0",
+ "attr1",
+ "attr2",
+ "attr3",
+ "binfile",
+ "gitattributes",
+ "macro_bad",
+ "macro_test",
+ "root_test1",
+ "root_test2",
+ "root_test3",
+ "root_test4.txt",
+ "sub/abc",
+ "sub/file",
+ "sub/sub/file",
+ "sub/sub/subsub.txt",
+ "sub/subdir_test1",
+ "sub/subdir_test2.txt",
+ "subdir/.gitattributes",
+ "subdir/abc",
+ "subdir/subdir_test1",
+ "subdir/subdir_test2.txt",
+ "subdir2/subdir2_test1",
+};
+
+static const char *expected_index_oids_0[] = {
+ "556f8c827b8e4a02ad5cab77dca2bcb3e226b0b3",
+ "3b74db7ab381105dc0d28f8295a77f6a82989292",
+ "2c66e14f77196ea763fb1e41612c1aa2bc2d8ed2",
+ "c485abe35abd4aa6fd83b076a78bbea9e2e7e06c",
+ "d800886d9c86731ae5c4a62b0b77c437015e00d2",
+ "2b40c5aca159b04ea8d20ffe36cdf8b09369b14a",
+ "5819a185d77b03325aaf87cafc771db36f6ddca7",
+ "ff69f8639ce2e6010b3f33a74160aad98b48da2b",
+ "45141a79a77842c59a63229403220a4e4be74e3d",
+ "4d713dc48e6b1bd75b0d61ad078ba9ca3a56745d",
+ "108bb4e7fd7b16490dc33ff7d972151e73d7166e",
+ "a0f7217ae99f5ac3e88534f5cea267febc5fa85b",
+ "3e42ffc54a663f9401cc25843d6c0e71a33e4249",
+ "45b983be36b73c0788dc9cbcb76cbb80fc7bb057",
+ "45b983be36b73c0788dc9cbcb76cbb80fc7bb057",
+ "9e5bdc47d6a80f2be0ea3049ad74231b94609242",
+ "e563cf4758f0d646f1b14b76016aa17fa9e549a4",
+ "fb5067b1aef3ac1ada4b379dbcb7d17255df7d78",
+ "99eae476896f4907224978b88e5ecaa6c5bb67a9",
+ "3e42ffc54a663f9401cc25843d6c0e71a33e4249",
+ "e563cf4758f0d646f1b14b76016aa17fa9e549a4",
+ "fb5067b1aef3ac1ada4b379dbcb7d17255df7d78",
+ "dccada462d3df8ac6de596fb8c896aba9344f941"
+};
+
+void test_diff_iterator__index_0(void)
+{
+ index_iterator_test(
+ "attr", NULL, NULL, 0, ARRAY_SIZE(expected_index_0),
+ expected_index_0, expected_index_oids_0);
+}
+
+static const char *expected_index_range[] = {
+ "root_test1",
+ "root_test2",
+ "root_test3",
+ "root_test4.txt",
+};
+
+static const char *expected_index_oids_range[] = {
+ "45141a79a77842c59a63229403220a4e4be74e3d",
+ "4d713dc48e6b1bd75b0d61ad078ba9ca3a56745d",
+ "108bb4e7fd7b16490dc33ff7d972151e73d7166e",
+ "a0f7217ae99f5ac3e88534f5cea267febc5fa85b",
+};
+
+void test_diff_iterator__index_range(void)
+{
+ index_iterator_test(
+ "attr", "root", "root", 0, ARRAY_SIZE(expected_index_range),
+ expected_index_range, expected_index_oids_range);
+}
+
+void test_diff_iterator__index_range_empty_0(void)
+{
+ index_iterator_test(
+ "attr", "empty", "empty", 0, 0, NULL, NULL);
+}
+
+void test_diff_iterator__index_range_empty_1(void)
+{
+ index_iterator_test(
+ "attr", "z_empty_after", NULL, 0, 0, NULL, NULL);
+}
+
+void test_diff_iterator__index_range_empty_2(void)
+{
+ index_iterator_test(
+ "attr", NULL, ".aaa_empty_before", 0, 0, NULL, NULL);
+}
+
+static const char *expected_index_1[] = {
+ "current_file",
+ "file_deleted",
+ "modified_file",
+ "staged_changes",
+ "staged_changes_file_deleted",
+ "staged_changes_modified_file",
+ "staged_new_file",
+ "staged_new_file_deleted_file",
+ "staged_new_file_modified_file",
+ "subdir.txt",
+ "subdir/current_file",
+ "subdir/deleted_file",
+ "subdir/modified_file",
+};
+
+static const char* expected_index_oids_1[] = {
+ "a0de7e0ac200c489c41c59dfa910154a70264e6e",
+ "5452d32f1dd538eb0405e8a83cc185f79e25e80f",
+ "452e4244b5d083ddf0460acf1ecc74db9dcfa11a",
+ "55d316c9ba708999f1918e9677d01dfcae69c6b9",
+ "a6be623522ce87a1d862128ac42672604f7b468b",
+ "906ee7711f4f4928ddcb2a5f8fbc500deba0d2a8",
+ "529a16e8e762d4acb7b9636ff540a00831f9155a",
+ "90b8c29d8ba39434d1c63e1b093daaa26e5bd972",
+ "ed062903b8f6f3dccb2fa81117ba6590944ef9bd",
+ "e8ee89e15bbe9b20137715232387b3de5b28972e",
+ "53ace0d1cc1145a5f4fe4f78a186a60263190733",
+ "1888c805345ba265b0ee9449b8877b6064592058",
+ "a6191982709b746d5650e93c2acf34ef74e11504"
+};
+
+void test_diff_iterator__index_1(void)
+{
+ index_iterator_test(
+ "status", NULL, NULL, 0, ARRAY_SIZE(expected_index_1),
+ expected_index_1, expected_index_oids_1);
+}
+
+static const char *expected_index_cs[] = {
+ "B", "D", "F", "H", "J", "L/1", "L/B", "L/D", "L/a", "L/c",
+ "a", "c", "e", "g", "i", "k/1", "k/B", "k/D", "k/a", "k/c",
+};
+
+static const char *expected_index_ci[] = {
+ "a", "B", "c", "D", "e", "F", "g", "H", "i", "J",
+ "k/1", "k/a", "k/B", "k/c", "k/D", "L/1", "L/a", "L/B", "L/c", "L/D",
+};
+
+void test_diff_iterator__index_case_folding(void)
+{
+ git_buf path = GIT_BUF_INIT;
+ int fs_is_ci = 0;
+
+ cl_git_pass(git_buf_joinpath(&path, cl_fixture("icase"), ".gitted/CoNfIg"));
+ fs_is_ci = git_path_exists(path.ptr);
+ git_buf_free(&path);
+
+ index_iterator_test(
+ "icase", NULL, NULL, 0, ARRAY_SIZE(expected_index_cs),
+ fs_is_ci ? expected_index_ci : expected_index_cs, NULL);
+
+ cl_git_sandbox_cleanup();
+
+ index_iterator_test(
+ "icase", NULL, NULL, GIT_ITERATOR_IGNORE_CASE,
+ ARRAY_SIZE(expected_index_ci), expected_index_ci, NULL);
+
+ cl_git_sandbox_cleanup();
+
+ index_iterator_test(
+ "icase", NULL, NULL, GIT_ITERATOR_DONT_IGNORE_CASE,
+ ARRAY_SIZE(expected_index_cs), expected_index_cs, NULL);
+}
+
+/* -- WORKDIR ITERATOR TESTS -- */
+
+static void workdir_iterator_test(
+ const char *sandbox,
+ const char *start,
+ const char *end,
+ int expected_count,
+ int expected_ignores,
+ const char **expected_names,
+ const char *an_ignored_name)
+{
+ git_iterator *i;
+ const git_index_entry *entry;
+ int error, count = 0, count_all = 0, count_all_post_reset = 0;
+ git_repository *repo = cl_git_sandbox_init(sandbox);
+
+ cl_git_pass(git_iterator_for_workdir(
+ &i, repo, GIT_ITERATOR_DONT_AUTOEXPAND, start, end));
+
+ error = git_iterator_current(&entry, i);
+ cl_assert((error == 0 && entry != NULL) ||
+ (error == GIT_ITEROVER && entry == NULL));
+
+ while (entry != NULL) {
+ int ignored = git_iterator_current_is_ignored(i);
+
+ if (S_ISDIR(entry->mode)) {
+ cl_git_pass(git_iterator_advance_into(&entry, i));
+ continue;
+ }
+
+ if (expected_names != NULL)
+ cl_assert_equal_s(expected_names[count_all], entry->path);
+
+ if (an_ignored_name && strcmp(an_ignored_name,entry->path)==0)
+ cl_assert(ignored);
+
+ if (!ignored)
+ count++;
+ count_all++;
+
+ error = git_iterator_advance(&entry, i);
+
+ cl_assert((error == 0 && entry != NULL) ||
+ (error == GIT_ITEROVER && entry == NULL));
+ }
+
+ cl_assert_equal_i(expected_count, count);
+ cl_assert_equal_i(expected_count + expected_ignores, count_all);
+
+ cl_git_pass(git_iterator_reset(i, NULL, NULL));
+
+ error = git_iterator_current(&entry, i);
+ cl_assert((error == 0 && entry != NULL) ||
+ (error == GIT_ITEROVER && entry == NULL));
+
+ while (entry != NULL) {
+ if (S_ISDIR(entry->mode)) {
+ cl_git_pass(git_iterator_advance_into(&entry, i));
+ continue;
+ }
+
+ if (expected_names != NULL)
+ cl_assert_equal_s(
+ expected_names[count_all_post_reset], entry->path);
+ count_all_post_reset++;
+
+ error = git_iterator_advance(&entry, i);
+ cl_assert(error == 0 || error == GIT_ITEROVER);
+ }
+
+ cl_assert_equal_i(count_all, count_all_post_reset);
+
+ git_iterator_free(i);
+}
+
+void test_diff_iterator__workdir_0(void)
+{
+ workdir_iterator_test("attr", NULL, NULL, 23, 5, NULL, "ign");
+}
+
+static const char *status_paths[] = {
+ "current_file",
+ "ignored_file",
+ "modified_file",
+ "new_file",
+ "staged_changes",
+ "staged_changes_modified_file",
+ "staged_delete_modified_file",
+ "staged_new_file",
+ "staged_new_file_modified_file",
+ "subdir.txt",
+ "subdir/current_file",
+ "subdir/modified_file",
+ "subdir/new_file",
+ "\xe8\xbf\x99",
+ NULL
+};
+
+void test_diff_iterator__workdir_1(void)
+{
+ workdir_iterator_test(
+ "status", NULL, NULL, 13, 1, status_paths, "ignored_file");
+}
+
+static const char *status_paths_range_0[] = {
+ "staged_changes",
+ "staged_changes_modified_file",
+ "staged_delete_modified_file",
+ "staged_new_file",
+ "staged_new_file_modified_file",
+ NULL
+};
+
+void test_diff_iterator__workdir_1_ranged_0(void)
+{
+ workdir_iterator_test(
+ "status", "staged", "staged", 5, 0, status_paths_range_0, NULL);
+}
+
+static const char *status_paths_range_1[] = {
+ "modified_file", NULL
+};
+
+void test_diff_iterator__workdir_1_ranged_1(void)
+{
+ workdir_iterator_test(
+ "status", "modified_file", "modified_file",
+ 1, 0, status_paths_range_1, NULL);
+}
+
+static const char *status_paths_range_3[] = {
+ "subdir.txt",
+ "subdir/current_file",
+ "subdir/modified_file",
+ NULL
+};
+
+void test_diff_iterator__workdir_1_ranged_3(void)
+{
+ workdir_iterator_test(
+ "status", "subdir", "subdir/modified_file",
+ 3, 0, status_paths_range_3, NULL);
+}
+
+static const char *status_paths_range_4[] = {
+ "subdir/current_file",
+ "subdir/modified_file",
+ "subdir/new_file",
+ "\xe8\xbf\x99",
+ NULL
+};
+
+void test_diff_iterator__workdir_1_ranged_4(void)
+{
+ workdir_iterator_test(
+ "status", "subdir/", NULL, 4, 0, status_paths_range_4, NULL);
+}
+
+static const char *status_paths_range_5[] = {
+ "subdir/modified_file",
+ NULL
+};
+
+void test_diff_iterator__workdir_1_ranged_5(void)
+{
+ workdir_iterator_test(
+ "status", "subdir/modified_file", "subdir/modified_file",
+ 1, 0, status_paths_range_5, NULL);
+}
+
+void test_diff_iterator__workdir_1_ranged_empty_0(void)
+{
+ workdir_iterator_test(
+ "status", "\xff_does_not_exist", NULL,
+ 0, 0, NULL, NULL);
+}
+
+void test_diff_iterator__workdir_1_ranged_empty_1(void)
+{
+ workdir_iterator_test(
+ "status", "empty", "empty",
+ 0, 0, NULL, NULL);
+}
+
+void test_diff_iterator__workdir_1_ranged_empty_2(void)
+{
+ workdir_iterator_test(
+ "status", NULL, "aaaa_empty_before",
+ 0, 0, NULL, NULL);
+}
+
+void test_diff_iterator__workdir_builtin_ignores(void)
+{
+ git_repository *repo = cl_git_sandbox_init("attr");
+ git_iterator *i;
+ const git_index_entry *entry;
+ int idx;
+ static struct {
+ const char *path;
+ bool ignored;
+ } expected[] = {
+ { "dir/", true },
+ { "file", false },
+ { "ign", true },
+ { "macro_bad", false },
+ { "macro_test", false },
+ { "root_test1", false },
+ { "root_test2", false },
+ { "root_test3", false },
+ { "root_test4.txt", false },
+ { "sub/", false },
+ { "sub/.gitattributes", false },
+ { "sub/abc", false },
+ { "sub/dir/", true },
+ { "sub/file", false },
+ { "sub/ign/", true },
+ { "sub/sub/", false },
+ { "sub/sub/.gitattributes", false },
+ { "sub/sub/dir", false }, /* file is not actually a dir */
+ { "sub/sub/file", false },
+ { NULL, false }
+ };
+
+ cl_git_pass(p_mkdir("attr/sub/sub/.git", 0777));
+ cl_git_mkfile("attr/sub/.git", "whatever");
+
+ cl_git_pass(git_iterator_for_workdir(
+ &i, repo, GIT_ITERATOR_DONT_AUTOEXPAND, "dir", "sub/sub/file"));
+ cl_git_pass(git_iterator_current(&entry, i));
+
+ for (idx = 0; entry != NULL; ++idx) {
+ int ignored = git_iterator_current_is_ignored(i);
+
+ cl_assert_equal_s(expected[idx].path, entry->path);
+ cl_assert_(ignored == expected[idx].ignored, expected[idx].path);
+
+ if (!ignored &&
+ (entry->mode == GIT_FILEMODE_TREE ||
+ entry->mode == GIT_FILEMODE_COMMIT))
+ {
+ /* it is possible to advance "into" a submodule */
+ cl_git_pass(git_iterator_advance_into(&entry, i));
+ } else {
+ int error = git_iterator_advance(&entry, i);
+ cl_assert(!error || error == GIT_ITEROVER);
+ }
+ }
+
+ cl_assert(expected[idx].path == NULL);
+
+ git_iterator_free(i);
+}
+
+static void check_wd_first_through_third_range(
+ git_repository *repo, const char *start, const char *end)
+{
+ git_iterator *i;
+ const git_index_entry *entry;
+ int error, idx;
+ static const char *expected[] = { "FIRST", "second", "THIRD", NULL };
+
+ cl_git_pass(git_iterator_for_workdir(
+ &i, repo, GIT_ITERATOR_IGNORE_CASE, start, end));
+ cl_git_pass(git_iterator_current(&entry, i));
+
+ for (idx = 0; entry != NULL; ++idx) {
+ cl_assert_equal_s(expected[idx], entry->path);
+
+ error = git_iterator_advance(&entry, i);
+ cl_assert(!error || error == GIT_ITEROVER);
+ }
+
+ cl_assert(expected[idx] == NULL);
+
+ git_iterator_free(i);
+}
+
+void test_diff_iterator__workdir_handles_icase_range(void)
+{
+ git_repository *repo;
+
+ repo = cl_git_sandbox_init("empty_standard_repo");
+ cl_git_remove_placeholders(git_repository_path(repo), "dummy-marker.txt");
+
+ cl_git_mkfile("empty_standard_repo/before", "whatever\n");
+ cl_git_mkfile("empty_standard_repo/FIRST", "whatever\n");
+ cl_git_mkfile("empty_standard_repo/second", "whatever\n");
+ cl_git_mkfile("empty_standard_repo/THIRD", "whatever\n");
+ cl_git_mkfile("empty_standard_repo/zafter", "whatever\n");
+ cl_git_mkfile("empty_standard_repo/Zlast", "whatever\n");
+
+ check_wd_first_through_third_range(repo, "first", "third");
+ check_wd_first_through_third_range(repo, "FIRST", "THIRD");
+ check_wd_first_through_third_range(repo, "first", "THIRD");
+ check_wd_first_through_third_range(repo, "FIRST", "third");
+ check_wd_first_through_third_range(repo, "FirSt", "tHiRd");
+}
+
+static void check_tree_range(
+ git_repository *repo,
+ const char *start,
+ const char *end,
+ bool ignore_case,
+ int expected_count)
+{
+ git_tree *head;
+ git_iterator *i;
+ int error, count;
+
+ cl_git_pass(git_repository_head_tree(&head, repo));
+
+ cl_git_pass(git_iterator_for_tree(
+ &i, head,
+ ignore_case ? GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE,
+ start, end));
+
+ for (count = 0; !(error = git_iterator_advance(NULL, i)); ++count)
+ /* count em up */;
+
+ cl_assert_equal_i(GIT_ITEROVER, error);
+ cl_assert_equal_i(expected_count, count);
+
+ git_iterator_free(i);
+ git_tree_free(head);
+}
+
+void test_diff_iterator__tree_handles_icase_range(void)
+{
+ git_repository *repo;
+
+ repo = cl_git_sandbox_init("testrepo");
+
+ check_tree_range(repo, "B", "C", false, 0);
+ check_tree_range(repo, "B", "C", true, 1);
+ check_tree_range(repo, "b", "c", false, 1);
+ check_tree_range(repo, "b", "c", true, 1);
+
+ check_tree_range(repo, "a", "z", false, 3);
+ check_tree_range(repo, "a", "z", true, 4);
+ check_tree_range(repo, "A", "Z", false, 1);
+ check_tree_range(repo, "A", "Z", true, 4);
+ check_tree_range(repo, "a", "Z", false, 0);
+ check_tree_range(repo, "a", "Z", true, 4);
+ check_tree_range(repo, "A", "z", false, 4);
+ check_tree_range(repo, "A", "z", true, 4);
+
+ check_tree_range(repo, "new.txt", "new.txt", true, 1);
+ check_tree_range(repo, "new.txt", "new.txt", false, 1);
+ check_tree_range(repo, "README", "README", true, 1);
+ check_tree_range(repo, "README", "README", false, 1);
+}
+
+static void check_index_range(
+ git_repository *repo,
+ const char *start,
+ const char *end,
+ bool ignore_case,
+ int expected_count)
+{
+ git_index *index;
+ git_iterator *i;
+ int error, count, caps;
+ bool is_ignoring_case;
+
+ cl_git_pass(git_repository_index(&index, repo));
+
+ caps = git_index_caps(index);
+ is_ignoring_case = ((caps & GIT_INDEXCAP_IGNORE_CASE) != 0);
+
+ if (ignore_case != is_ignoring_case)
+ cl_git_pass(git_index_set_caps(index, caps ^ GIT_INDEXCAP_IGNORE_CASE));
+
+ cl_git_pass(git_iterator_for_index(&i, index, 0, start, end));
+
+ cl_assert(git_iterator_ignore_case(i) == ignore_case);
+
+ for (count = 0; !(error = git_iterator_advance(NULL, i)); ++count)
+ /* count em up */;
+
+ cl_assert_equal_i(GIT_ITEROVER, error);
+ cl_assert_equal_i(expected_count, count);
+
+ git_iterator_free(i);
+ git_index_free(index);
+}
+
+void test_diff_iterator__index_handles_icase_range(void)
+{
+ git_repository *repo;
+ git_index *index;
+ git_tree *head;
+
+ repo = cl_git_sandbox_init("testrepo");
+
+ /* reset index to match HEAD */
+ cl_git_pass(git_repository_head_tree(&head, repo));
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_git_pass(git_index_read_tree(index, head));
+ cl_git_pass(git_index_write(index));
+ git_tree_free(head);
+ git_index_free(index);
+
+ /* do some ranged iterator checks toggling case sensitivity */
+ check_index_range(repo, "B", "C", false, 0);
+ check_index_range(repo, "B", "C", true, 1);
+ check_index_range(repo, "a", "z", false, 3);
+ check_index_range(repo, "a", "z", true, 4);
+}
diff --git a/tests/diff/notify.c b/tests/diff/notify.c
new file mode 100644
index 000000000..da7390d3f
--- /dev/null
+++ b/tests/diff/notify.c
@@ -0,0 +1,230 @@
+#include "clar_libgit2.h"
+#include "diff_helpers.h"
+
+static git_repository *g_repo = NULL;
+
+void test_diff_notify__initialize(void)
+{
+}
+
+void test_diff_notify__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+static int assert_called_notifications(
+ const git_diff *diff_so_far,
+ const git_diff_delta *delta_to_add,
+ const char *matched_pathspec,
+ void *payload)
+{
+ bool found = false;
+ notify_expected *exp = (notify_expected*)payload;
+ notify_expected *e;
+
+ GIT_UNUSED(diff_so_far);
+
+ for (e = exp; e->path != NULL; e++) {
+ if (strcmp(e->path, delta_to_add->new_file.path))
+ continue;
+
+ cl_assert_equal_s(e->matched_pathspec, matched_pathspec);
+
+ found = true;
+ break;
+ }
+
+ cl_assert(found);
+ return 0;
+}
+
+static void test_notify(
+ char **searched_pathspecs,
+ int pathspecs_count,
+ notify_expected *expected_matched_pathspecs,
+ int expected_diffed_files_count)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ diff_expects exp;
+
+ g_repo = cl_git_sandbox_init("status");
+
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
+ opts.notify_cb = assert_called_notifications;
+ opts.pathspec.strings = searched_pathspecs;
+ opts.pathspec.count = pathspecs_count;
+
+ opts.notify_payload = expected_matched_pathspecs;
+ memset(&exp, 0, sizeof(exp));
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+
+ cl_assert_equal_i(expected_diffed_files_count, exp.files);
+
+ git_diff_free(diff);
+}
+
+void test_diff_notify__notify_single_pathspec(void)
+{
+ char *searched_pathspecs[] = {
+ "*_deleted",
+ };
+ notify_expected expected_matched_pathspecs[] = {
+ { "file_deleted", "*_deleted" },
+ { "staged_changes_file_deleted", "*_deleted" },
+ { NULL, NULL }
+ };
+
+ test_notify(searched_pathspecs, 1, expected_matched_pathspecs, 2);
+}
+
+void test_diff_notify__notify_multiple_pathspec(void)
+{
+ char *searched_pathspecs[] = {
+ "staged_changes_cant_find_me",
+ "subdir/modified_cant_find_me",
+ "subdir/*",
+ "staged*"
+ };
+ notify_expected expected_matched_pathspecs[] = {
+ { "staged_changes_file_deleted", "staged*" },
+ { "staged_changes_modified_file", "staged*" },
+ { "staged_delete_modified_file", "staged*" },
+ { "staged_new_file_deleted_file", "staged*" },
+ { "staged_new_file_modified_file", "staged*" },
+ { "subdir/deleted_file", "subdir/*" },
+ { "subdir/modified_file", "subdir/*" },
+ { "subdir/new_file", "subdir/*" },
+ { NULL, NULL }
+ };
+
+ test_notify(searched_pathspecs, 4, expected_matched_pathspecs, 8);
+}
+
+void test_diff_notify__notify_catchall_with_empty_pathspecs(void)
+{
+ char *searched_pathspecs[] = {
+ "",
+ ""
+ };
+ notify_expected expected_matched_pathspecs[] = {
+ { "file_deleted", NULL },
+ { "ignored_file", NULL },
+ { "modified_file", NULL },
+ { "new_file", NULL },
+ { "\xe8\xbf\x99", NULL },
+ { "staged_changes_file_deleted", NULL },
+ { "staged_changes_modified_file", NULL },
+ { "staged_delete_modified_file", NULL },
+ { "staged_new_file_deleted_file", NULL },
+ { "staged_new_file_modified_file", NULL },
+ { "subdir/deleted_file", NULL },
+ { "subdir/modified_file", NULL },
+ { "subdir/new_file", NULL },
+ { NULL, NULL }
+ };
+
+ test_notify(searched_pathspecs, 1, expected_matched_pathspecs, 13);
+}
+
+void test_diff_notify__notify_catchall(void)
+{
+ char *searched_pathspecs[] = {
+ "*",
+ };
+ notify_expected expected_matched_pathspecs[] = {
+ { "file_deleted", "*" },
+ { "ignored_file", "*" },
+ { "modified_file", "*" },
+ { "new_file", "*" },
+ { "\xe8\xbf\x99", "*" },
+ { "staged_changes_file_deleted", "*" },
+ { "staged_changes_modified_file", "*" },
+ { "staged_delete_modified_file", "*" },
+ { "staged_new_file_deleted_file", "*" },
+ { "staged_new_file_modified_file", "*" },
+ { "subdir/deleted_file", "*" },
+ { "subdir/modified_file", "*" },
+ { "subdir/new_file", "*" },
+ { NULL, NULL }
+ };
+
+ test_notify(searched_pathspecs, 1, expected_matched_pathspecs, 13);
+}
+
+static int abort_diff(
+ const git_diff *diff_so_far,
+ const git_diff_delta *delta_to_add,
+ const char *matched_pathspec,
+ void *payload)
+{
+ GIT_UNUSED(diff_so_far);
+ GIT_UNUSED(delta_to_add);
+ GIT_UNUSED(matched_pathspec);
+ GIT_UNUSED(payload);
+
+ return -42;
+}
+
+void test_diff_notify__notify_cb_can_abort_diff(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ char *pathspec = NULL;
+
+ g_repo = cl_git_sandbox_init("status");
+
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
+ opts.notify_cb = abort_diff;
+ opts.pathspec.strings = &pathspec;
+ opts.pathspec.count = 1;
+
+ pathspec = "file_deleted";
+ cl_git_fail_with(
+ git_diff_index_to_workdir(&diff, g_repo, NULL, &opts), -42);
+
+ pathspec = "staged_changes_modified_file";
+ cl_git_fail_with(
+ git_diff_index_to_workdir(&diff, g_repo, NULL, &opts), -42);
+}
+
+static int filter_all(
+ const git_diff *diff_so_far,
+ const git_diff_delta *delta_to_add,
+ const char *matched_pathspec,
+ void *payload)
+{
+ GIT_UNUSED(diff_so_far);
+ GIT_UNUSED(delta_to_add);
+ GIT_UNUSED(matched_pathspec);
+ GIT_UNUSED(payload);
+
+ return 42;
+}
+
+void test_diff_notify__notify_cb_can_be_used_as_filtering_function(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ char *pathspec = NULL;
+ diff_expects exp;
+
+ g_repo = cl_git_sandbox_init("status");
+
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
+ opts.notify_cb = filter_all;
+ opts.pathspec.strings = &pathspec;
+ opts.pathspec.count = 1;
+
+ pathspec = "*_deleted";
+ memset(&exp, 0, sizeof(exp));
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+
+ cl_assert_equal_i(0, exp.files);
+
+ git_diff_free(diff);
+}
diff --git a/tests/diff/patch.c b/tests/diff/patch.c
new file mode 100644
index 000000000..1184d1968
--- /dev/null
+++ b/tests/diff/patch.c
@@ -0,0 +1,612 @@
+#include "clar_libgit2.h"
+#include "git2/sys/repository.h"
+
+#include "diff_helpers.h"
+#include "diff.h"
+#include "repository.h"
+#include "buf_text.h"
+
+static git_repository *g_repo = NULL;
+
+void test_diff_patch__initialize(void)
+{
+}
+
+void test_diff_patch__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+#define EXPECTED_HEADER "diff --git a/subdir.txt b/subdir.txt\n" \
+ "deleted file mode 100644\n" \
+ "index e8ee89e..0000000\n" \
+ "--- a/subdir.txt\n" \
+ "+++ /dev/null\n"
+
+#define EXPECTED_HUNK "@@ -1,2 +0,0 @@\n"
+
+static int check_removal_cb(
+ const git_diff_delta *delta,
+ const git_diff_hunk *hunk,
+ const git_diff_line *line,
+ void *payload)
+{
+ switch (line->origin) {
+ case GIT_DIFF_LINE_FILE_HDR:
+ cl_assert_equal_s(EXPECTED_HEADER, line->content);
+ cl_assert(hunk == NULL);
+ goto check_delta;
+
+ case GIT_DIFF_LINE_HUNK_HDR:
+ cl_assert_equal_s(EXPECTED_HUNK, line->content);
+ goto check_hunk;
+
+ case GIT_DIFF_LINE_CONTEXT:
+ case GIT_DIFF_LINE_DELETION:
+ if (payload != NULL)
+ return *(int *)payload;
+ goto check_hunk;
+
+ default:
+ /* unexpected code path */
+ return -1;
+ }
+
+check_hunk:
+ cl_assert(hunk != NULL);
+ cl_assert_equal_i(1, hunk->old_start);
+ cl_assert_equal_i(2, hunk->old_lines);
+ cl_assert_equal_i(0, hunk->new_start);
+ cl_assert_equal_i(0, hunk->new_lines);
+
+check_delta:
+ cl_assert_equal_s("subdir.txt", delta->old_file.path);
+ cl_assert_equal_s("subdir.txt", delta->new_file.path);
+ cl_assert_equal_i(GIT_DELTA_DELETED, delta->status);
+
+ return 0;
+}
+
+void test_diff_patch__can_properly_display_the_removal_of_a_file(void)
+{
+ /*
+ * $ git diff 26a125e..735b6a2
+ * diff --git a/subdir.txt b/subdir.txt
+ * deleted file mode 100644
+ * index e8ee89e..0000000
+ * --- a/subdir.txt
+ * +++ /dev/null
+ * @@ -1,2 +0,0 @@
+ * -Is it a bird?
+ * -Is it a plane?
+ */
+
+ const char *one_sha = "26a125e";
+ const char *another_sha = "735b6a2";
+ git_tree *one, *another;
+ git_diff *diff;
+
+ g_repo = cl_git_sandbox_init("status");
+
+ 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_git_pass(git_diff_print(
+ diff, GIT_DIFF_FORMAT_PATCH, check_removal_cb, NULL));
+
+ git_diff_free(diff);
+
+ git_tree_free(another);
+ git_tree_free(one);
+}
+
+void test_diff_patch__can_cancel_diff_print(void)
+{
+ const char *one_sha = "26a125e";
+ const char *another_sha = "735b6a2";
+ git_tree *one, *another;
+ git_diff *diff;
+ int fail_with;
+
+ g_repo = cl_git_sandbox_init("status");
+
+ 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));
+
+ fail_with = -2323;
+
+ cl_git_fail_with(git_diff_print(
+ diff, GIT_DIFF_FORMAT_PATCH, check_removal_cb, &fail_with),
+ fail_with);
+
+ fail_with = 45;
+
+ cl_git_fail_with(git_diff_print(
+ diff, GIT_DIFF_FORMAT_PATCH, check_removal_cb, &fail_with),
+ fail_with);
+
+ git_diff_free(diff);
+
+ 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 *diff;
+ git_patch *patch;
+ git_buf buf = GIT_BUF_INIT;
+ 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";
+
+ g_repo = cl_git_sandbox_init("status");
+
+ 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, (int)git_diff_num_deltas(diff));
+
+ cl_git_pass(git_patch_from_diff(&patch, diff, 0));
+
+ cl_git_pass(git_patch_to_buf(&buf, patch));
+
+ cl_assert_equal_s(expected, buf.ptr);
+
+ cl_assert_equal_sz(31, git_patch_size(patch, 0, 0, 0));
+ cl_assert_equal_sz(31, git_patch_size(patch, 1, 0, 0));
+ cl_assert_equal_sz(31 + 16, git_patch_size(patch, 1, 1, 0));
+ cl_assert_equal_sz(strlen(expected), git_patch_size(patch, 1, 1, 1));
+
+ git_buf_free(&buf);
+ git_patch_free(patch);
+ git_diff_free(diff);
+ git_tree_free(another);
+ git_tree_free(one);
+}
+
+void test_diff_patch__config_options(void)
+{
+ const char *one_sha = "26a125e"; /* current HEAD */
+ git_tree *one;
+ git_config *cfg;
+ git_diff *diff;
+ git_patch *patch;
+ git_buf buf = GIT_BUF_INIT;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ char *onefile = "staged_changes_modified_file";
+ const char *expected1 = "diff --git c/staged_changes_modified_file i/staged_changes_modified_file\nindex 70bd944..906ee77 100644\n--- c/staged_changes_modified_file\n+++ i/staged_changes_modified_file\n@@ -1 +1,2 @@\n staged_changes_modified_file\n+staged_changes_modified_file\n";
+ const char *expected2 = "diff --git i/staged_changes_modified_file w/staged_changes_modified_file\nindex 906ee77..011c344 100644\n--- i/staged_changes_modified_file\n+++ w/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";
+ const char *expected3 = "diff --git staged_changes_modified_file staged_changes_modified_file\nindex 906ee77..011c344 100644\n--- staged_changes_modified_file\n+++ 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";
+ const char *expected4 = "diff --git staged_changes_modified_file staged_changes_modified_file\nindex 70bd9443ada0..906ee7711f4f 100644\n--- staged_changes_modified_file\n+++ staged_changes_modified_file\n@@ -1 +1,2 @@\n staged_changes_modified_file\n+staged_changes_modified_file\n";
+
+ g_repo = cl_git_sandbox_init("status");
+ cl_git_pass(git_repository_config(&cfg, g_repo));
+ one = resolve_commit_oid_to_tree(g_repo, one_sha);
+ opts.pathspec.count = 1;
+ opts.pathspec.strings = &onefile;
+
+
+ cl_git_pass(git_config_set_string(cfg, "diff.mnemonicprefix", "true"));
+
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, one, NULL, &opts));
+
+ cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
+ cl_git_pass(git_patch_from_diff(&patch, diff, 0));
+ cl_git_pass(git_patch_to_buf(&buf, patch));
+ cl_assert_equal_s(expected1, buf.ptr);
+
+ git_buf_clear(&buf);
+ git_patch_free(patch);
+ git_diff_free(diff);
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
+ cl_git_pass(git_patch_from_diff(&patch, diff, 0));
+ cl_git_pass(git_patch_to_buf(&buf, patch));
+ cl_assert_equal_s(expected2, buf.ptr);
+
+ git_buf_clear(&buf);
+ git_patch_free(patch);
+ git_diff_free(diff);
+
+
+ cl_git_pass(git_config_set_string(cfg, "diff.noprefix", "true"));
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
+ cl_git_pass(git_patch_from_diff(&patch, diff, 0));
+ cl_git_pass(git_patch_to_buf(&buf, patch));
+ cl_assert_equal_s(expected3, buf.ptr);
+
+ git_buf_clear(&buf);
+ git_patch_free(patch);
+ git_diff_free(diff);
+
+
+ cl_git_pass(git_config_set_int32(cfg, "core.abbrev", 12));
+
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, one, NULL, &opts));
+
+ cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
+ cl_git_pass(git_patch_from_diff(&patch, diff, 0));
+ cl_git_pass(git_patch_to_buf(&buf, patch));
+ cl_assert_equal_s(expected4, buf.ptr);
+
+ git_buf_clear(&buf);
+ git_patch_free(patch);
+ git_diff_free(diff);
+
+ git_buf_free(&buf);
+ git_tree_free(one);
+ git_config_free(cfg);
+}
+
+void test_diff_patch__hunks_have_correct_line_numbers(void)
+{
+ git_config *cfg;
+ git_tree *head;
+ git_diff_options opt = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff;
+ git_patch *patch;
+ const git_diff_delta *delta;
+ const git_diff_hunk *hunk;
+ const git_diff_line *line;
+ size_t hunklen;
+ git_buf old_content = GIT_BUF_INIT, actual = GIT_BUF_INIT;
+ const char *new_content = "The Song of Seven Cities\n------------------------\n\nI WAS Lord of Cities very sumptuously builded.\nSeven roaring Cities paid me tribute from afar.\nIvory their outposts were--the guardrooms of them gilded,\nAnd garrisoned with Amazons invincible in war.\n\nThis is some new text;\nNot as good as the old text;\nBut here it is.\n\nSo they warred and trafficked only yesterday, my Cities.\nTo-day there is no mark or mound of where my Cities stood.\nFor the River rose at midnight and it washed away my Cities.\nThey are evened with Atlantis and the towns before the Flood.\n\nRain on rain-gorged channels raised the water-levels round them,\nFreshet backed on freshet swelled and swept their world from sight,\nTill the emboldened floods linked arms and, flashing forward, drowned them--\nDrowned my Seven Cities and their peoples in one night!\n\nLow among the alders lie their derelict foundations,\nThe beams wherein they trusted and the plinths whereon they built--\nMy rulers and their treasure and their unborn populations,\nDead, destroyed, aborted, and defiled with mud and silt!\n\nAnother replacement;\nBreaking up the poem;\nGenerating some hunks.\n\nTo the sound of trumpets shall their seed restore my Cities\nWealthy and well-weaponed, that once more may I behold\nAll the world go softly when it walks before my Cities,\nAnd the horses and the chariots fleeing from them as of old!\n\n -- Rudyard Kipling\n";
+
+ g_repo = cl_git_sandbox_init("renames");
+
+ cl_git_pass(git_config_new(&cfg));
+ git_repository_set_config(g_repo, cfg);
+ git_config_free(cfg);
+
+ git_repository_reinit_filesystem(g_repo, false);
+
+ cl_git_pass(
+ git_futils_readbuffer(&old_content, "renames/songof7cities.txt"));
+
+ cl_git_rewritefile("renames/songof7cities.txt", new_content);
+
+ cl_git_pass(git_repository_head_tree(&head, g_repo));
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, head, &opt));
+
+ cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
+
+ cl_git_pass(git_patch_from_diff(&patch, diff, 0));
+ cl_assert((delta = git_patch_get_delta(patch)) != NULL);
+
+ cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status);
+ cl_assert_equal_i(2, (int)git_patch_num_hunks(patch));
+
+ /* check hunk 0 */
+
+ cl_git_pass(
+ git_patch_get_hunk(&hunk, &hunklen, patch, 0));
+
+ cl_assert_equal_i(18, (int)hunklen);
+
+ cl_assert_equal_i(6, (int)hunk->old_start);
+ cl_assert_equal_i(15, (int)hunk->old_lines);
+ cl_assert_equal_i(6, (int)hunk->new_start);
+ cl_assert_equal_i(9, (int)hunk->new_lines);
+
+ cl_assert_equal_i(18, (int)git_patch_num_lines_in_hunk(patch, 0));
+
+ cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 0));
+ cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin);
+ cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
+ cl_assert_equal_s("Ivory their outposts were--the guardrooms of them gilded,\n", actual.ptr);
+ cl_assert_equal_i(6, line->old_lineno);
+ cl_assert_equal_i(6, line->new_lineno);
+ cl_assert_equal_i(-1, line->content_offset);
+
+ cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 3));
+ cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)line->origin);
+ cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
+ cl_assert_equal_s("All the world went softly when it walked before my Cities--\n", actual.ptr);
+ cl_assert_equal_i(9, line->old_lineno);
+ cl_assert_equal_i(-1, line->new_lineno);
+ cl_assert_equal_i(252, line->content_offset);
+
+ cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 12));
+ cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)line->origin);
+ cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
+ cl_assert_equal_s("This is some new text;\n", actual.ptr);
+ cl_assert_equal_i(-1, line->old_lineno);
+ cl_assert_equal_i(9, line->new_lineno);
+ cl_assert_equal_i(252, line->content_offset);
+
+ /* check hunk 1 */
+
+ cl_git_pass(git_patch_get_hunk(&hunk, &hunklen, patch, 1));
+
+ cl_assert_equal_i(18, (int)hunklen);
+
+ cl_assert_equal_i(31, (int)hunk->old_start);
+ cl_assert_equal_i(15, (int)hunk->old_lines);
+ cl_assert_equal_i(25, (int)hunk->new_start);
+ cl_assert_equal_i(9, (int)hunk->new_lines);
+
+ cl_assert_equal_i(18, (int)git_patch_num_lines_in_hunk(patch, 1));
+
+ cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 1, 0));
+ cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin);
+ cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
+ cl_assert_equal_s("My rulers and their treasure and their unborn populations,\n", actual.ptr);
+ cl_assert_equal_i(31, line->old_lineno);
+ cl_assert_equal_i(25, line->new_lineno);
+ cl_assert_equal_i(-1, line->content_offset);
+
+ cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 1, 3));
+ cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)line->origin);
+ cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
+ cl_assert_equal_s("The Daughters of the Palace whom they cherished in my Cities,\n", actual.ptr);
+ cl_assert_equal_i(34, line->old_lineno);
+ cl_assert_equal_i(-1, line->new_lineno);
+ cl_assert_equal_i(1468, line->content_offset);
+
+ cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 1, 12));
+ cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)line->origin);
+ cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
+ cl_assert_equal_s("Another replacement;\n", actual.ptr);
+ cl_assert_equal_i(-1, line->old_lineno);
+ cl_assert_equal_i(28, line->new_lineno);
+ cl_assert_equal_i(1066, line->content_offset);
+
+ git_patch_free(patch);
+ git_diff_free(diff);
+
+ /* Let's check line numbers when there is no newline */
+
+ git_buf_rtrim(&old_content);
+ cl_git_rewritefile("renames/songof7cities.txt", old_content.ptr);
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, head, &opt));
+
+ cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
+
+ cl_git_pass(git_patch_from_diff(&patch, diff, 0));
+ cl_assert((delta = git_patch_get_delta(patch)) != NULL);
+
+ cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status);
+ cl_assert_equal_i(1, (int)git_patch_num_hunks(patch));
+
+ /* check hunk 0 */
+
+ cl_git_pass(git_patch_get_hunk(&hunk, &hunklen, patch, 0));
+
+ cl_assert_equal_i(6, (int)hunklen);
+
+ cl_assert_equal_i(46, (int)hunk->old_start);
+ cl_assert_equal_i(4, (int)hunk->old_lines);
+ cl_assert_equal_i(46, (int)hunk->new_start);
+ cl_assert_equal_i(4, (int)hunk->new_lines);
+
+ cl_assert_equal_i(6, (int)git_patch_num_lines_in_hunk(patch, 0));
+
+ cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 1));
+ cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin);
+ cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
+ cl_assert_equal_s("And the horses and the chariots fleeing from them as of old!\n", actual.ptr);
+ cl_assert_equal_i(47, line->old_lineno);
+ cl_assert_equal_i(47, line->new_lineno);
+
+ cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 2));
+ cl_assert_equal_i(GIT_DIFF_LINE_CONTEXT, (int)line->origin);
+ cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
+ cl_assert_equal_s("\n", actual.ptr);
+ cl_assert_equal_i(48, line->old_lineno);
+ cl_assert_equal_i(48, line->new_lineno);
+
+ cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 3));
+ cl_assert_equal_i(GIT_DIFF_LINE_DELETION, (int)line->origin);
+ cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
+ cl_assert_equal_s(" -- Rudyard Kipling\n", actual.ptr);
+ cl_assert_equal_i(49, line->old_lineno);
+ cl_assert_equal_i(-1, line->new_lineno);
+
+ cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 4));
+ cl_assert_equal_i(GIT_DIFF_LINE_ADDITION, (int)line->origin);
+ cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
+ cl_assert_equal_s(" -- Rudyard Kipling", actual.ptr);
+ cl_assert_equal_i(-1, line->old_lineno);
+ cl_assert_equal_i(49, line->new_lineno);
+
+ cl_git_pass(git_patch_get_line_in_hunk(&line, patch, 0, 5));
+ cl_assert_equal_i(GIT_DIFF_LINE_DEL_EOFNL, (int)line->origin);
+ cl_git_pass(git_buf_set(&actual, line->content, line->content_len));
+ cl_assert_equal_s("\n\\ No newline at end of file\n", actual.ptr);
+ cl_assert_equal_i(-1, line->old_lineno);
+ cl_assert_equal_i(49, line->new_lineno);
+
+ git_patch_free(patch);
+ git_diff_free(diff);
+
+ git_buf_free(&actual);
+ git_buf_free(&old_content);
+ git_tree_free(head);
+}
+
+static void check_single_patch_stats(
+ git_repository *repo, size_t hunks,
+ size_t adds, size_t dels, size_t ctxt, size_t *sizes,
+ const char *expected)
+{
+ git_diff *diff;
+ git_patch *patch;
+ const git_diff_delta *delta;
+ size_t actual_ctxt, actual_adds, actual_dels;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, NULL));
+
+ cl_assert_equal_i(1, (int)git_diff_num_deltas(diff));
+
+ cl_git_pass(git_patch_from_diff(&patch, diff, 0));
+ cl_assert((delta = git_patch_get_delta(patch)) != NULL);
+ cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status);
+
+ cl_assert_equal_i((int)hunks, (int)git_patch_num_hunks(patch));
+
+ cl_git_pass( git_patch_line_stats(
+ &actual_ctxt, &actual_adds, &actual_dels, patch) );
+
+ cl_assert_equal_sz(ctxt, actual_ctxt);
+ cl_assert_equal_sz(adds, actual_adds);
+ cl_assert_equal_sz(dels, actual_dels);
+
+ if (expected != NULL) {
+ git_buf buf = GIT_BUF_INIT;
+ cl_git_pass(git_patch_to_buf(&buf, patch));
+ cl_assert_equal_s(expected, buf.ptr);
+ git_buf_free(&buf);
+
+ cl_assert_equal_sz(
+ strlen(expected), git_patch_size(patch, 1, 1, 1));
+ }
+
+ if (sizes) {
+ if (sizes[0])
+ cl_assert_equal_sz(sizes[0], git_patch_size(patch, 0, 0, 0));
+ if (sizes[1])
+ cl_assert_equal_sz(sizes[1], git_patch_size(patch, 1, 0, 0));
+ if (sizes[2])
+ cl_assert_equal_sz(sizes[2], git_patch_size(patch, 1, 1, 0));
+ }
+
+ /* walk lines in hunk with basic sanity checks */
+ for (; hunks > 0; --hunks) {
+ size_t i, max_i;
+ const git_diff_line *line;
+ int last_new_lineno = -1, last_old_lineno = -1;
+
+ max_i = git_patch_num_lines_in_hunk(patch, hunks - 1);
+
+ for (i = 0; i < max_i; ++i) {
+ int expected = 1;
+
+ cl_git_pass(
+ git_patch_get_line_in_hunk(&line, patch, hunks - 1, i));
+
+ if (line->origin == GIT_DIFF_LINE_ADD_EOFNL ||
+ line->origin == GIT_DIFF_LINE_DEL_EOFNL ||
+ line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL)
+ expected = 0;
+
+ if (line->old_lineno >= 0) {
+ if (last_old_lineno >= 0)
+ cl_assert_equal_i(
+ expected, line->old_lineno - last_old_lineno);
+ last_old_lineno = line->old_lineno;
+ }
+
+ if (line->new_lineno >= 0) {
+ if (last_new_lineno >= 0)
+ cl_assert_equal_i(
+ expected, line->new_lineno - last_new_lineno);
+ last_new_lineno = line->new_lineno;
+ }
+ }
+ }
+
+ git_patch_free(patch);
+ git_diff_free(diff);
+}
+
+void test_diff_patch__line_counts_with_eofnl(void)
+{
+ git_config *cfg;
+ git_buf content = GIT_BUF_INIT;
+ const char *end;
+ git_index *index;
+ const char *expected =
+ /* below is pasted output of 'git diff' with fn context removed */
+ "diff --git a/songof7cities.txt b/songof7cities.txt\n"
+ "index 378a7d9..3d0154e 100644\n"
+ "--- a/songof7cities.txt\n"
+ "+++ b/songof7cities.txt\n"
+ "@@ -42,7 +42,7 @@ With peoples undefeated of the dark, enduring blood.\n"
+ " \n"
+ " To the sound of trumpets shall their seed restore my Cities\n"
+ " Wealthy and well-weaponed, that once more may I behold\n"
+ "-All the world go softly when it walks before my Cities,\n"
+ "+#All the world go softly when it walks before my Cities,\n"
+ " And the horses and the chariots fleeing from them as of old!\n"
+ " \n"
+ " -- Rudyard Kipling\n"
+ "\\ No newline at end of file\n";
+ size_t expected_sizes[3] = { 115, 119 + 115 + 114, 119 + 115 + 114 + 71 };
+
+ g_repo = cl_git_sandbox_init("renames");
+
+ cl_git_pass(git_config_new(&cfg));
+ git_repository_set_config(g_repo, cfg);
+ git_config_free(cfg);
+
+ git_repository_reinit_filesystem(g_repo, false);
+
+ cl_git_pass(git_futils_readbuffer(&content, "renames/songof7cities.txt"));
+
+ /* remove first line */
+
+ end = git_buf_cstr(&content) + git_buf_find(&content, '\n') + 1;
+ git_buf_consume(&content, end);
+ cl_git_rewritefile("renames/songof7cities.txt", content.ptr);
+
+ check_single_patch_stats(g_repo, 1, 0, 1, 3, NULL, NULL);
+
+ /* remove trailing whitespace */
+
+ git_buf_rtrim(&content);
+ cl_git_rewritefile("renames/songof7cities.txt", content.ptr);
+
+ check_single_patch_stats(g_repo, 2, 1, 2, 6, NULL, NULL);
+
+ /* add trailing whitespace */
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_index_add_bypath(index, "songof7cities.txt"));
+ cl_git_pass(git_index_write(index));
+ git_index_free(index);
+
+ cl_git_pass(git_buf_putc(&content, '\n'));
+ cl_git_rewritefile("renames/songof7cities.txt", content.ptr);
+
+ check_single_patch_stats(g_repo, 1, 1, 1, 3, NULL, NULL);
+
+ /* no trailing whitespace as context line */
+
+ {
+ /* walk back a couple lines, make space and insert char */
+ char *scan = content.ptr + content.size;
+ int i;
+
+ for (i = 0; i < 5; ++i) {
+ for (--scan; scan > content.ptr && *scan != '\n'; --scan)
+ /* seek to prev \n */;
+ }
+ cl_assert(scan > content.ptr);
+
+ /* overwrite trailing \n with right-shifted content */
+ memmove(scan + 1, scan, content.size - (scan - content.ptr) - 1);
+ /* insert '#' char into space we created */
+ scan[1] = '#';
+ }
+ cl_git_rewritefile("renames/songof7cities.txt", content.ptr);
+
+ check_single_patch_stats(
+ g_repo, 1, 1, 1, 6, expected_sizes, expected);
+
+ git_buf_free(&content);
+}
diff --git a/tests/diff/pathspec.c b/tests/diff/pathspec.c
new file mode 100644
index 000000000..5761d2d2b
--- /dev/null
+++ b/tests/diff/pathspec.c
@@ -0,0 +1,93 @@
+#include "clar_libgit2.h"
+#include "diff_helpers.h"
+
+static git_repository *g_repo = NULL;
+
+void test_diff_pathspec__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("status");
+}
+
+void test_diff_pathspec__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_diff_pathspec__0(void)
+{
+ const char *a_commit = "26a125ee"; /* the current HEAD */
+ const char *b_commit = "0017bd4a"; /* the start */
+ git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit);
+ git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit);
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ git_strarray paths = { NULL, 1 };
+ char *path;
+ git_pathspec *ps;
+ git_pathspec_match_list *matches;
+
+ cl_assert(a);
+ cl_assert(b);
+
+ path = "*_file";
+ paths.strings = &path;
+ cl_git_pass(git_pathspec_new(&ps, &paths));
+
+ cl_git_pass(git_pathspec_match_tree(&matches, a, GIT_PATHSPEC_DEFAULT, ps));
+ cl_assert_equal_i(7, (int)git_pathspec_match_list_entrycount(matches));
+ cl_assert_equal_s("current_file", git_pathspec_match_list_entry(matches,0));
+ cl_assert(git_pathspec_match_list_diff_entry(matches,0) == NULL);
+ git_pathspec_match_list_free(matches);
+
+ cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, NULL, a, &opts));
+
+ cl_git_pass(git_pathspec_match_diff(
+ &matches, diff, GIT_PATHSPEC_DEFAULT, ps));
+ cl_assert_equal_i(7, (int)git_pathspec_match_list_entrycount(matches));
+ cl_assert(git_pathspec_match_list_diff_entry(matches, 0) != NULL);
+ cl_assert(git_pathspec_match_list_entry(matches, 0) == NULL);
+ cl_assert_equal_s("current_file",
+ git_pathspec_match_list_diff_entry(matches,0)->new_file.path);
+ cl_assert_equal_i(GIT_DELTA_ADDED,
+ (int)git_pathspec_match_list_diff_entry(matches,0)->status);
+ git_pathspec_match_list_free(matches);
+
+ git_diff_free(diff);
+ diff = NULL;
+
+ cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts));
+
+ cl_git_pass(git_pathspec_match_diff(
+ &matches, diff, GIT_PATHSPEC_DEFAULT, ps));
+ cl_assert_equal_i(3, (int)git_pathspec_match_list_entrycount(matches));
+ cl_assert(git_pathspec_match_list_diff_entry(matches, 0) != NULL);
+ cl_assert(git_pathspec_match_list_entry(matches, 0) == NULL);
+ cl_assert_equal_s("subdir/current_file",
+ git_pathspec_match_list_diff_entry(matches,0)->new_file.path);
+ cl_assert_equal_i(GIT_DELTA_DELETED,
+ (int)git_pathspec_match_list_diff_entry(matches,0)->status);
+ git_pathspec_match_list_free(matches);
+
+ git_diff_free(diff);
+ diff = NULL;
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts));
+
+ cl_git_pass(git_pathspec_match_diff(
+ &matches, diff, GIT_PATHSPEC_DEFAULT, ps));
+ cl_assert_equal_i(4, (int)git_pathspec_match_list_entrycount(matches));
+ cl_assert(git_pathspec_match_list_diff_entry(matches, 0) != NULL);
+ cl_assert(git_pathspec_match_list_entry(matches, 0) == NULL);
+ cl_assert_equal_s("modified_file",
+ git_pathspec_match_list_diff_entry(matches,0)->new_file.path);
+ cl_assert_equal_i(GIT_DELTA_MODIFIED,
+ (int)git_pathspec_match_list_diff_entry(matches,0)->status);
+ git_pathspec_match_list_free(matches);
+
+ git_diff_free(diff);
+ diff = NULL;
+
+ git_tree_free(a);
+ git_tree_free(b);
+ git_pathspec_free(ps);
+}
diff --git a/tests/diff/rename.c b/tests/diff/rename.c
new file mode 100644
index 000000000..4bc3eb54c
--- /dev/null
+++ b/tests/diff/rename.c
@@ -0,0 +1,1588 @@
+#include "clar_libgit2.h"
+#include "diff_helpers.h"
+#include "buf_text.h"
+
+static git_repository *g_repo = NULL;
+
+void test_diff_rename__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("renames");
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", false);
+}
+
+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)
+ * commit 1c068dee5790ef1580cfc4cd670915b48d790084
+ * songofseven.txt -> songofseven.txt (major rewrite, <20% match - split)
+ * sixserving.txt -> sixserving.txt (indentation change)
+ * sixserving.txt -> ikeepsix.txt (copy, add title, >80% match)
+ * sevencities.txt (no change)
+ * commit 19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13
+ * songofseven.txt -> untimely.txt (rename, convert to crlf)
+ * ikeepsix.txt -> ikeepsix.txt (reorder sections in file)
+ * sixserving.txt -> sixserving.txt (whitespace change - not just indent)
+ * sevencities.txt -> songof7cities.txt (rename, small text changes)
+ */
+
+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 *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ 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...
+ */
+ 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, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ 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
+ * don't use NULL opts to avoid config `diff.renames` contamination
+ */
+ opts.flags = GIT_DIFF_FIND_RENAMES;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ 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_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
+ */
+ 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, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ 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_free(diff);
+
+ cl_git_pass(git_diff_tree_to_tree(
+ &diff, g_repo, old_tree, new_tree, &diffopts));
+
+ /* git diff --find-copies-harder -M100 -B100 \
+ * 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \
+ * 2bc7f351d20b53f1c72c16c4b036e491c478c49a
+ */
+ opts.flags = GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED |
+ GIT_DIFF_FIND_EXACT_MATCH_ONLY;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ 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_free(diff);
+
+ git_tree_free(old_tree);
+ git_tree_free(new_tree);
+}
+
+void test_diff_rename__checks_options_version(void)
+{
+ const char *old_sha = "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2";
+ const char *new_sha = "2bc7f351d20b53f1c72c16c4b036e491c478c49a";
+ git_tree *old_tree, *new_tree;
+ git_diff *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ const git_error *err;
+
+ old_tree = resolve_commit_oid_to_tree(g_repo, old_sha);
+ new_tree = resolve_commit_oid_to_tree(g_repo, new_sha);
+ diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
+ cl_git_pass(git_diff_tree_to_tree(
+ &diff, g_repo, old_tree, new_tree, &diffopts));
+
+ opts.version = 0;
+ cl_git_fail(git_diff_find_similar(diff, &opts));
+ err = giterr_last();
+ cl_assert_equal_i(GITERR_INVALID, err->klass);
+
+ giterr_clear();
+ opts.version = 1024;
+ cl_git_fail(git_diff_find_similar(diff, &opts));
+ err = giterr_last();
+ cl_assert_equal_i(GITERR_INVALID, err->klass);
+
+ git_diff_free(diff);
+ git_tree_free(old_tree);
+ git_tree_free(new_tree);
+}
+
+void test_diff_rename__not_exact_match(void)
+{
+ const char *sha0 = "2bc7f351d20b53f1c72c16c4b036e491c478c49a";
+ const char *sha1 = "1c068dee5790ef1580cfc4cd670915b48d790084";
+ const char *sha2 = "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13";
+ git_tree *old_tree, *new_tree;
+ git_diff *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ diff_expects exp;
+
+ /* == Changes =====================================================
+ * songofseven.txt -> songofseven.txt (major rewrite, <20% match - split)
+ * sixserving.txt -> sixserving.txt (indentation change)
+ * sixserving.txt -> ikeepsix.txt (copy, add title, >80% match)
+ * sevencities.txt (no change)
+ */
+
+ old_tree = resolve_commit_oid_to_tree(g_repo, sha0);
+ new_tree = resolve_commit_oid_to_tree(g_repo, sha1);
+
+ /* Must pass GIT_DIFF_INCLUDE_UNMODIFIED if you expect to emulate
+ * --find-copies-harder during rename transformion...
+ */
+ 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 \
+ * 2bc7f351d20b53f1c72c16c4b036e491c478c49a \
+ * 1c068dee5790ef1580cfc4cd670915b48d790084
+ */
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ 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_MODIFIED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
+
+ /* git diff -M 2bc7f351d20b53f1c72c16c4b036e491c478c49a \
+ * 1c068dee5790ef1580cfc4cd670915b48d790084
+ *
+ * must not pass NULL for opts because it will pick up environment
+ * values for "diff.renames" and test won't be consistent.
+ */
+ opts.flags = GIT_DIFF_FIND_RENAMES;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ 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_MODIFIED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
+
+ git_diff_free(diff);
+
+ /* git diff -M -C \
+ * 2bc7f351d20b53f1c72c16c4b036e491c478c49a \
+ * 1c068dee5790ef1580cfc4cd670915b48d790084
+ */
+ cl_git_pass(git_diff_tree_to_tree(
+ &diff, g_repo, old_tree, new_tree, &diffopts));
+
+ opts.flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ 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_MODIFIED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]);
+
+ git_diff_free(diff);
+
+ /* git diff -M -C --find-copies-harder --break-rewrites \
+ * 2bc7f351d20b53f1c72c16c4b036e491c478c49a \
+ * 1c068dee5790ef1580cfc4cd670915b48d790084
+ */
+ cl_git_pass(git_diff_tree_to_tree(
+ &diff, g_repo, old_tree, new_tree, &diffopts));
+
+ opts.flags = GIT_DIFF_FIND_ALL;
+ opts.break_rewrite_threshold = 70;
+
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(5, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(1, 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_COPIED]);
+
+ git_diff_free(diff);
+
+ /* == Changes =====================================================
+ * songofseven.txt -> untimely.txt (rename, convert to crlf)
+ * ikeepsix.txt -> ikeepsix.txt (reorder sections in file)
+ * sixserving.txt -> sixserving.txt (whitespace - not just indent)
+ * sevencities.txt -> songof7cities.txt (rename, small text changes)
+ */
+
+ git_tree_free(old_tree);
+ old_tree = new_tree;
+ new_tree = resolve_commit_oid_to_tree(g_repo, sha2);
+
+ cl_git_pass(git_diff_tree_to_tree(
+ &diff, g_repo, old_tree, new_tree, &diffopts));
+
+ /* git diff --no-renames \
+ * 1c068dee5790ef1580cfc4cd670915b48d790084 \
+ * 19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13
+ */
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(6, exp.files);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]);
+ git_diff_free(diff);
+
+ /* git diff -M -C \
+ * 1c068dee5790ef1580cfc4cd670915b48d790084 \
+ * 19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13
+ */
+ cl_git_pass(git_diff_tree_to_tree(
+ &diff, g_repo, old_tree, new_tree, &diffopts));
+
+ opts.flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(4, exp.files);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]);
+
+ git_diff_free(diff);
+
+ /* git diff -M -C --find-copies-harder --break-rewrites \
+ * 1c068dee5790ef1580cfc4cd670915b48d790084 \
+ * 19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13
+ * with libgit2 default similarity comparison...
+ */
+ cl_git_pass(git_diff_tree_to_tree(
+ &diff, g_repo, old_tree, new_tree, &diffopts));
+
+ opts.flags = GIT_DIFF_FIND_ALL;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ /* the default match algorithm is going to find the internal
+ * whitespace differences in the lines of sixserving.txt to be
+ * significant enough that this will decide to split it into an ADD
+ * and a DELETE
+ */
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(5, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(1, 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_RENAMED]);
+
+ git_diff_free(diff);
+
+ /* git diff -M -C --find-copies-harder --break-rewrites \
+ * 1c068dee5790ef1580cfc4cd670915b48d790084 \
+ * 19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13
+ * with ignore_space whitespace comparision
+ */
+ cl_git_pass(git_diff_tree_to_tree(
+ &diff, g_repo, old_tree, new_tree, &diffopts));
+
+ opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_IGNORE_WHITESPACE;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ /* Ignoring whitespace, this should no longer split sixserver.txt */
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(4, exp.files);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]);
+
+ git_diff_free(diff);
+
+ git_tree_free(old_tree);
+ git_tree_free(new_tree);
+}
+
+void test_diff_rename__handles_small_files(void)
+{
+ const char *tree_sha = "2bc7f351d20b53f1c72c16c4b036e491c478c49a";
+ git_index *index;
+ git_tree *tree;
+ git_diff *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ tree = resolve_commit_oid_to_tree(g_repo, tree_sha);
+
+ cl_git_rewritefile("renames/songof7cities.txt", "single line\n");
+ cl_git_pass(git_index_add_bypath(index, "songof7cities.txt"));
+
+ cl_git_rewritefile("renames/untimely.txt", "untimely\n");
+ cl_git_pass(git_index_add_bypath(index, "untimely.txt"));
+
+ /* Tests that we can invoke find_similar on small files
+ * and that the GIT_EBUFS (too small) error code is not
+ * propagated to the caller.
+ */
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
+
+ opts.flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES |
+ GIT_DIFF_FIND_AND_BREAK_REWRITES;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ git_diff_free(diff);
+ git_tree_free(tree);
+ git_index_free(index);
+}
+
+void test_diff_rename__working_directory_changes(void)
+{
+ const char *sha0 = "2bc7f351d20b53f1c72c16c4b036e491c478c49a";
+ const char *blobsha = "66311f5cfbe7836c27510a3ba2f43e282e2c8bba";
+ git_oid id;
+ git_tree *tree;
+ git_blob *blob;
+ git_diff *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ diff_expects exp;
+ git_buf old_content = GIT_BUF_INIT, content = GIT_BUF_INIT;;
+
+ tree = resolve_commit_oid_to_tree(g_repo, sha0);
+ diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED | GIT_DIFF_INCLUDE_UNTRACKED;
+
+ /*
+ $ git cat-file -p 2bc7f351d20b53f1c72c16c4b036e491c478c49a^{tree}
+
+ 100644 blob 66311f5cfbe7836c27510a3ba2f43e282e2c8bba sevencities.txt
+ 100644 blob ad0a8e55a104ac54a8a29ed4b84b49e76837a113 sixserving.txt
+ 100644 blob 66311f5cfbe7836c27510a3ba2f43e282e2c8bba songofseven.txt
+
+ $ for f in *.txt; do
+ echo `git hash-object -t blob $f` $f
+ done
+
+ eaf4a3e3bfe68585e90cada20736ace491cd100b ikeepsix.txt
+ f90d4fc20ecddf21eebe6a37e9225d244339d2b5 sixserving.txt
+ 4210ffd5c390b21dd5483375e75288dea9ede512 songof7cities.txt
+ 9a69d960ae94b060f56c2a8702545e2bb1abb935 untimely.txt
+ */
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts));
+
+ /* git diff --no-renames 2bc7f351d20b53f1c72c16c4b036e491c478c49a */
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(6, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]);
+
+ /* git diff -M 2bc7f351d20b53f1c72c16c4b036e491c478c49a */
+ opts.flags = GIT_DIFF_FIND_ALL;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(5, exp.files);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]);
+
+ git_diff_free(diff);
+
+ /* rewrite files in the working directory with / without CRLF changes */
+
+ cl_git_pass(
+ git_futils_readbuffer(&old_content, "renames/songof7cities.txt"));
+ cl_git_pass(
+ git_buf_text_lf_to_crlf(&content, &old_content));
+ cl_git_pass(
+ git_futils_writebuffer(&content, "renames/songof7cities.txt", 0, 0));
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts));
+
+ /* git diff -M 2bc7f351d20b53f1c72c16c4b036e491c478c49a */
+ opts.flags = GIT_DIFF_FIND_ALL;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(5, exp.files);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]);
+
+ git_diff_free(diff);
+
+ /* try a different whitespace option */
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts));
+
+ opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE;
+ opts.rename_threshold = 70;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(6, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]);
+
+ git_diff_free(diff);
+
+ /* try a different matching option */
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts));
+
+ opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_EXACT_MATCH_ONLY;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(6, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]);
+
+ git_diff_free(diff);
+
+ /* again with exact match blob */
+
+ cl_git_pass(git_oid_fromstr(&id, blobsha));
+ cl_git_pass(git_blob_lookup(&blob, g_repo, &id));
+ cl_git_pass(git_buf_set(
+ &content, git_blob_rawcontent(blob), (size_t)git_blob_rawsize(blob)));
+ cl_git_rewritefile("renames/songof7cities.txt", content.ptr);
+ git_blob_free(blob);
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &diffopts));
+
+ opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_EXACT_MATCH_ONLY;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ /*
+ fprintf(stderr, "\n\n");
+ diff_print_raw(stderr, diff);
+ */
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(5, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNTRACKED]);
+
+ git_diff_free(diff);
+
+ git_tree_free(tree);
+ git_buf_free(&content);
+ git_buf_free(&old_content);
+}
+
+void test_diff_rename__patch(void)
+{
+ const char *sha0 = "2bc7f351d20b53f1c72c16c4b036e491c478c49a";
+ const char *sha1 = "1c068dee5790ef1580cfc4cd670915b48d790084";
+ git_tree *old_tree, *new_tree;
+ git_diff *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ git_patch *patch;
+ const git_diff_delta *delta;
+ git_buf buf = GIT_BUF_INIT;
+ const char *expected = "diff --git a/sixserving.txt b/ikeepsix.txt\nindex ad0a8e5..36020db 100644\n--- a/sixserving.txt\n+++ b/ikeepsix.txt\n@@ -1,3 +1,6 @@\n+I Keep Six Honest Serving-Men\n+=============================\n+\n I KEEP six honest serving-men\n (They taught me all I knew);\n Their names are What and Why and When\n@@ -21,4 +24,4 @@ She sends'em abroad on her own affairs,\n One million Hows, two million Wheres,\n And seven million Whys!\n \n- -- Rudyard Kipling\n+ -- Rudyard Kipling\n";
+
+ old_tree = resolve_commit_oid_to_tree(g_repo, sha0);
+ new_tree = resolve_commit_oid_to_tree(g_repo, sha1);
+
+ diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
+ cl_git_pass(git_diff_tree_to_tree(
+ &diff, g_repo, old_tree, new_tree, &diffopts));
+
+ opts.flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ /* == Changes =====================================================
+ * sixserving.txt -> ikeepsix.txt (copy, add title, >80% match)
+ * sevencities.txt (no change)
+ * sixserving.txt -> sixserving.txt (indentation change)
+ * songofseven.txt -> songofseven.txt (major rewrite, <20% match - split)
+ */
+
+ cl_assert_equal_i(4, (int)git_diff_num_deltas(diff));
+
+ cl_git_pass(git_patch_from_diff(&patch, diff, 0));
+ cl_assert((delta = git_patch_get_delta(patch)) != NULL);
+ cl_assert_equal_i(GIT_DELTA_COPIED, (int)delta->status);
+
+ cl_git_pass(git_patch_to_buf(&buf, patch));
+ cl_assert_equal_s(expected, buf.ptr);
+ git_buf_free(&buf);
+
+ git_patch_free(patch);
+
+ cl_assert((delta = git_diff_get_delta(diff, 1)) != NULL);
+ cl_assert_equal_i(GIT_DELTA_UNMODIFIED, (int)delta->status);
+
+ cl_assert((delta = git_diff_get_delta(diff, 2)) != NULL);
+ cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status);
+
+ cl_assert((delta = git_diff_get_delta(diff, 3)) != NULL);
+ cl_assert_equal_i(GIT_DELTA_MODIFIED, (int)delta->status);
+
+ git_diff_free(diff);
+ git_tree_free(old_tree);
+ git_tree_free(new_tree);
+}
+
+void test_diff_rename__file_exchange(void)
+{
+ git_buf c1 = GIT_BUF_INIT, c2 = GIT_BUF_INIT;
+ git_index *index;
+ git_tree *tree;
+ git_diff *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ diff_expects exp;
+
+ cl_git_pass(git_futils_readbuffer(&c1, "renames/untimely.txt"));
+ cl_git_pass(git_futils_readbuffer(&c2, "renames/songof7cities.txt"));
+ cl_git_pass(git_futils_writebuffer(&c1, "renames/songof7cities.txt", 0, 0));
+ cl_git_pass(git_futils_writebuffer(&c2, "renames/untimely.txt", 0, 0));
+
+ cl_git_pass(
+ git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_index_read_tree(index, tree));
+ cl_git_pass(git_index_add_bypath(index, "songof7cities.txt"));
+ cl_git_pass(git_index_add_bypath(index, "untimely.txt"));
+
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(2, exp.files);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
+
+ opts.flags = GIT_DIFF_FIND_ALL;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(2, exp.files);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_RENAMED]);
+
+ git_diff_free(diff);
+ git_tree_free(tree);
+ git_index_free(index);
+
+ git_buf_free(&c1);
+ git_buf_free(&c2);
+}
+
+void test_diff_rename__file_exchange_three(void)
+{
+ git_buf c1 = GIT_BUF_INIT, c2 = GIT_BUF_INIT, c3 = GIT_BUF_INIT;
+ git_index *index;
+ git_tree *tree;
+ git_diff *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ diff_expects exp;
+
+ cl_git_pass(git_futils_readbuffer(&c1, "renames/untimely.txt"));
+ cl_git_pass(git_futils_readbuffer(&c2, "renames/songof7cities.txt"));
+ cl_git_pass(git_futils_readbuffer(&c3, "renames/ikeepsix.txt"));
+
+ cl_git_pass(git_futils_writebuffer(&c1, "renames/ikeepsix.txt", 0, 0));
+ cl_git_pass(git_futils_writebuffer(&c2, "renames/untimely.txt", 0, 0));
+ cl_git_pass(git_futils_writebuffer(&c3, "renames/songof7cities.txt", 0, 0));
+
+ cl_git_pass(
+ git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_index_read_tree(index, tree));
+ cl_git_pass(git_index_add_bypath(index, "songof7cities.txt"));
+ cl_git_pass(git_index_add_bypath(index, "untimely.txt"));
+ cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt"));
+
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(3, exp.files);
+ cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]);
+
+ opts.flags = GIT_DIFF_FIND_ALL;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(3, exp.files);
+ cl_assert_equal_i(3, exp.file_status[GIT_DELTA_RENAMED]);
+
+ git_diff_free(diff);
+ git_tree_free(tree);
+ git_index_free(index);
+
+ git_buf_free(&c1);
+ git_buf_free(&c2);
+ git_buf_free(&c3);
+}
+
+void test_diff_rename__file_partial_exchange(void)
+{
+ git_buf c1 = GIT_BUF_INIT, c2 = GIT_BUF_INIT;
+ git_index *index;
+ git_tree *tree;
+ git_diff *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ diff_expects exp;
+ int i;
+
+ cl_git_pass(git_futils_readbuffer(&c1, "renames/untimely.txt"));
+ cl_git_pass(git_futils_writebuffer(&c1, "renames/songof7cities.txt", 0, 0));
+ for (i = 0; i < 100; ++i)
+ cl_git_pass(git_buf_puts(&c2, "this is not the content you are looking for\n"));
+ cl_git_pass(git_futils_writebuffer(&c2, "renames/untimely.txt", 0, 0));
+
+ cl_git_pass(
+ git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_index_read_tree(index, tree));
+ cl_git_pass(git_index_add_bypath(index, "songof7cities.txt"));
+ cl_git_pass(git_index_add_bypath(index, "untimely.txt"));
+
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(2, exp.files);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
+
+ opts.flags = GIT_DIFF_FIND_ALL;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(3, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+
+ git_diff_free(diff);
+ git_tree_free(tree);
+ git_index_free(index);
+
+ git_buf_free(&c1);
+ git_buf_free(&c2);
+}
+
+void test_diff_rename__rename_and_copy_from_same_source(void)
+{
+ git_buf c1 = GIT_BUF_INIT, c2 = GIT_BUF_INIT;
+ git_index *index;
+ git_tree *tree;
+ git_diff *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ diff_expects exp;
+
+ /* put the first 2/3 of file into one new place
+ * and the second 2/3 of file into another new place
+ */
+ cl_git_pass(git_futils_readbuffer(&c1, "renames/songof7cities.txt"));
+ cl_git_pass(git_buf_set(&c2, c1.ptr, c1.size));
+ git_buf_truncate(&c1, c1.size * 2 / 3);
+ git_buf_consume(&c2, ((char *)c2.ptr) + (c2.size / 3));
+ cl_git_pass(git_futils_writebuffer(&c1, "renames/song_a.txt", 0, 0));
+ cl_git_pass(git_futils_writebuffer(&c2, "renames/song_b.txt", 0, 0));
+
+ cl_git_pass(
+ git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_index_read_tree(index, tree));
+ cl_git_pass(git_index_add_bypath(index, "song_a.txt"));
+ cl_git_pass(git_index_add_bypath(index, "song_b.txt"));
+
+ diffopts.flags = GIT_DIFF_INCLUDE_UNMODIFIED;
+
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(6, exp.files);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNMODIFIED]);
+
+ opts.flags = GIT_DIFF_FIND_ALL;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(6, exp.files);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_COPIED]);
+ cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNMODIFIED]);
+
+ git_diff_free(diff);
+ git_tree_free(tree);
+ git_index_free(index);
+
+ git_buf_free(&c1);
+ git_buf_free(&c2);
+}
+
+void test_diff_rename__from_deleted_to_split(void)
+{
+ git_buf c1 = GIT_BUF_INIT;
+ git_index *index;
+ git_tree *tree;
+ git_diff *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ diff_expects exp;
+
+ /* old file is missing, new file is actually old file renamed */
+
+ cl_git_pass(git_futils_readbuffer(&c1, "renames/songof7cities.txt"));
+ cl_git_pass(git_futils_writebuffer(&c1, "renames/untimely.txt", 0, 0));
+
+ cl_git_pass(
+ git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_index_read_tree(index, tree));
+ cl_git_pass(git_index_remove_bypath(index, "songof7cities.txt"));
+ cl_git_pass(git_index_add_bypath(index, "untimely.txt"));
+
+ diffopts.flags = GIT_DIFF_INCLUDE_UNMODIFIED;
+
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(4, exp.files);
+ 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(2, exp.file_status[GIT_DELTA_UNMODIFIED]);
+
+ opts.flags = GIT_DIFF_FIND_ALL;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(4, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_UNMODIFIED]);
+
+ git_diff_free(diff);
+ git_tree_free(tree);
+ git_index_free(index);
+
+ git_buf_free(&c1);
+}
+
+struct rename_expected
+{
+ size_t len;
+
+ unsigned int *status;
+ const char **sources;
+ const char **targets;
+
+ size_t idx;
+};
+
+int test_names_expected(const git_diff_delta *delta, float progress, void *p)
+{
+ struct rename_expected *expected = p;
+
+ GIT_UNUSED(progress);
+
+ cl_assert(expected->idx < expected->len);
+
+ cl_assert_equal_i(delta->status, expected->status[expected->idx]);
+
+ cl_assert(git__strcmp(expected->sources[expected->idx],
+ delta->old_file.path) == 0);
+ cl_assert(git__strcmp(expected->targets[expected->idx],
+ delta->new_file.path) == 0);
+
+ expected->idx++;
+
+ return 0;
+}
+
+void test_diff_rename__rejected_match_can_match_others(void)
+{
+ git_reference *head, *selfsimilar;
+ git_index *index;
+ git_tree *tree;
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_diff *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
+ git_buf one = GIT_BUF_INIT, two = GIT_BUF_INIT;
+ unsigned int status[] = { GIT_DELTA_RENAMED, GIT_DELTA_RENAMED };
+ const char *sources[] = { "Class1.cs", "Class2.cs" };
+ const char *targets[] = { "ClassA.cs", "ClassB.cs" };
+ struct rename_expected expect = { 2, status, sources, targets };
+ char *ptr;
+
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+ findopts.flags = GIT_DIFF_FIND_RENAMES;
+
+ cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD"));
+ cl_git_pass(git_reference_symbolic_set_target(
+ &selfsimilar, head, "refs/heads/renames_similar", NULL, NULL));
+ cl_git_pass(git_checkout_head(g_repo, &opts));
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ cl_git_pass(git_futils_readbuffer(&one, "renames/Class1.cs"));
+ cl_git_pass(git_futils_readbuffer(&two, "renames/Class2.cs"));
+
+ cl_git_pass(p_unlink("renames/Class1.cs"));
+ cl_git_pass(p_unlink("renames/Class2.cs"));
+
+ cl_git_pass(git_index_remove_bypath(index, "Class1.cs"));
+ cl_git_pass(git_index_remove_bypath(index, "Class2.cs"));
+
+ cl_assert(ptr = strstr(one.ptr, "Class1"));
+ ptr[5] = 'A';
+
+ cl_assert(ptr = strstr(two.ptr, "Class2"));
+ ptr[5] = 'B';
+
+ cl_git_pass(
+ git_futils_writebuffer(&one, "renames/ClassA.cs", O_RDWR|O_CREAT, 0777));
+ cl_git_pass(
+ git_futils_writebuffer(&two, "renames/ClassB.cs", O_RDWR|O_CREAT, 0777));
+
+ cl_git_pass(git_index_add_bypath(index, "ClassA.cs"));
+ cl_git_pass(git_index_add_bypath(index, "ClassB.cs"));
+
+ cl_git_pass(git_index_write(index));
+
+ cl_git_pass(
+ git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
+
+ cl_git_pass(
+ git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
+
+ cl_git_pass(git_diff_find_similar(diff, &findopts));
+
+ cl_git_pass(
+ git_diff_foreach(diff, test_names_expected, NULL, NULL, &expect));
+
+ git_diff_free(diff);
+ git_tree_free(tree);
+ git_index_free(index);
+ git_reference_free(head);
+ git_reference_free(selfsimilar);
+ git_buf_free(&one);
+ git_buf_free(&two);
+}
+
+static void write_similarity_file_two(const char *filename, size_t b_lines)
+{
+ git_buf contents = GIT_BUF_INIT;
+ size_t i;
+
+ for (i = 0; i < b_lines; i++)
+ git_buf_printf(&contents, "%02d - bbbbb\r\n", (int)(i+1));
+
+ for (i = b_lines; i < 50; i++)
+ git_buf_printf(&contents, "%02d - aaaaa%s", (int)(i+1), (i == 49 ? "" : "\r\n"));
+
+ cl_git_pass(
+ git_futils_writebuffer(&contents, filename, O_RDWR|O_CREAT, 0777));
+
+ git_buf_free(&contents);
+}
+
+void test_diff_rename__rejected_match_can_match_others_two(void)
+{
+ git_reference *head, *selfsimilar;
+ git_index *index;
+ git_tree *tree;
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_diff *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
+ unsigned int status[] = { GIT_DELTA_RENAMED, GIT_DELTA_RENAMED };
+ const char *sources[] = { "a.txt", "b.txt" };
+ const char *targets[] = { "c.txt", "d.txt" };
+ struct rename_expected expect = { 2, status, sources, targets };
+
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+ findopts.flags = GIT_DIFF_FIND_RENAMES;
+
+ cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD"));
+ cl_git_pass(git_reference_symbolic_set_target(
+ &selfsimilar, head, "refs/heads/renames_similar_two", NULL, NULL));
+ cl_git_pass(git_checkout_head(g_repo, &opts));
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ cl_git_pass(p_unlink("renames/a.txt"));
+ cl_git_pass(p_unlink("renames/b.txt"));
+
+ cl_git_pass(git_index_remove_bypath(index, "a.txt"));
+ cl_git_pass(git_index_remove_bypath(index, "b.txt"));
+
+ write_similarity_file_two("renames/c.txt", 7);
+ write_similarity_file_two("renames/d.txt", 8);
+
+ cl_git_pass(git_index_add_bypath(index, "c.txt"));
+ cl_git_pass(git_index_add_bypath(index, "d.txt"));
+
+ cl_git_pass(git_index_write(index));
+
+ cl_git_pass(
+ git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
+
+ cl_git_pass(
+ git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
+
+ cl_git_pass(git_diff_find_similar(diff, &findopts));
+
+ cl_git_pass(
+ git_diff_foreach(diff, test_names_expected, NULL, NULL, &expect));
+ cl_assert(expect.idx > 0);
+
+ git_diff_free(diff);
+ git_tree_free(tree);
+ git_index_free(index);
+ git_reference_free(head);
+ git_reference_free(selfsimilar);
+}
+
+void test_diff_rename__rejected_match_can_match_others_three(void)
+{
+ git_reference *head, *selfsimilar;
+ git_index *index;
+ git_tree *tree;
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_diff *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
+
+ /* Both cannot be renames from a.txt */
+ unsigned int status[] = { GIT_DELTA_ADDED, GIT_DELTA_RENAMED };
+ const char *sources[] = { "0001.txt", "a.txt" };
+ const char *targets[] = { "0001.txt", "0002.txt" };
+ struct rename_expected expect = { 2, status, sources, targets };
+
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+ findopts.flags = GIT_DIFF_FIND_RENAMES;
+
+ cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD"));
+ cl_git_pass(git_reference_symbolic_set_target(
+ &selfsimilar, head, "refs/heads/renames_similar_two", NULL, NULL));
+ cl_git_pass(git_checkout_head(g_repo, &opts));
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ cl_git_pass(p_unlink("renames/a.txt"));
+
+ cl_git_pass(git_index_remove_bypath(index, "a.txt"));
+
+ write_similarity_file_two("renames/0001.txt", 7);
+ write_similarity_file_two("renames/0002.txt", 0);
+
+ cl_git_pass(git_index_add_bypath(index, "0001.txt"));
+ cl_git_pass(git_index_add_bypath(index, "0002.txt"));
+
+ cl_git_pass(git_index_write(index));
+
+ cl_git_pass(
+ git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
+
+ cl_git_pass(
+ git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
+
+ cl_git_pass(git_diff_find_similar(diff, &findopts));
+
+ cl_git_pass(
+ git_diff_foreach(diff, test_names_expected, NULL, NULL, &expect));
+
+ cl_assert(expect.idx == expect.len);
+
+ git_diff_free(diff);
+ git_tree_free(tree);
+ git_index_free(index);
+ git_reference_free(head);
+ git_reference_free(selfsimilar);
+}
+
+void test_diff_rename__can_rename_from_rewrite(void)
+{
+ git_index *index;
+ git_tree *tree;
+ git_diff *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
+
+ unsigned int status[] = { GIT_DELTA_RENAMED, GIT_DELTA_RENAMED };
+ const char *sources[] = { "ikeepsix.txt", "songof7cities.txt" };
+ const char *targets[] = { "songof7cities.txt", "this-is-a-rename.txt" };
+ struct rename_expected expect = { 2, status, sources, targets };
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ cl_git_pass(p_rename("renames/songof7cities.txt", "renames/this-is-a-rename.txt"));
+ cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/songof7cities.txt"));
+
+ cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
+
+ cl_git_pass(git_index_add_bypath(index, "songof7cities.txt"));
+ cl_git_pass(git_index_add_bypath(index, "this-is-a-rename.txt"));
+
+ cl_git_pass(git_index_write(index));
+
+ cl_git_pass(
+ git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
+
+ cl_git_pass(
+ git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
+
+ findopts.flags |= GIT_DIFF_FIND_AND_BREAK_REWRITES |
+ GIT_DIFF_FIND_REWRITES |
+ GIT_DIFF_FIND_RENAMES_FROM_REWRITES;
+
+ cl_git_pass(git_diff_find_similar(diff, &findopts));
+
+ cl_git_pass(
+ git_diff_foreach(diff, test_names_expected, NULL, NULL, &expect));
+
+ cl_assert(expect.idx == expect.len);
+
+ git_diff_free(diff);
+ git_tree_free(tree);
+ git_index_free(index);
+}
+
+void test_diff_rename__case_changes_are_split(void)
+{
+ git_index *index;
+ git_tree *tree;
+ git_diff *diff = NULL;
+ diff_expects exp;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ cl_git_pass(
+ git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
+
+ cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/IKEEPSIX.txt"));
+
+ cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
+ cl_git_pass(git_index_add_bypath(index, "IKEEPSIX.txt"));
+ cl_git_pass(git_index_write(index));
+
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, NULL));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(2, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
+
+ opts.flags = GIT_DIFF_FIND_ALL;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(1, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
+
+ git_diff_free(diff);
+ git_index_free(index);
+ git_tree_free(tree);
+}
+
+void test_diff_rename__unmodified_can_be_renamed(void)
+{
+ git_index *index;
+ git_tree *tree;
+ git_diff *diff = NULL;
+ diff_expects exp;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(
+ git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
+
+ cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/ikeepsix2.txt"));
+
+ cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
+ cl_git_pass(git_index_add_bypath(index, "ikeepsix2.txt"));
+ cl_git_pass(git_index_write(index));
+
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(2, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
+
+ opts.flags = GIT_DIFF_FIND_ALL;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(1, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(1, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
+
+ git_diff_free(diff);
+ git_index_free(index);
+ git_tree_free(tree);
+}
+
+void test_diff_rename__rewrite_on_single_file(void)
+{
+ git_index *index;
+ git_diff *diff = NULL;
+ diff_expects exp;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
+
+ diffopts.flags = GIT_DIFF_INCLUDE_UNTRACKED;
+
+ findopts.flags = GIT_DIFF_FIND_FOR_UNTRACKED |
+ GIT_DIFF_FIND_AND_BREAK_REWRITES |
+ GIT_DIFF_FIND_RENAMES_FROM_REWRITES;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ cl_git_rewritefile("renames/ikeepsix.txt",
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n");
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &diffopts));
+ cl_git_pass(git_diff_find_similar(diff, &findopts));
+
+ memset(&exp, 0, sizeof(exp));
+
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(2, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]);
+
+ git_diff_free(diff);
+ git_index_free(index);
+}
+
+void test_diff_rename__can_find_copy_to_split(void)
+{
+ git_buf c1 = GIT_BUF_INIT;
+ git_index *index;
+ git_tree *tree;
+ git_diff *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ diff_expects exp;
+
+ cl_git_pass(git_futils_readbuffer(&c1, "renames/songof7cities.txt"));
+ cl_git_pass(git_futils_writebuffer(&c1, "renames/untimely.txt", 0, 0));
+
+ cl_git_pass(
+ git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_index_read_tree(index, tree));
+ cl_git_pass(git_index_add_bypath(index, "untimely.txt"));
+
+ diffopts.flags = GIT_DIFF_INCLUDE_UNMODIFIED;
+
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(4, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNMODIFIED]);
+
+ opts.flags = GIT_DIFF_FIND_ALL;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(5, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]);
+ cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNMODIFIED]);
+
+ git_diff_free(diff);
+ git_tree_free(tree);
+ git_index_free(index);
+
+ git_buf_free(&c1);
+}
+
+void test_diff_rename__can_delete_unmodified_deltas(void)
+{
+ git_buf c1 = GIT_BUF_INIT;
+ git_index *index;
+ git_tree *tree;
+ git_diff *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ diff_expects exp;
+
+ cl_git_pass(git_futils_readbuffer(&c1, "renames/songof7cities.txt"));
+ cl_git_pass(git_futils_writebuffer(&c1, "renames/untimely.txt", 0, 0));
+
+ cl_git_pass(
+ git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_index_read_tree(index, tree));
+ cl_git_pass(git_index_add_bypath(index, "untimely.txt"));
+
+ diffopts.flags = GIT_DIFF_INCLUDE_UNMODIFIED;
+
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(4, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNMODIFIED]);
+
+ opts.flags = GIT_DIFF_FIND_ALL | GIT_DIFF_FIND_REMOVE_UNMODIFIED;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(2, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]);
+
+ git_diff_free(diff);
+ git_tree_free(tree);
+ git_index_free(index);
+
+ git_buf_free(&c1);
+}
+
+void test_diff_rename__matches_config_behavior(void)
+{
+ const char *sha0 = "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2";
+ const char *sha1 = "2bc7f351d20b53f1c72c16c4b036e491c478c49a";
+ const char *sha2 = "1c068dee5790ef1580cfc4cd670915b48d790084";
+
+ git_tree *tree0, *tree1, *tree2;
+ git_config *cfg;
+ git_diff *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ diff_expects exp;
+
+ opts.flags = GIT_DIFF_FIND_BY_CONFIG;
+ tree0 = resolve_commit_oid_to_tree(g_repo, sha0);
+ tree1 = resolve_commit_oid_to_tree(g_repo, sha1);
+ tree2 = resolve_commit_oid_to_tree(g_repo, sha2);
+
+ diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
+ cl_git_pass(git_repository_config(&cfg, g_repo));
+
+ /* diff.renames = false; no rename detection should happen */
+ cl_git_pass(git_config_set_bool(cfg, "diff.renames", false));
+ cl_git_pass(git_diff_tree_to_tree(
+ &diff, g_repo, tree0, tree1, &diffopts));
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+ cl_git_pass(git_diff_foreach(diff,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ 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_free(diff);
+
+ /* diff.renames = true; should act like -M */
+ cl_git_pass(git_config_set_bool(cfg, "diff.renames", true));
+ cl_git_pass(git_diff_tree_to_tree(
+ &diff, g_repo, tree0, tree1, &diffopts));
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+ cl_git_pass(git_diff_foreach(diff,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ 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_free(diff);
+
+ /* diff.renames = copies; should act like -M -C */
+ cl_git_pass(git_config_set_string(cfg, "diff.renames", "copies"));
+ cl_git_pass(git_diff_tree_to_tree(
+ &diff, g_repo, tree1, tree2, &diffopts));
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+ cl_git_pass(git_diff_foreach(diff,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ 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_MODIFIED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]);
+ git_diff_free(diff);
+
+ /* NULL find options is the same as GIT_DIFF_FIND_BY_CONFIG */
+ cl_git_pass(git_diff_tree_to_tree(
+ &diff, g_repo, tree1, tree2, &diffopts));
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_find_similar(diff, NULL));
+ cl_git_pass(git_diff_foreach(diff,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ 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_MODIFIED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]);
+ git_diff_free(diff);
+
+ /* Cleanup */
+ git_tree_free(tree0);
+ git_tree_free(tree1);
+ git_tree_free(tree2);
+ git_config_free(cfg);
+}
+
+void test_diff_rename__can_override_thresholds_when_obeying_config(void)
+{
+ const char *sha1 = "2bc7f351d20b53f1c72c16c4b036e491c478c49a";
+ const char *sha2 = "1c068dee5790ef1580cfc4cd670915b48d790084";
+
+ git_tree *tree1, *tree2;
+ git_config *cfg;
+ git_diff *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ diff_expects exp;
+
+ tree1 = resolve_commit_oid_to_tree(g_repo, sha1);
+ tree2 = resolve_commit_oid_to_tree(g_repo, sha2);
+
+ diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
+ opts.flags = GIT_DIFF_FIND_BY_CONFIG;
+
+ cl_git_pass(git_repository_config(&cfg, g_repo));
+ cl_git_pass(git_config_set_string(cfg, "diff.renames", "copies"));
+ git_config_free(cfg);
+
+ /* copy threshold = 96%, should see creation of ikeepsix.txt */
+ opts.copy_threshold = 96;
+ cl_git_pass(git_diff_tree_to_tree(
+ &diff, g_repo, tree1, tree2, &diffopts));
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+ cl_git_pass(git_diff_foreach(diff,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ 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_MODIFIED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
+ git_diff_free(diff);
+
+ /* copy threshold = 20%, should see sixserving.txt => ikeepsix.txt */
+ opts.copy_threshold = 20;
+ cl_git_pass(git_diff_tree_to_tree(
+ &diff, g_repo, tree1, tree2, &diffopts));
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+ cl_git_pass(git_diff_foreach(diff,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ 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_MODIFIED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]);
+ git_diff_free(diff);
+
+ /* Cleanup */
+ git_tree_free(tree1);
+ git_tree_free(tree2);
+}
+
+void test_diff_rename__by_config_doesnt_mess_with_whitespace_settings(void)
+{
+ const char *sha1 = "1c068dee5790ef1580cfc4cd670915b48d790084";
+ const char *sha2 = "19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13";
+
+ git_tree *tree1, *tree2;
+ git_config *cfg;
+ git_diff *diff;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ diff_expects exp;
+
+ tree1 = resolve_commit_oid_to_tree(g_repo, sha1);
+ tree2 = resolve_commit_oid_to_tree(g_repo, sha2);
+
+ diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
+ opts.flags = GIT_DIFF_FIND_BY_CONFIG;
+
+ cl_git_pass(git_repository_config(&cfg, g_repo));
+ cl_git_pass(git_config_set_string(cfg, "diff.renames", "copies"));
+ git_config_free(cfg);
+
+ /* Don't ignore whitespace; this should find a change in sixserving.txt */
+ opts.flags |= 0 | GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE;
+ cl_git_pass(git_diff_tree_to_tree(
+ &diff, g_repo, tree1, tree2, &diffopts));
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+ cl_git_pass(git_diff_foreach(diff,
+ diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(5, exp.files);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
+ git_diff_free(diff);
+
+ /* Cleanup */
+ git_tree_free(tree1);
+ git_tree_free(tree2);
+}
diff --git a/tests/diff/stats.c b/tests/diff/stats.c
new file mode 100644
index 000000000..f731997da
--- /dev/null
+++ b/tests/diff/stats.c
@@ -0,0 +1,289 @@
+#include "clar.h"
+#include "clar_libgit2.h"
+
+#include "buffer.h"
+#include "commit.h"
+#include "diff.h"
+
+static git_repository *_repo;
+static git_diff_stats *_stats;
+
+void test_diff_stats__initialize(void)
+{
+ _repo = cl_git_sandbox_init("diff_format_email");
+}
+
+void test_diff_stats__cleanup(void)
+{
+ git_diff_stats_free(_stats); _stats = NULL;
+ cl_git_sandbox_cleanup();
+}
+
+static void diff_stats_from_commit_oid(
+ git_diff_stats **stats, const char *oidstr, bool rename)
+{
+ git_oid oid;
+ git_commit *commit;
+ git_diff *diff;
+
+ git_oid_fromstr(&oid, oidstr);
+ cl_git_pass(git_commit_lookup(&commit, _repo, &oid));
+ cl_git_pass(git_diff__commit(&diff, _repo, commit, NULL));
+ if (rename)
+ cl_git_pass(git_diff_find_similar(diff, NULL));
+ cl_git_pass(git_diff_get_stats(stats, diff));
+
+ git_diff_free(diff);
+ git_commit_free(commit);
+}
+
+void test_diff_stats__stat(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+ const char *stat =
+ " file1.txt | 8 +++++---\n" \
+ " 1 file changed, 5 insertions(+), 3 deletions(-)\n";
+
+ diff_stats_from_commit_oid(
+ &_stats, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", false);
+
+ cl_assert_equal_sz(1, git_diff_stats_files_changed(_stats));
+ cl_assert_equal_sz(5, git_diff_stats_insertions(_stats));
+ cl_assert_equal_sz(3, git_diff_stats_deletions(_stats));
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0));
+ cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
+ git_buf_free(&buf);
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 80));
+ cl_assert(strcmp(git_buf_cstr(&buf), stat) == 0);
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__multiple_hunks(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+ const char *stat =
+ " file2.txt | 5 +++--\n" \
+ " file3.txt | 6 ++++--\n" \
+ " 2 files changed, 7 insertions(+), 4 deletions(-)\n";
+
+ diff_stats_from_commit_oid(
+ &_stats, "cd471f0d8770371e1bc78bcbb38db4c7e4106bd2", false);
+
+ cl_assert_equal_sz(2, git_diff_stats_files_changed(_stats));
+ cl_assert_equal_sz(7, git_diff_stats_insertions(_stats));
+ cl_assert_equal_sz(4, git_diff_stats_deletions(_stats));
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__numstat(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+ const char *stat =
+ "3 2 file2.txt\n"
+ "4 2 file3.txt\n";
+
+ diff_stats_from_commit_oid(
+ &_stats, "cd471f0d8770371e1bc78bcbb38db4c7e4106bd2", false);
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_NUMBER, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__shortstat(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+ const char *stat =
+ " 1 file changed, 5 insertions(+), 3 deletions(-)\n";
+
+ diff_stats_from_commit_oid(
+ &_stats, "9264b96c6d104d0e07ae33d3007b6a48246c6f92", false);
+
+ cl_assert_equal_sz(1, git_diff_stats_files_changed(_stats));
+ cl_assert_equal_sz(5, git_diff_stats_insertions(_stats));
+ cl_assert_equal_sz(3, git_diff_stats_deletions(_stats));
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_SHORT, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__rename(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+ const char *stat =
+ " file2.txt => file2.txt.renamed | 1 +\n"
+ " file3.txt => file3.txt.renamed | 4 +++-\n"
+ " 2 files changed, 4 insertions(+), 1 deletion(-)\n";
+
+ diff_stats_from_commit_oid(
+ &_stats, "8947a46e2097638ca6040ad4877246f4186ec3bd", true);
+
+ cl_assert_equal_sz(2, git_diff_stats_files_changed(_stats));
+ cl_assert_equal_sz(4, git_diff_stats_insertions(_stats));
+ cl_assert_equal_sz(1, git_diff_stats_deletions(_stats));
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__rename_nochanges(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+ const char *stat =
+ " file2.txt.renamed => file2.txt.renamed2 | 0\n"
+ " file3.txt.renamed => file3.txt.renamed2 | 0\n"
+ " 2 files changed, 0 insertions(+), 0 deletions(-)\n";
+
+ diff_stats_from_commit_oid(
+ &_stats, "3991dce9e71a0641ca49a6a4eea6c9e7ff402ed4", true);
+
+ cl_assert_equal_sz(2, git_diff_stats_files_changed(_stats));
+ cl_assert_equal_sz(0, git_diff_stats_insertions(_stats));
+ cl_assert_equal_sz(0, git_diff_stats_deletions(_stats));
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__rename_and_modifiy(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+ const char *stat =
+ " file2.txt.renamed2 | 2 +-\n"
+ " file3.txt.renamed2 => file3.txt.renamed | 0\n"
+ " 2 files changed, 1 insertion(+), 1 deletion(-)\n";
+
+ diff_stats_from_commit_oid(
+ &_stats, "4ca10087e696d2ba78d07b146a118e9a7096ed4f", true);
+
+ cl_assert_equal_sz(2, git_diff_stats_files_changed(_stats));
+ cl_assert_equal_sz(1, git_diff_stats_insertions(_stats));
+ cl_assert_equal_sz(1, git_diff_stats_deletions(_stats));
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__rename_no_find(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+ const char *stat =
+ " file2.txt | 5 -----\n"
+ " file2.txt.renamed | 6 ++++++\n"
+ " file3.txt | 5 -----\n"
+ " file3.txt.renamed | 7 +++++++\n"
+ " 4 files changed, 13 insertions(+), 10 deletions(-)\n";
+
+ diff_stats_from_commit_oid(
+ &_stats, "8947a46e2097638ca6040ad4877246f4186ec3bd", false);
+
+ cl_assert_equal_sz(4, git_diff_stats_files_changed(_stats));
+ cl_assert_equal_sz(13, git_diff_stats_insertions(_stats));
+ cl_assert_equal_sz(10, git_diff_stats_deletions(_stats));
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__rename_nochanges_no_find(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+ const char *stat =
+ " file2.txt.renamed | 6 ------\n"
+ " file2.txt.renamed2 | 6 ++++++\n"
+ " file3.txt.renamed | 7 -------\n"
+ " file3.txt.renamed2 | 7 +++++++\n"
+ " 4 files changed, 13 insertions(+), 13 deletions(-)\n";
+
+ diff_stats_from_commit_oid(
+ &_stats, "3991dce9e71a0641ca49a6a4eea6c9e7ff402ed4", false);
+
+ cl_assert_equal_sz(4, git_diff_stats_files_changed(_stats));
+ cl_assert_equal_sz(13, git_diff_stats_insertions(_stats));
+ cl_assert_equal_sz(13, git_diff_stats_deletions(_stats));
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__rename_and_modifiy_no_find(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+ const char *stat =
+ " file2.txt.renamed2 | 2 +-\n"
+ " file3.txt.renamed | 7 +++++++\n"
+ " file3.txt.renamed2 | 7 -------\n"
+ " 3 files changed, 8 insertions(+), 8 deletions(-)\n";
+
+ diff_stats_from_commit_oid(
+ &_stats, "4ca10087e696d2ba78d07b146a118e9a7096ed4f", false);
+
+ cl_assert_equal_sz(3, git_diff_stats_files_changed(_stats));
+ cl_assert_equal_sz(8, git_diff_stats_insertions(_stats));
+ cl_assert_equal_sz(8, git_diff_stats_deletions(_stats));
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__binary(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+ const char *stat =
+ " binary.bin | Bin 3 -> 0 bytes\n"
+ " 1 file changed, 0 insertions(+), 0 deletions(-)\n";
+ /* TODO: Actually 0 bytes here should be 5!. Seems like we don't load the new content for binary files? */
+
+ diff_stats_from_commit_oid(
+ &_stats, "8d7523f6fcb2404257889abe0d96f093d9f524f9", false);
+
+ cl_assert_equal_sz(1, git_diff_stats_files_changed(_stats));
+ cl_assert_equal_sz(0, git_diff_stats_insertions(_stats));
+ cl_assert_equal_sz(0, git_diff_stats_deletions(_stats));
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__binary_numstat(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+ const char *stat =
+ "- - binary.bin\n";
+
+ diff_stats_from_commit_oid(
+ &_stats, "8d7523f6fcb2404257889abe0d96f093d9f524f9", false);
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_NUMBER, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+}
+
+void test_diff_stats__mode_change(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+ const char *stat =
+ " file1.txt.renamed | 0\n" \
+ " 1 file changed, 0 insertions(+), 0 deletions(-)\n" \
+ " mode change 100644 => 100755 file1.txt.renamed\n";
+
+ diff_stats_from_commit_oid(
+ &_stats, "7ade76dd34bba4733cf9878079f9fd4a456a9189", false);
+
+ cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_INCLUDE_SUMMARY, 0));
+ cl_assert_equal_s(stat, git_buf_cstr(&buf));
+ git_buf_free(&buf);
+}
diff --git a/tests/diff/submodules.c b/tests/diff/submodules.c
new file mode 100644
index 000000000..02870ac86
--- /dev/null
+++ b/tests/diff/submodules.c
@@ -0,0 +1,495 @@
+#include "clar_libgit2.h"
+#include "repository.h"
+#include "posix.h"
+#include "diff_helpers.h"
+#include "../submodule/submodule_helpers.h"
+
+static git_repository *g_repo = NULL;
+
+void test_diff_submodules__initialize(void)
+{
+}
+
+void test_diff_submodules__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+#define get_buf_ptr(buf) ((buf)->asize ? (buf)->ptr : NULL)
+
+static void check_diff_patches_at_line(
+ git_diff *diff, const char **expected, const char *file, int line)
+{
+ const git_diff_delta *delta;
+ git_patch *patch = NULL;
+ size_t d, num_d = git_diff_num_deltas(diff);
+ git_buf buf = GIT_BUF_INIT;
+
+ for (d = 0; d < num_d; ++d, git_patch_free(patch)) {
+ cl_git_pass(git_patch_from_diff(&patch, diff, d));
+ cl_assert((delta = git_patch_get_delta(patch)) != NULL);
+
+ if (delta->status == GIT_DELTA_UNMODIFIED) {
+ cl_assert_at_line(expected[d] == NULL, file, line);
+ continue;
+ }
+
+ if (expected[d] && !strcmp(expected[d], "<SKIP>"))
+ continue;
+ if (expected[d] && !strcmp(expected[d], "<UNTRACKED>")) {
+ cl_assert_at_line(delta->status == GIT_DELTA_UNTRACKED, file, line);
+ continue;
+ }
+ if (expected[d] && !strcmp(expected[d], "<END>")) {
+ cl_git_pass(git_patch_to_buf(&buf, patch));
+ cl_assert_at_line(!strcmp(expected[d], "<END>"), file, line);
+ }
+
+ cl_git_pass(git_patch_to_buf(&buf, patch));
+
+ clar__assert_equal(
+ file, line, "expected diff did not match actual diff", 1,
+ "%s", expected[d], get_buf_ptr(&buf));
+ git_buf_free(&buf);
+ }
+
+ cl_assert_at_line(expected[d] && !strcmp(expected[d], "<END>"), file, line);
+}
+
+#define check_diff_patches(diff, exp) \
+ check_diff_patches_at_line(diff, exp, __FILE__, __LINE__)
+
+void test_diff_submodules__unmodified_submodule(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ static const char *expected[] = {
+ "<SKIP>", /* .gitmodules */
+ NULL, /* added */
+ NULL, /* ignored */
+ "diff --git a/modified b/modified\nindex 092bfb9..452216e 100644\n--- a/modified\n+++ b/modified\n@@ -1 +1,2 @@\n-yo\n+changed\n+\n", /* modified */
+ NULL, /* testrepo.git */
+ NULL, /* unmodified */
+ NULL, /* untracked */
+ "<END>"
+ };
+
+ g_repo = setup_fixture_submodules();
+
+ opts.flags = GIT_DIFF_INCLUDE_IGNORED |
+ GIT_DIFF_INCLUDE_UNTRACKED |
+ GIT_DIFF_INCLUDE_UNMODIFIED;
+ opts.old_prefix = "a"; opts.new_prefix = "b";
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected);
+ git_diff_free(diff);
+}
+
+void test_diff_submodules__dirty_submodule(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ static const char *expected[] = {
+ "<SKIP>", /* .gitmodules */
+ NULL, /* added */
+ NULL, /* ignored */
+ "diff --git a/modified b/modified\nindex 092bfb9..452216e 100644\n--- a/modified\n+++ b/modified\n@@ -1 +1,2 @@\n-yo\n+changed\n+\n", /* modified */
+ "diff --git a/testrepo b/testrepo\nindex a65fedf..a65fedf 160000\n--- a/testrepo\n+++ b/testrepo\n@@ -1 +1 @@\n-Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750\n+Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750-dirty\n", /* testrepo.git */
+ NULL, /* unmodified */
+ NULL, /* untracked */
+ "<END>"
+ };
+
+ g_repo = setup_fixture_submodules();
+
+ cl_git_rewritefile("submodules/testrepo/README", "heyheyhey");
+ cl_git_mkfile("submodules/testrepo/all_new.txt", "never seen before");
+
+ opts.flags = GIT_DIFF_INCLUDE_IGNORED |
+ GIT_DIFF_INCLUDE_UNTRACKED |
+ GIT_DIFF_INCLUDE_UNMODIFIED;
+ opts.old_prefix = "a"; opts.new_prefix = "b";
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected);
+ git_diff_free(diff);
+}
+
+void test_diff_submodules__dirty_submodule_2(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL, *diff2 = NULL;
+ char *smpath = "testrepo";
+ static const char *expected_none[] = {
+ "<END>"
+ };
+ static const char *expected_dirty[] = {
+ "diff --git a/testrepo b/testrepo\nindex a65fedf..a65fedf 160000\n--- a/testrepo\n+++ b/testrepo\n@@ -1 +1 @@\n-Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750\n+Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750-dirty\n", /* testrepo.git */
+ "<END>"
+ };
+
+ g_repo = setup_fixture_submodules();
+
+ opts.flags = GIT_DIFF_INCLUDE_UNTRACKED |
+ GIT_DIFF_SHOW_UNTRACKED_CONTENT |
+ GIT_DIFF_RECURSE_UNTRACKED_DIRS |
+ GIT_DIFF_DISABLE_PATHSPEC_MATCH;
+ opts.old_prefix = "a"; opts.new_prefix = "b";
+ opts.pathspec.count = 1;
+ opts.pathspec.strings = &smpath;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_none);
+ git_diff_free(diff);
+
+ cl_git_rewritefile("submodules/testrepo/README", "heyheyhey");
+ cl_git_mkfile("submodules/testrepo/all_new.txt", "never seen before");
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_dirty);
+
+ {
+ git_tree *head;
+
+ cl_git_pass(git_repository_head_tree(&head, g_repo));
+ cl_git_pass(git_diff_tree_to_index(&diff2, g_repo, head, NULL, &opts));
+ cl_git_pass(git_diff_merge(diff, diff2));
+ git_diff_free(diff2);
+ git_tree_free(head);
+
+ check_diff_patches(diff, expected_dirty);
+ }
+
+ git_diff_free(diff);
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_dirty);
+ git_diff_free(diff);
+}
+
+void test_diff_submodules__submod2_index_to_wd(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ static const char *expected[] = {
+ "<SKIP>", /* .gitmodules */
+ "<UNTRACKED>", /* not-submodule */
+ "<UNTRACKED>", /* not */
+ "diff --git a/sm_changed_file b/sm_changed_file\nindex 4800958..4800958 160000\n--- a/sm_changed_file\n+++ b/sm_changed_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_file */
+ "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */
+ "<UNTRACKED>", /* sm_changed_head- */
+ "<UNTRACKED>", /* sm_changed_head_ */
+ "diff --git a/sm_changed_index b/sm_changed_index\nindex 4800958..4800958 160000\n--- a/sm_changed_index\n+++ b/sm_changed_index\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_index */
+ "diff --git a/sm_changed_untracked_file b/sm_changed_untracked_file\nindex 4800958..4800958 160000\n--- a/sm_changed_untracked_file\n+++ b/sm_changed_untracked_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_untracked_file */
+ "diff --git a/sm_missing_commits b/sm_missing_commits\nindex 4800958..5e49635 160000\n--- a/sm_missing_commits\n+++ b/sm_missing_commits\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 5e4963595a9774b90524d35a807169049de8ccad\n", /* sm_missing_commits */
+ "<END>"
+ };
+
+ g_repo = setup_fixture_submod2();
+
+ /* bracket existing submodule with similarly named items */
+ cl_git_mkfile("submod2/sm_changed_head-", "hello");
+ cl_git_mkfile("submod2/sm_changed_head_", "hello");
+
+ opts.flags = GIT_DIFF_INCLUDE_UNTRACKED;
+ opts.old_prefix = "a"; opts.new_prefix = "b";
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected);
+ git_diff_free(diff);
+}
+
+void test_diff_submodules__submod2_head_to_index(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_tree *head;
+ git_diff *diff = NULL;
+ static const char *expected[] = {
+ "<SKIP>", /* .gitmodules */
+ "diff --git a/sm_added_and_uncommited b/sm_added_and_uncommited\nnew file mode 160000\nindex 0000000..4800958\n--- /dev/null\n+++ b/sm_added_and_uncommited\n@@ -0,0 +1 @@\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n", /* sm_added_and_uncommited */
+ "<END>"
+ };
+
+ g_repo = setup_fixture_submod2();
+
+ cl_git_pass(git_repository_head_tree(&head, g_repo));
+
+ opts.flags = GIT_DIFF_INCLUDE_UNTRACKED;
+ opts.old_prefix = "a"; opts.new_prefix = "b";
+
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, head, NULL, &opts));
+ check_diff_patches(diff, expected);
+ git_diff_free(diff);
+
+ git_tree_free(head);
+}
+
+void test_diff_submodules__invalid_cache(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ git_submodule *sm;
+ char *smpath = "sm_changed_head";
+ git_repository *smrepo;
+ git_index *smindex;
+ static const char *expected_baseline[] = {
+ "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */
+ "<END>"
+ };
+ static const char *expected_unchanged[] = { "<END>" };
+ static const char *expected_dirty[] = {
+ "diff --git a/sm_changed_head b/sm_changed_head\nindex 3d9386c..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247-dirty\n",
+ "<END>"
+ };
+ static const char *expected_moved[] = {
+ "diff --git a/sm_changed_head b/sm_changed_head\nindex 3d9386c..7002348 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n+Subproject commit 700234833f6ccc20d744b238612646be071acaae\n",
+ "<END>"
+ };
+ static const char *expected_moved_dirty[] = {
+ "diff --git a/sm_changed_head b/sm_changed_head\nindex 3d9386c..7002348 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n+Subproject commit 700234833f6ccc20d744b238612646be071acaae-dirty\n",
+ "<END>"
+ };
+
+ g_repo = setup_fixture_submod2();
+
+ opts.flags = GIT_DIFF_INCLUDE_UNTRACKED;
+ opts.old_prefix = "a"; opts.new_prefix = "b";
+ opts.pathspec.count = 1;
+ opts.pathspec.strings = &smpath;
+
+ /* baseline */
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_baseline);
+ git_diff_free(diff);
+
+ /* update index with new HEAD */
+ cl_git_pass(git_submodule_lookup(&sm, g_repo, smpath));
+ cl_git_pass(git_submodule_add_to_index(sm, 1));
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_unchanged);
+ git_diff_free(diff);
+
+ /* create untracked file in submodule working directory */
+ cl_git_mkfile("submod2/sm_changed_head/new_around_here", "hello");
+ git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_NONE);
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_dirty);
+ git_diff_free(diff);
+
+ git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_UNTRACKED);
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_unchanged);
+ git_diff_free(diff);
+
+ /* modify tracked file in submodule working directory */
+ cl_git_append2file(
+ "submod2/sm_changed_head/file_to_modify", "\nmore stuff\n");
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_dirty);
+ git_diff_free(diff);
+
+ git_submodule_free(sm);
+
+ cl_git_pass(git_submodule_lookup(&sm, g_repo, smpath));
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_dirty);
+ git_diff_free(diff);
+
+ git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_DIRTY);
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_unchanged);
+ git_diff_free(diff);
+
+ /* add file to index in submodule */
+ cl_git_pass(git_submodule_open(&smrepo, sm));
+ cl_git_pass(git_repository_index(&smindex, smrepo));
+ cl_git_pass(git_index_add_bypath(smindex, "file_to_modify"));
+
+ git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_UNTRACKED);
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_dirty);
+ git_diff_free(diff);
+
+ git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_DIRTY);
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_unchanged);
+ git_diff_free(diff);
+
+ /* commit changed index of submodule */
+ cl_repo_commit_from_index(NULL, smrepo, NULL, 1372350000, "Move it");
+
+ git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_DIRTY);
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_moved);
+ git_diff_free(diff);
+
+ git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_ALL);
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_unchanged);
+ git_diff_free(diff);
+
+ git_submodule_set_ignore(sm, GIT_SUBMODULE_IGNORE_NONE);
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_moved_dirty);
+ git_diff_free(diff);
+
+ p_unlink("submod2/sm_changed_head/new_around_here");
+
+ git_submodule_free(sm);
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_moved);
+ git_diff_free(diff);
+
+ git_index_free(smindex);
+ git_repository_free(smrepo);
+}
+
+void test_diff_submodules__diff_ignore_options(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ git_config *cfg;
+ static const char *expected_normal[] = {
+ "<SKIP>", /* .gitmodules */
+ "<UNTRACKED>", /* not-submodule */
+ "<UNTRACKED>", /* not */
+ "diff --git a/sm_changed_file b/sm_changed_file\nindex 4800958..4800958 160000\n--- a/sm_changed_file\n+++ b/sm_changed_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_file */
+ "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */
+ "diff --git a/sm_changed_index b/sm_changed_index\nindex 4800958..4800958 160000\n--- a/sm_changed_index\n+++ b/sm_changed_index\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_index */
+ "diff --git a/sm_changed_untracked_file b/sm_changed_untracked_file\nindex 4800958..4800958 160000\n--- a/sm_changed_untracked_file\n+++ b/sm_changed_untracked_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_untracked_file */
+ "diff --git a/sm_missing_commits b/sm_missing_commits\nindex 4800958..5e49635 160000\n--- a/sm_missing_commits\n+++ b/sm_missing_commits\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 5e4963595a9774b90524d35a807169049de8ccad\n", /* sm_missing_commits */
+ "<END>"
+ };
+ static const char *expected_ignore_all[] = {
+ "<SKIP>", /* .gitmodules */
+ "<UNTRACKED>", /* not-submodule */
+ "<UNTRACKED>", /* not */
+ "<END>"
+ };
+ static const char *expected_ignore_dirty[] = {
+ "<SKIP>", /* .gitmodules */
+ "<UNTRACKED>", /* not-submodule */
+ "<UNTRACKED>", /* not */
+ "diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */
+ "diff --git a/sm_missing_commits b/sm_missing_commits\nindex 4800958..5e49635 160000\n--- a/sm_missing_commits\n+++ b/sm_missing_commits\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 5e4963595a9774b90524d35a807169049de8ccad\n", /* sm_missing_commits */
+ "<END>"
+ };
+
+ g_repo = setup_fixture_submod2();
+
+ opts.flags = GIT_DIFF_INCLUDE_UNTRACKED;
+ opts.old_prefix = "a"; opts.new_prefix = "b";
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_normal);
+ git_diff_free(diff);
+
+ opts.flags |= GIT_DIFF_IGNORE_SUBMODULES;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_ignore_all);
+ git_diff_free(diff);
+
+ opts.flags &= ~GIT_DIFF_IGNORE_SUBMODULES;
+ opts.ignore_submodules = GIT_SUBMODULE_IGNORE_ALL;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_ignore_all);
+ git_diff_free(diff);
+
+ opts.ignore_submodules = GIT_SUBMODULE_IGNORE_DIRTY;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_ignore_dirty);
+ git_diff_free(diff);
+
+ opts.ignore_submodules = 0;
+ cl_git_pass(git_repository_config(&cfg, g_repo));
+ cl_git_pass(git_config_set_bool(cfg, "diff.ignoreSubmodules", false));
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_normal);
+ git_diff_free(diff);
+
+ cl_git_pass(git_config_set_bool(cfg, "diff.ignoreSubmodules", true));
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_ignore_all);
+ git_diff_free(diff);
+
+ cl_git_pass(git_config_set_string(cfg, "diff.ignoreSubmodules", "none"));
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_normal);
+ git_diff_free(diff);
+
+ cl_git_pass(git_config_set_string(cfg, "diff.ignoreSubmodules", "dirty"));
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ check_diff_patches(diff, expected_ignore_dirty);
+ git_diff_free(diff);
+
+ git_config_free(cfg);
+}
+
+void test_diff_submodules__skips_empty_includes_used(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ diff_expects exp;
+
+ /* A side effect of of Git's handling of untracked directories and
+ * auto-ignoring of ".git" entries is that a newly initialized Git
+ * repo inside another repo will be skipped by diff, but one that
+ * actually has a commit it in will show as an untracked directory.
+ * Let's make sure that works.
+ */
+
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(0, exp.files);
+ git_diff_free(diff);
+
+ {
+ git_repository *r2;
+ cl_git_pass(git_repository_init(&r2, "empty_standard_repo/subrepo", 0));
+ git_repository_free(r2);
+ }
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(1, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
+ git_diff_free(diff);
+
+ cl_git_mkfile("empty_standard_repo/subrepo/README.txt", "hello\n");
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(1, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]);
+ git_diff_free(diff);
+}
diff --git a/tests/diff/tree.c b/tests/diff/tree.c
new file mode 100644
index 000000000..6ab49fdb0
--- /dev/null
+++ b/tests/diff/tree.c
@@ -0,0 +1,526 @@
+#include "clar_libgit2.h"
+#include "diff_helpers.h"
+
+static git_repository *g_repo = NULL;
+static git_diff_options opts;
+static git_diff *diff;
+static git_tree *a, *b;
+static diff_expects expect;
+
+void test_diff_tree__initialize(void)
+{
+ cl_git_pass(git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION));
+
+ memset(&expect, 0, sizeof(expect));
+
+ diff = NULL;
+ a = NULL;
+ b = NULL;
+}
+
+void test_diff_tree__cleanup(void)
+{
+ git_diff_free(diff);
+ git_tree_free(a);
+ git_tree_free(b);
+
+ cl_git_sandbox_cleanup();
+
+}
+
+void test_diff_tree__0(void)
+{
+ /* grabbed a couple of commit oids from the history of the attr repo */
+ const char *a_commit = "605812a";
+ const char *b_commit = "370fe9ec22";
+ const char *c_commit = "f5b0af1fb4f5c";
+ git_tree *c;
+
+ g_repo = cl_git_sandbox_init("attr");
+
+ cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL);
+ 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);
+
+ opts.context_lines = 1;
+ opts.interhunk_lines = 1;
+
+
+ cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts));
+
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &expect));
+
+ cl_assert_equal_i(5, expect.files);
+ cl_assert_equal_i(2, expect.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(1, expect.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(2, expect.file_status[GIT_DELTA_MODIFIED]);
+
+ cl_assert_equal_i(5, expect.hunks);
+
+ cl_assert_equal_i(7 + 24 + 1 + 6 + 6, expect.lines);
+ cl_assert_equal_i(1, expect.line_ctxt);
+ cl_assert_equal_i(24 + 1 + 5 + 5, expect.line_adds);
+ cl_assert_equal_i(7 + 1, expect.line_dels);
+
+ git_diff_free(diff);
+ diff = NULL;
+
+ memset(&expect, 0, sizeof(expect));
+
+ cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, c, b, &opts));
+
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &expect));
+
+ cl_assert_equal_i(2, expect.files);
+ cl_assert_equal_i(0, expect.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(0, expect.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(2, expect.file_status[GIT_DELTA_MODIFIED]);
+
+ cl_assert_equal_i(2, expect.hunks);
+
+ cl_assert_equal_i(8 + 15, expect.lines);
+ cl_assert_equal_i(1, expect.line_ctxt);
+ cl_assert_equal_i(1, expect.line_adds);
+ cl_assert_equal_i(7 + 14, expect.line_dels);
+
+ git_tree_free(c);
+}
+
+#define DIFF_OPTS(FLAGS, CTXT) \
+ {GIT_DIFF_OPTIONS_VERSION, (FLAGS), GIT_SUBMODULE_IGNORE_DEFAULT, \
+ {NULL,0}, NULL, NULL, (CTXT), 1}
+
+void test_diff_tree__options(void)
+{
+ /* grabbed a couple of commit oids from the history of the attr repo */
+ const char *a_commit = "6bab5c79cd5140d0";
+ const char *b_commit = "605812ab7fe421fdd";
+ const char *c_commit = "f5b0af1fb4f5";
+ const char *d_commit = "a97cc019851";
+ git_tree *c, *d;
+ diff_expects actual;
+ int test_ab_or_cd[] = { 0, 0, 0, 0, 1, 1, 1, 1, 1 };
+ git_diff_options test_options[] = {
+ /* a vs b tests */
+ DIFF_OPTS(GIT_DIFF_NORMAL, 1),
+ DIFF_OPTS(GIT_DIFF_NORMAL, 3),
+ DIFF_OPTS(GIT_DIFF_REVERSE, 2),
+ DIFF_OPTS(GIT_DIFF_FORCE_TEXT, 2),
+ /* c vs d tests */
+ DIFF_OPTS(GIT_DIFF_NORMAL, 3),
+ DIFF_OPTS(GIT_DIFF_IGNORE_WHITESPACE, 3),
+ DIFF_OPTS(GIT_DIFF_IGNORE_WHITESPACE_CHANGE, 3),
+ DIFF_OPTS(GIT_DIFF_IGNORE_WHITESPACE_EOL, 3),
+ DIFF_OPTS(GIT_DIFF_IGNORE_WHITESPACE | GIT_DIFF_REVERSE, 1),
+ };
+
+ /* to generate these values:
+ * - cd to tests/resources/attr,
+ * - mv .gitted .git
+ * - 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, 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, 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, j;
+
+ g_repo = cl_git_sandbox_init("attr");
+
+ cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL);
+ 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_assert((d = resolve_commit_oid_to_tree(g_repo, d_commit)) != NULL);
+
+ for (i = 0; test_expects[i].files > 0; i++) {
+ memset(&actual, 0, sizeof(actual)); /* clear accumulator */
+ opts = test_options[i];
+
+ if (test_ab_or_cd[i] == 0)
+ cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts));
+ else
+ cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, c, d, &opts));
+
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &actual));
+
+ expected = &test_expects[i];
+ cl_assert_equal_i(actual.files, expected->files);
+ 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);
+ cl_assert_equal_i(actual.line_adds, expected->line_adds);
+ cl_assert_equal_i(actual.line_dels, expected->line_dels);
+
+ git_diff_free(diff);
+ diff = NULL;
+ }
+
+ git_tree_free(c);
+ git_tree_free(d);
+}
+
+void test_diff_tree__bare(void)
+{
+ const char *a_commit = "8496071c1b46c85";
+ const char *b_commit = "be3563ae3f79";
+
+ g_repo = cl_git_sandbox_init("testrepo.git");
+
+ cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL);
+ cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL);
+
+ opts.context_lines = 1;
+ opts.interhunk_lines = 1;
+
+ cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts));
+
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &expect));
+
+ cl_assert_equal_i(3, expect.files);
+ cl_assert_equal_i(2, expect.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(0, expect.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, expect.file_status[GIT_DELTA_MODIFIED]);
+
+ cl_assert_equal_i(3, expect.hunks);
+
+ cl_assert_equal_i(4, expect.lines);
+ cl_assert_equal_i(0, expect.line_ctxt);
+ cl_assert_equal_i(3, expect.line_adds);
+ cl_assert_equal_i(1, expect.line_dels);
+}
+
+void test_diff_tree__merge(void)
+{
+ /* grabbed a couple of commit oids from the history of the attr repo */
+ const char *a_commit = "605812a";
+ const char *b_commit = "370fe9ec22";
+ const char *c_commit = "f5b0af1fb4f5c";
+ git_tree *c;
+ git_diff *diff1 = NULL, *diff2 = NULL;
+
+ g_repo = cl_git_sandbox_init("attr");
+
+ cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL);
+ 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(&diff1, g_repo, a, b, NULL));
+
+ cl_git_pass(git_diff_tree_to_tree(&diff2, g_repo, c, b, NULL));
+
+ git_tree_free(c);
+
+ cl_git_pass(git_diff_merge(diff1, diff2));
+
+ git_diff_free(diff2);
+
+ cl_git_pass(git_diff_foreach(
+ diff1, diff_file_cb, diff_hunk_cb, diff_line_cb, &expect));
+
+ cl_assert_equal_i(6, expect.files);
+ cl_assert_equal_i(2, expect.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(1, expect.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(3, expect.file_status[GIT_DELTA_MODIFIED]);
+
+ cl_assert_equal_i(6, expect.hunks);
+
+ cl_assert_equal_i(59, expect.lines);
+ cl_assert_equal_i(1, expect.line_ctxt);
+ cl_assert_equal_i(36, expect.line_adds);
+ cl_assert_equal_i(22, expect.line_dels);
+
+ git_diff_free(diff1);
+}
+
+void test_diff_tree__larger_hunks(void)
+{
+ const char *a_commit = "d70d245ed97ed2aa596dd1af6536e4bfdb047b69";
+ const char *b_commit = "7a9e0b02e63179929fed24f0a3e0f19168114d10";
+ size_t d, num_d, h, num_h, l, num_l;
+ git_patch *patch;
+ const git_diff_hunk *hunk;
+ const git_diff_line *line;
+
+ g_repo = cl_git_sandbox_init("diff");
+
+ cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL);
+ cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL);
+
+ opts.context_lines = 1;
+ opts.interhunk_lines = 0;
+
+ 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) {
+ cl_git_pass(git_patch_from_diff(&patch, diff, d));
+ cl_assert(patch);
+
+ num_h = git_patch_num_hunks(patch);
+ for (h = 0; h < num_h; h++) {
+ cl_git_pass(git_patch_get_hunk(&hunk, &num_l, patch, h));
+
+ for (l = 0; l < num_l; ++l) {
+ cl_git_pass(git_patch_get_line_in_hunk(&line, patch, h, l));
+ cl_assert(line);
+ }
+
+ cl_git_fail(git_patch_get_line_in_hunk(&line, patch, h, num_l));
+ }
+
+ cl_git_fail(git_patch_get_hunk(&hunk, &num_l, patch, num_h));
+
+ git_patch_free(patch);
+ }
+
+ cl_git_fail(git_patch_from_diff(&patch, diff, num_d));
+
+ cl_assert_equal_i(2, (int)num_d);
+}
+
+void test_diff_tree__checks_options_version(void)
+{
+ const char *a_commit = "8496071c1b46c85";
+ const char *b_commit = "be3563ae3f79";
+ const git_error *err;
+
+ g_repo = cl_git_sandbox_init("testrepo.git");
+
+ cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL);
+ cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL);
+
+ opts.version = 0;
+ cl_git_fail(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts));
+ err = giterr_last();
+ cl_assert_equal_i(GITERR_INVALID, err->klass);
+
+ giterr_clear();
+ opts.version = 1024;
+ cl_git_fail(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts));
+ err = giterr_last();
+}
+
+void process_tree_to_tree_diffing(
+ const char *old_commit,
+ const char *new_commit)
+{
+ g_repo = cl_git_sandbox_init("unsymlinked.git");
+
+ cl_assert((a = resolve_commit_oid_to_tree(g_repo, old_commit)) != NULL);
+ cl_assert((b = resolve_commit_oid_to_tree(g_repo, new_commit)) != NULL);
+
+ cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts));
+
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, NULL, NULL, &expect));
+}
+
+void test_diff_tree__symlink_blob_mode_changed_to_regular_file(void)
+{
+ /*
+ * $ git diff 7fccd7..806999
+ * diff --git a/include/Nu/Nu.h b/include/Nu/Nu.h
+ * deleted file mode 120000
+ * index 19bf568..0000000
+ * --- a/include/Nu/Nu.h
+ * +++ /dev/null
+ * @@ -1 +0,0 @@
+ * -../../objc/Nu.h
+ * \ No newline at end of file
+ * diff --git a/include/Nu/Nu.h b/include/Nu/Nu.h
+ * new file mode 100644
+ * index 0000000..f9e6561
+ * --- /dev/null
+ * +++ b/include/Nu/Nu.h
+ * @@ -0,0 +1 @@
+ * +awesome content
+ * diff --git a/objc/Nu.h b/objc/Nu.h
+ * deleted file mode 100644
+ * index f9e6561..0000000
+ * --- a/objc/Nu.h
+ * +++ /dev/null
+ * @@ -1 +0,0 @@
+ * -awesome content
+ */
+
+ process_tree_to_tree_diffing("7fccd7", "806999");
+
+ cl_assert_equal_i(3, expect.files);
+ cl_assert_equal_i(2, expect.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(0, expect.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(1, expect.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(0, expect.file_status[GIT_DELTA_TYPECHANGE]);
+}
+
+void test_diff_tree__symlink_blob_mode_changed_to_regular_file_as_typechange(void)
+{
+ /*
+ * $ git diff 7fccd7..a8595c
+ * diff --git a/include/Nu/Nu.h b/include/Nu/Nu.h
+ * deleted file mode 120000
+ * index 19bf568..0000000
+ * --- a/include/Nu/Nu.h
+ * +++ /dev/null
+ * @@ -1 +0,0 @@
+ * -../../objc/Nu.h
+ * \ No newline at end of file
+ * diff --git a/include/Nu/Nu.h b/include/Nu/Nu.h
+ * new file mode 100755
+ * index 0000000..f9e6561
+ * --- /dev/null
+ * +++ b/include/Nu/Nu.h
+ * @@ -0,0 +1 @@
+ * +awesome content
+ * diff --git a/objc/Nu.h b/objc/Nu.h
+ * deleted file mode 100644
+ * index f9e6561..0000000
+ * --- a/objc/Nu.h
+ * +++ /dev/null
+ * @@ -1 +0,0 @@
+ * -awesome content
+ */
+
+ opts.flags = GIT_DIFF_INCLUDE_TYPECHANGE;
+ process_tree_to_tree_diffing("7fccd7", "a8595c");
+
+ cl_assert_equal_i(2, expect.files);
+ cl_assert_equal_i(1, expect.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(0, expect.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(0, expect.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(1, expect.file_status[GIT_DELTA_TYPECHANGE]);
+}
+
+void test_diff_tree__regular_blob_mode_changed_to_executable_file(void)
+{
+ /*
+ * $ git diff 806999..a8595c
+ * diff --git a/include/Nu/Nu.h b/include/Nu/Nu.h
+ * old mode 100644
+ * new mode 100755
+ */
+
+ process_tree_to_tree_diffing("806999", "a8595c");
+
+ cl_assert_equal_i(1, expect.files);
+ cl_assert_equal_i(0, expect.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, expect.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(0, expect.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(0, expect.file_status[GIT_DELTA_TYPECHANGE]);
+}
+
+void test_diff_tree__issue_1397(void)
+{
+ /* this test shows that it is not needed */
+
+ g_repo = cl_git_sandbox_init("issue_1397");
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", true);
+
+ cl_assert((a = resolve_commit_oid_to_tree(g_repo, "8a7ef04")) != NULL);
+ cl_assert((b = resolve_commit_oid_to_tree(g_repo, "7f483a7")) != NULL);
+
+ cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts));
+
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &expect));
+
+ cl_assert_equal_i(1, expect.files);
+ cl_assert_equal_i(0, expect.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, expect.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(0, expect.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(0, expect.file_status[GIT_DELTA_TYPECHANGE]);
+}
+
+static void set_config_int(git_repository *repo, const char *name, int value)
+{
+ git_config *cfg;
+
+ cl_git_pass(git_repository_config(&cfg, repo));
+ cl_git_pass(git_config_set_int32(cfg, name, value));
+ git_config_free(cfg);
+}
+
+void test_diff_tree__diff_configs(void)
+{
+ const char *a_commit = "d70d245e";
+ const char *b_commit = "7a9e0b02";
+
+ g_repo = cl_git_sandbox_init("diff");
+
+ cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL);
+ cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL);
+
+ cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, NULL));
+
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &expect));
+
+ cl_assert_equal_i(2, expect.files);
+ cl_assert_equal_i(2, expect.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(6, expect.hunks);
+ cl_assert_equal_i(55, expect.lines);
+ cl_assert_equal_i(33, expect.line_ctxt);
+ cl_assert_equal_i(7, expect.line_adds);
+ cl_assert_equal_i(15, expect.line_dels);
+
+ git_diff_free(diff);
+ diff = NULL;
+
+ set_config_int(g_repo, "diff.context", 1);
+
+ memset(&expect, 0, sizeof(expect));
+
+ cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, NULL));
+
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &expect));
+
+ cl_assert_equal_i(2, expect.files);
+ cl_assert_equal_i(2, expect.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(7, expect.hunks);
+ cl_assert_equal_i(34, expect.lines);
+ cl_assert_equal_i(12, expect.line_ctxt);
+ cl_assert_equal_i(7, expect.line_adds);
+ cl_assert_equal_i(15, expect.line_dels);
+
+ git_diff_free(diff);
+ diff = NULL;
+
+ set_config_int(g_repo, "diff.context", 0);
+ set_config_int(g_repo, "diff.noprefix", 1);
+
+ memset(&expect, 0, sizeof(expect));
+
+ cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, NULL));
+
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &expect));
+
+ cl_assert_equal_i(2, expect.files);
+ cl_assert_equal_i(2, expect.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(7, expect.hunks);
+ cl_assert_equal_i(22, expect.lines);
+ cl_assert_equal_i(0, expect.line_ctxt);
+ cl_assert_equal_i(7, expect.line_adds);
+ cl_assert_equal_i(15, expect.line_dels);
+}
diff --git a/tests/diff/workdir.c b/tests/diff/workdir.c
new file mode 100644
index 000000000..963be9481
--- /dev/null
+++ b/tests/diff/workdir.c
@@ -0,0 +1,1696 @@
+#include "clar_libgit2.h"
+#include "diff_helpers.h"
+#include "repository.h"
+#include "git2/sys/diff.h"
+
+static git_repository *g_repo = NULL;
+
+void test_diff_workdir__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_diff_workdir__to_index(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ diff_expects exp;
+ int use_iterator;
+
+ g_repo = cl_git_sandbox_init("status");
+
+ opts.context_lines = 3;
+ opts.interhunk_lines = 1;
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+ memset(&exp, 0, sizeof(exp));
+
+ if (use_iterator)
+ cl_git_pass(diff_foreach_via_iterator(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ else
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ /* to generate these values:
+ * - cd to tests/resources/status,
+ * - mv .gitted .git
+ * - git diff --name-status
+ * - git diff
+ * - mv .git .gitted
+ */
+ cl_assert_equal_i(13, exp.files);
+ 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);
+
+ cl_assert_equal_i(14, exp.lines);
+ cl_assert_equal_i(5, exp.line_ctxt);
+ cl_assert_equal_i(4, exp.line_adds);
+ cl_assert_equal_i(5, exp.line_dels);
+ }
+
+ {
+ git_diff_perfdata perf = GIT_DIFF_PERFDATA_INIT;
+ cl_git_pass(git_diff_get_perfdata(&perf, diff));
+ cl_assert_equal_sz(
+ 13 /* in root */ + 3 /* in subdir */, perf.stat_calls);
+ cl_assert_equal_sz(5, perf.oid_calculations);
+ }
+
+ git_diff_free(diff);
+}
+
+void test_diff_workdir__to_index_with_assume_unchanged(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ git_index *idx = NULL;
+ diff_expects exp;
+ const git_index_entry *iep;
+ git_index_entry ie;
+
+ g_repo = cl_git_sandbox_init("status");
+
+ /* do initial diff */
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(8, exp.files);
+ 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]);
+ git_diff_free(diff);
+
+ /* mark a couple of entries with ASSUME_UNCHANGED */
+
+ cl_git_pass(git_repository_index(&idx, g_repo));
+
+ cl_assert((iep = git_index_get_bypath(idx, "modified_file", 0)) != NULL);
+ memcpy(&ie, iep, sizeof(ie));
+ ie.flags |= GIT_IDXENTRY_VALID;
+ cl_git_pass(git_index_add(idx, &ie));
+
+ cl_assert((iep = git_index_get_bypath(idx, "file_deleted", 0)) != NULL);
+ memcpy(&ie, iep, sizeof(ie));
+ ie.flags |= GIT_IDXENTRY_VALID;
+ cl_git_pass(git_index_add(idx, &ie));
+
+ cl_git_pass(git_index_write(idx));
+ git_index_free(idx);
+
+ /* redo diff and see that entries are skipped */
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ cl_assert_equal_i(6, exp.files);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(3, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]);
+ git_diff_free(diff);
+
+}
+
+void test_diff_workdir__to_tree(void)
+{
+ /* grabbed a couple of commit oids from the history of the attr repo */
+ const char *a_commit = "26a125ee1bf"; /* the current HEAD */
+ const char *b_commit = "0017bd4ab1ec3"; /* the start */
+ git_tree *a, *b;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ git_diff *diff2 = NULL;
+ diff_expects exp;
+ int use_iterator;
+
+ g_repo = cl_git_sandbox_init("status");
+
+ a = resolve_commit_oid_to_tree(g_repo, a_commit);
+ b = resolve_commit_oid_to_tree(g_repo, b_commit);
+
+ opts.context_lines = 3;
+ opts.interhunk_lines = 1;
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
+
+ /* You can't really generate the equivalent of git_diff_tree_to_workdir()
+ * using C git. It really wants to interpose the index into the diff.
+ *
+ * To validate the following results with command line git, I ran the
+ * following:
+ * - git ls-tree 26a125
+ * - find . ! -path ./.git/\* -a -type f | git hash-object --stdin-paths
+ * The results are documented at the bottom of this file in the
+ * long comment entitled "PREPARATION OF TEST DATA".
+ */
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts));
+
+ for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+ memset(&exp, 0, sizeof(exp));
+
+ if (use_iterator)
+ cl_git_pass(diff_foreach_via_iterator(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ else
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(14, exp.files);
+ 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
+ * text diffs produced by git_diff_foreach are accurate here. We will
+ * do more apples-to-apples test comparison below.
+ */
+
+ git_diff_free(diff);
+ diff = NULL;
+ memset(&exp, 0, sizeof(exp));
+
+ /* This is a compatible emulation of "git diff <sha>" which looks like
+ * 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_tree_to_index(&diff, g_repo, a, NULL, &opts));
+ cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts));
+ cl_git_pass(git_diff_merge(diff, diff2));
+ git_diff_free(diff2);
+
+ for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+ memset(&exp, 0, sizeof(exp));
+
+ if (use_iterator)
+ cl_git_pass(diff_foreach_via_iterator(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ else
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(15, exp.files);
+ 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);
+
+ cl_assert_equal_i(17, exp.lines);
+ cl_assert_equal_i(4, exp.line_ctxt);
+ cl_assert_equal_i(8, exp.line_adds);
+ cl_assert_equal_i(5, exp.line_dels);
+ }
+
+ git_diff_free(diff);
+ diff = NULL;
+ memset(&exp, 0, sizeof(exp));
+
+ /* Again, emulating "git diff <sha>" for testing purposes using
+ * "git diff --name-status 0017bd4ab1ec3" instead.
+ */
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, b, NULL, &opts));
+ cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts));
+ cl_git_pass(git_diff_merge(diff, diff2));
+ git_diff_free(diff2);
+
+ for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+ memset(&exp, 0, sizeof(exp));
+
+ if (use_iterator)
+ cl_git_pass(diff_foreach_via_iterator(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ else
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(16, exp.files);
+ 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);
+
+ cl_assert_equal_i(19, exp.lines);
+ cl_assert_equal_i(3, exp.line_ctxt);
+ cl_assert_equal_i(12, exp.line_adds);
+ cl_assert_equal_i(4, exp.line_dels);
+ }
+
+ git_diff_free(diff);
+
+ /* Let's try that once more with a reversed diff */
+
+ opts.flags |= GIT_DIFF_REVERSE;
+
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, b, NULL, &opts));
+ cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts));
+ cl_git_pass(git_diff_merge(diff, diff2));
+ git_diff_free(diff2);
+
+ memset(&exp, 0, sizeof(exp));
+
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(16, exp.files);
+ cl_assert_equal_i(5, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(4, exp.file_status[GIT_DELTA_ADDED]);
+ 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);
+
+ cl_assert_equal_i(19, exp.lines);
+ cl_assert_equal_i(3, exp.line_ctxt);
+ cl_assert_equal_i(12, exp.line_dels);
+ cl_assert_equal_i(4, exp.line_adds);
+
+ git_diff_free(diff);
+
+ /* all done now */
+
+ git_tree_free(a);
+ git_tree_free(b);
+}
+
+void test_diff_workdir__to_index_with_pathspec(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ diff_expects exp;
+ char *pathspec = NULL;
+ int use_iterator;
+
+ g_repo = cl_git_sandbox_init("status");
+
+ opts.context_lines = 3;
+ opts.interhunk_lines = 1;
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
+ opts.pathspec.strings = &pathspec;
+ opts.pathspec.count = 1;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+ memset(&exp, 0, sizeof(exp));
+
+ if (use_iterator)
+ cl_git_pass(diff_foreach_via_iterator(
+ diff, diff_file_cb, NULL, NULL, &exp));
+ else
+ cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+
+ cl_assert_equal_i(13, exp.files);
+ 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_free(diff);
+
+ pathspec = "modified_file";
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+ memset(&exp, 0, sizeof(exp));
+
+ if (use_iterator)
+ cl_git_pass(diff_foreach_via_iterator(
+ diff, diff_file_cb, NULL, NULL, &exp));
+ else
+ cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+
+ cl_assert_equal_i(1, exp.files);
+ 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_free(diff);
+
+ pathspec = "subdir";
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+ memset(&exp, 0, sizeof(exp));
+
+ if (use_iterator)
+ cl_git_pass(diff_foreach_via_iterator(
+ diff, diff_file_cb, NULL, NULL, &exp));
+ else
+ cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+
+ cl_assert_equal_i(3, exp.files);
+ 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_free(diff);
+
+ pathspec = "*_deleted";
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+ memset(&exp, 0, sizeof(exp));
+
+ if (use_iterator)
+ cl_git_pass(diff_foreach_via_iterator(
+ diff, diff_file_cb, NULL, NULL, &exp));
+ else
+ cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+
+ cl_assert_equal_i(2, exp.files);
+ 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_free(diff);
+}
+
+void test_diff_workdir__filemode_changes(void)
+{
+ git_diff *diff = NULL;
+ diff_expects exp;
+ int use_iterator;
+
+ if (!cl_is_chmod_supported())
+ return;
+
+ g_repo = cl_git_sandbox_init("issue_592");
+
+ cl_repo_set_bool(g_repo, "core.filemode", true);
+
+ /* test once with no mods */
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL));
+
+ for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+ memset(&exp, 0, sizeof(exp));
+
+ if (use_iterator)
+ cl_git_pass(diff_foreach_via_iterator(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ else
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(0, exp.files);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(0, exp.hunks);
+ }
+
+ git_diff_free(diff);
+
+ /* chmod file and test again */
+
+ cl_assert(cl_toggle_filemode("issue_592/a.txt"));
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL));
+
+ for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+ memset(&exp, 0, sizeof(exp));
+
+ if (use_iterator)
+ cl_git_pass(diff_foreach_via_iterator(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ else
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(1, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(0, exp.hunks);
+ }
+
+ git_diff_free(diff);
+
+ cl_assert(cl_toggle_filemode("issue_592/a.txt"));
+}
+
+void test_diff_workdir__filemode_changes_with_filemode_false(void)
+{
+ git_diff *diff = NULL;
+ diff_expects exp;
+
+ if (!cl_is_chmod_supported())
+ return;
+
+ g_repo = cl_git_sandbox_init("issue_592");
+
+ cl_repo_set_bool(g_repo, "core.filemode", false);
+
+ /* test once with no mods */
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(0, exp.files);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(0, exp.hunks);
+
+ git_diff_free(diff);
+
+ /* chmod file and test again */
+
+ cl_assert(cl_toggle_filemode("issue_592/a.txt"));
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(0, exp.files);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(0, exp.hunks);
+
+ git_diff_free(diff);
+
+ cl_assert(cl_toggle_filemode("issue_592/a.txt"));
+}
+
+void test_diff_workdir__head_index_and_workdir_all_differ(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff_i2t = NULL, *diff_w2i = NULL;
+ diff_expects exp;
+ char *pathspec = "staged_changes_modified_file";
+ git_tree *tree;
+ int use_iterator;
+
+ /* For this file,
+ * - head->index diff has 1 line of context, 1 line of diff
+ * - index->workdir diff has 2 lines of context, 1 line of diff
+ * but
+ * - head->workdir diff has 1 line of context, 2 lines of diff
+ * Let's make sure the right one is returned from each fn.
+ */
+
+ g_repo = cl_git_sandbox_init("status");
+
+ tree = resolve_commit_oid_to_tree(g_repo, "26a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f");
+
+ opts.pathspec.strings = &pathspec;
+ opts.pathspec.count = 1;
+
+ cl_git_pass(git_diff_tree_to_index(&diff_i2t, g_repo, tree, NULL, &opts));
+ cl_git_pass(git_diff_index_to_workdir(&diff_w2i, g_repo, NULL, &opts));
+
+ for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+ memset(&exp, 0, sizeof(exp));
+
+ if (use_iterator)
+ cl_git_pass(diff_foreach_via_iterator(
+ diff_i2t, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ else
+ cl_git_pass(git_diff_foreach(
+ diff_i2t, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(1, exp.files);
+ 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);
+ cl_assert_equal_i(1, exp.line_adds);
+ cl_assert_equal_i(0, exp.line_dels);
+ }
+
+ for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+ memset(&exp, 0, sizeof(exp));
+
+ if (use_iterator)
+ cl_git_pass(diff_foreach_via_iterator(
+ diff_w2i, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ else
+ cl_git_pass(git_diff_foreach(
+ diff_w2i, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(1, exp.files);
+ 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);
+ cl_assert_equal_i(1, exp.line_adds);
+ cl_assert_equal_i(0, exp.line_dels);
+ }
+
+ cl_git_pass(git_diff_merge(diff_i2t, diff_w2i));
+
+ for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+ memset(&exp, 0, sizeof(exp));
+
+ if (use_iterator)
+ cl_git_pass(diff_foreach_via_iterator(
+ diff_i2t, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ else
+ cl_git_pass(git_diff_foreach(
+ diff_i2t, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(1, exp.files);
+ 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);
+ cl_assert_equal_i(2, exp.line_adds);
+ cl_assert_equal_i(0, exp.line_dels);
+ }
+
+ git_diff_free(diff_i2t);
+ git_diff_free(diff_w2i);
+
+ git_tree_free(tree);
+}
+
+void test_diff_workdir__eof_newline_changes(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ diff_expects exp;
+ char *pathspec = "current_file";
+ int use_iterator;
+
+ g_repo = cl_git_sandbox_init("status");
+
+ opts.pathspec.strings = &pathspec;
+ opts.pathspec.count = 1;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+ memset(&exp, 0, sizeof(exp));
+
+ if (use_iterator)
+ cl_git_pass(diff_foreach_via_iterator(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ else
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(0, exp.files);
+ 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);
+ cl_assert_equal_i(0, exp.line_adds);
+ cl_assert_equal_i(0, exp.line_dels);
+ }
+
+ git_diff_free(diff);
+
+ cl_git_append2file("status/current_file", "\n");
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+ memset(&exp, 0, sizeof(exp));
+
+ if (use_iterator)
+ cl_git_pass(diff_foreach_via_iterator(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ else
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(1, exp.files);
+ 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);
+ cl_assert_equal_i(1, exp.line_adds);
+ cl_assert_equal_i(0, exp.line_dels);
+ }
+
+ git_diff_free(diff);
+
+ cl_git_rewritefile("status/current_file", "current_file");
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+ memset(&exp, 0, sizeof(exp));
+
+ if (use_iterator)
+ cl_git_pass(diff_foreach_via_iterator(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+ else
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(1, exp.files);
+ 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);
+ cl_assert_equal_i(1, exp.line_adds);
+ cl_assert_equal_i(2, exp.line_dels);
+ }
+
+ git_diff_free(diff);
+}
+
+/* PREPARATION OF TEST DATA
+ *
+ * Since there is no command line equivalent of git_diff_tree_to_workdir,
+ * it was a bit of a pain to confirm that I was getting the expected
+ * results in the first part of this tests. Here is what I ended up
+ * doing to set my expectation for the file counts and results:
+ *
+ * Running "git ls-tree 26a125" and "git ls-tree aa27a6" shows:
+ *
+ * A a0de7e0ac200c489c41c59dfa910154a70264e6e current_file
+ * B 5452d32f1dd538eb0405e8a83cc185f79e25e80f file_deleted
+ * C 452e4244b5d083ddf0460acf1ecc74db9dcfa11a modified_file
+ * D 32504b727382542f9f089e24fddac5e78533e96c staged_changes
+ * E 061d42a44cacde5726057b67558821d95db96f19 staged_changes_file_deleted
+ * F 70bd9443ada07063e7fbf0b3ff5c13f7494d89c2 staged_changes_modified_file
+ * G e9b9107f290627c04d097733a10055af941f6bca staged_delete_file_deleted
+ * H dabc8af9bd6e9f5bbe96a176f1a24baf3d1f8916 staged_delete_modified_file
+ * I 53ace0d1cc1145a5f4fe4f78a186a60263190733 subdir/current_file
+ * J 1888c805345ba265b0ee9449b8877b6064592058 subdir/deleted_file
+ * K a6191982709b746d5650e93c2acf34ef74e11504 subdir/modified_file
+ * L e8ee89e15bbe9b20137715232387b3de5b28972e subdir.txt
+ *
+ * --------
+ *
+ * find . ! -path ./.git/\* -a -type f | git hash-object --stdin-paths
+ *
+ * A a0de7e0ac200c489c41c59dfa910154a70264e6e current_file
+ * M 6a79f808a9c6bc9531ac726c184bbcd9351ccf11 ignored_file
+ * C 0a539630525aca2e7bc84975958f92f10a64c9b6 modified_file
+ * N d4fa8600b4f37d7516bef4816ae2c64dbf029e3a new_file
+ * D 55d316c9ba708999f1918e9677d01dfcae69c6b9 staged_changes
+ * F 011c3440d5c596e21d836aa6d7b10eb581f68c49 staged_changes_modified_file
+ * H dabc8af9bd6e9f5bbe96a176f1a24baf3d1f8916 staged_delete_modified_file
+ * O 529a16e8e762d4acb7b9636ff540a00831f9155a staged_new_file
+ * P 8b090c06d14ffa09c4e880088ebad33893f921d1 staged_new_file_modified_file
+ * I 53ace0d1cc1145a5f4fe4f78a186a60263190733 subdir/current_file
+ * K 57274b75eeb5f36fd55527806d567b2240a20c57 subdir/modified_file
+ * Q 80a86a6931b91bc01c2dbf5ca55bdd24ad1ef466 subdir/new_file
+ * L e8ee89e15bbe9b20137715232387b3de5b28972e subdir.txt
+ *
+ * --------
+ *
+ * A - current_file (UNMODIFIED) -> not in results
+ * B D file_deleted
+ * M I ignored_file (IGNORED)
+ * C M modified_file
+ * N U new_file (UNTRACKED)
+ * D M staged_changes
+ * E D staged_changes_file_deleted
+ * F M staged_changes_modified_file
+ * G D staged_delete_file_deleted
+ * H - staged_delete_modified_file (UNMODIFIED) -> not in results
+ * O U staged_new_file
+ * P U staged_new_file_modified_file
+ * I - subdir/current_file (UNMODIFIED) -> not in results
+ * J D subdir/deleted_file
+ * K M subdir/modified_file
+ * Q U subdir/new_file
+ * L - subdir.txt (UNMODIFIED) -> not in results
+ *
+ * Expect 13 files, 0 ADD, 4 DEL, 4 MOD, 1 IGN, 4 UNTR
+ */
+
+
+void test_diff_workdir__larger_hunks(void)
+{
+ const char *a_commit = "d70d245ed97ed2aa596dd1af6536e4bfdb047b69";
+ const char *b_commit = "7a9e0b02e63179929fed24f0a3e0f19168114d10";
+ git_tree *a, *b;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ size_t i, d, num_d, h, num_h, l, num_l;
+
+ g_repo = cl_git_sandbox_init("diff");
+
+ cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL);
+ cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL);
+
+ opts.context_lines = 1;
+ opts.interhunk_lines = 0;
+
+ for (i = 0; i <= 2; ++i) {
+ git_diff *diff = NULL;
+ git_patch *patch;
+ const git_diff_hunk *hunk;
+ const git_diff_line *line;
+
+ /* okay, this is a bit silly, but oh well */
+ switch (i) {
+ case 0:
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ break;
+ case 1:
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts));
+ break;
+ case 2:
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, b, &opts));
+ break;
+ }
+
+ num_d = git_diff_num_deltas(diff);
+ cl_assert_equal_i(2, (int)num_d);
+
+ for (d = 0; d < num_d; ++d) {
+ cl_git_pass(git_patch_from_diff(&patch, diff, d));
+ cl_assert(patch);
+
+ num_h = git_patch_num_hunks(patch);
+ for (h = 0; h < num_h; h++) {
+ cl_git_pass(git_patch_get_hunk(&hunk, &num_l, patch, h));
+
+ for (l = 0; l < num_l; ++l) {
+ cl_git_pass(
+ git_patch_get_line_in_hunk(&line, patch, h, l));
+ cl_assert(line);
+ }
+
+ /* confirm fail after the last item */
+ cl_git_fail(
+ git_patch_get_line_in_hunk(&line, patch, h, num_l));
+ }
+
+ /* confirm fail after the last item */
+ cl_git_fail(git_patch_get_hunk(&hunk, &num_l, patch, num_h));
+
+ git_patch_free(patch);
+ }
+
+ git_diff_free(diff);
+ }
+
+ git_tree_free(a);
+ git_tree_free(b);
+}
+
+/* Set up a test that exercises this code. The easiest test using existing
+ * test data is probably to create a sandbox of submod2 and then run a
+ * git_diff_tree_to_workdir against tree
+ * 873585b94bdeabccea991ea5e3ec1a277895b698. As for what you should actually
+ * test, you can start by just checking that the number of lines of diff
+ * content matches the actual output of git diff. That will at least
+ * demonstrate that the submodule content is being used to generate somewhat
+ * comparable outputs. It is a test that would fail without this code and
+ * will succeed with it.
+ */
+
+#include "../submodule/submodule_helpers.h"
+
+void test_diff_workdir__submodules(void)
+{
+ const char *a_commit = "873585b94bdeabccea991ea5e3ec1a277895b698";
+ git_tree *a;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ diff_expects exp;
+
+ g_repo = setup_fixture_submod2();
+
+ a = resolve_commit_oid_to_tree(g_repo, a_commit);
+
+ opts.flags =
+ GIT_DIFF_INCLUDE_UNTRACKED |
+ GIT_DIFF_INCLUDE_IGNORED |
+ GIT_DIFF_RECURSE_UNTRACKED_DIRS |
+ GIT_DIFF_SHOW_UNTRACKED_CONTENT;
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts));
+
+ /* diff_print(stderr, diff); */
+
+ /* essentially doing: git diff 873585b94bdeabccea991ea5e3ec1a277895b698 */
+
+ memset(&exp, 0, sizeof(exp));
+
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ /* so "git diff 873585" returns:
+ * M .gitmodules
+ * A just_a_dir/contents
+ * A just_a_file
+ * A sm_added_and_uncommited
+ * A sm_changed_file
+ * A sm_changed_head
+ * A sm_changed_index
+ * A sm_changed_untracked_file
+ * M sm_missing_commits
+ * A sm_unchanged
+ * which is a little deceptive because of the difference between the
+ * "git diff <treeish>" results from "git_diff_tree_to_workdir". The
+ * only significant difference is that those Added items will show up
+ * as Untracked items in the pure libgit2 diff.
+ *
+ * Then add in the two extra untracked items "not" and "not-submodule"
+ * to get the 12 files reported here.
+ */
+
+ cl_assert_equal_i(12, exp.files);
+
+ 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(0, exp.file_status[GIT_DELTA_IGNORED]);
+ cl_assert_equal_i(10, exp.file_status[GIT_DELTA_UNTRACKED]);
+
+ /* the following numbers match "git diff 873585" exactly */
+
+ cl_assert_equal_i(9, exp.hunks);
+
+ cl_assert_equal_i(33, exp.lines);
+ cl_assert_equal_i(2, exp.line_ctxt);
+ cl_assert_equal_i(30, exp.line_adds);
+ cl_assert_equal_i(1, exp.line_dels);
+
+ git_diff_free(diff);
+ git_tree_free(a);
+}
+
+void test_diff_workdir__cannot_diff_against_a_bare_repository(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ git_tree *tree;
+
+ g_repo = cl_git_sandbox_init("testrepo.git");
+
+ cl_assert_equal_i(
+ GIT_EBAREREPO, git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ cl_git_pass(git_repository_head_tree(&tree, g_repo));
+
+ cl_assert_equal_i(
+ GIT_EBAREREPO, git_diff_tree_to_workdir(&diff, g_repo, tree, &opts));
+
+ git_tree_free(tree);
+}
+
+void test_diff_workdir__to_null_tree(void)
+{
+ git_diff *diff;
+ diff_expects exp;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+
+ opts.flags = GIT_DIFF_INCLUDE_UNTRACKED |
+ GIT_DIFF_RECURSE_UNTRACKED_DIRS;
+
+ g_repo = cl_git_sandbox_init("status");
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, NULL, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(exp.files, exp.file_status[GIT_DELTA_UNTRACKED]);
+
+ git_diff_free(diff);
+}
+
+void test_diff_workdir__checks_options_version(void)
+{
+ git_diff *diff;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ const git_error *err;
+
+ g_repo = cl_git_sandbox_init("status");
+
+ opts.version = 0;
+ cl_git_fail(git_diff_tree_to_workdir(&diff, g_repo, NULL, &opts));
+ err = giterr_last();
+ cl_assert_equal_i(GITERR_INVALID, err->klass);
+
+ giterr_clear();
+ opts.version = 1024;
+ cl_git_fail(git_diff_tree_to_workdir(&diff, g_repo, NULL, &opts));
+ err = giterr_last();
+ cl_assert_equal_i(GITERR_INVALID, err->klass);
+}
+
+void test_diff_workdir__can_diff_empty_file(void)
+{
+ git_diff *diff;
+ git_tree *tree;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ struct stat st;
+ git_patch *patch;
+
+ g_repo = cl_git_sandbox_init("attr_index");
+
+ tree = resolve_commit_oid_to_tree(g_repo, "3812cfef3661"); /* HEAD */
+
+ /* baseline - make sure there are no outstanding diffs */
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts));
+ cl_assert_equal_i(2, (int)git_diff_num_deltas(diff));
+ git_diff_free(diff);
+
+ /* empty contents of file */
+
+ cl_git_rewritefile("attr_index/README.txt", "");
+ cl_git_pass(git_path_lstat("attr_index/README.txt", &st));
+ cl_assert_equal_i(0, (int)st.st_size);
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts));
+ cl_assert_equal_i(3, (int)git_diff_num_deltas(diff));
+ /* diffs are: .gitattributes, README.txt, sub/sub/.gitattributes */
+ cl_git_pass(git_patch_from_diff(&patch, diff, 1));
+ git_patch_free(patch);
+ git_diff_free(diff);
+
+ /* remove a file altogether */
+
+ cl_git_pass(p_unlink("attr_index/README.txt"));
+ cl_assert(!git_path_exists("attr_index/README.txt"));
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts));
+ cl_assert_equal_i(3, (int)git_diff_num_deltas(diff));
+ cl_git_pass(git_patch_from_diff(&patch, diff, 1));
+ git_patch_free(patch);
+ git_diff_free(diff);
+
+ git_tree_free(tree);
+}
+
+void test_diff_workdir__to_index_issue_1397(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ diff_expects exp;
+
+ g_repo = cl_git_sandbox_init("issue_1397");
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", true);
+
+ opts.context_lines = 3;
+ opts.interhunk_lines = 1;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(0, exp.files);
+ cl_assert_equal_i(0, exp.hunks);
+ cl_assert_equal_i(0, exp.lines);
+
+ git_diff_free(diff);
+ diff = NULL;
+
+ cl_git_rewritefile("issue_1397/crlf_file.txt",
+ "first line\r\nsecond line modified\r\nboth with crlf");
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(1, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
+
+ cl_assert_equal_i(1, exp.hunks);
+
+ cl_assert_equal_i(5, exp.lines);
+ cl_assert_equal_i(3, exp.line_ctxt);
+ cl_assert_equal_i(1, exp.line_adds);
+ cl_assert_equal_i(1, exp.line_dels);
+
+ git_diff_free(diff);
+}
+
+void test_diff_workdir__to_tree_issue_1397(void)
+{
+ const char *a_commit = "7f483a738"; /* the current HEAD */
+ git_tree *a;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ git_diff *diff2 = NULL;
+ diff_expects exp;
+
+ g_repo = cl_git_sandbox_init("issue_1397");
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", true);
+
+ a = resolve_commit_oid_to_tree(g_repo, a_commit);
+
+ opts.context_lines = 3;
+ opts.interhunk_lines = 1;
+
+ cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(0, exp.files);
+ cl_assert_equal_i(0, exp.hunks);
+ cl_assert_equal_i(0, exp.lines);
+
+ git_diff_free(diff);
+ diff = NULL;
+
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, NULL, &opts));
+ cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts));
+ cl_git_pass(git_diff_merge(diff, diff2));
+ git_diff_free(diff2);
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(0, exp.files);
+ cl_assert_equal_i(0, exp.hunks);
+ cl_assert_equal_i(0, exp.lines);
+
+ git_diff_free(diff);
+ git_tree_free(a);
+}
+
+void test_diff_workdir__untracked_directory_scenarios(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ diff_expects exp;
+ char *pathspec = NULL;
+ static const char *files0[] = {
+ "subdir/deleted_file",
+ "subdir/modified_file",
+ "subdir/new_file",
+ NULL
+ };
+ static const char *files1[] = {
+ "subdir/deleted_file",
+ "subdir/directory/",
+ "subdir/modified_file",
+ "subdir/new_file",
+ NULL
+ };
+ static const char *files2[] = {
+ "subdir/deleted_file",
+ "subdir/directory/more/notignored",
+ "subdir/modified_file",
+ "subdir/new_file",
+ NULL
+ };
+
+ g_repo = cl_git_sandbox_init("status");
+ cl_git_mkfile("status/.gitignore", "ignored\n");
+
+ opts.context_lines = 3;
+ opts.interhunk_lines = 1;
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
+ opts.pathspec.strings = &pathspec;
+ opts.pathspec.count = 1;
+ pathspec = "subdir";
+
+ /* baseline for "subdir" pathspec */
+
+ memset(&exp, 0, sizeof(exp));
+ exp.names = files0;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+
+ cl_assert_equal_i(3, exp.files);
+ 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_free(diff);
+
+ /* empty directory */
+
+ cl_git_pass(p_mkdir("status/subdir/directory", 0777));
+
+ memset(&exp, 0, sizeof(exp));
+ exp.names = files1;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+
+ cl_assert_equal_i(4, exp.files);
+ 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(1, exp.file_status[GIT_DELTA_IGNORED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]);
+
+ git_diff_free(diff);
+
+ /* empty directory in empty directory */
+
+ cl_git_pass(p_mkdir("status/subdir/directory/empty", 0777));
+
+ memset(&exp, 0, sizeof(exp));
+ exp.names = files1;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+
+ cl_assert_equal_i(4, exp.files);
+ 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(1, exp.file_status[GIT_DELTA_IGNORED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]);
+
+ git_diff_free(diff);
+
+ /* directory with only ignored files */
+
+ cl_git_pass(p_mkdir("status/subdir/directory/deeper", 0777));
+ cl_git_mkfile("status/subdir/directory/deeper/ignored", "ignore me\n");
+
+ cl_git_pass(p_mkdir("status/subdir/directory/another", 0777));
+ cl_git_mkfile("status/subdir/directory/another/ignored", "ignore me\n");
+
+ memset(&exp, 0, sizeof(exp));
+ exp.names = files1;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+
+ cl_assert_equal_i(4, exp.files);
+ 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(1, exp.file_status[GIT_DELTA_IGNORED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]);
+
+ git_diff_free(diff);
+
+ /* directory with ignored directory (contents irrelevant) */
+
+ cl_git_pass(p_mkdir("status/subdir/directory/more", 0777));
+ cl_git_pass(p_mkdir("status/subdir/directory/more/ignored", 0777));
+ cl_git_mkfile("status/subdir/directory/more/ignored/notignored",
+ "inside ignored dir\n");
+
+ memset(&exp, 0, sizeof(exp));
+ exp.names = files1;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+
+ cl_assert_equal_i(4, exp.files);
+ 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(1, exp.file_status[GIT_DELTA_IGNORED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]);
+
+ git_diff_free(diff);
+
+ /* quick version avoids directory scan */
+
+ opts.flags = opts.flags | GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS;
+
+ memset(&exp, 0, sizeof(exp));
+ exp.names = files1;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+
+ cl_assert_equal_i(4, exp.files);
+ 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(2, exp.file_status[GIT_DELTA_UNTRACKED]);
+
+ git_diff_free(diff);
+
+ /* directory with nested non-ignored content */
+
+ opts.flags = opts.flags & ~GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS;
+
+ cl_git_mkfile("status/subdir/directory/more/notignored",
+ "not ignored deep under untracked\n");
+
+ memset(&exp, 0, sizeof(exp));
+ exp.names = files1;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+
+ cl_assert_equal_i(4, exp.files);
+ 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(2, exp.file_status[GIT_DELTA_UNTRACKED]);
+
+ git_diff_free(diff);
+
+ /* use RECURSE_UNTRACKED_DIRS to get actual untracked files (no ignores) */
+
+ opts.flags = opts.flags & ~GIT_DIFF_INCLUDE_IGNORED;
+ opts.flags = opts.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
+
+ memset(&exp, 0, sizeof(exp));
+ exp.names = files2;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+
+ cl_assert_equal_i(4, exp.files);
+ 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(2, exp.file_status[GIT_DELTA_UNTRACKED]);
+
+ git_diff_free(diff);
+}
+
+
+void test_diff_workdir__untracked_directory_comes_last(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+
+ g_repo = cl_git_sandbox_init("renames");
+
+ cl_git_mkfile("renames/.gitignore", "*.ign\n");
+ cl_git_pass(p_mkdir("renames/zzz_untracked", 0777));
+ cl_git_mkfile("renames/zzz_untracked/an.ign", "ignore me please");
+ cl_git_mkfile("renames/zzz_untracked/skip.ign", "ignore me really");
+ cl_git_mkfile("renames/zzz_untracked/test.ign", "ignore me now");
+
+ opts.context_lines = 3;
+ opts.interhunk_lines = 1;
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ cl_assert(diff != NULL);
+
+ git_diff_free(diff);
+}
+
+void test_diff_workdir__untracked_with_bom(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ const git_diff_delta *delta;
+
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+ cl_repo_set_bool(g_repo, "core.autocrlf", true);
+
+ cl_git_write2file("empty_standard_repo/bom.txt",
+ "\xFF\xFE\x31\x00\x32\x00\x33\x00\x34\x00", 10, O_WRONLY|O_CREAT, 0664);
+
+ opts.flags =
+ GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_SHOW_UNTRACKED_CONTENT;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ cl_assert_equal_i(1, git_diff_num_deltas(diff));
+ cl_assert((delta = git_diff_get_delta(diff, 0)) != NULL);
+ cl_assert_equal_i(GIT_DELTA_UNTRACKED, delta->status);
+
+ /* not known at this point
+ * cl_assert((delta->flags & GIT_DIFF_FLAG_BINARY) != 0);
+ */
+
+ git_diff_free(diff);
+}
+
+void test_diff_workdir__patience_diff(void)
+{
+ git_index *index;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ git_patch *patch = NULL;
+ git_buf buf = GIT_BUF_INIT;
+ const char *expected_normal = "diff --git a/test.txt b/test.txt\nindex 34a5acc..d52725f 100644\n--- a/test.txt\n+++ b/test.txt\n@@ -1,10 +1,7 @@\n When I wrote this\n I did not know\n-how to create\n-a patience diff\n I did not know\n how to create\n+a patience diff\n another problem\n-I did not know\n-how to create\n a minimal diff\n";
+ const char *expected_patience = "diff --git a/test.txt b/test.txt\nindex 34a5acc..d52725f 100644\n--- a/test.txt\n+++ b/test.txt\n@@ -1,10 +1,7 @@\n When I wrote this\n I did not know\n+I did not know\n how to create\n a patience diff\n-I did not know\n-how to create\n another problem\n-I did not know\n-how to create\n a minimal diff\n";
+
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+ cl_repo_set_bool(g_repo, "core.autocrlf", true);
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ cl_git_mkfile(
+ "empty_standard_repo/test.txt",
+ "When I wrote this\nI did not know\nhow to create\na patience diff\nI did not know\nhow to create\nanother problem\nI did not know\nhow to create\na minimal diff\n");
+ cl_git_pass(git_index_add_bypath(index, "test.txt"));
+ cl_repo_commit_from_index(NULL, g_repo, NULL, 1372350000, "Base");
+ git_index_free(index);
+
+ cl_git_rewritefile(
+ "empty_standard_repo/test.txt",
+ "When I wrote this\nI did not know\nI did not know\nhow to create\na patience diff\nanother problem\na minimal diff\n");
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ cl_assert_equal_i(1, git_diff_num_deltas(diff));
+ cl_git_pass(git_patch_from_diff(&patch, diff, 0));
+ cl_git_pass(git_patch_to_buf(&buf, patch));
+
+ cl_assert_equal_s(expected_normal, buf.ptr);
+ git_buf_clear(&buf);
+ git_patch_free(patch);
+ git_diff_free(diff);
+
+ opts.flags |= GIT_DIFF_PATIENCE;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+ cl_assert_equal_i(1, git_diff_num_deltas(diff));
+ cl_git_pass(git_patch_from_diff(&patch, diff, 0));
+ cl_git_pass(git_patch_to_buf(&buf, patch));
+
+ cl_assert_equal_s(expected_patience, buf.ptr);
+ git_buf_clear(&buf);
+
+ git_buf_free(&buf);
+ git_patch_free(patch);
+ git_diff_free(diff);
+}
+
+void test_diff_workdir__with_stale_index(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ git_index *idx = NULL;
+ diff_expects exp;
+
+ g_repo = cl_git_sandbox_init("status");
+ cl_git_pass(git_repository_index(&idx, g_repo));
+
+ /* make the in-memory index invalid */
+ {
+ git_repository *r2;
+ git_index *idx2;
+ cl_git_pass(git_repository_open(&r2, "status"));
+ cl_git_pass(git_repository_index(&idx2, r2));
+ cl_git_pass(git_index_add_bypath(idx2, "new_file"));
+ cl_git_pass(git_index_add_bypath(idx2, "subdir/new_file"));
+ cl_git_pass(git_index_remove_bypath(idx2, "staged_new_file"));
+ cl_git_pass(git_index_remove_bypath(idx2, "staged_changes_file_deleted"));
+ cl_git_pass(git_index_write(idx2));
+ git_index_free(idx2);
+ git_repository_free(r2);
+ }
+
+ opts.context_lines = 3;
+ opts.interhunk_lines = 1;
+ opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_INCLUDE_UNMODIFIED;
+
+ /* first try with index pointer which should prevent reload */
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, idx, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(17, exp.files);
+ 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(4, exp.file_status[GIT_DELTA_UNTRACKED]);
+ cl_assert_equal_i(5, exp.file_status[GIT_DELTA_UNMODIFIED]);
+
+ git_diff_free(diff);
+
+ /* now let's try without the index pointer which should trigger reload */
+
+ /* two files that were UNTRACKED should have become UNMODIFIED */
+ /* one file that was UNMODIFIED should now have become UNTRACKED */
+ /* one file that was DELETED should now be gone completely */
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ git_diff_free(diff);
+
+ cl_assert_equal_i(16, exp.files);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(3, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]);
+ cl_assert_equal_i(6, exp.file_status[GIT_DELTA_UNMODIFIED]);
+
+ git_index_free(idx);
+}
+
+static int touch_file(void *payload, git_buf *path)
+{
+ int fd;
+ char b;
+
+ GIT_UNUSED(payload);
+ if (git_path_isdir(path->ptr))
+ return 0;
+
+ cl_assert((fd = p_open(path->ptr, O_RDWR)) >= 0);
+ cl_assert_equal_i(1, p_read(fd, &b, 1));
+ cl_must_pass(p_lseek(fd, 0, SEEK_SET));
+ cl_must_pass(p_write(fd, &b, 1));
+ cl_must_pass(p_close(fd));
+
+ return 0;
+}
+
+static void basic_diff_status(git_diff **out, const git_diff_options *opts)
+{
+ diff_expects exp;
+
+ cl_git_pass(git_diff_index_to_workdir(out, g_repo, NULL, opts));
+
+ memset(&exp, 0, sizeof(exp));
+
+ cl_git_pass(git_diff_foreach(
+ *out, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+
+ cl_assert_equal_i(13, exp.files);
+ 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]);
+}
+
+void test_diff_workdir__can_update_index(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ git_diff_perfdata perf = GIT_DIFF_PERFDATA_INIT;
+
+ g_repo = cl_git_sandbox_init("status");
+
+ /* touch all the files so stat times are different */
+ {
+ git_buf path = GIT_BUF_INIT;
+ cl_git_pass(git_buf_sets(&path, "status"));
+ cl_git_pass(git_path_direach(&path, 0, touch_file, NULL));
+ git_buf_free(&path);
+ }
+
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
+
+ basic_diff_status(&diff, &opts);
+
+ cl_git_pass(git_diff_get_perfdata(&perf, diff));
+ cl_assert_equal_sz(13 + 3, perf.stat_calls);
+ cl_assert_equal_sz(5, perf.oid_calculations);
+
+ git_diff_free(diff);
+
+ /* now allow diff to update stat cache */
+ opts.flags |= GIT_DIFF_UPDATE_INDEX;
+
+ basic_diff_status(&diff, &opts);
+
+ cl_git_pass(git_diff_get_perfdata(&perf, diff));
+ cl_assert_equal_sz(13 + 3, perf.stat_calls);
+ cl_assert_equal_sz(5, perf.oid_calculations);
+
+ git_diff_free(diff);
+
+ /* now if we do it again, we should see fewer OID calculations */
+
+ basic_diff_status(&diff, &opts);
+
+ cl_git_pass(git_diff_get_perfdata(&perf, diff));
+ cl_assert_equal_sz(13 + 3, perf.stat_calls);
+ cl_assert_equal_sz(0, perf.oid_calculations);
+
+ git_diff_free(diff);
+}
+
+#define STR7 "0123456"
+#define STR8 "01234567"
+#define STR40 STR8 STR8 STR8 STR8 STR8
+#define STR200 STR40 STR40 STR40 STR40 STR40
+#define STR999Z STR200 STR200 STR200 STR200 STR40 STR40 STR40 STR40 \
+ STR8 STR8 STR8 STR8 STR7 "\0"
+#define STR1000 STR200 STR200 STR200 STR200 STR200
+#define STR3999Z STR1000 STR1000 STR1000 STR999Z
+#define STR4000 STR1000 STR1000 STR1000 STR1000
+
+static void assert_delta_binary(git_diff *diff, size_t idx, int is_binary)
+{
+ git_patch *patch;
+ const git_diff_delta *delta;
+
+ cl_git_pass(git_patch_from_diff(&patch, diff, idx));
+ delta = git_patch_get_delta(patch);
+ cl_assert_equal_b((delta->flags & GIT_DIFF_FLAG_BINARY), is_binary);
+ git_patch_free(patch);
+}
+
+void test_diff_workdir__binary_detection(void)
+{
+ git_index *idx;
+ git_diff *diff = NULL;
+ git_buf b = GIT_BUF_INIT;
+ int i;
+ git_buf data[10] = {
+ { "1234567890", 0, 0 }, /* 0 - all ascii text control */
+ { "\xC3\x85\xC3\xBC\xE2\x80\xA0\x48\xC3\xB8\xCF\x80\xCE\xA9", 0, 0 }, /* 1 - UTF-8 multibyte text */
+ { "\xEF\xBB\xBF\xC3\x9C\xE2\xA4\x92\xC6\x92\x38\xC2\xA3\xE2\x82\xAC", 0, 0 }, /* 2 - UTF-8 with BOM */
+ { STR999Z, 0, 1000 }, /* 3 - ASCII with NUL at 1000 */
+ { STR3999Z, 0, 4000 }, /* 4 - ASCII with NUL at 4000 */
+ { STR4000 STR3999Z "x", 0, 8001 }, /* 5 - ASCII with NUL at 8000 */
+ { STR4000 STR4000 "\0", 0, 8001 }, /* 6 - ASCII with NUL at 8001 */
+ { "\x00\xDC\x00\x6E\x21\x39\xFE\x0E\x00\x63\x00\xF8"
+ "\x00\x64\x00\x65\x20\x48", 0, 18 }, /* 7 - UTF-16 text */
+ { "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d",
+ 0, 26 }, /* 8 - All non-printable characters (no NUL) */
+ { "Hello \x01\x02\x03\x04\x05\x06 World!\x01\x02\x03\x04"
+ "\x05\x06\x07", 0, 26 }, /* 9 - 50-50 non-printable (no NUL) */
+ };
+
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+ cl_git_pass(git_repository_index(&idx, g_repo));
+
+ /* We start with ASCII in index and test data in workdir,
+ * then we will try with test data in index and ASCII in workdir.
+ */
+
+ cl_git_pass(git_buf_sets(&b, "empty_standard_repo/0"));
+ for (i = 0; i < 10; ++i) {
+ b.ptr[b.size - 1] = '0' + i;
+ cl_git_mkfile(b.ptr, "baseline");
+ cl_git_pass(git_index_add_bypath(idx, &b.ptr[b.size - 1]));
+
+ if (data[i].size == 0)
+ data[i].size = strlen(data[i].ptr);
+ cl_git_write2file(
+ b.ptr, data[i].ptr, data[i].size, O_WRONLY|O_TRUNC, 0664);
+ }
+ git_index_write(idx);
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL));
+
+ cl_assert_equal_i(10, git_diff_num_deltas(diff));
+
+ /* using diff binary detection (i.e. looking for NUL byte) */
+ assert_delta_binary(diff, 0, false);
+ assert_delta_binary(diff, 1, false);
+ assert_delta_binary(diff, 2, false);
+ assert_delta_binary(diff, 3, true);
+ assert_delta_binary(diff, 4, true);
+ assert_delta_binary(diff, 5, true);
+ assert_delta_binary(diff, 6, false);
+ assert_delta_binary(diff, 7, true);
+ assert_delta_binary(diff, 8, false);
+ assert_delta_binary(diff, 9, false);
+ /* The above have been checked to match command-line Git */
+
+ git_diff_free(diff);
+
+ cl_git_pass(git_buf_sets(&b, "empty_standard_repo/0"));
+ for (i = 0; i < 10; ++i) {
+ b.ptr[b.size - 1] = '0' + i;
+ cl_git_pass(git_index_add_bypath(idx, &b.ptr[b.size - 1]));
+
+ cl_git_write2file(b.ptr, "baseline\n", 9, O_WRONLY|O_TRUNC, 0664);
+ }
+ git_index_write(idx);
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL));
+
+ cl_assert_equal_i(10, git_diff_num_deltas(diff));
+
+ /* using diff binary detection (i.e. looking for NUL byte) */
+ assert_delta_binary(diff, 0, false);
+ assert_delta_binary(diff, 1, false);
+ assert_delta_binary(diff, 2, false);
+ assert_delta_binary(diff, 3, true);
+ assert_delta_binary(diff, 4, true);
+ assert_delta_binary(diff, 5, true);
+ assert_delta_binary(diff, 6, false);
+ assert_delta_binary(diff, 7, true);
+ assert_delta_binary(diff, 8, false);
+ assert_delta_binary(diff, 9, false);
+
+ git_diff_free(diff);
+
+ git_index_free(idx);
+ git_buf_free(&b);
+}
diff --git a/tests/fetchhead/fetchhead_data.h b/tests/fetchhead/fetchhead_data.h
new file mode 100644
index 000000000..94402abd5
--- /dev/null
+++ b/tests/fetchhead/fetchhead_data.h
@@ -0,0 +1,33 @@
+
+#define FETCH_HEAD_WILDCARD_DATA_LOCAL \
+ "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_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" \
+ "6e0c7bdb9b4ed93212491ee778ca1c65047cab4e\tnot-for-merge\ttag 'nearly-dangling' 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" \
+ "6e0c7bdb9b4ed93212491ee778ca1c65047cab4e\tnot-for-merge\ttag 'nearly-dangling' of git://github.com/libgit2/TestGitRepository\n"
+
+
+#define FETCH_HEAD_EXPLICIT_DATA \
+ "0966a434eb1a025db6b71485ab63a3bfbea520b6\t\tbranch 'first-merge' of git://github.com/libgit2/TestGitRepository\n"
+
+#define FETCH_HEAD_QUOTE_DATA \
+ "0966a434eb1a025db6b71485ab63a3bfbea520b6\t\tbranch 'first's-merge' of git://github.com/libgit2/TestGitRepository\n"
diff --git a/tests/fetchhead/nonetwork.c b/tests/fetchhead/nonetwork.c
new file mode 100644
index 000000000..e7ff2ca30
--- /dev/null
+++ b/tests/fetchhead/nonetwork.c
@@ -0,0 +1,354 @@
+#include "clar_libgit2.h"
+
+#include "fileops.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);
+ g_repo = NULL;
+ }
+
+ cl_fixture_cleanup((const char *)path);
+}
+
+static void populate_fetchhead(git_vector *out, git_repository *repo)
+{
+ git_fetchhead_ref *fetchhead_ref;
+ git_oid oid;
+
+ cl_git_pass(git_oid_fromstr(&oid,
+ "49322bb17d3acc9146f98c97d078513228bbf3c0"));
+ cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 1,
+ "refs/heads/master",
+ "git://github.com/libgit2/TestGitRepository"));
+ cl_git_pass(git_vector_insert(out, fetchhead_ref));
+
+ cl_git_pass(git_oid_fromstr(&oid,
+ "0966a434eb1a025db6b71485ab63a3bfbea520b6"));
+ cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0,
+ "refs/heads/first-merge",
+ "git://github.com/libgit2/TestGitRepository"));
+ cl_git_pass(git_vector_insert(out, fetchhead_ref));
+
+ cl_git_pass(git_oid_fromstr(&oid,
+ "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1"));
+ cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0,
+ "refs/heads/no-parent",
+ "git://github.com/libgit2/TestGitRepository"));
+ cl_git_pass(git_vector_insert(out, fetchhead_ref));
+
+ cl_git_pass(git_oid_fromstr(&oid,
+ "d96c4e80345534eccee5ac7b07fc7603b56124cb"));
+ cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0,
+ "refs/tags/annotated_tag",
+ "git://github.com/libgit2/TestGitRepository"));
+ cl_git_pass(git_vector_insert(out, fetchhead_ref));
+
+ cl_git_pass(git_oid_fromstr(&oid,
+ "55a1a760df4b86a02094a904dfa511deb5655905"));
+ cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0,
+ "refs/tags/blob",
+ "git://github.com/libgit2/TestGitRepository"));
+ cl_git_pass(git_vector_insert(out, fetchhead_ref));
+
+ cl_git_pass(git_oid_fromstr(&oid,
+ "8f50ba15d49353813cc6e20298002c0d17b0a9ee"));
+ cl_git_pass(git_fetchhead_ref_create(&fetchhead_ref, &oid, 0,
+ "refs/tags/commit_tree",
+ "git://github.com/libgit2/TestGitRepository"));
+ cl_git_pass(git_vector_insert(out, fetchhead_ref));
+
+ cl_git_pass(git_fetchhead_write(repo, out));
+}
+
+void test_fetchhead_nonetwork__write(void)
+{
+ git_vector fetchhead_vector = GIT_VECTOR_INIT;
+ git_fetchhead_ref *fetchhead_ref;
+ git_buf fetchhead_buf = GIT_BUF_INIT;
+ int equals = 0;
+ size_t i;
+
+ git_vector_init(&fetchhead_vector, 6, NULL);
+
+ cl_set_cleanup(&cleanup_repository, "./test1");
+ cl_git_pass(git_repository_init(&g_repo, "./test1", 0));
+
+ populate_fetchhead(&fetchhead_vector, g_repo);
+
+ cl_git_pass(git_futils_readbuffer(&fetchhead_buf,
+ "./test1/.git/FETCH_HEAD"));
+
+ equals = (strcmp(fetchhead_buf.ptr, FETCH_HEAD_WILDCARD_DATA_LOCAL) == 0);
+
+ git_buf_free(&fetchhead_buf);
+
+ git_vector_foreach(&fetchhead_vector, i, fetchhead_ref) {
+ git_fetchhead_ref_free(fetchhead_ref);
+ }
+
+ git_vector_free(&fetchhead_vector);
+
+ cl_assert(equals);
+}
+
+typedef struct {
+ git_vector *fetchhead_vector;
+ size_t idx;
+} fetchhead_ref_cb_data;
+
+static int fetchhead_ref_cb(const char *name, const char *url,
+ const git_oid *oid, unsigned int is_merge, void *payload)
+{
+ fetchhead_ref_cb_data *cb_data = payload;
+ git_fetchhead_ref *expected;
+
+ cl_assert(payload);
+
+ expected = git_vector_get(cb_data->fetchhead_vector, cb_data->idx);
+
+ cl_assert(git_oid_cmp(&expected->oid, oid) == 0);
+ cl_assert(expected->is_merge == is_merge);
+
+ if (expected->ref_name)
+ cl_assert_equal_s(expected->ref_name, name);
+ else
+ cl_assert(name == NULL);
+
+ if (expected->remote_url)
+ cl_assert_equal_s(expected->remote_url, url);
+ else
+ cl_assert(url == NULL);
+
+ cb_data->idx++;
+
+ return 0;
+}
+
+void test_fetchhead_nonetwork__read(void)
+{
+ git_vector fetchhead_vector = GIT_VECTOR_INIT;
+ git_fetchhead_ref *fetchhead_ref;
+ fetchhead_ref_cb_data cb_data;
+ size_t i;
+
+ memset(&cb_data, 0x0, sizeof(fetchhead_ref_cb_data));
+
+ cl_set_cleanup(&cleanup_repository, "./test1");
+ cl_git_pass(git_repository_init(&g_repo, "./test1", 0));
+
+ populate_fetchhead(&fetchhead_vector, g_repo);
+
+ cb_data.fetchhead_vector = &fetchhead_vector;
+
+ cl_git_pass(git_repository_fetchhead_foreach(g_repo, fetchhead_ref_cb, &cb_data));
+
+ git_vector_foreach(&fetchhead_vector, i, fetchhead_ref) {
+ git_fetchhead_ref_free(fetchhead_ref);
+ }
+
+ git_vector_free(&fetchhead_vector);
+}
+
+static int read_old_style_cb(const char *name, const char *url,
+ const git_oid *oid, unsigned int is_merge, void *payload)
+{
+ git_oid expected;
+
+ GIT_UNUSED(payload);
+
+ git_oid_fromstr(&expected, "49322bb17d3acc9146f98c97d078513228bbf3c0");
+
+ cl_assert(name == NULL);
+ cl_assert(url == NULL);
+ cl_assert(git_oid_cmp(&expected, oid) == 0);
+ cl_assert(is_merge == 1);
+
+ return 0;
+}
+
+void test_fetchhead_nonetwork__read_old_style(void)
+{
+ cl_set_cleanup(&cleanup_repository, "./test1");
+ cl_git_pass(git_repository_init(&g_repo, "./test1", 0));
+
+ cl_git_rewritefile("./test1/.git/FETCH_HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0\n");
+
+ cl_git_pass(git_repository_fetchhead_foreach(g_repo, read_old_style_cb, NULL));
+}
+
+static int read_type_missing(const char *ref_name, const char *remote_url,
+ const git_oid *oid, unsigned int is_merge, void *payload)
+{
+ git_oid expected;
+
+ GIT_UNUSED(payload);
+
+ git_oid_fromstr(&expected, "49322bb17d3acc9146f98c97d078513228bbf3c0");
+
+ cl_assert_equal_s("name", ref_name);
+ cl_assert_equal_s("remote_url", remote_url);
+ cl_assert(git_oid_cmp(&expected, oid) == 0);
+ cl_assert(is_merge == 0);
+
+ return 0;
+}
+
+void test_fetchhead_nonetwork__type_missing(void)
+{
+ cl_set_cleanup(&cleanup_repository, "./test1");
+ cl_git_pass(git_repository_init(&g_repo, "./test1", 0));
+
+ cl_git_rewritefile("./test1/.git/FETCH_HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\t'name' of remote_url\n");
+
+ cl_git_pass(git_repository_fetchhead_foreach(g_repo, read_type_missing, NULL));
+}
+
+static int read_name_missing(const char *ref_name, const char *remote_url,
+ const git_oid *oid, unsigned int is_merge, void *payload)
+{
+ git_oid expected;
+
+ GIT_UNUSED(payload);
+
+ git_oid_fromstr(&expected, "49322bb17d3acc9146f98c97d078513228bbf3c0");
+
+ cl_assert(ref_name == NULL);
+ cl_assert_equal_s("remote_url", remote_url);
+ cl_assert(git_oid_cmp(&expected, oid) == 0);
+ cl_assert(is_merge == 0);
+
+ return 0;
+}
+
+void test_fetchhead_nonetwork__name_missing(void)
+{
+ cl_set_cleanup(&cleanup_repository, "./test1");
+ cl_git_pass(git_repository_init(&g_repo, "./test1", 0));
+
+ cl_git_rewritefile("./test1/.git/FETCH_HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\tremote_url\n");
+
+ cl_git_pass(git_repository_fetchhead_foreach(g_repo, read_name_missing, NULL));
+}
+
+static int read_noop(const char *ref_name, const char *remote_url,
+ const git_oid *oid, unsigned int is_merge, void *payload)
+{
+ GIT_UNUSED(ref_name);
+ GIT_UNUSED(remote_url);
+ GIT_UNUSED(oid);
+ GIT_UNUSED(is_merge);
+ GIT_UNUSED(payload);
+
+ return 0;
+}
+
+void test_fetchhead_nonetwork__nonexistent(void)
+{
+ int error;
+
+ cl_set_cleanup(&cleanup_repository, "./test1");
+ cl_git_pass(git_repository_init(&g_repo, "./test1", 0));
+
+ cl_git_fail((error = git_repository_fetchhead_foreach(g_repo, read_noop, NULL)));
+ cl_assert(error == GIT_ENOTFOUND);
+}
+
+void test_fetchhead_nonetwork__invalid_unterminated_last_line(void)
+{
+ cl_set_cleanup(&cleanup_repository, "./test1");
+ cl_git_pass(git_repository_init(&g_repo, "./test1", 0));
+
+ cl_git_rewritefile("./test1/.git/FETCH_HEAD", "unterminated");
+ cl_git_fail(git_repository_fetchhead_foreach(g_repo, read_noop, NULL));
+}
+
+void test_fetchhead_nonetwork__invalid_oid(void)
+{
+ cl_set_cleanup(&cleanup_repository, "./test1");
+ cl_git_pass(git_repository_init(&g_repo, "./test1", 0));
+
+ cl_git_rewritefile("./test1/.git/FETCH_HEAD", "shortoid\n");
+ cl_git_fail(git_repository_fetchhead_foreach(g_repo, read_noop, NULL));
+}
+
+void test_fetchhead_nonetwork__invalid_for_merge(void)
+{
+ cl_set_cleanup(&cleanup_repository, "./test1");
+ cl_git_pass(git_repository_init(&g_repo, "./test1", 0));
+
+ cl_git_rewritefile("./test1/.git/FETCH_HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0\tinvalid-merge\t\n");
+ cl_git_fail(git_repository_fetchhead_foreach(g_repo, read_noop, NULL));
+
+ cl_assert(git__prefixcmp(giterr_last()->message, "Invalid for-merge") == 0);
+}
+
+void test_fetchhead_nonetwork__invalid_description(void)
+{
+ cl_set_cleanup(&cleanup_repository, "./test1");
+ cl_git_pass(git_repository_init(&g_repo, "./test1", 0));
+
+ cl_git_rewritefile("./test1/.git/FETCH_HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\n");
+ cl_git_fail(git_repository_fetchhead_foreach(g_repo, read_noop, NULL));
+
+ cl_assert(git__prefixcmp(giterr_last()->message, "Invalid description") == 0);
+}
+
+static int assert_master_for_merge(const char *ref, const char *url, const git_oid *id, unsigned int is_merge, void *data)
+{
+ GIT_UNUSED(url);
+ GIT_UNUSED(id);
+ GIT_UNUSED(data);
+
+ if (!strcmp("refs/heads/master", ref) && !is_merge)
+ return -1;
+
+ return 0;
+}
+
+void test_fetchhead_nonetwork__unborn_with_upstream(void)
+{
+ git_repository *repo;
+ git_remote *remote;
+
+ /* Create an empty repo to clone from */
+ cl_set_cleanup(&cleanup_repository, "./test1");
+ cl_git_pass(git_repository_init(&g_repo, "./test1", 0));
+ cl_set_cleanup(&cleanup_repository, "./repowithunborn");
+ cl_git_pass(git_clone(&repo, "./test1", "./repowithunborn", NULL));
+
+ /* Simulate someone pushing to it by changing to one that has stuff */
+ cl_git_pass(git_remote_load(&remote, repo, "origin"));
+ cl_git_pass(git_remote_set_url(remote, cl_fixture("testrepo.git")));
+ cl_git_pass(git_remote_save(remote));
+
+ cl_git_pass(git_remote_fetch(remote, NULL, NULL));
+ git_remote_free(remote);
+
+ cl_git_pass(git_repository_fetchhead_foreach(repo, assert_master_for_merge, NULL));
+
+ git_repository_free(repo);
+ cl_fixture_cleanup("./repowithunborn");
+}
+
+void test_fetchhead_nonetwork__quote_in_branch_name(void)
+{
+ cl_set_cleanup(&cleanup_repository, "./test1");
+ cl_git_pass(git_repository_init(&g_repo, "./test1", 0));
+
+ cl_git_rewritefile("./test1/.git/FETCH_HEAD", FETCH_HEAD_QUOTE_DATA);
+ cl_git_pass(git_repository_fetchhead_foreach(g_repo, read_noop, NULL));
+}
diff --git a/tests/filter/blob.c b/tests/filter/blob.c
new file mode 100644
index 000000000..48edb8405
--- /dev/null
+++ b/tests/filter/blob.c
@@ -0,0 +1,117 @@
+#include "clar_libgit2.h"
+#include "crlf.h"
+
+static git_repository *g_repo = NULL;
+
+void test_filter_blob__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("crlf");
+ cl_git_mkfile("crlf/.gitattributes",
+ "*.txt text\n*.bin binary\n"
+ "*.crlf text eol=crlf\n"
+ "*.lf text eol=lf\n"
+ "*.ident text ident\n"
+ "*.identcrlf ident text eol=crlf\n"
+ "*.identlf ident text eol=lf\n");
+}
+
+void test_filter_blob__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_filter_blob__all_crlf(void)
+{
+ git_blob *blob;
+ git_buf buf = { 0 };
+
+ cl_git_pass(git_revparse_single(
+ (git_object **)&blob, g_repo, "a9a2e891")); /* all-crlf */
+
+ cl_assert_equal_s(ALL_CRLF_TEXT_RAW, git_blob_rawcontent(blob));
+
+ cl_git_pass(git_blob_filtered_content(&buf, blob, "file.bin", 1));
+
+ cl_assert_equal_s(ALL_CRLF_TEXT_RAW, buf.ptr);
+
+ cl_git_pass(git_blob_filtered_content(&buf, blob, "file.crlf", 1));
+
+ /* in this case, raw content has crlf in it already */
+ cl_assert_equal_s(ALL_CRLF_TEXT_AS_CRLF, buf.ptr);
+
+ cl_git_pass(git_blob_filtered_content(&buf, blob, "file.lf", 1));
+
+ /* we never convert CRLF -> LF on platforms that have LF */
+ cl_assert_equal_s(ALL_CRLF_TEXT_AS_CRLF, buf.ptr);
+
+ git_buf_free(&buf);
+ git_blob_free(blob);
+}
+
+void test_filter_blob__sanitizes(void)
+{
+ git_blob *blob;
+ git_buf buf;
+
+ cl_git_pass(git_revparse_single(
+ (git_object **)&blob, g_repo, "e69de29")); /* zero-byte */
+
+ cl_assert_equal_i(0, git_blob_rawsize(blob));
+ cl_assert_equal_s("", git_blob_rawcontent(blob));
+
+ memset(&buf, 0, sizeof(git_buf));
+ cl_git_pass(git_blob_filtered_content(&buf, blob, "file.bin", 1));
+ cl_assert_equal_sz(0, buf.size);
+ cl_assert_equal_s("", buf.ptr);
+ git_buf_free(&buf);
+
+ memset(&buf, 0, sizeof(git_buf));
+ cl_git_pass(git_blob_filtered_content(&buf, blob, "file.crlf", 1));
+ cl_assert_equal_sz(0, buf.size);
+ cl_assert_equal_s("", buf.ptr);
+ git_buf_free(&buf);
+
+ memset(&buf, 0, sizeof(git_buf));
+ cl_git_pass(git_blob_filtered_content(&buf, blob, "file.lf", 1));
+ cl_assert_equal_sz(0, buf.size);
+ cl_assert_equal_s("", buf.ptr);
+ git_buf_free(&buf);
+
+ git_blob_free(blob);
+}
+
+void test_filter_blob__ident(void)
+{
+ git_oid id;
+ git_blob *blob;
+ git_buf buf = { 0 };
+
+ cl_git_mkfile("crlf/test.ident", "Some text\n$Id$\nGoes there\n");
+ cl_git_pass(git_blob_create_fromworkdir(&id, g_repo, "test.ident"));
+ cl_git_pass(git_blob_lookup(&blob, g_repo, &id));
+ cl_assert_equal_s(
+ "Some text\n$Id$\nGoes there\n", git_blob_rawcontent(blob));
+ git_blob_free(blob);
+
+ cl_git_mkfile("crlf/test.ident", "Some text\n$Id: Any old just you want$\nGoes there\n");
+ cl_git_pass(git_blob_create_fromworkdir(&id, g_repo, "test.ident"));
+ cl_git_pass(git_blob_lookup(&blob, g_repo, &id));
+ cl_assert_equal_s(
+ "Some text\n$Id$\nGoes there\n", git_blob_rawcontent(blob));
+
+ cl_git_pass(git_blob_filtered_content(&buf, blob, "filter.bin", 1));
+ cl_assert_equal_s(
+ "Some text\n$Id$\nGoes there\n", buf.ptr);
+
+ cl_git_pass(git_blob_filtered_content(&buf, blob, "filter.identcrlf", 1));
+ cl_assert_equal_s(
+ "Some text\r\n$Id: 3164f585d548ac68027d22b104f2d8100b2b6845$\r\nGoes there\r\n", buf.ptr);
+
+ cl_git_pass(git_blob_filtered_content(&buf, blob, "filter.identlf", 1));
+ cl_assert_equal_s(
+ "Some text\n$Id: 3164f585d548ac68027d22b104f2d8100b2b6845$\nGoes there\n", buf.ptr);
+
+ git_buf_free(&buf);
+ git_blob_free(blob);
+
+}
diff --git a/tests/filter/crlf.c b/tests/filter/crlf.c
new file mode 100644
index 000000000..a31dac965
--- /dev/null
+++ b/tests/filter/crlf.c
@@ -0,0 +1,239 @@
+#include "clar_libgit2.h"
+#include "git2/sys/filter.h"
+#include "buffer.h"
+
+static git_repository *g_repo = NULL;
+
+void test_filter_crlf__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("crlf");
+
+ cl_git_mkfile("crlf/.gitattributes",
+ "*.txt text\n*.bin binary\n*.crlf text eol=crlf\n*.lf text eol=lf\n");
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", true);
+}
+
+void test_filter_crlf__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_filter_crlf__to_worktree(void)
+{
+ git_filter_list *fl;
+ git_filter *crlf;
+ git_buf in = { 0 }, out = { 0 };
+
+ cl_git_pass(git_filter_list_new(
+ &fl, g_repo, GIT_FILTER_TO_WORKTREE, 0));
+
+ crlf = git_filter_lookup(GIT_FILTER_CRLF);
+ cl_assert(crlf != NULL);
+
+ cl_git_pass(git_filter_list_push(fl, crlf, NULL));
+
+ in.ptr = "Some text\nRight here\n";
+ in.size = strlen(in.ptr);
+
+ cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in));
+
+#ifdef GIT_WIN32
+ cl_assert_equal_s("Some text\r\nRight here\r\n", out.ptr);
+#else
+ cl_assert_equal_s("Some text\nRight here\n", out.ptr);
+#endif
+
+ git_filter_list_free(fl);
+ git_buf_free(&out);
+}
+
+void test_filter_crlf__to_odb(void)
+{
+ git_filter_list *fl;
+ git_filter *crlf;
+ git_buf in = { 0 }, out = { 0 };
+
+ cl_git_pass(git_filter_list_new(
+ &fl, g_repo, GIT_FILTER_TO_ODB, 0));
+
+ crlf = git_filter_lookup(GIT_FILTER_CRLF);
+ cl_assert(crlf != NULL);
+
+ cl_git_pass(git_filter_list_push(fl, crlf, NULL));
+
+ in.ptr = "Some text\r\nRight here\r\n";
+ in.size = strlen(in.ptr);
+
+ cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in));
+
+ cl_assert_equal_s("Some text\nRight here\n", out.ptr);
+
+ git_filter_list_free(fl);
+ git_buf_free(&out);
+}
+
+void test_filter_crlf__with_safecrlf(void)
+{
+ git_filter_list *fl;
+ git_filter *crlf;
+ git_buf in = {0}, out = GIT_BUF_INIT;
+
+ cl_repo_set_bool(g_repo, "core.safecrlf", true);
+
+ cl_git_pass(git_filter_list_new(
+ &fl, g_repo, GIT_FILTER_TO_ODB, 0));
+
+ crlf = git_filter_lookup(GIT_FILTER_CRLF);
+ cl_assert(crlf != NULL);
+
+ cl_git_pass(git_filter_list_push(fl, crlf, NULL));
+
+ /* Normalized \r\n succeeds with safecrlf */
+ in.ptr = "Normal\r\nCRLF\r\nline-endings.\r\n";
+ in.size = strlen(in.ptr);
+
+ cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in));
+ cl_assert_equal_s("Normal\nCRLF\nline-endings.\n", out.ptr);
+
+ /* Mix of line endings fails with safecrlf */
+ in.ptr = "Mixed\nup\r\nLF\nand\r\nCRLF\nline-endings.\r\n";
+ in.size = strlen(in.ptr);
+
+ cl_git_fail(git_filter_list_apply_to_data(&out, fl, &in));
+ cl_assert_equal_i(giterr_last()->klass, GITERR_FILTER);
+
+ /* Normalized \n is reversible, so does not fail with safecrlf */
+ in.ptr = "Normal\nLF\nonly\nline-endings.\n";
+ in.size = strlen(in.ptr);
+
+ cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in));
+ cl_assert_equal_s(in.ptr, out.ptr);
+
+ git_filter_list_free(fl);
+ git_buf_free(&out);
+}
+
+void test_filter_crlf__with_safecrlf_and_unsafe_allowed(void)
+{
+ git_filter_list *fl;
+ git_filter *crlf;
+ git_buf in = {0}, out = GIT_BUF_INIT;
+
+ cl_repo_set_bool(g_repo, "core.safecrlf", true);
+
+ cl_git_pass(git_filter_list_new(
+ &fl, g_repo, GIT_FILTER_TO_ODB, GIT_FILTER_OPT_ALLOW_UNSAFE));
+
+ crlf = git_filter_lookup(GIT_FILTER_CRLF);
+ cl_assert(crlf != NULL);
+
+ cl_git_pass(git_filter_list_push(fl, crlf, NULL));
+
+ /* Normalized \r\n succeeds with safecrlf */
+ in.ptr = "Normal\r\nCRLF\r\nline-endings.\r\n";
+ in.size = strlen(in.ptr);
+
+ cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in));
+ cl_assert_equal_s("Normal\nCRLF\nline-endings.\n", out.ptr);
+
+ /* Mix of line endings fails with safecrlf, but allowed to pass */
+ in.ptr = "Mixed\nup\r\nLF\nand\r\nCRLF\nline-endings.\r\n";
+ in.size = strlen(in.ptr);
+
+ cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in));
+ /* TODO: check for warning */
+ cl_assert_equal_s("Mixed\nup\nLF\nand\nCRLF\nline-endings.\n", out.ptr);
+
+ /* Normalized \n fails with safecrlf, but allowed to pass */
+ in.ptr = "Normal\nLF\nonly\nline-endings.\n";
+ in.size = strlen(in.ptr);
+
+ cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in));
+ /* TODO: check for warning */
+ cl_assert_equal_s("Normal\nLF\nonly\nline-endings.\n", out.ptr);
+
+ git_filter_list_free(fl);
+ git_buf_free(&out);
+}
+
+void test_filter_crlf__no_safecrlf(void)
+{
+ git_filter_list *fl;
+ git_filter *crlf;
+ git_buf in = {0}, out = GIT_BUF_INIT;
+
+ cl_git_pass(git_filter_list_new(
+ &fl, g_repo, GIT_FILTER_TO_ODB, 0));
+
+ crlf = git_filter_lookup(GIT_FILTER_CRLF);
+ cl_assert(crlf != NULL);
+
+ cl_git_pass(git_filter_list_push(fl, crlf, NULL));
+
+ /* Normalized \r\n succeeds with safecrlf */
+ in.ptr = "Normal\r\nCRLF\r\nline-endings.\r\n";
+ in.size = strlen(in.ptr);
+
+ cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in));
+ cl_assert_equal_s("Normal\nCRLF\nline-endings.\n", out.ptr);
+
+ /* Mix of line endings fails with safecrlf */
+ in.ptr = "Mixed\nup\r\nLF\nand\r\nCRLF\nline-endings.\r\n";
+ in.size = strlen(in.ptr);
+
+ cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in));
+ cl_assert_equal_s("Mixed\nup\nLF\nand\nCRLF\nline-endings.\n", out.ptr);
+
+ /* Normalized \n fails with safecrlf */
+ in.ptr = "Normal\nLF\nonly\nline-endings.\n";
+ in.size = strlen(in.ptr);
+
+ cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in));
+ cl_assert_equal_s("Normal\nLF\nonly\nline-endings.\n", out.ptr);
+
+ git_filter_list_free(fl);
+ git_buf_free(&out);
+}
+
+void test_filter_crlf__safecrlf_warn(void)
+{
+ git_filter_list *fl;
+ git_filter *crlf;
+ git_buf in = {0}, out = GIT_BUF_INIT;
+
+ cl_repo_set_string(g_repo, "core.safecrlf", "warn");
+
+ cl_git_pass(git_filter_list_new(
+ &fl, g_repo, GIT_FILTER_TO_ODB, 0));
+
+ crlf = git_filter_lookup(GIT_FILTER_CRLF);
+ cl_assert(crlf != NULL);
+
+ cl_git_pass(git_filter_list_push(fl, crlf, NULL));
+
+ /* Normalized \r\n succeeds with safecrlf=warn */
+ in.ptr = "Normal\r\nCRLF\r\nline-endings.\r\n";
+ in.size = strlen(in.ptr);
+
+ cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in));
+ cl_assert_equal_s("Normal\nCRLF\nline-endings.\n", out.ptr);
+
+ /* Mix of line endings succeeds with safecrlf=warn */
+ in.ptr = "Mixed\nup\r\nLF\nand\r\nCRLF\nline-endings.\r\n";
+ in.size = strlen(in.ptr);
+
+ cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in));
+ /* TODO: check for warning */
+ cl_assert_equal_s("Mixed\nup\nLF\nand\nCRLF\nline-endings.\n", out.ptr);
+
+ /* Normalized \n is reversible, so does not fail with safecrlf=warn */
+ in.ptr = "Normal\nLF\nonly\nline-endings.\n";
+ in.size = strlen(in.ptr);
+
+ cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in));
+ cl_assert_equal_s(in.ptr, out.ptr);
+
+ git_filter_list_free(fl);
+ git_buf_free(&out);
+}
diff --git a/tests/filter/crlf.h b/tests/filter/crlf.h
new file mode 100644
index 000000000..9cb98ad4c
--- /dev/null
+++ b/tests/filter/crlf.h
@@ -0,0 +1,25 @@
+#ifndef INCLUDE_filter_crlf_h__
+#define INCLUDE_filter_crlf_h__
+
+/*
+ * file content for files in the resources/crlf repository
+ */
+
+#define UTF8_BOM "\xEF\xBB\xBF"
+
+#define ALL_CRLF_TEXT_RAW "crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n"
+#define ALL_LF_TEXT_RAW "lf\nlf\nlf\nlf\nlf\n"
+#define MORE_CRLF_TEXT_RAW "crlf\r\ncrlf\r\nlf\ncrlf\r\ncrlf\r\n"
+#define MORE_LF_TEXT_RAW "lf\nlf\ncrlf\r\nlf\nlf\n"
+
+#define ALL_CRLF_TEXT_AS_CRLF ALL_CRLF_TEXT_RAW
+#define ALL_LF_TEXT_AS_CRLF "lf\r\nlf\r\nlf\r\nlf\r\nlf\r\n"
+#define MORE_CRLF_TEXT_AS_CRLF "crlf\r\ncrlf\r\nlf\r\ncrlf\r\ncrlf\r\n"
+#define MORE_LF_TEXT_AS_CRLF "lf\r\nlf\r\ncrlf\r\nlf\r\nlf\r\n"
+
+#define ALL_CRLF_TEXT_AS_LF "crlf\ncrlf\ncrlf\ncrlf\n"
+#define ALL_LF_TEXT_AS_LF ALL_LF_TEXT_RAW
+#define MORE_CRLF_TEXT_AS_LF "crlf\ncrlf\nlf\ncrlf\ncrlf\n"
+#define MORE_LF_TEXT_AS_LF "lf\nlf\ncrlf\nlf\nlf\n"
+
+#endif
diff --git a/tests/filter/custom.c b/tests/filter/custom.c
new file mode 100644
index 000000000..0fd7c3744
--- /dev/null
+++ b/tests/filter/custom.c
@@ -0,0 +1,338 @@
+#include "clar_libgit2.h"
+#include "posix.h"
+#include "blob.h"
+#include "filter.h"
+#include "buf_text.h"
+#include "git2/sys/filter.h"
+#include "git2/sys/repository.h"
+
+/* going TO_WORKDIR, filters are executed low to high
+ * going TO_ODB, filters are executed high to low
+ */
+#define BITFLIP_FILTER_PRIORITY -1
+#define REVERSE_FILTER_PRIORITY -2
+
+#define VERY_SECURE_ENCRYPTION(b) ((b) ^ 0xff)
+
+#ifdef GIT_WIN32
+# define NEWLINE "\r\n"
+#else
+# define NEWLINE "\n"
+#endif
+
+static char workdir_data[] =
+ "some simple" NEWLINE
+ "data" NEWLINE
+ "that will be" NEWLINE
+ "trivially" NEWLINE
+ "scrambled." NEWLINE;
+
+/* Represents the data above scrambled (bits flipped) after \r\n -> \n
+ * conversion, then bytewise reversed
+ */
+static unsigned char bitflipped_and_reversed_data[] =
+ { 0xf5, 0xd1, 0x9b, 0x9a, 0x93, 0x9d, 0x92, 0x9e, 0x8d, 0x9c, 0x8c,
+ 0xf5, 0x86, 0x93, 0x93, 0x9e, 0x96, 0x89, 0x96, 0x8d, 0x8b, 0xf5,
+ 0x9a, 0x9d, 0xdf, 0x93, 0x93, 0x96, 0x88, 0xdf, 0x8b, 0x9e, 0x97,
+ 0x8b, 0xf5, 0x9e, 0x8b, 0x9e, 0x9b, 0xf5, 0x9a, 0x93, 0x8f, 0x92,
+ 0x96, 0x8c, 0xdf, 0x9a, 0x92, 0x90, 0x8c };
+
+#define BITFLIPPED_AND_REVERSED_DATA_LEN 51
+
+static git_repository *g_repo = NULL;
+
+static void register_custom_filters(void);
+
+void test_filter_custom__initialize(void)
+{
+ register_custom_filters();
+
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+
+ cl_git_mkfile(
+ "empty_standard_repo/.gitattributes",
+ "hero* bitflip reverse\n"
+ "herofile text\n"
+ "heroflip -reverse binary\n"
+ "*.bin binary\n");
+}
+
+void test_filter_custom__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+ g_repo = NULL;
+}
+
+static int bitflip_filter_apply(
+ git_filter *self,
+ void **payload,
+ git_buf *to,
+ const git_buf *from,
+ const git_filter_source *source)
+{
+ const unsigned char *src = (const unsigned char *)from->ptr;
+ unsigned char *dst;
+ size_t i;
+
+ GIT_UNUSED(self); GIT_UNUSED(payload);
+
+ /* verify that attribute path match worked as expected */
+ cl_assert_equal_i(
+ 0, git__strncmp("hero", git_filter_source_path(source), 4));
+
+ if (!from->size)
+ return 0;
+
+ cl_git_pass(git_buf_grow(to, from->size));
+
+ dst = (unsigned char *)to->ptr;
+
+ for (i = 0; i < from->size; i++)
+ dst[i] = VERY_SECURE_ENCRYPTION(src[i]);
+
+ to->size = from->size;
+
+ return 0;
+}
+
+static void bitflip_filter_free(git_filter *f)
+{
+ git__free(f);
+}
+
+static git_filter *create_bitflip_filter(void)
+{
+ git_filter *filter = git__calloc(1, sizeof(git_filter));
+ cl_assert(filter);
+
+ filter->version = GIT_FILTER_VERSION;
+ filter->attributes = "+bitflip";
+ filter->shutdown = bitflip_filter_free;
+ filter->apply = bitflip_filter_apply;
+
+ return filter;
+}
+
+
+static int reverse_filter_apply(
+ git_filter *self,
+ void **payload,
+ git_buf *to,
+ const git_buf *from,
+ const git_filter_source *source)
+{
+ const unsigned char *src = (const unsigned char *)from->ptr;
+ const unsigned char *end = src + from->size;
+ unsigned char *dst;
+
+ GIT_UNUSED(self); GIT_UNUSED(payload); GIT_UNUSED(source);
+
+ /* verify that attribute path match worked as expected */
+ cl_assert_equal_i(
+ 0, git__strncmp("hero", git_filter_source_path(source), 4));
+
+ if (!from->size)
+ return 0;
+
+ cl_git_pass(git_buf_grow(to, from->size));
+
+ dst = (unsigned char *)to->ptr + from->size - 1;
+
+ while (src < end)
+ *dst-- = *src++;
+
+ to->size = from->size;
+
+ return 0;
+}
+
+static void reverse_filter_free(git_filter *f)
+{
+ git__free(f);
+}
+
+static git_filter *create_reverse_filter(const char *attrs)
+{
+ git_filter *filter = git__calloc(1, sizeof(git_filter));
+ cl_assert(filter);
+
+ filter->version = GIT_FILTER_VERSION;
+ filter->attributes = attrs;
+ filter->shutdown = reverse_filter_free;
+ filter->apply = reverse_filter_apply;
+
+ return filter;
+}
+
+static void register_custom_filters(void)
+{
+ static int filters_registered = 0;
+
+ if (!filters_registered) {
+ cl_git_pass(git_filter_register(
+ "bitflip", create_bitflip_filter(), BITFLIP_FILTER_PRIORITY));
+
+ cl_git_pass(git_filter_register(
+ "reverse", create_reverse_filter("+reverse"),
+ REVERSE_FILTER_PRIORITY));
+
+ /* re-register reverse filter with standard filter=xyz priority */
+ cl_git_pass(git_filter_register(
+ "pre-reverse",
+ create_reverse_filter("+prereverse"),
+ GIT_FILTER_DRIVER_PRIORITY));
+
+ filters_registered = 1;
+ }
+}
+
+
+void test_filter_custom__to_odb(void)
+{
+ git_filter_list *fl;
+ git_buf out = { 0 };
+ git_buf in = GIT_BUF_INIT_CONST(workdir_data, strlen(workdir_data));
+
+ cl_git_pass(git_filter_list_load(
+ &fl, g_repo, NULL, "herofile", GIT_FILTER_TO_ODB, 0));
+
+ cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in));
+
+ cl_assert_equal_i(BITFLIPPED_AND_REVERSED_DATA_LEN, out.size);
+
+ cl_assert_equal_i(
+ 0, memcmp(bitflipped_and_reversed_data, out.ptr, out.size));
+
+ git_filter_list_free(fl);
+ git_buf_free(&out);
+}
+
+void test_filter_custom__to_workdir(void)
+{
+ git_filter_list *fl;
+ git_buf out = { 0 };
+ git_buf in = GIT_BUF_INIT_CONST(
+ bitflipped_and_reversed_data, BITFLIPPED_AND_REVERSED_DATA_LEN);
+
+ cl_git_pass(git_filter_list_load(
+ &fl, g_repo, NULL, "herofile", GIT_FILTER_TO_WORKTREE, 0));
+
+ cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in));
+
+ cl_assert_equal_i(strlen(workdir_data), out.size);
+
+ cl_assert_equal_i(
+ 0, memcmp(workdir_data, out.ptr, out.size));
+
+ git_filter_list_free(fl);
+ git_buf_free(&out);
+}
+
+void test_filter_custom__can_register_a_custom_filter_in_the_repository(void)
+{
+ git_filter_list *fl;
+
+ cl_git_pass(git_filter_list_load(
+ &fl, g_repo, NULL, "herofile", GIT_FILTER_TO_WORKTREE, 0));
+ /* expect: bitflip, reverse, crlf */
+ cl_assert_equal_sz(3, git_filter_list_length(fl));
+ git_filter_list_free(fl);
+
+ cl_git_pass(git_filter_list_load(
+ &fl, g_repo, NULL, "herocorp", GIT_FILTER_TO_WORKTREE, 0));
+ /* expect: bitflip, reverse - possibly crlf depending on global config */
+ {
+ size_t flen = git_filter_list_length(fl);
+ cl_assert(flen == 2 || flen == 3);
+ }
+ git_filter_list_free(fl);
+
+ cl_git_pass(git_filter_list_load(
+ &fl, g_repo, NULL, "hero.bin", GIT_FILTER_TO_WORKTREE, 0));
+ /* expect: bitflip, reverse */
+ cl_assert_equal_sz(2, git_filter_list_length(fl));
+ git_filter_list_free(fl);
+
+ cl_git_pass(git_filter_list_load(
+ &fl, g_repo, NULL, "heroflip", GIT_FILTER_TO_WORKTREE, 0));
+ /* expect: bitflip (because of -reverse) */
+ cl_assert_equal_sz(1, git_filter_list_length(fl));
+ git_filter_list_free(fl);
+
+ cl_git_pass(git_filter_list_load(
+ &fl, g_repo, NULL, "doesntapplytome.bin",
+ GIT_FILTER_TO_WORKTREE, 0));
+ /* expect: none */
+ cl_assert_equal_sz(0, git_filter_list_length(fl));
+ git_filter_list_free(fl);
+}
+
+void test_filter_custom__order_dependency(void)
+{
+ git_index *index;
+ git_blob *blob;
+ git_buf buf = { 0 };
+
+ /* so if ident and reverse are used together, an interesting thing
+ * happens - a reversed "$Id$" string is no longer going to trigger
+ * ident correctly. When checking out, the filters should be applied
+ * in order CLRF, then ident, then reverse, so ident expansion should
+ * work correctly. On check in, the content should be reversed, then
+ * ident, then CRLF filtered. Let's make sure that works...
+ */
+
+ cl_git_mkfile(
+ "empty_standard_repo/.gitattributes",
+ "hero.*.rev-ident text ident prereverse eol=lf\n");
+
+ cl_git_mkfile(
+ "empty_standard_repo/hero.1.rev-ident",
+ "This is a test\n$Id$\nHave fun!\n");
+
+ cl_git_mkfile(
+ "empty_standard_repo/hero.2.rev-ident",
+ "Another test\n$dI$\nCrazy!\n");
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_index_add_bypath(index, "hero.1.rev-ident"));
+ cl_git_pass(git_index_add_bypath(index, "hero.2.rev-ident"));
+ cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "Filter chains\n");
+ git_index_free(index);
+
+ cl_git_pass(git_blob_lookup(&blob, g_repo,
+ & git_index_get_bypath(index, "hero.1.rev-ident", 0)->id));
+ cl_assert_equal_s(
+ "\n!nuf evaH\n$dI$\ntset a si sihT", git_blob_rawcontent(blob));
+ cl_git_pass(git_blob_filtered_content(&buf, blob, "hero.1.rev-ident", 0));
+ /* no expansion because id was reversed at checkin and now at ident
+ * time, reverse is not applied yet */
+ cl_assert_equal_s(
+ "This is a test\n$Id$\nHave fun!\n", buf.ptr);
+ git_blob_free(blob);
+
+ cl_git_pass(git_blob_lookup(&blob, g_repo,
+ & git_index_get_bypath(index, "hero.2.rev-ident", 0)->id));
+ cl_assert_equal_s(
+ "\n!yzarC\n$Id$\ntset rehtonA", git_blob_rawcontent(blob));
+ cl_git_pass(git_blob_filtered_content(&buf, blob, "hero.2.rev-ident", 0));
+ /* expansion because reverse was applied at checkin and at ident time,
+ * reverse is not applied yet */
+ cl_assert_equal_s(
+ "Another test\n$59001fe193103b1016b27027c0c827d036fd0ac8 :dI$\nCrazy!\n", buf.ptr);
+ cl_assert_equal_i(0, git_oid_strcmp(
+ git_blob_id(blob), "8ca0df630d728c0c72072b6101b301391ef10095"));
+ git_blob_free(blob);
+
+ git_buf_free(&buf);
+}
+
+void test_filter_custom__filter_registry_failure_cases(void)
+{
+ git_filter fake = { GIT_FILTER_VERSION, 0 };
+
+ cl_assert_equal_i(GIT_EEXISTS, git_filter_register("bitflip", &fake, 0));
+
+ cl_git_fail(git_filter_unregister(GIT_FILTER_CRLF));
+ cl_git_fail(git_filter_unregister(GIT_FILTER_IDENT));
+ cl_assert_equal_i(GIT_ENOTFOUND, git_filter_unregister("not-a-filter"));
+}
diff --git a/tests/filter/ident.c b/tests/filter/ident.c
new file mode 100644
index 000000000..2c9a3eb68
--- /dev/null
+++ b/tests/filter/ident.c
@@ -0,0 +1,133 @@
+#include "clar_libgit2.h"
+#include "git2/sys/filter.h"
+
+static git_repository *g_repo = NULL;
+
+void test_filter_ident__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("crlf");
+}
+
+void test_filter_ident__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+static void add_blob_and_filter(
+ const char *data,
+ git_filter_list *fl,
+ const char *expected)
+{
+ git_oid id;
+ git_blob *blob;
+ git_buf out = { 0 };
+
+ cl_git_mkfile("crlf/identtest", data);
+ cl_git_pass(git_blob_create_fromworkdir(&id, g_repo, "identtest"));
+ cl_git_pass(git_blob_lookup(&blob, g_repo, &id));
+
+ cl_git_pass(git_filter_list_apply_to_blob(&out, fl, blob));
+
+ cl_assert_equal_s(expected, out.ptr);
+
+ git_blob_free(blob);
+ git_buf_free(&out);
+}
+
+void test_filter_ident__to_worktree(void)
+{
+ git_filter_list *fl;
+ git_filter *ident;
+
+ cl_git_pass(git_filter_list_new(
+ &fl, g_repo, GIT_FILTER_TO_WORKTREE, 0));
+
+ ident = git_filter_lookup(GIT_FILTER_IDENT);
+ cl_assert(ident != NULL);
+
+ cl_git_pass(git_filter_list_push(fl, ident, NULL));
+
+ add_blob_and_filter(
+ "Hello\n$Id$\nFun stuff\n", fl,
+ "Hello\n$Id: b69e2387aafcaf73c4de5b9ab59abe27fdadee30$\nFun stuff\n");
+ add_blob_and_filter(
+ "Hello\n$Id: Junky$\nFun stuff\n", fl,
+ "Hello\n$Id: 45cd107a7102911cb2a7df08404674327fa050b9$\nFun stuff\n");
+ add_blob_and_filter(
+ "$Id$\nAt the start\n", fl,
+ "$Id: b13415c767abc196fb95bd17070e8c1113e32160$\nAt the start\n");
+ add_blob_and_filter(
+ "At the end\n$Id$", fl,
+ "At the end\n$Id: 1344925c6bc65b34c5a7b50f86bf688e48e9a272$");
+ add_blob_and_filter(
+ "$Id$", fl,
+ "$Id: b3f5ebfb5843bc43ceecff6d4f26bb37c615beb1$");
+ add_blob_and_filter(
+ "$Id: Some sort of junk goes here$", fl,
+ "$Id: ab2dd3853c7c9a4bff55aca2bea077a73c32ac06$");
+
+ add_blob_and_filter("$Id: ", fl, "$Id: ");
+ add_blob_and_filter("$Id", fl, "$Id");
+ add_blob_and_filter("$I", fl, "$I");
+ add_blob_and_filter("Id$", fl, "Id$");
+
+ git_filter_list_free(fl);
+}
+
+void test_filter_ident__to_odb(void)
+{
+ git_filter_list *fl;
+ git_filter *ident;
+
+ cl_git_pass(git_filter_list_new(
+ &fl, g_repo, GIT_FILTER_TO_ODB, 0));
+
+ ident = git_filter_lookup(GIT_FILTER_IDENT);
+ cl_assert(ident != NULL);
+
+ cl_git_pass(git_filter_list_push(fl, ident, NULL));
+
+ add_blob_and_filter(
+ "Hello\n$Id$\nFun stuff\n",
+ fl, "Hello\n$Id$\nFun stuff\n");
+ add_blob_and_filter(
+ "Hello\n$Id: b69e2387aafcaf73c4de5b9ab59abe27fdadee30$\nFun stuff\n",
+ fl, "Hello\n$Id$\nFun stuff\n");
+ add_blob_and_filter(
+ "Hello\n$Id: Any junk you may have left here$\nFun stuff\n",
+ fl, "Hello\n$Id$\nFun stuff\n");
+ add_blob_and_filter(
+ "Hello\n$Id:$\nFun stuff\n",
+ fl, "Hello\n$Id$\nFun stuff\n");
+ add_blob_and_filter(
+ "Hello\n$Id:x$\nFun stuff\n",
+ fl, "Hello\n$Id$\nFun stuff\n");
+
+ add_blob_and_filter(
+ "$Id$\nAt the start\n", fl, "$Id$\nAt the start\n");
+ add_blob_and_filter(
+ "$Id: lots of random text that should be removed from here$\nAt the start\n", fl, "$Id$\nAt the start\n");
+ add_blob_and_filter(
+ "$Id: lots of random text that should not be removed without a terminator\nAt the start\n", fl, "$Id: lots of random text that should not be removed without a terminator\nAt the start\n");
+
+ add_blob_and_filter(
+ "At the end\n$Id$", fl, "At the end\n$Id$");
+ add_blob_and_filter(
+ "At the end\n$Id:$", fl, "At the end\n$Id$");
+ add_blob_and_filter(
+ "At the end\n$Id:asdfasdf$", fl, "At the end\n$Id$");
+ add_blob_and_filter(
+ "At the end\n$Id", fl, "At the end\n$Id");
+ add_blob_and_filter(
+ "At the end\n$IddI", fl, "At the end\n$IddI");
+
+ add_blob_and_filter("$Id$", fl, "$Id$");
+ add_blob_and_filter("$Id: any$", fl, "$Id$");
+ add_blob_and_filter("$Id: any long stuff goes here you see$", fl, "$Id$");
+ add_blob_and_filter("$Id: ", fl, "$Id: ");
+ add_blob_and_filter("$Id", fl, "$Id");
+ add_blob_and_filter("$I", fl, "$I");
+ add_blob_and_filter("Id$", fl, "Id$");
+
+ git_filter_list_free(fl);
+}
diff --git a/tests/generate.py b/tests/generate.py
new file mode 100644
index 000000000..d4fe8f2a3
--- /dev/null
+++ b/tests/generate.py
@@ -0,0 +1,244 @@
+#!/usr/bin/env python
+#
+# Copyright (c) Vicent Marti. All rights reserved.
+#
+# This file is part of clar, distributed under the ISC license.
+# For full terms see the included COPYING file.
+#
+
+from __future__ import with_statement
+from string import Template
+import re, fnmatch, os, codecs, pickle
+
+class Module(object):
+ class Template(object):
+ def __init__(self, module):
+ self.module = module
+
+ def _render_callback(self, cb):
+ if not cb:
+ return ' { NULL, NULL }'
+ return ' { "%s", &%s }' % (cb['short_name'], cb['symbol'])
+
+ class DeclarationTemplate(Template):
+ def render(self):
+ out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n"
+
+ if self.module.initialize:
+ out += "extern %s;\n" % self.module.initialize['declaration']
+
+ if self.module.cleanup:
+ out += "extern %s;\n" % self.module.cleanup['declaration']
+
+ return out
+
+ class CallbacksTemplate(Template):
+ def render(self):
+ out = "static const struct clar_func _clar_cb_%s[] = {\n" % self.module.name
+ out += ",\n".join(self._render_callback(cb) for cb in self.module.callbacks)
+ out += "\n};\n"
+ return out
+
+ class InfoTemplate(Template):
+ def render(self):
+ return Template(
+ r"""
+ {
+ "${clean_name}",
+ ${initialize},
+ ${cleanup},
+ ${cb_ptr}, ${cb_count}, ${enabled}
+ }"""
+ ).substitute(
+ clean_name = self.module.clean_name(),
+ initialize = self._render_callback(self.module.initialize),
+ cleanup = self._render_callback(self.module.cleanup),
+ cb_ptr = "_clar_cb_%s" % self.module.name,
+ cb_count = len(self.module.callbacks),
+ enabled = int(self.module.enabled)
+ )
+
+ def __init__(self, name):
+ self.name = name
+
+ self.mtime = 0
+ self.enabled = True
+ self.modified = False
+
+ def clean_name(self):
+ return self.name.replace("_", "::")
+
+ def _skip_comments(self, text):
+ SKIP_COMMENTS_REGEX = re.compile(
+ r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
+ re.DOTALL | re.MULTILINE)
+
+ def _replacer(match):
+ s = match.group(0)
+ return "" if s.startswith('/') else s
+
+ return re.sub(SKIP_COMMENTS_REGEX, _replacer, text)
+
+ def parse(self, contents):
+ TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\(\s*void\s*\))\s*\{"
+
+ contents = self._skip_comments(contents)
+ regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE)
+
+ self.callbacks = []
+ self.initialize = None
+ self.cleanup = None
+
+ for (declaration, symbol, short_name) in regex.findall(contents):
+ data = {
+ "short_name" : short_name,
+ "declaration" : declaration,
+ "symbol" : symbol
+ }
+
+ if short_name == 'initialize':
+ self.initialize = data
+ elif short_name == 'cleanup':
+ self.cleanup = data
+ else:
+ self.callbacks.append(data)
+
+ return self.callbacks != []
+
+ def refresh(self, path):
+ self.modified = False
+
+ try:
+ st = os.stat(path)
+
+ # Not modified
+ if st.st_mtime == self.mtime:
+ return True
+
+ self.modified = True
+ self.mtime = st.st_mtime
+
+ with open(path) as fp:
+ raw_content = fp.read()
+
+ except IOError:
+ return False
+
+ return self.parse(raw_content)
+
+class TestSuite(object):
+
+ def __init__(self, path):
+ self.path = path
+
+ def should_generate(self, path):
+ if not os.path.isfile(path):
+ return True
+
+ if any(module.modified for module in self.modules.values()):
+ return True
+
+ return False
+
+ def find_modules(self):
+ modules = []
+ for root, _, files in os.walk(self.path):
+ module_root = root[len(self.path):]
+ module_root = [c for c in module_root.split(os.sep) if c]
+
+ tests_in_module = fnmatch.filter(files, "*.c")
+
+ for test_file in tests_in_module:
+ full_path = os.path.join(root, test_file)
+ module_name = "_".join(module_root + [test_file[:-2]])
+
+ modules.append((full_path, module_name))
+
+ return modules
+
+ def load_cache(self):
+ path = os.path.join(self.path, '.clarcache')
+ cache = {}
+
+ try:
+ fp = open(path, 'rb')
+ cache = pickle.load(fp)
+ fp.close()
+ except (IOError, ValueError):
+ pass
+
+ return cache
+
+ def save_cache(self):
+ path = os.path.join(self.path, '.clarcache')
+ with open(path, 'wb') as cache:
+ pickle.dump(self.modules, cache)
+
+ def load(self, force = False):
+ module_data = self.find_modules()
+ self.modules = {} if force else self.load_cache()
+
+ for path, name in module_data:
+ if name not in self.modules:
+ self.modules[name] = Module(name)
+
+ if not self.modules[name].refresh(path):
+ del self.modules[name]
+
+ def disable(self, excluded):
+ for exclude in excluded:
+ for module in self.modules.values():
+ name = module.clean_name()
+ if name.startswith(exclude):
+ module.enabled = False
+ module.modified = True
+
+ def suite_count(self):
+ return len(self.modules)
+
+ def callback_count(self):
+ return sum(len(module.callbacks) for module in self.modules.values())
+
+ def write(self):
+ output = os.path.join(self.path, 'clar.suite')
+
+ if not self.should_generate(output):
+ return False
+
+ with open(output, 'w') as data:
+ for module in self.modules.values():
+ t = Module.DeclarationTemplate(module)
+ data.write(t.render())
+
+ for module in self.modules.values():
+ t = Module.CallbacksTemplate(module)
+ data.write(t.render())
+
+ suites = "static struct clar_suite _clar_suites[] = {" + ','.join(
+ Module.InfoTemplate(module).render() for module in sorted(self.modules.values(), key=lambda module: module.name)
+ ) + "\n};\n"
+
+ data.write(suites)
+
+ data.write("static const size_t _clar_suite_count = %d;\n" % self.suite_count())
+ data.write("static const size_t _clar_callback_count = %d;\n" % self.callback_count())
+
+ suite.save_cache()
+ return True
+
+if __name__ == '__main__':
+ from optparse import OptionParser
+
+ parser = OptionParser()
+ parser.add_option('-f', '--force', dest='force', default=False)
+ parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[])
+
+ options, args = parser.parse_args()
+
+ for path in args or ['.']:
+ suite = TestSuite(path)
+ suite.load(options.force)
+ suite.disable(options.excluded)
+ if suite.write():
+ print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
+
diff --git a/tests/graph/descendant_of.c b/tests/graph/descendant_of.c
new file mode 100644
index 000000000..8e9952a09
--- /dev/null
+++ b/tests/graph/descendant_of.c
@@ -0,0 +1,55 @@
+#include "clar_libgit2.h"
+
+static git_repository *_repo;
+static git_commit *commit;
+
+void test_graph_descendant_of__initialize(void)
+{
+ git_oid oid;
+
+ cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git")));
+
+ git_oid_fromstr(&oid, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ cl_git_pass(git_commit_lookup(&commit, _repo, &oid));
+}
+
+void test_graph_descendant_of__cleanup(void)
+{
+ git_commit_free(commit);
+ commit = NULL;
+
+ git_repository_free(_repo);
+ _repo = NULL;
+}
+
+void test_graph_descendant_of__returns_correct_result(void)
+{
+ git_commit *other;
+
+ cl_assert_equal_i(0, git_graph_descendant_of(_repo, git_commit_id(commit), git_commit_id(commit)));
+
+
+ cl_git_pass(git_commit_nth_gen_ancestor(&other, commit, 1));
+
+ cl_assert_equal_i(1, git_graph_descendant_of(_repo, git_commit_id(commit), git_commit_id(other)));
+ cl_assert_equal_i(0, git_graph_descendant_of(_repo, git_commit_id(other), git_commit_id(commit)));
+
+ git_commit_free(other);
+
+
+ cl_git_pass(git_commit_nth_gen_ancestor(&other, commit, 3));
+
+ cl_assert_equal_i(1, git_graph_descendant_of(_repo, git_commit_id(commit), git_commit_id(other)));
+ cl_assert_equal_i(0, git_graph_descendant_of(_repo, git_commit_id(other), git_commit_id(commit)));
+
+ git_commit_free(other);
+
+}
+
+void test_graph_descendant_of__nopath(void)
+{
+ git_oid oid;
+
+ git_oid_fromstr(&oid, "e90810b8df3e80c413d903f631643c716887138d");
+ cl_assert_equal_i(0, git_graph_descendant_of(_repo, git_commit_id(commit), &oid));
+}
diff --git a/tests/index/addall.c b/tests/index/addall.c
new file mode 100644
index 000000000..a7e2583b2
--- /dev/null
+++ b/tests/index/addall.c
@@ -0,0 +1,356 @@
+#include "clar_libgit2.h"
+#include "../status/status_helpers.h"
+#include "posix.h"
+#include "fileops.h"
+
+static git_repository *g_repo = NULL;
+#define TEST_DIR "addall"
+
+void test_index_addall__initialize(void)
+{
+}
+
+void test_index_addall__cleanup(void)
+{
+ git_repository_free(g_repo);
+ g_repo = NULL;
+
+ cl_fixture_cleanup(TEST_DIR);
+}
+
+#define STATUS_INDEX_FLAGS \
+ (GIT_STATUS_INDEX_NEW | GIT_STATUS_INDEX_MODIFIED | \
+ GIT_STATUS_INDEX_DELETED | GIT_STATUS_INDEX_RENAMED | \
+ GIT_STATUS_INDEX_TYPECHANGE)
+
+#define STATUS_WT_FLAGS \
+ (GIT_STATUS_WT_NEW | GIT_STATUS_WT_MODIFIED | \
+ GIT_STATUS_WT_DELETED | GIT_STATUS_WT_TYPECHANGE | \
+ GIT_STATUS_WT_RENAMED)
+
+typedef struct {
+ size_t index_adds;
+ size_t index_dels;
+ size_t index_mods;
+ size_t wt_adds;
+ size_t wt_dels;
+ size_t wt_mods;
+ size_t ignores;
+} index_status_counts;
+
+static int index_status_cb(
+ const char *path, unsigned int status_flags, void *payload)
+{
+ index_status_counts *vals = payload;
+
+ /* cb_status__print(path, status_flags, NULL); */
+
+ GIT_UNUSED(path);
+
+ if (status_flags & GIT_STATUS_INDEX_NEW)
+ vals->index_adds++;
+ if (status_flags & GIT_STATUS_INDEX_MODIFIED)
+ vals->index_mods++;
+ if (status_flags & GIT_STATUS_INDEX_DELETED)
+ vals->index_dels++;
+ if (status_flags & GIT_STATUS_INDEX_TYPECHANGE)
+ vals->index_mods++;
+
+ if (status_flags & GIT_STATUS_WT_NEW)
+ vals->wt_adds++;
+ if (status_flags & GIT_STATUS_WT_MODIFIED)
+ vals->wt_mods++;
+ if (status_flags & GIT_STATUS_WT_DELETED)
+ vals->wt_dels++;
+ if (status_flags & GIT_STATUS_WT_TYPECHANGE)
+ vals->wt_mods++;
+
+ if (status_flags & GIT_STATUS_IGNORED)
+ vals->ignores++;
+
+ return 0;
+}
+
+static void check_status_at_line(
+ git_repository *repo,
+ size_t index_adds, size_t index_dels, size_t index_mods,
+ size_t wt_adds, size_t wt_dels, size_t wt_mods, size_t ignores,
+ const char *file, int line)
+{
+ index_status_counts vals;
+
+ memset(&vals, 0, sizeof(vals));
+
+ cl_git_pass(git_status_foreach(repo, index_status_cb, &vals));
+
+ clar__assert_equal(
+ file,line,"wrong index adds", 1, "%"PRIuZ, index_adds, vals.index_adds);
+ clar__assert_equal(
+ file,line,"wrong index dels", 1, "%"PRIuZ, index_dels, vals.index_dels);
+ clar__assert_equal(
+ file,line,"wrong index mods", 1, "%"PRIuZ, index_mods, vals.index_mods);
+ clar__assert_equal(
+ file,line,"wrong workdir adds", 1, "%"PRIuZ, wt_adds, vals.wt_adds);
+ clar__assert_equal(
+ file,line,"wrong workdir dels", 1, "%"PRIuZ, wt_dels, vals.wt_dels);
+ clar__assert_equal(
+ file,line,"wrong workdir mods", 1, "%"PRIuZ, wt_mods, vals.wt_mods);
+ clar__assert_equal(
+ file,line,"wrong ignores", 1, "%"PRIuZ, ignores, vals.ignores);
+}
+
+#define check_status(R,IA,ID,IM,WA,WD,WM,IG) \
+ check_status_at_line(R,IA,ID,IM,WA,WD,WM,IG,__FILE__,__LINE__)
+
+static void check_stat_data(git_index *index, const char *path, bool match)
+{
+ const git_index_entry *entry;
+ struct stat st;
+
+ cl_must_pass(p_lstat(path, &st));
+
+ /* skip repo base dir name */
+ while (*path != '/')
+ ++path;
+ ++path;
+
+ entry = git_index_get_bypath(index, path, 0);
+ cl_assert(entry);
+
+ if (match) {
+ cl_assert(st.st_ctime == entry->ctime.seconds);
+ cl_assert(st.st_mtime == entry->mtime.seconds);
+ cl_assert(st.st_size == entry->file_size);
+ cl_assert(st.st_uid == entry->uid);
+ cl_assert(st.st_gid == entry->gid);
+ cl_assert_equal_i_fmt(
+ GIT_MODE_TYPE(st.st_mode), GIT_MODE_TYPE(entry->mode), "%07o");
+ if (cl_is_chmod_supported())
+ cl_assert_equal_b(
+ GIT_PERMS_IS_EXEC(st.st_mode), GIT_PERMS_IS_EXEC(entry->mode));
+ } else {
+ /* most things will still match */
+ cl_assert(st.st_size != entry->file_size);
+ /* would check mtime, but with second resolution it won't work :( */
+ }
+}
+
+static void addall_create_test_repo(bool check_every_step)
+{
+ cl_git_pass(git_repository_init(&g_repo, TEST_DIR, false));
+ if (check_every_step)
+ check_status(g_repo, 0, 0, 0, 0, 0, 0, 0);
+
+ cl_git_mkfile(TEST_DIR "/file.foo", "a file");
+ if (check_every_step)
+ check_status(g_repo, 0, 0, 0, 1, 0, 0, 0);
+
+ cl_git_mkfile(TEST_DIR "/.gitignore", "*.foo\n");
+ if (check_every_step)
+ check_status(g_repo, 0, 0, 0, 1, 0, 0, 1);
+
+ cl_git_mkfile(TEST_DIR "/file.bar", "another file");
+ if (check_every_step)
+ check_status(g_repo, 0, 0, 0, 2, 0, 0, 1);
+}
+
+void test_index_addall__repo_lifecycle(void)
+{
+ int error;
+ git_index *index;
+ git_strarray paths = { NULL, 0 };
+ char *strs[1];
+
+ addall_create_test_repo(true);
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ strs[0] = "file.*";
+ paths.strings = strs;
+ paths.count = 1;
+
+ cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL));
+ check_stat_data(index, TEST_DIR "/file.bar", true);
+ check_status(g_repo, 1, 0, 0, 1, 0, 0, 1);
+
+ cl_git_rewritefile(TEST_DIR "/file.bar", "new content for file");
+ check_stat_data(index, TEST_DIR "/file.bar", false);
+ check_status(g_repo, 1, 0, 0, 1, 0, 1, 1);
+
+ cl_git_mkfile(TEST_DIR "/file.zzz", "yet another one");
+ cl_git_mkfile(TEST_DIR "/other.zzz", "yet another one");
+ cl_git_mkfile(TEST_DIR "/more.zzz", "yet another one");
+ check_status(g_repo, 1, 0, 0, 4, 0, 1, 1);
+
+ cl_git_pass(git_index_update_all(index, NULL, NULL, NULL));
+ check_stat_data(index, TEST_DIR "/file.bar", true);
+ check_status(g_repo, 1, 0, 0, 4, 0, 0, 1);
+
+ cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL));
+ check_stat_data(index, TEST_DIR "/file.zzz", true);
+ check_status(g_repo, 2, 0, 0, 3, 0, 0, 1);
+
+ cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "first commit");
+ check_status(g_repo, 0, 0, 0, 3, 0, 0, 1);
+
+ /* attempt to add an ignored file - does nothing */
+ strs[0] = "file.foo";
+ cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL));
+ check_status(g_repo, 0, 0, 0, 3, 0, 0, 1);
+
+ /* add with check - should generate error */
+ error = git_index_add_all(
+ index, &paths, GIT_INDEX_ADD_CHECK_PATHSPEC, NULL, NULL);
+ cl_assert_equal_i(GIT_EINVALIDSPEC, error);
+ check_status(g_repo, 0, 0, 0, 3, 0, 0, 1);
+
+ /* add with force - should allow */
+ cl_git_pass(git_index_add_all(
+ index, &paths, GIT_INDEX_ADD_FORCE, NULL, NULL));
+ check_stat_data(index, TEST_DIR "/file.foo", true);
+ check_status(g_repo, 1, 0, 0, 3, 0, 0, 0);
+
+ /* now it's in the index, so regular add should work */
+ cl_git_rewritefile(TEST_DIR "/file.foo", "new content for file");
+ check_stat_data(index, TEST_DIR "/file.foo", false);
+ check_status(g_repo, 1, 0, 0, 3, 0, 1, 0);
+
+ cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL));
+ check_stat_data(index, TEST_DIR "/file.foo", true);
+ check_status(g_repo, 1, 0, 0, 3, 0, 0, 0);
+
+ cl_git_pass(git_index_add_bypath(index, "more.zzz"));
+ check_stat_data(index, TEST_DIR "/more.zzz", true);
+ check_status(g_repo, 2, 0, 0, 2, 0, 0, 0);
+
+ cl_git_rewritefile(TEST_DIR "/file.zzz", "new content for file");
+ check_status(g_repo, 2, 0, 0, 2, 0, 1, 0);
+
+ cl_git_pass(git_index_add_bypath(index, "file.zzz"));
+ check_stat_data(index, TEST_DIR "/file.zzz", true);
+ check_status(g_repo, 2, 0, 1, 2, 0, 0, 0);
+
+ strs[0] = "*.zzz";
+ cl_git_pass(git_index_remove_all(index, &paths, NULL, NULL));
+ check_status(g_repo, 1, 1, 0, 4, 0, 0, 0);
+
+ cl_git_pass(git_index_add_bypath(index, "file.zzz"));
+ check_status(g_repo, 1, 0, 1, 3, 0, 0, 0);
+
+ cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "second commit");
+ check_status(g_repo, 0, 0, 0, 3, 0, 0, 0);
+
+ cl_must_pass(p_unlink(TEST_DIR "/file.zzz"));
+ check_status(g_repo, 0, 0, 0, 3, 1, 0, 0);
+
+ /* update_all should be able to remove entries */
+ cl_git_pass(git_index_update_all(index, NULL, NULL, NULL));
+ check_status(g_repo, 0, 1, 0, 3, 0, 0, 0);
+
+ strs[0] = "*";
+ cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL));
+ check_status(g_repo, 3, 1, 0, 0, 0, 0, 0);
+
+ /* must be able to remove at any position while still updating other files */
+ cl_must_pass(p_unlink(TEST_DIR "/.gitignore"));
+ cl_git_rewritefile(TEST_DIR "/file.zzz", "reconstructed file");
+ cl_git_rewritefile(TEST_DIR "/more.zzz", "altered file reality");
+ check_status(g_repo, 3, 1, 0, 1, 1, 1, 0);
+
+ cl_git_pass(git_index_update_all(index, NULL, NULL, NULL));
+ check_status(g_repo, 2, 1, 0, 1, 0, 0, 0);
+ /* this behavior actually matches 'git add -u' where "file.zzz" has
+ * been removed from the index, so when you go to update, even though
+ * it exists in the HEAD, it is not re-added to the index, leaving it
+ * as a DELETE when comparing HEAD to index and as an ADD comparing
+ * index to worktree
+ */
+
+ git_index_free(index);
+}
+
+static int addall_match_prefix(
+ const char *path, const char *matched_pathspec, void *payload)
+{
+ GIT_UNUSED(matched_pathspec);
+ return !git__prefixcmp(path, payload) ? 0 : 1;
+}
+
+static int addall_match_suffix(
+ const char *path, const char *matched_pathspec, void *payload)
+{
+ GIT_UNUSED(matched_pathspec);
+ return !git__suffixcmp(path, payload) ? 0 : 1;
+}
+
+static int addall_cancel_at(
+ const char *path, const char *matched_pathspec, void *payload)
+{
+ GIT_UNUSED(matched_pathspec);
+ return !strcmp(path, payload) ? -123 : 0;
+}
+
+void test_index_addall__callback_filtering(void)
+{
+ git_index *index;
+
+ addall_create_test_repo(false);
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ cl_git_pass(
+ git_index_add_all(index, NULL, 0, addall_match_prefix, "file."));
+ check_stat_data(index, TEST_DIR "/file.bar", true);
+ check_status(g_repo, 1, 0, 0, 1, 0, 0, 1);
+
+ cl_git_mkfile(TEST_DIR "/file.zzz", "yet another one");
+ cl_git_mkfile(TEST_DIR "/more.zzz", "yet another one");
+ cl_git_mkfile(TEST_DIR "/other.zzz", "yet another one");
+
+ cl_git_pass(git_index_update_all(index, NULL, NULL, NULL));
+ check_stat_data(index, TEST_DIR "/file.bar", true);
+ check_status(g_repo, 1, 0, 0, 4, 0, 0, 1);
+
+ cl_git_pass(
+ git_index_add_all(index, NULL, 0, addall_match_prefix, "other"));
+ check_stat_data(index, TEST_DIR "/other.zzz", true);
+ check_status(g_repo, 2, 0, 0, 3, 0, 0, 1);
+
+ cl_git_pass(
+ git_index_add_all(index, NULL, 0, addall_match_suffix, ".zzz"));
+ check_status(g_repo, 4, 0, 0, 1, 0, 0, 1);
+
+ cl_git_pass(
+ git_index_remove_all(index, NULL, addall_match_suffix, ".zzz"));
+ check_status(g_repo, 1, 0, 0, 4, 0, 0, 1);
+
+ cl_git_fail_with(
+ git_index_add_all(index, NULL, 0, addall_cancel_at, "more.zzz"), -123);
+ check_status(g_repo, 3, 0, 0, 2, 0, 0, 1);
+
+ cl_git_fail_with(
+ git_index_add_all(index, NULL, 0, addall_cancel_at, "other.zzz"), -123);
+ check_status(g_repo, 4, 0, 0, 1, 0, 0, 1);
+
+ cl_git_pass(
+ git_index_add_all(index, NULL, 0, addall_match_suffix, ".zzz"));
+ check_status(g_repo, 5, 0, 0, 0, 0, 0, 1);
+
+ cl_must_pass(p_unlink(TEST_DIR "/file.zzz"));
+ cl_must_pass(p_unlink(TEST_DIR "/more.zzz"));
+ cl_must_pass(p_unlink(TEST_DIR "/other.zzz"));
+
+ cl_git_fail_with(
+ git_index_update_all(index, NULL, addall_cancel_at, "more.zzz"), -123);
+ /* file.zzz removed from index (so Index Adds 5 -> 4) and
+ * more.zzz + other.zzz removed (so Worktree Dels 0 -> 2) */
+ check_status(g_repo, 4, 0, 0, 0, 2, 0, 1);
+
+ cl_git_fail_with(
+ git_index_update_all(index, NULL, addall_cancel_at, "other.zzz"), -123);
+ /* more.zzz removed from index (so Index Adds 4 -> 3) and
+ * Just other.zzz removed (so Worktree Dels 2 -> 1) */
+ check_status(g_repo, 3, 0, 0, 0, 1, 0, 1);
+
+ git_index_free(index);
+}
diff --git a/tests/index/collision.c b/tests/index/collision.c
new file mode 100644
index 000000000..1f002e8d3
--- /dev/null
+++ b/tests/index/collision.c
@@ -0,0 +1,106 @@
+#include "clar_libgit2.h"
+#include "git2/repository.h"
+#include "git2/index.h"
+
+git_repository *repo = NULL;
+
+void test_index_collision__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+ repo = NULL;
+}
+
+void test_index_collision__add(void)
+{
+ git_index *index;
+ git_index_entry entry;
+ git_oid tree_id;
+ git_tree *tree;
+
+ repo = cl_git_sandbox_init("empty_standard_repo");
+ cl_git_pass(git_repository_index(&index, repo));
+
+ memset(&entry, 0, sizeof(entry));
+ entry.ctime.seconds = 12346789;
+ entry.mtime.seconds = 12346789;
+ entry.mode = 0100644;
+ entry.file_size = 0;
+ git_oid_fromstr(&entry.id, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391");
+
+ entry.path = "a/b";
+ cl_git_pass(git_index_add(index, &entry));
+
+ /* create a tree/blob collision */
+ entry.path = "a/b/c";
+ cl_git_fail(git_index_add(index, &entry));
+
+ cl_git_pass(git_index_write_tree(&tree_id, index));
+ cl_git_pass(git_tree_lookup(&tree, repo, &tree_id));
+
+ git_tree_free(tree);
+ git_index_free(index);
+}
+
+void test_index_collision__add_with_highstage_1(void)
+{
+ git_index *index;
+ git_index_entry entry;
+
+ repo = cl_git_sandbox_init("empty_standard_repo");
+ cl_git_pass(git_repository_index(&index, repo));
+
+ memset(&entry, 0, sizeof(entry));
+ entry.ctime.seconds = 12346789;
+ entry.mtime.seconds = 12346789;
+ entry.mode = 0100644;
+ entry.file_size = 0;
+ git_oid_fromstr(&entry.id, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391");
+
+ entry.path = "a/b";
+ entry.flags = (2 << GIT_IDXENTRY_STAGESHIFT);
+ cl_git_pass(git_index_add(index, &entry));
+
+ /* create a blob beneath the previous tree entry */
+ entry.path = "a/b/c";
+ entry.flags = 0;
+ cl_git_pass(git_index_add(index, &entry));
+
+ /* create another tree entry above the blob */
+ entry.path = "a/b";
+ entry.flags = (1 << GIT_IDXENTRY_STAGESHIFT);
+ cl_git_pass(git_index_add(index, &entry));
+
+ git_index_free(index);
+}
+
+void test_index_collision__add_with_highstage_2(void)
+{
+ git_index *index;
+ git_index_entry entry;
+
+ repo = cl_git_sandbox_init("empty_standard_repo");
+ cl_git_pass(git_repository_index(&index, repo));
+
+ memset(&entry, 0, sizeof(entry));
+ entry.ctime.seconds = 12346789;
+ entry.mtime.seconds = 12346789;
+ entry.mode = 0100644;
+ entry.file_size = 0;
+ git_oid_fromstr(&entry.id, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391");
+
+ entry.path = "a/b/c";
+ entry.flags = (1 << GIT_IDXENTRY_STAGESHIFT);
+ cl_git_pass(git_index_add(index, &entry));
+
+ /* create a blob beneath the previous tree entry */
+ entry.path = "a/b/c";
+ entry.flags = (2 << GIT_IDXENTRY_STAGESHIFT);
+ cl_git_pass(git_index_add(index, &entry));
+
+ /* create another tree entry above the blob */
+ entry.path = "a/b";
+ entry.flags = (3 << GIT_IDXENTRY_STAGESHIFT);
+ cl_git_pass(git_index_add(index, &entry));
+
+ git_index_free(index);
+}
diff --git a/tests/index/conflicts.c b/tests/index/conflicts.c
new file mode 100644
index 000000000..90aaa442d
--- /dev/null
+++ b/tests/index/conflicts.c
@@ -0,0 +1,287 @@
+#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);
+ repo_index = NULL;
+
+ 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.id, TEST_ANCESTOR_OID);
+
+ our_entry.path = "test-one.txt";
+ ancestor_entry.flags |= (2 << GIT_IDXENTRY_STAGESHIFT);
+ git_oid_fromstr(&our_entry.id, TEST_OUR_OID);
+
+ their_entry.path = "test-one.txt";
+ ancestor_entry.flags |= (3 << GIT_IDXENTRY_STAGESHIFT);
+ git_oid_fromstr(&their_entry.id, 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;
+ const 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.id, TEST_ANCESTOR_OID);
+
+ our_entry.path = "test-one.txt";
+ ancestor_entry.flags |= (1 << GIT_IDXENTRY_STAGESHIFT);
+ git_oid_fromstr(&our_entry.id, TEST_OUR_OID);
+
+ their_entry.path = "test-one.txt";
+ ancestor_entry.flags |= (2 << GIT_IDXENTRY_STAGESHIFT);
+ git_oid_fromstr(&their_entry.id, 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)
+{
+ const 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_equal_s("conflicts-one.txt", conflict_entry[0]->path);
+
+ git_oid_fromstr(&oid, CONFLICTS_ONE_ANCESTOR_OID);
+ cl_assert(git_oid_cmp(&conflict_entry[0]->id, &oid) == 0);
+
+ git_oid_fromstr(&oid, CONFLICTS_ONE_OUR_OID);
+ cl_assert(git_oid_cmp(&conflict_entry[1]->id, &oid) == 0);
+
+ git_oid_fromstr(&oid, CONFLICTS_ONE_THEIR_OID);
+ cl_assert(git_oid_cmp(&conflict_entry[2]->id, &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_equal_s("conflicts-two.txt", conflict_entry[0]->path);
+
+ git_oid_fromstr(&oid, CONFLICTS_TWO_ANCESTOR_OID);
+ cl_assert(git_oid_cmp(&conflict_entry[0]->id, &oid) == 0);
+
+ git_oid_fromstr(&oid, CONFLICTS_TWO_OUR_OID);
+ cl_assert(git_oid_cmp(&conflict_entry[1]->id, &oid) == 0);
+
+ git_oid_fromstr(&oid, CONFLICTS_TWO_THEIR_OID);
+ cl_assert(git_oid_cmp(&conflict_entry[2]->id, &oid) == 0);
+}
+
+void test_index_conflicts__iterate(void)
+{
+ git_index_conflict_iterator *iterator;
+ const git_index_entry *conflict_entry[3];
+ git_oid oid;
+
+ cl_git_pass(git_index_conflict_iterator_new(&iterator, repo_index));
+
+ cl_git_pass(git_index_conflict_next(&conflict_entry[0], &conflict_entry[1], &conflict_entry[2], iterator));
+
+ git_oid_fromstr(&oid, CONFLICTS_ONE_ANCESTOR_OID);
+ cl_assert(git_oid_cmp(&conflict_entry[0]->id, &oid) == 0);
+ cl_assert(git__strcmp(conflict_entry[0]->path, "conflicts-one.txt") == 0);
+
+ git_oid_fromstr(&oid, CONFLICTS_ONE_OUR_OID);
+ cl_assert(git_oid_cmp(&conflict_entry[1]->id, &oid) == 0);
+ cl_assert(git__strcmp(conflict_entry[0]->path, "conflicts-one.txt") == 0);
+
+ git_oid_fromstr(&oid, CONFLICTS_ONE_THEIR_OID);
+ cl_assert(git_oid_cmp(&conflict_entry[2]->id, &oid) == 0);
+ cl_assert(git__strcmp(conflict_entry[0]->path, "conflicts-one.txt") == 0);
+
+ cl_git_pass(git_index_conflict_next(&conflict_entry[0], &conflict_entry[1], &conflict_entry[2], iterator));
+
+ git_oid_fromstr(&oid, CONFLICTS_TWO_ANCESTOR_OID);
+ cl_assert(git_oid_cmp(&conflict_entry[0]->id, &oid) == 0);
+ cl_assert(git__strcmp(conflict_entry[0]->path, "conflicts-two.txt") == 0);
+
+ git_oid_fromstr(&oid, CONFLICTS_TWO_OUR_OID);
+ cl_assert(git_oid_cmp(&conflict_entry[1]->id, &oid) == 0);
+ cl_assert(git__strcmp(conflict_entry[0]->path, "conflicts-two.txt") == 0);
+
+ git_oid_fromstr(&oid, CONFLICTS_TWO_THEIR_OID);
+ cl_assert(git_oid_cmp(&conflict_entry[2]->id, &oid) == 0);
+ cl_assert(git__strcmp(conflict_entry[0]->path, "conflicts-two.txt") == 0);
+
+ cl_assert(git_index_conflict_next(&conflict_entry[0], &conflict_entry[1], &conflict_entry[2], iterator) == GIT_ITEROVER);
+
+ cl_assert(conflict_entry[0] == NULL);
+ cl_assert(conflict_entry[2] == NULL);
+ cl_assert(conflict_entry[2] == NULL);
+
+ git_index_conflict_iterator_free(iterator);
+}
+
+void test_index_conflicts__remove(void)
+{
+ const 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_on_add(void)
+{
+ const 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_bypath(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__moved_to_reuc_on_remove(void)
+{
+ const git_index_entry *entry;
+ size_t i;
+
+ cl_assert(git_index_entrycount(repo_index) == 8);
+
+ cl_git_pass(p_unlink("./mergedrepo/conflicts-one.txt"));
+
+ cl_git_pass(git_index_remove_bypath(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);
+ }
+}
+
+void test_index_conflicts__remove_all_conflicts(void)
+{
+ size_t i;
+ const 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;
+ const 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.id, 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.id, &conflict_entry[0]->id) == 0);
+ cl_assert(conflict_entry[1] == NULL);
+ cl_assert(conflict_entry[2] == NULL);
+}
diff --git a/tests/index/crlf.c b/tests/index/crlf.c
new file mode 100644
index 000000000..7babd5939
--- /dev/null
+++ b/tests/index/crlf.c
@@ -0,0 +1,154 @@
+#include "clar_libgit2.h"
+#include "../filter/crlf.h"
+
+#include "git2/checkout.h"
+#include "repository.h"
+#include "posix.h"
+
+#define FILE_CONTENTS_LF "one\ntwo\nthree\nfour\n"
+#define FILE_CONTENTS_CRLF "one\r\ntwo\r\nthree\r\nfour\r\n"
+
+#define FILE_OID_LF "f384549cbeb481e437091320de6d1f2e15e11b4a"
+#define FILE_OID_CRLF "7fbf4d847b191141d80f30c8ab03d2ad4cd543a9"
+
+static git_repository *g_repo;
+static git_index *g_index;
+
+void test_index_crlf__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("crlf");
+ cl_git_pass(git_repository_index(&g_index, g_repo));
+}
+
+void test_index_crlf__cleanup(void)
+{
+ git_index_free(g_index);
+ cl_git_sandbox_cleanup();
+}
+
+void test_index_crlf__autocrlf_false_no_attrs(void)
+{
+ const git_index_entry *entry;
+ git_oid oid;
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", false);
+
+ cl_git_mkfile("./crlf/newfile.txt",
+ (GIT_EOL_NATIVE == GIT_EOL_CRLF) ? FILE_CONTENTS_CRLF : FILE_CONTENTS_LF);
+
+ cl_git_pass(git_index_add_bypath(g_index, "newfile.txt"));
+ entry = git_index_get_bypath(g_index, "newfile.txt", 0);
+
+ cl_git_pass(git_oid_fromstr(&oid,
+ (GIT_EOL_NATIVE == GIT_EOL_CRLF) ? FILE_OID_CRLF : FILE_OID_LF));
+ cl_assert(git_oid_cmp(&oid, &entry->id) == 0);
+}
+
+void test_index_crlf__autocrlf_true_no_attrs(void)
+{
+ const git_index_entry *entry;
+ git_oid oid;
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", true);
+
+ cl_git_mkfile("./crlf/newfile.txt",
+ (GIT_EOL_NATIVE == GIT_EOL_CRLF) ? FILE_CONTENTS_CRLF : FILE_CONTENTS_LF);
+
+ cl_git_pass(git_index_add_bypath(g_index, "newfile.txt"));
+ entry = git_index_get_bypath(g_index, "newfile.txt", 0);
+
+ cl_git_pass(git_oid_fromstr(&oid, FILE_OID_LF));
+ cl_assert(git_oid_cmp(&oid, &entry->id) == 0);
+}
+
+void test_index_crlf__autocrlf_input_no_attrs(void)
+{
+ const git_index_entry *entry;
+ git_oid oid;
+
+ cl_repo_set_string(g_repo, "core.autocrlf", "input");
+
+ cl_git_mkfile("./crlf/newfile.txt",
+ (GIT_EOL_NATIVE == GIT_EOL_CRLF) ? FILE_CONTENTS_CRLF : FILE_CONTENTS_LF);
+
+ cl_git_pass(git_index_add_bypath(g_index, "newfile.txt"));
+ entry = git_index_get_bypath(g_index, "newfile.txt", 0);
+
+ cl_git_pass(git_oid_fromstr(&oid, FILE_OID_LF));
+ cl_assert(git_oid_cmp(&oid, &entry->id) == 0);
+}
+
+void test_index_crlf__autocrlf_false_text_auto_attr(void)
+{
+ const git_index_entry *entry;
+ git_oid oid;
+
+ cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n");
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", false);
+
+ cl_git_mkfile("./crlf/newfile.txt",
+ (GIT_EOL_NATIVE == GIT_EOL_CRLF) ? FILE_CONTENTS_CRLF : FILE_CONTENTS_LF);
+
+ cl_git_pass(git_index_add_bypath(g_index, "newfile.txt"));
+ entry = git_index_get_bypath(g_index, "newfile.txt", 0);
+
+ cl_git_pass(git_oid_fromstr(&oid, FILE_OID_LF));
+ cl_assert(git_oid_cmp(&oid, &entry->id) == 0);
+}
+
+void test_index_crlf__autocrlf_true_text_auto_attr(void)
+{
+ const git_index_entry *entry;
+ git_oid oid;
+
+ cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n");
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", false);
+
+ cl_git_mkfile("./crlf/newfile.txt",
+ (GIT_EOL_NATIVE == GIT_EOL_CRLF) ? FILE_CONTENTS_CRLF : FILE_CONTENTS_LF);
+
+ cl_git_pass(git_index_add_bypath(g_index, "newfile.txt"));
+ entry = git_index_get_bypath(g_index, "newfile.txt", 0);
+
+ cl_git_pass(git_oid_fromstr(&oid, FILE_OID_LF));
+ cl_assert(git_oid_cmp(&oid, &entry->id) == 0);
+}
+
+void test_index_crlf__autocrlf_input_text_auto_attr(void)
+{
+ const git_index_entry *entry;
+ git_oid oid;
+
+ cl_git_mkfile("./crlf/.gitattributes", "* text=auto\n");
+
+ cl_repo_set_string(g_repo, "core.autocrlf", "input");
+
+ cl_git_mkfile("./crlf/newfile.txt",
+ (GIT_EOL_NATIVE == GIT_EOL_CRLF) ? FILE_CONTENTS_CRLF : FILE_CONTENTS_LF);
+
+ cl_git_pass(git_index_add_bypath(g_index, "newfile.txt"));
+ entry = git_index_get_bypath(g_index, "newfile.txt", 0);
+
+ cl_git_pass(git_oid_fromstr(&oid, FILE_OID_LF));
+ cl_assert(git_oid_cmp(&oid, &entry->id) == 0);
+}
+
+void test_index_crlf__safecrlf_true_no_attrs(void)
+{
+ cl_repo_set_bool(g_repo, "core.autocrlf", true);
+ cl_repo_set_bool(g_repo, "core.safecrlf", true);
+
+ cl_git_mkfile("crlf/newfile.txt", ALL_LF_TEXT_RAW);
+ cl_git_pass(git_index_add_bypath(g_index, "newfile.txt"));
+
+ cl_git_mkfile("crlf/newfile.txt", ALL_CRLF_TEXT_RAW);
+ cl_git_pass(git_index_add_bypath(g_index, "newfile.txt"));
+
+ cl_git_mkfile("crlf/newfile.txt", MORE_CRLF_TEXT_RAW);
+ cl_git_fail(git_index_add_bypath(g_index, "newfile.txt"));
+
+ cl_git_mkfile("crlf/newfile.txt", MORE_LF_TEXT_RAW);
+ cl_git_fail(git_index_add_bypath(g_index, "newfile.txt"));
+}
diff --git a/tests/index/filemodes.c b/tests/index/filemodes.c
new file mode 100644
index 000000000..58d7935a0
--- /dev/null
+++ b/tests/index/filemodes.c
@@ -0,0 +1,171 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "posix.h"
+#include "index.h"
+
+static git_repository *g_repo = NULL;
+
+void test_index_filemodes__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("filemodes");
+}
+
+void test_index_filemodes__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_index_filemodes__read(void)
+{
+ git_index *index;
+ unsigned int i;
+ static bool expected[6] = { 0, 1, 0, 1, 0, 1 };
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_assert_equal_i(6, (int)git_index_entrycount(index));
+
+ for (i = 0; i < 6; ++i) {
+ const git_index_entry *entry = git_index_get_byindex(index, i);
+ cl_assert(entry != NULL);
+ cl_assert(((entry->mode & 0100) ? 1 : 0) == expected[i]);
+ }
+
+ git_index_free(index);
+}
+
+static void replace_file_with_mode(
+ const char *filename, const char *backup, unsigned int create_mode)
+{
+ git_buf path = GIT_BUF_INIT, content = GIT_BUF_INIT;
+
+ cl_git_pass(git_buf_joinpath(&path, "filemodes", filename));
+ cl_git_pass(git_buf_printf(&content, "%s as %08u (%d)",
+ filename, create_mode, rand()));
+
+ cl_git_pass(p_rename(path.ptr, backup));
+ cl_git_write2file(
+ path.ptr, content.ptr, content.size,
+ O_WRONLY|O_CREAT|O_TRUNC, create_mode);
+
+ git_buf_free(&path);
+ git_buf_free(&content);
+}
+
+#define add_and_check_mode(I,F,X) add_and_check_mode_(I,F,X,__FILE__,__LINE__)
+
+static void add_and_check_mode_(
+ git_index *index, const char *filename, unsigned int expect_mode,
+ const char *file, int line)
+{
+ size_t pos;
+ const git_index_entry *entry;
+
+ cl_git_pass(git_index_add_bypath(index, filename));
+
+ clar__assert(!git_index_find(&pos, index, filename),
+ file, line, "Cannot find index entry", NULL, 1);
+
+ entry = git_index_get_byindex(index, pos);
+
+ clar__assert_equal(file, line, "Expected mode does not match index",
+ 1, "%07o", (unsigned int)entry->mode, (unsigned int)expect_mode);
+}
+
+void test_index_filemodes__untrusted(void)
+{
+ git_index *index;
+
+ cl_repo_set_bool(g_repo, "core.filemode", false);
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_assert((git_index_caps(index) & GIT_INDEXCAP_NO_FILEMODE) != 0);
+
+ /* 1 - add 0644 over existing 0644 -> expect 0644 */
+ replace_file_with_mode("exec_off", "filemodes/exec_off.0", 0644);
+ add_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB);
+
+ /* 2 - add 0644 over existing 0755 -> expect 0755 */
+ replace_file_with_mode("exec_on", "filemodes/exec_on.0", 0644);
+ add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE);
+
+ /* 3 - add 0755 over existing 0644 -> expect 0644 */
+ replace_file_with_mode("exec_off", "filemodes/exec_off.1", 0755);
+ add_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB);
+
+ /* 4 - add 0755 over existing 0755 -> expect 0755 */
+ replace_file_with_mode("exec_on", "filemodes/exec_on.1", 0755);
+ add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE);
+
+ /* 5 - add new 0644 -> expect 0644 */
+ cl_git_write2file("filemodes/new_off", "blah", 0,
+ O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ add_and_check_mode(index, "new_off", GIT_FILEMODE_BLOB);
+
+ /* 6 - add new 0755 -> expect 0644 if core.filemode == false */
+ cl_git_write2file("filemodes/new_on", "blah", 0,
+ O_WRONLY | O_CREAT | O_TRUNC, 0755);
+ add_and_check_mode(index, "new_on", GIT_FILEMODE_BLOB);
+
+ git_index_free(index);
+}
+
+void test_index_filemodes__trusted(void)
+{
+ git_index *index;
+
+ /* Only run these tests on platforms where I can actually
+ * chmod a file and get the stat results I expect!
+ */
+ if (!cl_is_chmod_supported())
+ return;
+
+ cl_repo_set_bool(g_repo, "core.filemode", true);
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_assert((git_index_caps(index) & GIT_INDEXCAP_NO_FILEMODE) == 0);
+
+ /* 1 - add 0644 over existing 0644 -> expect 0644 */
+ replace_file_with_mode("exec_off", "filemodes/exec_off.0", 0644);
+ add_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB);
+
+ /* 2 - add 0644 over existing 0755 -> expect 0644 */
+ replace_file_with_mode("exec_on", "filemodes/exec_on.0", 0644);
+ add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB);
+
+ /* 3 - add 0755 over existing 0644 -> expect 0755 */
+ replace_file_with_mode("exec_off", "filemodes/exec_off.1", 0755);
+ add_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB_EXECUTABLE);
+
+ /* 4 - add 0755 over existing 0755 -> expect 0755 */
+ replace_file_with_mode("exec_on", "filemodes/exec_on.1", 0755);
+ add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE);
+
+ /* 5 - add new 0644 -> expect 0644 */
+ cl_git_write2file("filemodes/new_off", "blah", 0,
+ O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ add_and_check_mode(index, "new_off", GIT_FILEMODE_BLOB);
+
+ /* 6 - add 0755 -> expect 0755 */
+ cl_git_write2file("filemodes/new_on", "blah", 0,
+ O_WRONLY | O_CREAT | O_TRUNC, 0755);
+ add_and_check_mode(index, "new_on", GIT_FILEMODE_BLOB_EXECUTABLE);
+
+ git_index_free(index);
+}
+
+void test_index_filemodes__invalid(void)
+{
+ git_index *index;
+ git_index_entry entry;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ entry.path = "foo";
+ entry.mode = GIT_OBJ_BLOB;
+ cl_git_fail(git_index_add(index, &entry));
+
+ entry.mode = GIT_FILEMODE_BLOB;
+ cl_git_pass(git_index_add(index, &entry));
+
+ git_index_free(index);
+}
diff --git a/tests/index/inmemory.c b/tests/index/inmemory.c
new file mode 100644
index 000000000..38e91e0fd
--- /dev/null
+++ b/tests/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, (int)git_index_entrycount(index));
+
+ git_index_free(index);
+}
+
+void test_index_inmemory__cannot_add_bypath_to_an_inmemory_index(void)
+{
+ git_index *index;
+
+ cl_git_pass(git_index_new(&index));
+
+ cl_assert_equal_i(GIT_ERROR, git_index_add_bypath(index, "test.txt"));
+
+ git_index_free(index);
+}
diff --git a/tests/index/names.c b/tests/index/names.c
new file mode 100644
index 000000000..52922e9f1
--- /dev/null
+++ b/tests/index/names.c
@@ -0,0 +1,148 @@
+#include "clar_libgit2.h"
+#include "index.h"
+#include "git2/sys/index.h"
+#include "git2/repository.h"
+#include "../reset/reset_helpers.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_names__initialize(void)
+{
+ repo = cl_git_sandbox_init("mergedrepo");
+ git_repository_index(&repo_index, repo);
+}
+
+void test_index_names__cleanup(void)
+{
+ git_index_free(repo_index);
+ repo_index = NULL;
+
+ cl_git_sandbox_cleanup();
+}
+
+void test_index_names__add(void)
+{
+ const git_index_name_entry *conflict_name;
+
+ cl_git_pass(git_index_name_add(repo_index, "ancestor", "ours", "theirs"));
+ cl_git_pass(git_index_name_add(repo_index, "ancestor2", "ours2", NULL));
+ cl_git_pass(git_index_name_add(repo_index, "ancestor3", NULL, "theirs3"));
+
+ cl_assert(git_index_name_entrycount(repo_index) == 3);
+
+ conflict_name = git_index_name_get_byindex(repo_index, 0);
+ cl_assert(strcmp(conflict_name->ancestor, "ancestor") == 0);
+ cl_assert(strcmp(conflict_name->ours, "ours") == 0);
+ cl_assert(strcmp(conflict_name->theirs, "theirs") == 0);
+
+ conflict_name = git_index_name_get_byindex(repo_index, 1);
+ cl_assert(strcmp(conflict_name->ancestor, "ancestor2") == 0);
+ cl_assert(strcmp(conflict_name->ours, "ours2") == 0);
+ cl_assert(conflict_name->theirs == NULL);
+
+ conflict_name = git_index_name_get_byindex(repo_index, 2);
+ cl_assert(strcmp(conflict_name->ancestor, "ancestor3") == 0);
+ cl_assert(conflict_name->ours == NULL);
+ cl_assert(strcmp(conflict_name->theirs, "theirs3") == 0);
+}
+
+void test_index_names__roundtrip(void)
+{
+ const git_index_name_entry *conflict_name;
+
+ cl_git_pass(git_index_name_add(repo_index, "ancestor", "ours", "theirs"));
+ cl_git_pass(git_index_name_add(repo_index, "ancestor2", "ours2", NULL));
+ cl_git_pass(git_index_name_add(repo_index, "ancestor3", NULL, "theirs3"));
+
+ cl_git_pass(git_index_write(repo_index));
+ git_index_clear(repo_index);
+ cl_assert(git_index_name_entrycount(repo_index) == 0);
+
+ cl_git_pass(git_index_read(repo_index, true));
+ cl_assert(git_index_name_entrycount(repo_index) == 3);
+
+ conflict_name = git_index_name_get_byindex(repo_index, 0);
+ cl_assert(strcmp(conflict_name->ancestor, "ancestor") == 0);
+ cl_assert(strcmp(conflict_name->ours, "ours") == 0);
+ cl_assert(strcmp(conflict_name->theirs, "theirs") == 0);
+
+ conflict_name = git_index_name_get_byindex(repo_index, 1);
+ cl_assert(strcmp(conflict_name->ancestor, "ancestor2") == 0);
+ cl_assert(strcmp(conflict_name->ours, "ours2") == 0);
+ cl_assert(conflict_name->theirs == NULL);
+
+ conflict_name = git_index_name_get_byindex(repo_index, 2);
+ cl_assert(strcmp(conflict_name->ancestor, "ancestor3") == 0);
+ cl_assert(conflict_name->ours == NULL);
+ cl_assert(strcmp(conflict_name->theirs, "theirs3") == 0);
+}
+
+void test_index_names__cleaned_on_reset_hard(void)
+{
+ git_object *target;
+
+ cl_git_pass(git_revparse_single(&target, repo, "3a34580"));
+
+ test_index_names__add();
+ cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL, NULL));
+ cl_assert(git_index_name_entrycount(repo_index) == 0);
+
+ git_object_free(target);
+}
+
+void test_index_names__cleaned_on_reset_mixed(void)
+{
+ git_object *target;
+
+ cl_git_pass(git_revparse_single(&target, repo, "3a34580"));
+
+ test_index_names__add();
+ cl_git_pass(git_reset(repo, target, GIT_RESET_MIXED, NULL, NULL));
+ cl_assert(git_index_name_entrycount(repo_index) == 0);
+
+ git_object_free(target);
+}
+
+void test_index_names__cleaned_on_checkout_tree(void)
+{
+ git_oid oid;
+ git_object *obj;
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY;
+
+ test_index_names__add();
+ git_reference_name_to_id(&oid, repo, "refs/heads/master");
+ git_object_lookup(&obj, repo, &oid, GIT_OBJ_ANY);
+ git_checkout_tree(repo, obj, &opts);
+ cl_assert_equal_sz(0, git_index_name_entrycount(repo_index));
+
+ git_object_free(obj);
+}
+
+void test_index_names__cleaned_on_checkout_head(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY;
+
+ test_index_names__add();
+ git_checkout_head(repo, &opts);
+ cl_assert_equal_sz(0, git_index_name_entrycount(repo_index));
+}
+
+void test_index_names__retained_on_checkout_index(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY;
+
+ test_index_names__add();
+ git_checkout_index(repo, repo_index, &opts);
+ cl_assert(git_index_name_entrycount(repo_index) > 0);
+}
diff --git a/tests/index/read_tree.c b/tests/index/read_tree.c
new file mode 100644
index 000000000..6c6b40121
--- /dev/null
+++ b/tests/index/read_tree.c
@@ -0,0 +1,46 @@
+#include "clar_libgit2.h"
+#include "posix.h"
+
+/* Test that reading and writing a tree is a no-op */
+void test_index_read_tree__read_write_involution(void)
+{
+ git_repository *repo;
+ git_index *index;
+ git_oid tree_oid;
+ git_tree *tree;
+ 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);
+
+ p_mkdir("./read_tree/abc", 0700);
+
+ /* Sort order: '-' < '/' < '_' */
+ cl_git_mkfile("./read_tree/abc-d", NULL);
+ cl_git_mkfile("./read_tree/abc/d", NULL);
+ cl_git_mkfile("./read_tree/abc_d", NULL);
+
+ cl_git_pass(git_index_add_bypath(index, "abc-d"));
+ cl_git_pass(git_index_add_bypath(index, "abc_d"));
+ cl_git_pass(git_index_add_bypath(index, "abc/d"));
+
+ /* write-tree */
+ 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));
+ git_tree_free(tree);
+
+ cl_git_pass(git_index_write_tree(&tree_oid, index));
+ cl_assert(git_oid_cmp(&expected, &tree_oid) == 0);
+
+ git_index_free(index);
+ git_repository_free(repo);
+
+ cl_fixture_cleanup("read_tree");
+}
diff --git a/tests/index/rename.c b/tests/index/rename.c
new file mode 100644
index 000000000..b6fb61d10
--- /dev/null
+++ b/tests/index/rename.c
@@ -0,0 +1,50 @@
+#include "clar_libgit2.h"
+#include "posix.h"
+
+void test_index_rename__single_file(void)
+{
+ git_repository *repo;
+ git_index *index;
+ size_t position;
+ git_oid expected;
+ const git_index_entry *entry;
+
+ p_mkdir("rename", 0700);
+
+ cl_git_pass(git_repository_init(&repo, "./rename", 0));
+ cl_git_pass(git_repository_index(&index, repo));
+
+ cl_assert(git_index_entrycount(index) == 0);
+
+ 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_bypath(index, "lame.name.txt"));
+ cl_assert(git_index_entrycount(index) == 1);
+
+ cl_git_pass(git_oid_fromstr(&expected, "d4fa8600b4f37d7516bef4816ae2c64dbf029e3a"));
+
+ cl_assert(!git_index_find(&position, index, "lame.name.txt"));
+
+ entry = git_index_get_byindex(index, position);
+ cl_assert(git_oid_cmp(&expected, &entry->id) == 0);
+
+ /* This removes the entry from the index, but not from the object database */
+ 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_bypath(index, "fancy.name.txt"));
+ cl_assert(git_index_entrycount(index) == 1);
+
+ cl_assert(!git_index_find(&position, index, "fancy.name.txt"));
+
+ entry = git_index_get_byindex(index, position);
+ cl_assert(git_oid_cmp(&expected, &entry->id) == 0);
+
+ git_index_free(index);
+ git_repository_free(repo);
+
+ cl_fixture_cleanup("rename");
+}
diff --git a/tests/index/reuc.c b/tests/index/reuc.c
new file mode 100644
index 000000000..27240a30f
--- /dev/null
+++ b/tests/index/reuc.c
@@ -0,0 +1,372 @@
+#include "clar_libgit2.h"
+#include "index.h"
+#include "git2/sys/index.h"
+#include "git2/repository.h"
+#include "../reset/reset_helpers.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);
+ repo_index = NULL;
+
+ cl_git_sandbox_cleanup();
+}
+
+void test_index_reuc__add(void)
+{
+ git_oid ancestor_oid, our_oid, their_oid;
+ const git_index_reuc_entry *reuc;
+
+ 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, "newfile.txt",
+ 0100644, &ancestor_oid,
+ 0100644, &our_oid,
+ 0100644, &their_oid));
+
+ cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "newfile.txt"));
+
+ cl_assert_equal_s("newfile.txt", reuc->path);
+ cl_assert(reuc->mode[0] == 0100644);
+ cl_assert(reuc->mode[1] == 0100644);
+ cl_assert(reuc->mode[2] == 0100644);
+ cl_assert(git_oid_cmp(&reuc->oid[0], &ancestor_oid) == 0);
+ cl_assert(git_oid_cmp(&reuc->oid[1], &our_oid) == 0);
+ cl_assert(git_oid_cmp(&reuc->oid[2], &their_oid) == 0);
+}
+
+void test_index_reuc__add_no_ancestor(void)
+{
+ git_oid ancestor_oid, our_oid, their_oid;
+ const git_index_reuc_entry *reuc;
+
+ memset(&ancestor_oid, 0x0, sizeof(git_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, "newfile.txt",
+ 0, NULL,
+ 0100644, &our_oid,
+ 0100644, &their_oid));
+
+ cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "newfile.txt"));
+
+ cl_assert_equal_s("newfile.txt", reuc->path);
+ cl_assert(reuc->mode[0] == 0);
+ cl_assert(reuc->mode[1] == 0100644);
+ cl_assert(reuc->mode[2] == 0100644);
+ cl_assert(git_oid_cmp(&reuc->oid[0], &ancestor_oid) == 0);
+ cl_assert(git_oid_cmp(&reuc->oid[1], &our_oid) == 0);
+ cl_assert(git_oid_cmp(&reuc->oid[2], &their_oid) == 0);
+}
+
+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_equal_s("two.txt", reuc->path);
+ 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_equal_s("one.txt", reuc->path);
+ 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_equal_s("two.txt", reuc->path);
+ 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_equal_s("one.txt", reuc->path);
+ 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_equal_s("two.txt", reuc->path);
+ 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_equal_s("TWO.txt", reuc->path);
+ 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_equal_s("two.txt", reuc->path);
+ 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_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_equal_s("one.txt", reuc->path);
+
+ cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 1));
+ cl_assert_equal_s("two.txt", reuc->path);
+}
+
+static int reuc_entry_exists(void)
+{
+ return (git_index_reuc_get_bypath(repo_index, "newfile.txt") != NULL);
+}
+
+void test_index_reuc__cleaned_on_reset_hard(void)
+{
+ git_object *target;
+
+ cl_git_pass(git_revparse_single(&target, repo, "3a34580"));
+
+ test_index_reuc__add();
+ cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL, NULL));
+ cl_assert(reuc_entry_exists() == false);
+
+ git_object_free(target);
+}
+
+void test_index_reuc__cleaned_on_reset_mixed(void)
+{
+ git_object *target;
+
+ cl_git_pass(git_revparse_single(&target, repo, "3a34580"));
+
+ test_index_reuc__add();
+ cl_git_pass(git_reset(repo, target, GIT_RESET_MIXED, NULL, NULL));
+ cl_assert(reuc_entry_exists() == false);
+
+ git_object_free(target);
+}
+
+void test_index_reuc__retained_on_reset_soft(void)
+{
+ git_object *target;
+
+ cl_git_pass(git_revparse_single(&target, repo, "3a34580"));
+
+ git_reset(repo, target, GIT_RESET_HARD, NULL, NULL);
+
+ test_index_reuc__add();
+ cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL, NULL));
+ cl_assert(reuc_entry_exists() == true);
+
+ git_object_free(target);
+}
+
+void test_index_reuc__cleaned_on_checkout_tree(void)
+{
+ git_oid oid;
+ git_object *obj;
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY;
+
+ test_index_reuc__add();
+ git_reference_name_to_id(&oid, repo, "refs/heads/master");
+ git_object_lookup(&obj, repo, &oid, GIT_OBJ_ANY);
+ git_checkout_tree(repo, obj, &opts);
+ cl_assert(reuc_entry_exists() == false);
+
+ git_object_free(obj);
+}
+
+void test_index_reuc__cleaned_on_checkout_head(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY;
+
+ test_index_reuc__add();
+ git_checkout_head(repo, &opts);
+ cl_assert(reuc_entry_exists() == false);
+}
+
+void test_index_reuc__retained_on_checkout_index(void)
+{
+ git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY;
+
+ test_index_reuc__add();
+ git_checkout_index(repo, repo_index, &opts);
+ cl_assert(reuc_entry_exists() == true);
+}
diff --git a/tests/index/stage.c b/tests/index/stage.c
new file mode 100644
index 000000000..58dc1fb5e
--- /dev/null
+++ b/tests/index/stage.c
@@ -0,0 +1,62 @@
+#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);
+ repo_index = NULL;
+
+ cl_git_sandbox_cleanup();
+}
+
+
+void test_index_stage__add_always_adds_stage_0(void)
+{
+ size_t entry_idx;
+ const git_index_entry *entry;
+
+ cl_git_mkfile("./mergedrepo/new-file.txt", "new-file\n");
+
+ cl_git_pass(git_index_add_bypath(repo_index, "new-file.txt"));
+
+ cl_assert(!git_index_find(&entry_idx, repo_index, "new-file.txt"));
+ 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)
+{
+ size_t entry_idx;
+ const git_index_entry *entry;
+
+ cl_assert(!git_index_find(&entry_idx, repo_index, "one.txt"));
+ cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL);
+ cl_assert(git_index_entry_stage(entry) == 0);
+
+ cl_assert(!git_index_find(&entry_idx, repo_index, "two.txt"));
+ cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL);
+ cl_assert(git_index_entry_stage(entry) == 0);
+
+ cl_assert(!git_index_find(&entry_idx, repo_index, "conflicts-one.txt"));
+ cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL);
+ cl_assert(git_index_entry_stage(entry) == 1);
+
+ cl_assert(!git_index_find(&entry_idx, repo_index, "conflicts-two.txt"));
+ cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL);
+ cl_assert(git_index_entry_stage(entry) == 1);
+}
+
diff --git a/tests/index/tests.c b/tests/index/tests.c
new file mode 100644
index 000000000..fa5c0bb1a
--- /dev/null
+++ b/tests/index/tests.c
@@ -0,0 +1,565 @@
+#include "clar_libgit2.h"
+#include "index.h"
+
+static const size_t index_entry_count = 109;
+static const size_t index_entry_count_2 = 1437;
+#define TEST_INDEX_PATH cl_fixture("testrepo.git/index")
+#define TEST_INDEX2_PATH cl_fixture("gitgit.index")
+#define TEST_INDEXBIG_PATH cl_fixture("big.index")
+#define TEST_INDEXBAD_PATH cl_fixture("bad.index")
+
+
+/* Suite data */
+struct test_entry {
+ size_t index;
+ char path[128];
+ git_off_t file_size;
+ git_time_t mtime;
+};
+
+static struct test_entry test_entries[] = {
+ {4, "Makefile", 5064, 0x4C3F7F33},
+ {62, "tests/Makefile", 2631, 0x4C3F7F33},
+ {36, "src/index.c", 10014, 0x4C43368D},
+ {6, "git.git-authors", 2709, 0x4C3F7F33},
+ {48, "src/revobject.h", 1448, 0x4C3F7FE2}
+};
+
+/* Helpers */
+static void copy_file(const char *src, const char *dst)
+{
+ git_buf source_buf = GIT_BUF_INIT;
+ git_file dst_fd;
+
+ cl_git_pass(git_futils_readbuffer(&source_buf, src));
+
+ dst_fd = git_futils_creat_withpath(dst, 0777, 0666); /* -V536 */
+ if (dst_fd < 0)
+ goto cleanup;
+
+ cl_git_pass(p_write(dst_fd, source_buf.ptr, source_buf.size));
+
+cleanup:
+ git_buf_free(&source_buf);
+ p_close(dst_fd);
+}
+
+static void files_are_equal(const char *a, const char *b)
+{
+ git_buf buf_a = GIT_BUF_INIT;
+ git_buf buf_b = GIT_BUF_INIT;
+ int pass;
+
+ if (git_futils_readbuffer(&buf_a, a) < 0)
+ cl_assert(0);
+
+ if (git_futils_readbuffer(&buf_b, b) < 0) {
+ git_buf_free(&buf_a);
+ cl_assert(0);
+ }
+
+ pass = (buf_a.size == buf_b.size && !memcmp(buf_a.ptr, buf_b.ptr, buf_a.size));
+
+ git_buf_free(&buf_a);
+ git_buf_free(&buf_b);
+
+ cl_assert(pass);
+}
+
+
+/* Fixture setup and teardown */
+void test_index_tests__initialize(void)
+{
+}
+
+void test_index_tests__empty_index(void)
+{
+ git_index *index;
+
+ cl_git_pass(git_index_open(&index, "in-memory-index"));
+ cl_assert(index->on_disk == 0);
+
+ cl_assert(git_index_entrycount(index) == 0);
+ cl_assert(git_vector_is_sorted(&index->entries));
+
+ git_index_free(index);
+}
+
+void test_index_tests__default_test_index(void)
+{
+ git_index *index;
+ unsigned int i;
+ git_index_entry **entries;
+
+ cl_git_pass(git_index_open(&index, TEST_INDEX_PATH));
+ cl_assert(index->on_disk);
+
+ cl_assert(git_index_entrycount(index) == index_entry_count);
+ cl_assert(git_vector_is_sorted(&index->entries));
+
+ entries = (git_index_entry **)index->entries.contents;
+
+ for (i = 0; i < ARRAY_SIZE(test_entries); ++i) {
+ git_index_entry *e = entries[test_entries[i].index];
+
+ cl_assert_equal_s(e->path, test_entries[i].path);
+ cl_assert(e->mtime.seconds == test_entries[i].mtime);
+ cl_assert(e->file_size == test_entries[i].file_size);
+ }
+
+ git_index_free(index);
+}
+
+void test_index_tests__gitgit_index(void)
+{
+ git_index *index;
+
+ cl_git_pass(git_index_open(&index, TEST_INDEX2_PATH));
+ cl_assert(index->on_disk);
+
+ cl_assert(git_index_entrycount(index) == index_entry_count_2);
+ cl_assert(git_vector_is_sorted(&index->entries));
+ cl_assert(index->tree != NULL);
+
+ git_index_free(index);
+}
+
+void test_index_tests__find_in_existing(void)
+{
+ git_index *index;
+ unsigned int i;
+
+ cl_git_pass(git_index_open(&index, TEST_INDEX_PATH));
+
+ for (i = 0; i < ARRAY_SIZE(test_entries); ++i) {
+ size_t idx;
+
+ cl_assert(!git_index_find(&idx, index, test_entries[i].path));
+ cl_assert(idx == test_entries[i].index);
+ }
+
+ git_index_free(index);
+}
+
+void test_index_tests__find_in_empty(void)
+{
+ git_index *index;
+ unsigned int i;
+
+ cl_git_pass(git_index_open(&index, "fake-index"));
+
+ for (i = 0; i < ARRAY_SIZE(test_entries); ++i) {
+ cl_assert(GIT_ENOTFOUND == git_index_find(NULL, index, test_entries[i].path));
+ }
+
+ git_index_free(index);
+}
+
+void test_index_tests__write(void)
+{
+ git_index *index;
+
+ copy_file(TEST_INDEXBIG_PATH, "index_rewrite");
+
+ cl_git_pass(git_index_open(&index, "index_rewrite"));
+ cl_assert(index->on_disk);
+
+ cl_git_pass(git_index_write(index));
+ files_are_equal(TEST_INDEXBIG_PATH, "index_rewrite");
+
+ git_index_free(index);
+
+ p_unlink("index_rewrite");
+}
+
+void test_index_tests__sort0(void)
+{
+ /* sort the entires in an index */
+
+ /*
+ * TODO: This no longer applies:
+ * index sorting in Git uses some specific changes to the way
+ * directories are sorted.
+ *
+ * We need to specificially check for this by creating a new
+ * index, adding entries in random order and then
+ * checking for consistency
+ */
+}
+
+void test_index_tests__sort1(void)
+{
+ /* sort the entires in an empty index */
+ git_index *index;
+
+ cl_git_pass(git_index_open(&index, "fake-index"));
+
+ /* FIXME: this test is slightly dumb */
+ cl_assert(git_vector_is_sorted(&index->entries));
+
+ git_index_free(index);
+}
+
+static void cleanup_myrepo(void *opaque)
+{
+ GIT_UNUSED(opaque);
+ cl_fixture_cleanup("myrepo");
+}
+
+void test_index_tests__add(void)
+{
+ git_index *index;
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_repository *repo;
+ const git_index_entry *entry;
+ git_oid id1;
+
+ cl_set_cleanup(&cleanup_myrepo, NULL);
+
+ /* Intialize a new repository */
+ cl_git_pass(git_repository_init(&repo, "./myrepo", 0));
+
+ /* Ensure we're the only guy in the room */
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_assert(git_index_entrycount(index) == 0);
+
+ /* Create a new file in the working directory */
+ cl_git_pass(git_futils_mkpath2file("myrepo/test.txt", 0777));
+ cl_git_pass(git_filebuf_open(&file, "myrepo/test.txt", 0, 0666));
+ cl_git_pass(git_filebuf_write(&file, "hey there\n", 10));
+ cl_git_pass(git_filebuf_commit(&file));
+
+ /* Store the expected hash of the file/blob
+ * This has been generated by executing the following
+ * $ echo "hey there" | git hash-object --stdin
+ */
+ cl_git_pass(git_oid_fromstr(&id1, "a8233120f6ad708f843d861ce2b7228ec4e3dec6"));
+
+ /* Add the new file to the index */
+ cl_git_pass(git_index_add_bypath(index, "test.txt"));
+
+ /* Wow... it worked! */
+ cl_assert(git_index_entrycount(index) == 1);
+ entry = git_index_get_byindex(index, 0);
+
+ /* And the built-in hashing mechanism worked as expected */
+ cl_assert(git_oid_cmp(&id1, &entry->id) == 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->id) == 0);
+
+ git_index_free(index);
+ git_repository_free(repo);
+}
+
+static void cleanup_1397(void *opaque)
+{
+ GIT_UNUSED(opaque);
+ cl_git_sandbox_cleanup();
+}
+
+void test_index_tests__add_issue_1397(void)
+{
+ git_index *index;
+ git_repository *repo;
+ const git_index_entry *entry;
+ git_oid id1;
+
+ cl_set_cleanup(&cleanup_1397, NULL);
+
+ repo = cl_git_sandbox_init("issue_1397");
+
+ cl_repo_set_bool(repo, "core.autocrlf", true);
+
+ /* Ensure we're the only guy in the room */
+ cl_git_pass(git_repository_index(&index, repo));
+
+ /* Store the expected hash of the file/blob
+ * This has been generated by executing the following
+ * $ git hash-object crlf_file.txt
+ */
+ cl_git_pass(git_oid_fromstr(&id1, "8312e0889a9cbab77c732b6bc39b51a683e3a318"));
+
+ /* Make sure the initial SHA-1 is correct */
+ cl_assert((entry = git_index_get_bypath(index, "crlf_file.txt", 0)) != NULL);
+ cl_assert_(git_oid_cmp(&id1, &entry->id) == 0, "first oid check");
+
+ /* Update the index */
+ cl_git_pass(git_index_add_bypath(index, "crlf_file.txt"));
+
+ /* Check the new SHA-1 */
+ cl_assert((entry = git_index_get_bypath(index, "crlf_file.txt", 0)) != NULL);
+ cl_assert_(git_oid_cmp(&id1, &entry->id) == 0, "second oid check");
+
+ git_index_free(index);
+}
+
+void test_index_tests__add_bypath_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_bypath(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_bypath(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");
+}
+
+void test_index_tests__remove_entry(void)
+{
+ git_repository *repo;
+ git_index *index;
+
+ p_mkdir("index_test", 0770);
+
+ cl_git_pass(git_repository_init(&repo, "index_test", 0));
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_assert(git_index_entrycount(index) == 0);
+
+ cl_git_mkfile("index_test/hello", NULL);
+ cl_git_pass(git_index_add_bypath(index, "hello"));
+ cl_git_pass(git_index_write(index));
+
+ cl_git_pass(git_index_read(index, true)); /* reload */
+ cl_assert(git_index_entrycount(index) == 1);
+ cl_assert(git_index_get_bypath(index, "hello", 0) != NULL);
+
+ cl_git_pass(git_index_remove(index, "hello", 0));
+ cl_git_pass(git_index_write(index));
+
+ cl_git_pass(git_index_read(index, true)); /* reload */
+ cl_assert(git_index_entrycount(index) == 0);
+ cl_assert(git_index_get_bypath(index, "hello", 0) == NULL);
+
+ git_index_free(index);
+ git_repository_free(repo);
+ cl_fixture_cleanup("index_test");
+}
+
+void test_index_tests__remove_directory(void)
+{
+ git_repository *repo;
+ git_index *index;
+
+ p_mkdir("index_test", 0770);
+
+ cl_git_pass(git_repository_init(&repo, "index_test", 0));
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_assert_equal_i(0, (int)git_index_entrycount(index));
+
+ p_mkdir("index_test/a", 0770);
+ cl_git_mkfile("index_test/a/1.txt", NULL);
+ cl_git_mkfile("index_test/a/2.txt", NULL);
+ cl_git_mkfile("index_test/a/3.txt", NULL);
+ cl_git_mkfile("index_test/b.txt", NULL);
+
+ cl_git_pass(git_index_add_bypath(index, "a/1.txt"));
+ cl_git_pass(git_index_add_bypath(index, "a/2.txt"));
+ cl_git_pass(git_index_add_bypath(index, "a/3.txt"));
+ cl_git_pass(git_index_add_bypath(index, "b.txt"));
+ cl_git_pass(git_index_write(index));
+
+ cl_git_pass(git_index_read(index, true)); /* reload */
+ cl_assert_equal_i(4, (int)git_index_entrycount(index));
+ cl_assert(git_index_get_bypath(index, "a/1.txt", 0) != NULL);
+ cl_assert(git_index_get_bypath(index, "a/2.txt", 0) != NULL);
+ cl_assert(git_index_get_bypath(index, "b.txt", 0) != NULL);
+
+ cl_git_pass(git_index_remove(index, "a/1.txt", 0));
+ cl_git_pass(git_index_write(index));
+
+ cl_git_pass(git_index_read(index, true)); /* reload */
+ cl_assert_equal_i(3, (int)git_index_entrycount(index));
+ cl_assert(git_index_get_bypath(index, "a/1.txt", 0) == NULL);
+ cl_assert(git_index_get_bypath(index, "a/2.txt", 0) != NULL);
+ cl_assert(git_index_get_bypath(index, "b.txt", 0) != NULL);
+
+ cl_git_pass(git_index_remove_directory(index, "a", 0));
+ cl_git_pass(git_index_write(index));
+
+ cl_git_pass(git_index_read(index, true)); /* reload */
+ cl_assert_equal_i(1, (int)git_index_entrycount(index));
+ cl_assert(git_index_get_bypath(index, "a/1.txt", 0) == NULL);
+ cl_assert(git_index_get_bypath(index, "a/2.txt", 0) == NULL);
+ cl_assert(git_index_get_bypath(index, "b.txt", 0) != NULL);
+
+ git_index_free(index);
+ git_repository_free(repo);
+ cl_fixture_cleanup("index_test");
+}
+
+void test_index_tests__preserves_case(void)
+{
+ git_repository *repo;
+ git_index *index;
+ const git_index_entry *entry;
+ int index_caps;
+
+ cl_set_cleanup(&cleanup_myrepo, NULL);
+
+ cl_git_pass(git_repository_init(&repo, "./myrepo", 0));
+ cl_git_pass(git_repository_index(&index, repo));
+
+ index_caps = git_index_caps(index);
+
+ cl_git_rewritefile("myrepo/test.txt", "hey there\n");
+ cl_git_pass(git_index_add_bypath(index, "test.txt"));
+
+ cl_git_pass(p_rename("myrepo/test.txt", "myrepo/TEST.txt"));
+ cl_git_rewritefile("myrepo/TEST.txt", "hello again\n");
+ cl_git_pass(git_index_add_bypath(index, "TEST.txt"));
+
+ if (index_caps & GIT_INDEXCAP_IGNORE_CASE)
+ cl_assert_equal_i(1, (int)git_index_entrycount(index));
+ else
+ cl_assert_equal_i(2, (int)git_index_entrycount(index));
+
+ /* Test access by path instead of index */
+ cl_assert((entry = git_index_get_bypath(index, "test.txt", 0)) != NULL);
+ /* The path should *not* have changed without an explicit remove */
+ cl_assert(git__strcmp(entry->path, "test.txt") == 0);
+
+ cl_assert((entry = git_index_get_bypath(index, "TEST.txt", 0)) != NULL);
+ if (index_caps & GIT_INDEXCAP_IGNORE_CASE)
+ /* The path should *not* have changed without an explicit remove */
+ cl_assert(git__strcmp(entry->path, "test.txt") == 0);
+ else
+ cl_assert(git__strcmp(entry->path, "TEST.txt") == 0);
+
+ git_index_free(index);
+ git_repository_free(repo);
+}
+
+void test_index_tests__elocked(void)
+{
+ git_repository *repo;
+ git_index *index;
+ git_filebuf file = GIT_FILEBUF_INIT;
+ const git_error *err;
+ int error;
+
+ cl_set_cleanup(&cleanup_myrepo, NULL);
+
+ cl_git_pass(git_repository_init(&repo, "./myrepo", 0));
+ cl_git_pass(git_repository_index(&index, repo));
+
+ /* Lock the index file so we fail to lock it */
+ cl_git_pass(git_filebuf_open(&file, index->index_file_path, 0, 0666));
+ error = git_index_write(index);
+ cl_assert_equal_i(GIT_ELOCKED, error);
+
+ err = giterr_last();
+ cl_assert_equal_i(err->klass, GITERR_INDEX);
+
+ git_filebuf_cleanup(&file);
+ git_index_free(index);
+ git_repository_free(repo);
+}
+
+void test_index_tests__reload_from_disk(void)
+{
+ git_repository *repo;
+ git_index *read_index;
+ git_index *write_index;
+
+ cl_set_cleanup(&cleanup_myrepo, NULL);
+
+ cl_git_pass(git_futils_mkdir("./myrepo", NULL, 0777, GIT_MKDIR_PATH));
+ cl_git_mkfile("./myrepo/a.txt", "a\n");
+ cl_git_mkfile("./myrepo/b.txt", "b\n");
+
+ cl_git_pass(git_repository_init(&repo, "./myrepo", 0));
+ cl_git_pass(git_repository_index(&write_index, repo));
+ cl_assert_equal_i(false, write_index->on_disk);
+
+ cl_git_pass(git_index_open(&read_index, write_index->index_file_path));
+ cl_assert_equal_i(false, read_index->on_disk);
+
+ /* Stage two new files agaisnt the write_index */
+ cl_git_pass(git_index_add_bypath(write_index, "a.txt"));
+ cl_git_pass(git_index_add_bypath(write_index, "b.txt"));
+
+ cl_assert_equal_sz(2, git_index_entrycount(write_index));
+
+ /* Persist the index changes to disk */
+ cl_git_pass(git_index_write(write_index));
+ cl_assert_equal_i(true, write_index->on_disk);
+
+ /* Sync the changes back into the read_index */
+ cl_assert_equal_sz(0, git_index_entrycount(read_index));
+
+ cl_git_pass(git_index_read(read_index, true));
+ cl_assert_equal_i(true, read_index->on_disk);
+
+ cl_assert_equal_sz(2, git_index_entrycount(read_index));
+
+ /* Remove the index file from the filesystem */
+ cl_git_pass(p_unlink(write_index->index_file_path));
+
+ /* Sync the changes back into the read_index */
+ cl_git_pass(git_index_read(read_index, true));
+ cl_assert_equal_i(false, read_index->on_disk);
+ cl_assert_equal_sz(0, git_index_entrycount(read_index));
+
+ git_index_free(read_index);
+ git_index_free(write_index);
+ git_repository_free(repo);
+}
+
+void test_index_tests__corrupted_extension(void)
+{
+ git_index *index;
+
+ cl_git_fail_with(git_index_open(&index, TEST_INDEXBAD_PATH), GIT_ERROR);
+}
+
+void test_index_tests__reload_while_ignoring_case(void)
+{
+ git_index *index;
+ unsigned int caps;
+
+ cl_git_pass(git_index_open(&index, TEST_INDEX_PATH));
+ cl_git_pass(git_vector_verify_sorted(&index->entries));
+
+ caps = git_index_caps(index);
+ cl_git_pass(git_index_set_caps(index, caps &= ~GIT_INDEXCAP_IGNORE_CASE));
+ cl_git_pass(git_index_read(index, true));
+ cl_git_pass(git_vector_verify_sorted(&index->entries));
+
+ cl_git_pass(git_index_set_caps(index, caps | GIT_INDEXCAP_IGNORE_CASE));
+ cl_git_pass(git_index_read(index, true));
+ cl_git_pass(git_vector_verify_sorted(&index->entries));
+
+ git_index_free(index);
+}
diff --git a/tests/main.c b/tests/main.c
new file mode 100644
index 000000000..3de4f9801
--- /dev/null
+++ b/tests/main.c
@@ -0,0 +1,25 @@
+#include "clar_libgit2.h"
+
+#ifdef _WIN32
+int __cdecl main(int argc, char *argv[])
+#else
+int main(int argc, char *argv[])
+#endif
+{
+ int res;
+
+ clar_test_init(argc, argv);
+
+ git_threads_init();
+ cl_sandbox_set_search_path_defaults();
+
+ /* Run the test suite */
+ res = clar_test_run();
+
+ clar_test_shutdown();
+
+ giterr_clear();
+ git_threads_shutdown();
+
+ return res;
+}
diff --git a/tests/merge/files.c b/tests/merge/files.c
new file mode 100644
index 000000000..c377471e2
--- /dev/null
+++ b/tests/merge/files.c
@@ -0,0 +1,175 @@
+#include "clar_libgit2.h"
+#include "git2/repository.h"
+#include "git2/merge.h"
+#include "buffer.h"
+#include "merge.h"
+#include "merge_helpers.h"
+#include "refs.h"
+#include "fileops.h"
+
+#define TEST_REPO_PATH "merge-resolve"
+#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index"
+
+static git_repository *repo;
+static git_index *repo_index;
+
+// Fixture setup and teardown
+void test_merge_files__initialize(void)
+{
+ git_config *cfg;
+
+ repo = cl_git_sandbox_init(TEST_REPO_PATH);
+ git_repository_index(&repo_index, repo);
+
+ /* Ensure that the user's merge.conflictstyle doesn't interfere */
+ cl_git_pass(git_repository_config(&cfg, repo));
+ cl_git_pass(git_config_set_string(cfg, "merge.conflictstyle", "merge"));
+ git_config_free(cfg);
+}
+
+void test_merge_files__cleanup(void)
+{
+ git_index_free(repo_index);
+ cl_git_sandbox_cleanup();
+}
+
+void test_merge_files__automerge_from_bufs(void)
+{
+ git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT,
+ ours = GIT_MERGE_FILE_INPUT_INIT,
+ theirs = GIT_MERGE_FILE_INPUT_INIT;
+ git_merge_file_result result = {0};
+ const char *expected = "Zero\n1\n2\n3\n4\n5\n6\n7\n8\n9\nTen\n";
+
+ ancestor.ptr = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n";
+ ancestor.size = strlen(ancestor.ptr);
+ ancestor.path = "testfile.txt";
+ ancestor.mode = 0100755;
+
+ ours.ptr = "Zero\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n";
+ ours.size = strlen(ours.ptr);
+ ours.path = "testfile.txt";
+ ours.mode = 0100755;
+
+ theirs.ptr = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\nTen\n";
+ theirs.size = strlen(theirs.ptr);
+ theirs.path = "testfile.txt";
+ theirs.mode = 0100755;
+
+ cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, 0));
+
+ cl_assert_equal_i(1, result.automergeable);
+
+ cl_assert_equal_s("testfile.txt", result.path);
+ cl_assert_equal_i(0100755, result.mode);
+
+ cl_assert_equal_i(strlen(expected), result.len);
+ cl_assert_equal_strn(expected, result.ptr, result.len);
+
+ git_merge_file_result_free(&result);
+}
+
+void test_merge_files__automerge_use_best_path_and_mode(void)
+{
+ git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT,
+ ours = GIT_MERGE_FILE_INPUT_INIT,
+ theirs = GIT_MERGE_FILE_INPUT_INIT;
+ git_merge_file_result result = {0};
+ const char *expected = "Zero\n1\n2\n3\n4\n5\n6\n7\n8\n9\nTen\n";
+
+ ancestor.ptr = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n";
+ ancestor.size = strlen(ancestor.ptr);
+ ancestor.path = "testfile.txt";
+ ancestor.mode = 0100755;
+
+ ours.ptr = "Zero\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n";
+ ours.size = strlen(ours.ptr);
+ ours.path = "testfile.txt";
+ ours.mode = 0100644;
+
+ theirs.ptr = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\nTen\n";
+ theirs.size = strlen(theirs.ptr);
+ theirs.path = "theirs.txt";
+ theirs.mode = 0100755;
+
+ cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, 0));
+
+ cl_assert_equal_i(1, result.automergeable);
+
+ cl_assert_equal_s("theirs.txt", result.path);
+ cl_assert_equal_i(0100644, result.mode);
+
+ cl_assert_equal_i(strlen(expected), result.len);
+ cl_assert_equal_strn(expected, result.ptr, result.len);
+
+ git_merge_file_result_free(&result);
+}
+
+void test_merge_files__conflict_from_bufs(void)
+{
+ git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT,
+ ours = GIT_MERGE_FILE_INPUT_INIT,
+ theirs = GIT_MERGE_FILE_INPUT_INIT;
+ git_merge_file_result result = {0};
+
+ const char *expected = "<<<<<<< testfile.txt\nAloha!\nOurs.\n=======\nHi!\nTheirs.\n>>>>>>> theirs.txt\n";
+ size_t expected_len = strlen(expected);
+
+ ancestor.ptr = "Hello!\nAncestor!\n";
+ ancestor.size = strlen(ancestor.ptr);
+ ancestor.path = "testfile.txt";
+ ancestor.mode = 0100755;
+
+ ours.ptr = "Aloha!\nOurs.\n";
+ ours.size = strlen(ours.ptr);
+ ours.path = "testfile.txt";
+ ours.mode = 0100644;
+
+ theirs.ptr = "Hi!\nTheirs.\n";
+ theirs.size = strlen(theirs.ptr);
+ theirs.path = "theirs.txt";
+ theirs.mode = 0100755;
+
+ cl_git_pass(git_merge_file(&result, &ancestor, &ours, &theirs, NULL));
+
+ cl_assert_equal_i(0, result.automergeable);
+
+ cl_assert_equal_s("theirs.txt", result.path);
+ cl_assert_equal_i(0100644, result.mode);
+
+ cl_assert_equal_i(expected_len, result.len);
+ cl_assert_equal_strn(expected, result.ptr, expected_len);
+
+ git_merge_file_result_free(&result);
+}
+
+void test_merge_files__automerge_from_index(void)
+{
+ git_merge_file_result result = {0};
+ git_index_entry ancestor, ours, theirs;
+
+ git_oid_fromstr(&ancestor.id, "6212c31dab5e482247d7977e4f0dd3601decf13b");
+ ancestor.path = "automergeable.txt";
+ ancestor.mode = 0100644;
+
+ git_oid_fromstr(&ours.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf");
+ ours.path = "automergeable.txt";
+ ours.mode = 0100755;
+
+ git_oid_fromstr(&theirs.id, "058541fc37114bfc1dddf6bd6bffc7fae5c2e6fe");
+ theirs.path = "newname.txt";
+ theirs.mode = 0100644;
+
+ cl_git_pass(git_merge_file_from_index(&result, repo,
+ &ancestor, &ours, &theirs, 0));
+
+ cl_assert_equal_i(1, result.automergeable);
+
+ cl_assert_equal_s("newname.txt", result.path);
+ cl_assert_equal_i(0100755, result.mode);
+
+ cl_assert_equal_i(strlen(AUTOMERGEABLE_MERGED_FILE), result.len);
+ cl_assert_equal_strn(AUTOMERGEABLE_MERGED_FILE, result.ptr, result.len);
+
+ git_merge_file_result_free(&result);
+}
diff --git a/tests/merge/merge_helpers.c b/tests/merge/merge_helpers.c
new file mode 100644
index 000000000..154985f11
--- /dev/null
+++ b/tests/merge/merge_helpers.c
@@ -0,0 +1,362 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+#include "refs.h"
+#include "tree.h"
+#include "merge_helpers.h"
+#include "merge.h"
+#include "git2/merge.h"
+#include "git2/sys/index.h"
+
+int merge_trees_from_branches(
+ git_index **index, git_repository *repo,
+ const char *ours_name, const char *theirs_name,
+ git_merge_options *opts)
+{
+ git_commit *our_commit, *their_commit, *ancestor_commit = NULL;
+ git_tree *our_tree, *their_tree, *ancestor_tree = NULL;
+ git_oid our_oid, their_oid, ancestor_oid;
+ git_buf branch_buf = GIT_BUF_INIT;
+ int error;
+
+ git_buf_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, ours_name);
+ cl_git_pass(git_reference_name_to_id(&our_oid, repo, branch_buf.ptr));
+ cl_git_pass(git_commit_lookup(&our_commit, repo, &our_oid));
+
+ git_buf_clear(&branch_buf);
+ git_buf_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, theirs_name);
+ cl_git_pass(git_reference_name_to_id(&their_oid, repo, branch_buf.ptr));
+ cl_git_pass(git_commit_lookup(&their_commit, repo, &their_oid));
+
+ error = git_merge_base(&ancestor_oid, repo, git_commit_id(our_commit), git_commit_id(their_commit));
+
+ if (error != GIT_ENOTFOUND) {
+ cl_git_pass(error);
+
+ cl_git_pass(git_commit_lookup(&ancestor_commit, repo, &ancestor_oid));
+ cl_git_pass(git_commit_tree(&ancestor_tree, ancestor_commit));
+ }
+
+ cl_git_pass(git_commit_tree(&our_tree, our_commit));
+ cl_git_pass(git_commit_tree(&their_tree, their_commit));
+
+ cl_git_pass(git_merge_trees(index, repo, ancestor_tree, our_tree, their_tree, opts));
+
+ git_buf_free(&branch_buf);
+ git_tree_free(our_tree);
+ git_tree_free(their_tree);
+ git_tree_free(ancestor_tree);
+ git_commit_free(our_commit);
+ git_commit_free(their_commit);
+ git_commit_free(ancestor_commit);
+
+ return 0;
+}
+
+int merge_commits_from_branches(
+ git_index **index, git_repository *repo,
+ const char *ours_name, const char *theirs_name,
+ git_merge_options *opts)
+{
+ git_commit *our_commit, *their_commit;
+ git_oid our_oid, their_oid;
+ git_buf branch_buf = GIT_BUF_INIT;
+
+ git_buf_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, ours_name);
+ cl_git_pass(git_reference_name_to_id(&our_oid, repo, branch_buf.ptr));
+ cl_git_pass(git_commit_lookup(&our_commit, repo, &our_oid));
+
+ git_buf_clear(&branch_buf);
+ git_buf_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, theirs_name);
+ cl_git_pass(git_reference_name_to_id(&their_oid, repo, branch_buf.ptr));
+ cl_git_pass(git_commit_lookup(&their_commit, repo, &their_oid));
+
+ cl_git_pass(git_merge_commits(index, repo, our_commit, their_commit, opts));
+
+ git_buf_free(&branch_buf);
+ git_commit_free(our_commit);
+ git_commit_free(their_commit);
+
+ return 0;
+}
+
+int merge_branches(git_repository *repo,
+ const char *ours_branch, const char *theirs_branch,
+ git_merge_options *merge_opts, git_checkout_options *checkout_opts)
+{
+ git_reference *head_ref, *theirs_ref;
+ git_merge_head *theirs_head;
+ git_checkout_options head_checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ head_checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ cl_git_pass(git_reference_symbolic_create(&head_ref, repo, "HEAD", ours_branch, 1, NULL, NULL));
+ cl_git_pass(git_checkout_head(repo, &head_checkout_opts));
+
+ cl_git_pass(git_reference_lookup(&theirs_ref, repo, theirs_branch));
+ cl_git_pass(git_merge_head_from_ref(&theirs_head, repo, theirs_ref));
+
+ cl_git_pass(git_merge(repo, (const git_merge_head **)&theirs_head, 1, merge_opts, checkout_opts));
+
+ git_reference_free(head_ref);
+ git_reference_free(theirs_ref);
+ git_merge_head_free(theirs_head);
+
+ return 0;
+}
+
+void merge__dump_index_entries(git_vector *index_entries)
+{
+ size_t i;
+ const git_index_entry *index_entry;
+
+ printf ("\nINDEX [%d]:\n", (int)index_entries->length);
+ for (i = 0; i < index_entries->length; i++) {
+ index_entry = index_entries->contents[i];
+
+ printf("%o ", index_entry->mode);
+ printf("%s ", git_oid_allocfmt(&index_entry->id));
+ printf("%d ", git_index_entry_stage(index_entry));
+ printf("%s ", index_entry->path);
+ printf("\n");
+ }
+ printf("\n");
+}
+
+void merge__dump_names(git_index *index)
+{
+ size_t i;
+ const git_index_name_entry *conflict_name;
+
+ for (i = 0; i < git_index_name_entrycount(index); i++) {
+ conflict_name = git_index_name_get_byindex(index, i);
+
+ printf("%s %s %s\n", conflict_name->ancestor, conflict_name->ours, conflict_name->theirs);
+ }
+ printf("\n");
+}
+
+void merge__dump_reuc(git_index *index)
+{
+ size_t i;
+ const git_index_reuc_entry *reuc;
+
+ printf ("\nREUC:\n");
+ for (i = 0; i < git_index_reuc_entrycount(index); i++) {
+ reuc = git_index_reuc_get_byindex(index, i);
+
+ printf("%s ", reuc->path);
+ printf("%o ", reuc->mode[0]);
+ printf("%s\n", git_oid_allocfmt(&reuc->oid[0]));
+ printf(" %o ", reuc->mode[1]);
+ printf(" %s\n", git_oid_allocfmt(&reuc->oid[1]));
+ printf(" %o ", reuc->mode[2]);
+ printf(" %s ", git_oid_allocfmt(&reuc->oid[2]));
+ printf("\n");
+ }
+ printf("\n");
+}
+
+static int index_entry_eq_merge_index_entry(const struct merge_index_entry *expected, const git_index_entry *actual)
+{
+ git_oid expected_oid;
+ bool test_oid;
+
+ if (strlen(expected->oid_str) != 0) {
+ cl_git_pass(git_oid_fromstr(&expected_oid, expected->oid_str));
+ test_oid = 1;
+ } else
+ test_oid = 0;
+
+ if (actual->mode != expected->mode ||
+ (test_oid && git_oid_cmp(&actual->id, &expected_oid) != 0) ||
+ git_index_entry_stage(actual) != expected->stage)
+ return 0;
+
+ if (actual->mode == 0 && (actual->path != NULL || strlen(expected->path) > 0))
+ return 0;
+
+ if (actual->mode != 0 && (strcmp(actual->path, expected->path) != 0))
+ return 0;
+
+ return 1;
+}
+
+static int name_entry_eq(const char *expected, const char *actual)
+{
+ if (strlen(expected) == 0)
+ return (actual == NULL) ? 1 : 0;
+
+ return (strcmp(expected, actual) == 0) ? 1 : 0;
+}
+
+static int name_entry_eq_merge_name_entry(const struct merge_name_entry *expected, const git_index_name_entry *actual)
+{
+ if (name_entry_eq(expected->ancestor_path, actual->ancestor) == 0 ||
+ name_entry_eq(expected->our_path, actual->ours) == 0 ||
+ name_entry_eq(expected->their_path, actual->theirs) == 0)
+ return 0;
+
+ return 1;
+}
+
+static int index_conflict_data_eq_merge_diff(const struct merge_index_conflict_data *expected, git_merge_diff *actual)
+{
+ if (!index_entry_eq_merge_index_entry(&expected->ancestor.entry, &actual->ancestor_entry) ||
+ !index_entry_eq_merge_index_entry(&expected->ours.entry, &actual->our_entry) ||
+ !index_entry_eq_merge_index_entry(&expected->theirs.entry, &actual->their_entry))
+ return 0;
+
+ if (expected->ours.status != actual->our_status ||
+ expected->theirs.status != actual->their_status)
+ return 0;
+
+ return 1;
+}
+
+int merge_test_merge_conflicts(git_vector *conflicts, const struct merge_index_conflict_data expected[], size_t expected_len)
+{
+ git_merge_diff *actual;
+ size_t i;
+
+ if (conflicts->length != expected_len)
+ return 0;
+
+ for (i = 0; i < expected_len; i++) {
+ actual = conflicts->contents[i];
+
+ if (!index_conflict_data_eq_merge_diff(&expected[i], actual))
+ return 0;
+ }
+
+ return 1;
+}
+
+int merge_test_index(git_index *index, const struct merge_index_entry expected[], size_t expected_len)
+{
+ size_t i;
+ const git_index_entry *index_entry;
+
+ /*
+ dump_index_entries(&index->entries);
+ */
+
+ if (git_index_entrycount(index) != expected_len)
+ return 0;
+
+ for (i = 0; i < expected_len; i++) {
+ if ((index_entry = git_index_get_byindex(index, i)) == NULL)
+ return 0;
+
+ if (!index_entry_eq_merge_index_entry(&expected[i], index_entry))
+ return 0;
+ }
+
+ return 1;
+}
+
+int merge_test_names(git_index *index, const struct merge_name_entry expected[], size_t expected_len)
+{
+ size_t i;
+ const git_index_name_entry *name_entry;
+
+ /*
+ dump_names(index);
+ */
+
+ if (git_index_name_entrycount(index) != expected_len)
+ return 0;
+
+ for (i = 0; i < expected_len; i++) {
+ if ((name_entry = git_index_name_get_byindex(index, i)) == NULL)
+ return 0;
+
+ if (! name_entry_eq_merge_name_entry(&expected[i], name_entry))
+ return 0;
+ }
+
+ return 1;
+}
+
+int merge_test_reuc(git_index *index, const struct merge_reuc_entry expected[], size_t expected_len)
+{
+ size_t i;
+ const git_index_reuc_entry *reuc_entry;
+ git_oid expected_oid;
+
+ /*
+ dump_reuc(index);
+ */
+
+ if (git_index_reuc_entrycount(index) != expected_len)
+ return 0;
+
+ for (i = 0; i < expected_len; i++) {
+ if ((reuc_entry = git_index_reuc_get_byindex(index, i)) == NULL)
+ return 0;
+
+ if (strcmp(reuc_entry->path, expected[i].path) != 0 ||
+ reuc_entry->mode[0] != expected[i].ancestor_mode ||
+ reuc_entry->mode[1] != expected[i].our_mode ||
+ reuc_entry->mode[2] != expected[i].their_mode)
+ return 0;
+
+ if (expected[i].ancestor_mode > 0) {
+ cl_git_pass(git_oid_fromstr(&expected_oid, expected[i].ancestor_oid_str));
+
+ if (git_oid_cmp(&reuc_entry->oid[0], &expected_oid) != 0)
+ return 0;
+ }
+
+ if (expected[i].our_mode > 0) {
+ cl_git_pass(git_oid_fromstr(&expected_oid, expected[i].our_oid_str));
+
+ if (git_oid_cmp(&reuc_entry->oid[1], &expected_oid) != 0)
+ return 0;
+ }
+
+ if (expected[i].their_mode > 0) {
+ cl_git_pass(git_oid_fromstr(&expected_oid, expected[i].their_oid_str));
+
+ if (git_oid_cmp(&reuc_entry->oid[2], &expected_oid) != 0)
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+int dircount(void *payload, git_buf *pathbuf)
+{
+ int *entries = payload;
+ size_t len = git_buf_len(pathbuf);
+
+ if (len < 5 || strcmp(pathbuf->ptr + (git_buf_len(pathbuf) - 5), "/.git") != 0)
+ (*entries)++;
+
+ return 0;
+}
+
+int merge_test_workdir(git_repository *repo, const struct merge_index_entry expected[], size_t expected_len)
+{
+ size_t actual_len = 0, i;
+ git_oid actual_oid, expected_oid;
+ git_buf wd = GIT_BUF_INIT;
+
+ git_buf_puts(&wd, repo->workdir);
+ git_path_direach(&wd, 0, dircount, &actual_len);
+
+ if (actual_len != expected_len)
+ return 0;
+
+ for (i = 0; i < expected_len; i++) {
+ git_blob_create_fromworkdir(&actual_oid, repo, expected[i].path);
+ git_oid_fromstr(&expected_oid, expected[i].oid_str);
+
+ if (git_oid_cmp(&actual_oid, &expected_oid) != 0)
+ return 0;
+ }
+
+ git_buf_free(&wd);
+
+ return 1;
+}
diff --git a/tests/merge/merge_helpers.h b/tests/merge/merge_helpers.h
new file mode 100644
index 000000000..fddf8fab1
--- /dev/null
+++ b/tests/merge/merge_helpers.h
@@ -0,0 +1,111 @@
+#ifndef INCLUDE_cl_merge_helpers_h__
+#define INCLUDE_cl_merge_helpers_h__
+
+#include "merge.h"
+#include "git2/merge.h"
+
+#define AUTOMERGEABLE_MERGED_FILE \
+ "this file is changed in master\n" \
+ "this file is automergeable\n" \
+ "this file is automergeable\n" \
+ "this file is automergeable\n" \
+ "this file is automergeable\n" \
+ "this file is automergeable\n" \
+ "this file is automergeable\n" \
+ "this file is automergeable\n" \
+ "this file is changed in branch\n"
+
+#define AUTOMERGEABLE_MERGED_FILE_CRLF \
+ "this file is changed in master\r\n" \
+ "this file is automergeable\r\n" \
+ "this file is automergeable\r\n" \
+ "this file is automergeable\r\n" \
+ "this file is automergeable\r\n" \
+ "this file is automergeable\r\n" \
+ "this file is automergeable\r\n" \
+ "this file is automergeable\r\n" \
+ "this file is changed in branch\r\n"
+
+#define CONFLICTING_MERGE_FILE \
+ "<<<<<<< HEAD\n" \
+ "this file is changed in master and branch\n" \
+ "=======\n" \
+ "this file is changed in branch and master\n" \
+ ">>>>>>> 7cb63eed597130ba4abb87b3e544b85021905520\n"
+
+#define CONFLICTING_DIFF3_FILE \
+ "<<<<<<< HEAD\n" \
+ "this file is changed in master and branch\n" \
+ "||||||| initial\n" \
+ "this file is a conflict\n" \
+ "=======\n" \
+ "this file is changed in branch and master\n" \
+ ">>>>>>> 7cb63eed597130ba4abb87b3e544b85021905520\n"
+
+#define CONFLICTING_UNION_FILE \
+ "this file is changed in master and branch\n" \
+ "this file is changed in branch and master\n"
+
+
+struct merge_index_entry {
+ uint16_t mode;
+ char oid_str[41];
+ int stage;
+ char path[128];
+};
+
+struct merge_name_entry {
+ char ancestor_path[128];
+ char our_path[128];
+ char their_path[128];
+};
+
+struct merge_index_with_status {
+ struct merge_index_entry entry;
+ unsigned int status;
+};
+
+struct merge_reuc_entry {
+ char path[128];
+ unsigned int ancestor_mode;
+ unsigned int our_mode;
+ unsigned int their_mode;
+ char ancestor_oid_str[41];
+ char our_oid_str[41];
+ char their_oid_str[41];
+};
+
+struct merge_index_conflict_data {
+ struct merge_index_with_status ancestor;
+ struct merge_index_with_status ours;
+ struct merge_index_with_status theirs;
+ git_merge_diff_type_t change_type;
+};
+
+int merge_trees_from_branches(
+ git_index **index, git_repository *repo,
+ const char *ours_name, const char *theirs_name,
+ git_merge_options *opts);
+
+int merge_commits_from_branches(
+ git_index **index, git_repository *repo,
+ const char *ours_name, const char *theirs_name,
+ git_merge_options *opts);
+
+int merge_branches(git_repository *repo,
+ const char *ours_branch, const char *theirs_branch,
+ git_merge_options *merge_opts, git_checkout_options *checkout_opts);
+
+int merge_test_diff_list(git_merge_diff_list *diff_list, const struct merge_index_entry expected[], size_t expected_len);
+
+int merge_test_merge_conflicts(git_vector *conflicts, const struct merge_index_conflict_data expected[], size_t expected_len);
+
+int merge_test_index(git_index *index, const struct merge_index_entry expected[], size_t expected_len);
+
+int merge_test_names(git_index *index, const struct merge_name_entry expected[], size_t expected_len);
+
+int merge_test_reuc(git_index *index, const struct merge_reuc_entry expected[], size_t expected_len);
+
+int merge_test_workdir(git_repository *repo, const struct merge_index_entry expected[], size_t expected_len);
+
+#endif
diff --git a/tests/merge/trees/automerge.c b/tests/merge/trees/automerge.c
new file mode 100644
index 000000000..c18881d7c
--- /dev/null
+++ b/tests/merge/trees/automerge.c
@@ -0,0 +1,195 @@
+#include "clar_libgit2.h"
+#include "git2/repository.h"
+#include "git2/merge.h"
+#include "buffer.h"
+#include "merge.h"
+#include "../merge_helpers.h"
+#include "fileops.h"
+
+static git_repository *repo;
+
+#define TEST_REPO_PATH "merge-resolve"
+#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index"
+
+#define THEIRS_AUTOMERGE_BRANCH "branch"
+
+#define THEIRS_UNRELATED_BRANCH "unrelated"
+#define THEIRS_UNRELATED_OID "55b4e4687e7a0d9ca367016ed930f385d4022e6f"
+#define THEIRS_UNRELATED_PARENT "d6cf6c7741b3316826af1314042550c97ded1d50"
+
+#define OURS_DIRECTORY_FILE "df_side1"
+#define THEIRS_DIRECTORY_FILE "df_side2"
+
+/* Non-conflicting files, index entries are common to every merge operation */
+#define ADDED_IN_MASTER_INDEX_ENTRY \
+ { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }
+#define AUTOMERGEABLE_INDEX_ENTRY \
+ { 0100644, "f2e1550a0c9e53d5811175864a29536642ae3821", 0, "automergeable.txt" }
+#define CHANGED_IN_BRANCH_INDEX_ENTRY \
+ { 0100644, "4eb04c9e79e88f6640d01ff5b25ca2a60764f216", 0, "changed-in-branch.txt" }
+#define CHANGED_IN_MASTER_INDEX_ENTRY \
+ { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" }
+#define UNCHANGED_INDEX_ENTRY \
+ { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" }
+
+/* Expected REUC entries */
+#define AUTOMERGEABLE_REUC_ENTRY \
+ { "automergeable.txt", 0100644, 0100644, 0100644, \
+ "6212c31dab5e482247d7977e4f0dd3601decf13b", \
+ "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", \
+ "058541fc37114bfc1dddf6bd6bffc7fae5c2e6fe" }
+#define CONFLICTING_REUC_ENTRY \
+ { "conflicting.txt", 0100644, 0100644, 0100644, \
+ "d427e0b2e138501a3d15cc376077a3631e15bd46", \
+ "4e886e602529caa9ab11d71f86634bd1b6e0de10", \
+ "2bd0a343aeef7a2cf0d158478966a6e587ff3863" }
+#define REMOVED_IN_BRANCH_REUC_ENTRY \
+ { "removed-in-branch.txt", 0100644, 0100644, 0, \
+ "dfe3f22baa1f6fce5447901c3086bae368de6bdd", \
+ "dfe3f22baa1f6fce5447901c3086bae368de6bdd", \
+ "" }
+#define REMOVED_IN_MASTER_REUC_ENTRY \
+ { "removed-in-master.txt", 0100644, 0, 0100644, \
+ "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", \
+ "", \
+ "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5" }
+
+// Fixture setup and teardown
+void test_merge_trees_automerge__initialize(void)
+{
+ repo = cl_git_sandbox_init(TEST_REPO_PATH);
+}
+
+void test_merge_trees_automerge__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_merge_trees_automerge__automerge(void)
+{
+ git_index *index;
+ const git_index_entry *entry;
+ git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
+ git_blob *blob;
+
+ struct merge_index_entry merge_index_entries[] = {
+ ADDED_IN_MASTER_INDEX_ENTRY,
+ AUTOMERGEABLE_INDEX_ENTRY,
+ CHANGED_IN_BRANCH_INDEX_ENTRY,
+ CHANGED_IN_MASTER_INDEX_ENTRY,
+
+ { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" },
+ { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" },
+ { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" },
+
+ UNCHANGED_INDEX_ENTRY,
+ };
+
+ struct merge_reuc_entry merge_reuc_entries[] = {
+ AUTOMERGEABLE_REUC_ENTRY,
+ REMOVED_IN_BRANCH_REUC_ENTRY,
+ REMOVED_IN_MASTER_REUC_ENTRY
+ };
+
+ cl_git_pass(merge_trees_from_branches(&index, repo, "master", THEIRS_AUTOMERGE_BRANCH, &opts));
+
+ cl_assert(merge_test_index(index, merge_index_entries, 8));
+ cl_assert(merge_test_reuc(index, merge_reuc_entries, 3));
+
+ cl_assert((entry = git_index_get_bypath(index, "automergeable.txt", 0)) != NULL);
+ cl_assert(entry->file_size == strlen(AUTOMERGEABLE_MERGED_FILE));
+
+ cl_git_pass(git_object_lookup((git_object **)&blob, repo, &entry->id, GIT_OBJ_BLOB));
+ cl_assert(memcmp(git_blob_rawcontent(blob), AUTOMERGEABLE_MERGED_FILE, (size_t)entry->file_size) == 0);
+
+ git_index_free(index);
+ git_blob_free(blob);
+}
+
+void test_merge_trees_automerge__favor_ours(void)
+{
+ git_index *index;
+ git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ ADDED_IN_MASTER_INDEX_ENTRY,
+ AUTOMERGEABLE_INDEX_ENTRY,
+ CHANGED_IN_BRANCH_INDEX_ENTRY,
+ CHANGED_IN_MASTER_INDEX_ENTRY,
+ { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 0, "conflicting.txt" },
+ UNCHANGED_INDEX_ENTRY,
+ };
+
+ struct merge_reuc_entry merge_reuc_entries[] = {
+ AUTOMERGEABLE_REUC_ENTRY,
+ CONFLICTING_REUC_ENTRY,
+ REMOVED_IN_BRANCH_REUC_ENTRY,
+ REMOVED_IN_MASTER_REUC_ENTRY,
+ };
+
+ opts.file_favor = GIT_MERGE_FILE_FAVOR_OURS;
+
+ cl_git_pass(merge_trees_from_branches(&index, repo, "master", THEIRS_AUTOMERGE_BRANCH, &opts));
+
+ cl_assert(merge_test_index(index, merge_index_entries, 6));
+ cl_assert(merge_test_reuc(index, merge_reuc_entries, 4));
+
+ git_index_free(index);
+}
+
+void test_merge_trees_automerge__favor_theirs(void)
+{
+ git_index *index;
+ git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ ADDED_IN_MASTER_INDEX_ENTRY,
+ AUTOMERGEABLE_INDEX_ENTRY,
+ CHANGED_IN_BRANCH_INDEX_ENTRY,
+ CHANGED_IN_MASTER_INDEX_ENTRY,
+ { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 0, "conflicting.txt" },
+ UNCHANGED_INDEX_ENTRY,
+ };
+
+ struct merge_reuc_entry merge_reuc_entries[] = {
+ AUTOMERGEABLE_REUC_ENTRY,
+ CONFLICTING_REUC_ENTRY,
+ REMOVED_IN_BRANCH_REUC_ENTRY,
+ REMOVED_IN_MASTER_REUC_ENTRY,
+ };
+
+ opts.file_favor = GIT_MERGE_FILE_FAVOR_THEIRS;
+
+ cl_git_pass(merge_trees_from_branches(&index, repo, "master", THEIRS_AUTOMERGE_BRANCH, &opts));
+
+ cl_assert(merge_test_index(index, merge_index_entries, 6));
+ cl_assert(merge_test_reuc(index, merge_reuc_entries, 4));
+
+ git_index_free(index);
+}
+
+void test_merge_trees_automerge__unrelated(void)
+{
+ git_index *index;
+ git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" },
+ { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 2, "automergeable.txt" },
+ { 0100644, "d07ec190c306ec690bac349e87d01c4358e49bb2", 3, "automergeable.txt" },
+ { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-branch.txt" },
+ { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" },
+ { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" },
+ { 0100644, "4b253da36a0ae8bfce63aeabd8c5b58429925594", 3, "conflicting.txt" },
+ { 0100644, "ef58fdd8086c243bdc81f99e379acacfd21d32d6", 0, "new-in-unrelated1.txt" },
+ { 0100644, "948ba6e701c1edab0c2d394fb7c5538334129793", 0, "new-in-unrelated2.txt" },
+ { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" },
+ { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" },
+ };
+
+ cl_git_pass(merge_trees_from_branches(&index, repo, "master", THEIRS_UNRELATED_BRANCH, &opts));
+
+ cl_assert(merge_test_index(index, merge_index_entries, 11));
+
+ git_index_free(index);
+}
diff --git a/tests/merge/trees/commits.c b/tests/merge/trees/commits.c
new file mode 100644
index 000000000..c4e470997
--- /dev/null
+++ b/tests/merge/trees/commits.c
@@ -0,0 +1,131 @@
+#include "clar_libgit2.h"
+#include "git2/repository.h"
+#include "git2/merge.h"
+#include "merge.h"
+#include "../merge_helpers.h"
+
+static git_repository *repo;
+
+#define TEST_REPO_PATH "merge-resolve"
+
+void test_merge_trees_commits__initialize(void)
+{
+ repo = cl_git_sandbox_init(TEST_REPO_PATH);
+}
+
+void test_merge_trees_commits__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_merge_trees_commits__automerge(void)
+{
+ git_index *index;
+ const git_index_entry *entry;
+ git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
+ git_blob *blob;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" },
+ { 0100644, "f2e1550a0c9e53d5811175864a29536642ae3821", 0, "automergeable.txt" },
+ { 0100644, "4eb04c9e79e88f6640d01ff5b25ca2a60764f216", 0, "changed-in-branch.txt" },
+ { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" },
+
+ { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" },
+ { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" },
+ { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" },
+
+ { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" },
+ };
+
+ struct merge_reuc_entry merge_reuc_entries[] = {
+ { "automergeable.txt", 0100644, 0100644, 0100644, \
+ "6212c31dab5e482247d7977e4f0dd3601decf13b", \
+ "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", \
+ "058541fc37114bfc1dddf6bd6bffc7fae5c2e6fe" },
+ { "removed-in-branch.txt", 0100644, 0100644, 0, \
+ "dfe3f22baa1f6fce5447901c3086bae368de6bdd", \
+ "dfe3f22baa1f6fce5447901c3086bae368de6bdd", \
+ "" },
+ { "removed-in-master.txt", 0100644, 0, 0100644, \
+ "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", \
+ "", \
+ "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5" },
+ };
+
+ cl_git_pass(merge_commits_from_branches(&index, repo, "master", "branch", &opts));
+
+ cl_assert(merge_test_index(index, merge_index_entries, 8));
+ cl_assert(merge_test_reuc(index, merge_reuc_entries, 3));
+
+ cl_assert((entry = git_index_get_bypath(index, "automergeable.txt", 0)) != NULL);
+ cl_assert(entry->file_size == strlen(AUTOMERGEABLE_MERGED_FILE));
+
+ cl_git_pass(git_object_lookup((git_object **)&blob, repo, &entry->id, GIT_OBJ_BLOB));
+ cl_assert(memcmp(git_blob_rawcontent(blob), AUTOMERGEABLE_MERGED_FILE, (size_t)entry->file_size) == 0);
+
+ git_index_free(index);
+ git_blob_free(blob);
+}
+
+void test_merge_trees_commits__no_ancestor(void)
+{
+ git_index *index;
+ git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" },
+ { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 2, "automergeable.txt" },
+ { 0100644, "d07ec190c306ec690bac349e87d01c4358e49bb2", 3, "automergeable.txt" },
+ { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-branch.txt" },
+ { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" },
+ { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" },
+ { 0100644, "4b253da36a0ae8bfce63aeabd8c5b58429925594", 3, "conflicting.txt" },
+ { 0100644, "ef58fdd8086c243bdc81f99e379acacfd21d32d6", 0, "new-in-unrelated1.txt" },
+ { 0100644, "948ba6e701c1edab0c2d394fb7c5538334129793", 0, "new-in-unrelated2.txt" },
+ { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" },
+ { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" },
+ };
+
+ cl_git_pass(merge_commits_from_branches(&index, repo, "master", "unrelated", &opts));
+
+ cl_assert(merge_test_index(index, merge_index_entries, 11));
+
+ git_index_free(index);
+}
+
+
+void test_merge_trees_commits__df_conflict(void)
+{
+ git_index *index;
+ git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "49130a28ef567af9a6a6104c38773fedfa5f9742", 2, "dir-10" },
+ { 0100644, "6c06dcd163587c2cc18be44857e0b71116382aeb", 3, "dir-10" },
+ { 0100644, "43aafd43bea779ec74317dc361f45ae3f532a505", 0, "dir-6" },
+ { 0100644, "a031a28ae70e33a641ce4b8a8f6317f1ab79dee4", 3, "dir-7" },
+ { 0100644, "5012fd565b1393bdfda1805d4ec38ce6619e1fd1", 1, "dir-7/file.txt" },
+ { 0100644, "a5563304ddf6caba25cb50323a2ea6f7dbfcadca", 2, "dir-7/file.txt" },
+ { 0100644, "e9ad6ec3e38364a3d07feda7c4197d4d845c53b5", 0, "dir-8" },
+ { 0100644, "3ef4d30382ca33fdeba9fda895a99e0891ba37aa", 2, "dir-9" },
+ { 0100644, "fc4c636d6515e9e261f9260dbcf3cc6eca97ea08", 1, "dir-9/file.txt" },
+ { 0100644, "76ab0e2868197ec158ddd6c78d8a0d2fd73d38f9", 3, "dir-9/file.txt" },
+ { 0100644, "5c2411f8075f48a6b2fdb85ebc0d371747c4df15", 0, "file-1/new" },
+ { 0100644, "a39a620dae5bc8b4e771cd4d251b7d080401a21e", 1, "file-2" },
+ { 0100644, "d963979c237d08b6ba39062ee7bf64c7d34a27f8", 2, "file-2" },
+ { 0100644, "5c341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d", 0, "file-2/new" },
+ { 0100644, "9efe7723802d4305142eee177e018fee1572c4f4", 0, "file-3/new" },
+ { 0100644, "bacac9b3493509aa15e1730e1545fc0919d1dae0", 1, "file-4" },
+ { 0100644, "7663fce0130db092936b137cabd693ec234eb060", 3, "file-4" },
+ { 0100644, "e49f917b448d1340b31d76e54ba388268fd4c922", 0, "file-4/new" },
+ { 0100644, "cab2cf23998b40f1af2d9d9a756dc9e285a8df4b", 2, "file-5/new" },
+ { 0100644, "f5504f36e6f4eb797a56fc5bac6c6c7f32969bf2", 3, "file-5/new" },
+ };
+
+ cl_git_pass(merge_trees_from_branches(&index, repo, "df_side1", "df_side2", &opts));
+
+ cl_assert(merge_test_index(index, merge_index_entries, 20));
+
+ git_index_free(index);
+}
diff --git a/tests/merge/trees/modeconflict.c b/tests/merge/trees/modeconflict.c
new file mode 100644
index 000000000..d858b8f66
--- /dev/null
+++ b/tests/merge/trees/modeconflict.c
@@ -0,0 +1,59 @@
+#include "clar_libgit2.h"
+#include "git2/repository.h"
+#include "git2/merge.h"
+#include "buffer.h"
+#include "merge.h"
+#include "../merge_helpers.h"
+#include "fileops.h"
+
+static git_repository *repo;
+
+#define TEST_REPO_PATH "merge-resolve"
+
+#define DF_SIDE1_BRANCH "df_side1"
+#define DF_SIDE2_BRANCH "df_side2"
+
+// Fixture setup and teardown
+void test_merge_trees_modeconflict__initialize(void)
+{
+ repo = cl_git_sandbox_init(TEST_REPO_PATH);
+}
+
+void test_merge_trees_modeconflict__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_merge_trees_modeconflict__df_conflict(void)
+{
+ git_index *index;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "49130a28ef567af9a6a6104c38773fedfa5f9742", 2, "dir-10" },
+ { 0100644, "6c06dcd163587c2cc18be44857e0b71116382aeb", 3, "dir-10" },
+ { 0100644, "43aafd43bea779ec74317dc361f45ae3f532a505", 0, "dir-6" },
+ { 0100644, "a031a28ae70e33a641ce4b8a8f6317f1ab79dee4", 3, "dir-7" },
+ { 0100644, "5012fd565b1393bdfda1805d4ec38ce6619e1fd1", 1, "dir-7/file.txt" },
+ { 0100644, "a5563304ddf6caba25cb50323a2ea6f7dbfcadca", 2, "dir-7/file.txt" },
+ { 0100644, "e9ad6ec3e38364a3d07feda7c4197d4d845c53b5", 0, "dir-8" },
+ { 0100644, "3ef4d30382ca33fdeba9fda895a99e0891ba37aa", 2, "dir-9" },
+ { 0100644, "fc4c636d6515e9e261f9260dbcf3cc6eca97ea08", 1, "dir-9/file.txt" },
+ { 0100644, "76ab0e2868197ec158ddd6c78d8a0d2fd73d38f9", 3, "dir-9/file.txt" },
+ { 0100644, "5c2411f8075f48a6b2fdb85ebc0d371747c4df15", 0, "file-1/new" },
+ { 0100644, "a39a620dae5bc8b4e771cd4d251b7d080401a21e", 1, "file-2" },
+ { 0100644, "d963979c237d08b6ba39062ee7bf64c7d34a27f8", 2, "file-2" },
+ { 0100644, "5c341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d", 0, "file-2/new" },
+ { 0100644, "9efe7723802d4305142eee177e018fee1572c4f4", 0, "file-3/new" },
+ { 0100644, "bacac9b3493509aa15e1730e1545fc0919d1dae0", 1, "file-4" },
+ { 0100644, "7663fce0130db092936b137cabd693ec234eb060", 3, "file-4" },
+ { 0100644, "e49f917b448d1340b31d76e54ba388268fd4c922", 0, "file-4/new" },
+ { 0100644, "cab2cf23998b40f1af2d9d9a756dc9e285a8df4b", 2, "file-5/new" },
+ { 0100644, "f5504f36e6f4eb797a56fc5bac6c6c7f32969bf2", 3, "file-5/new" },
+ };
+
+ cl_git_pass(merge_trees_from_branches(&index, repo, DF_SIDE1_BRANCH, DF_SIDE2_BRANCH, NULL));
+
+ cl_assert(merge_test_index(index, merge_index_entries, 20));
+
+ git_index_free(index);
+}
diff --git a/tests/merge/trees/renames.c b/tests/merge/trees/renames.c
new file mode 100644
index 000000000..d7721c894
--- /dev/null
+++ b/tests/merge/trees/renames.c
@@ -0,0 +1,252 @@
+#include "clar_libgit2.h"
+#include "git2/repository.h"
+#include "git2/merge.h"
+#include "buffer.h"
+#include "merge.h"
+#include "../merge_helpers.h"
+#include "fileops.h"
+
+static git_repository *repo;
+
+#define TEST_REPO_PATH "merge-resolve"
+
+#define BRANCH_RENAME_OURS "rename_conflict_ours"
+#define BRANCH_RENAME_THEIRS "rename_conflict_theirs"
+
+// Fixture setup and teardown
+void test_merge_trees_renames__initialize(void)
+{
+ repo = cl_git_sandbox_init(TEST_REPO_PATH);
+}
+
+void test_merge_trees_renames__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_merge_trees_renames__index(void)
+{
+ git_index *index;
+ git_merge_options *opts = NULL;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" },
+ { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" },
+ { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 1, "0b-rewritten-in-ours.txt" },
+ { 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 2, "0b-rewritten-in-ours.txt" },
+ { 0100644, "b2d399ae15224e1d58066e3c8df70ce37de7a656", 3, "0b-rewritten-in-ours.txt" },
+ { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" },
+ { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 1, "0c-rewritten-in-theirs.txt" },
+ { 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 2, "0c-rewritten-in-theirs.txt" },
+ { 0100644, "712ebba6669ea847d9829e4f1059d6c830c8b531", 3, "0c-rewritten-in-theirs.txt" },
+ { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" },
+ { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" },
+ { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" },
+ { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" },
+ { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" },
+ { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 2, "3a-newname-in-ours-deleted-in-theirs.txt" },
+ { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 1, "3a-renamed-in-ours-deleted-in-theirs.txt" },
+ { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 3, "3b-newname-in-theirs-deleted-in-ours.txt" },
+ { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 1, "3b-renamed-in-theirs-deleted-in-ours.txt" },
+ { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 2, "4a-newname-in-ours-added-in-theirs.txt" },
+ { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 3, "4a-newname-in-ours-added-in-theirs.txt" },
+ { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 1, "4a-renamed-in-ours-added-in-theirs.txt" },
+ { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 2, "4b-newname-in-theirs-added-in-ours.txt" },
+ { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 3, "4b-newname-in-theirs-added-in-ours.txt" },
+ { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 1, "4b-renamed-in-theirs-added-in-ours.txt" },
+ { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 2, "5a-newname-in-ours-added-in-theirs.txt" },
+ { 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 3, "5a-newname-in-ours-added-in-theirs.txt" },
+ { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 1, "5a-renamed-in-ours-added-in-theirs.txt" },
+ { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 3, "5a-renamed-in-ours-added-in-theirs.txt" },
+ { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 2, "5b-newname-in-theirs-added-in-ours.txt" },
+ { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 3, "5b-newname-in-theirs-added-in-ours.txt" },
+ { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 1, "5b-renamed-in-theirs-added-in-ours.txt" },
+ { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 2, "5b-renamed-in-theirs-added-in-ours.txt" },
+ { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 2, "6-both-renamed-1-to-2-ours.txt" },
+ { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 3, "6-both-renamed-1-to-2-theirs.txt" },
+ { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 1, "6-both-renamed-1-to-2.txt" },
+ { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 1, "7-both-renamed-side-1.txt" },
+ { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 3, "7-both-renamed-side-1.txt" },
+ { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 1, "7-both-renamed-side-2.txt" },
+ { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 2, "7-both-renamed-side-2.txt" },
+ { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "7-both-renamed.txt" },
+ { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "7-both-renamed.txt" },
+ };
+
+ struct merge_name_entry merge_name_entries[] = {
+ {
+ "3a-renamed-in-ours-deleted-in-theirs.txt",
+ "3a-newname-in-ours-deleted-in-theirs.txt",
+ ""
+ },
+
+ {
+ "3b-renamed-in-theirs-deleted-in-ours.txt",
+ "",
+ "3b-newname-in-theirs-deleted-in-ours.txt",
+ },
+
+ {
+ "4a-renamed-in-ours-added-in-theirs.txt",
+ "4a-newname-in-ours-added-in-theirs.txt",
+ "",
+ },
+
+ {
+ "4b-renamed-in-theirs-added-in-ours.txt",
+ "",
+ "4b-newname-in-theirs-added-in-ours.txt",
+ },
+
+ {
+ "5a-renamed-in-ours-added-in-theirs.txt",
+ "5a-newname-in-ours-added-in-theirs.txt",
+ "5a-renamed-in-ours-added-in-theirs.txt",
+ },
+
+ {
+ "5b-renamed-in-theirs-added-in-ours.txt",
+ "5b-renamed-in-theirs-added-in-ours.txt",
+ "5b-newname-in-theirs-added-in-ours.txt",
+ },
+
+ {
+ "6-both-renamed-1-to-2.txt",
+ "6-both-renamed-1-to-2-ours.txt",
+ "6-both-renamed-1-to-2-theirs.txt",
+ },
+
+ {
+ "7-both-renamed-side-1.txt",
+ "7-both-renamed.txt",
+ "7-both-renamed-side-1.txt",
+ },
+
+ {
+ "7-both-renamed-side-2.txt",
+ "7-both-renamed-side-2.txt",
+ "7-both-renamed.txt",
+ },
+ };
+
+ struct merge_reuc_entry merge_reuc_entries[] = {
+ { "1a-newname-in-ours-edited-in-theirs.txt",
+ 0, 0100644, 0,
+ "",
+ "c3d02eeef75183df7584d8d13ac03053910c1301",
+ "" },
+
+ { "1a-newname-in-ours.txt",
+ 0, 0100644, 0,
+ "",
+ "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb",
+ "" },
+
+ { "1a-renamed-in-ours-edited-in-theirs.txt",
+ 0100644, 0, 0100644,
+ "c3d02eeef75183df7584d8d13ac03053910c1301",
+ "",
+ "0d872f8e871a30208305978ecbf9e66d864f1638" },
+
+ { "1a-renamed-in-ours.txt",
+ 0100644, 0, 0100644,
+ "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb",
+ "",
+ "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb" },
+
+ { "1b-newname-in-theirs-edited-in-ours.txt",
+ 0, 0, 0100644,
+ "",
+ "",
+ "241a1005cd9b980732741b74385b891142bcba28" },
+
+ { "1b-newname-in-theirs.txt",
+ 0, 0, 0100644,
+ "",
+ "",
+ "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136" },
+
+ { "1b-renamed-in-theirs-edited-in-ours.txt",
+ 0100644, 0100644, 0,
+ "241a1005cd9b980732741b74385b891142bcba28",
+ "ed9523e62e453e50dd9be1606af19399b96e397a",
+ "" },
+
+ { "1b-renamed-in-theirs.txt",
+ 0100644, 0100644, 0,
+ "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136",
+ "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136",
+ "" },
+
+ { "2-newname-in-both.txt",
+ 0, 0100644, 0100644,
+ "",
+ "178940b450f238a56c0d75b7955cb57b38191982",
+ "178940b450f238a56c0d75b7955cb57b38191982" },
+
+ { "2-renamed-in-both.txt",
+ 0100644, 0, 0,
+ "178940b450f238a56c0d75b7955cb57b38191982",
+ "",
+ "" },
+ };
+
+ cl_git_pass(merge_trees_from_branches(&index, repo,
+ BRANCH_RENAME_OURS, BRANCH_RENAME_THEIRS,
+ opts));
+
+ cl_assert(merge_test_index(index, merge_index_entries, 41));
+ cl_assert(merge_test_names(index, merge_name_entries, 9));
+ cl_assert(merge_test_reuc(index, merge_reuc_entries, 10));
+
+ git_index_free(index);
+}
+
+void test_merge_trees_renames__no_rename_index(void)
+{
+ git_index *index;
+ git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" },
+ { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" },
+ { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 1, "0b-rewritten-in-ours.txt" },
+ { 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 2, "0b-rewritten-in-ours.txt" },
+ { 0100644, "b2d399ae15224e1d58066e3c8df70ce37de7a656", 3, "0b-rewritten-in-ours.txt" },
+ { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" },
+ { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 1, "0c-rewritten-in-theirs.txt" },
+ { 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 2, "0c-rewritten-in-theirs.txt" },
+ { 0100644, "712ebba6669ea847d9829e4f1059d6c830c8b531", 3, "0c-rewritten-in-theirs.txt" },
+ { 0100644, "c3d02eeef75183df7584d8d13ac03053910c1301", 0, "1a-newname-in-ours-edited-in-theirs.txt" },
+ { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" },
+ { 0100644, "c3d02eeef75183df7584d8d13ac03053910c1301", 1, "1a-renamed-in-ours-edited-in-theirs.txt" },
+ { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 3, "1a-renamed-in-ours-edited-in-theirs.txt" },
+ { 0100644, "241a1005cd9b980732741b74385b891142bcba28", 0, "1b-newname-in-theirs-edited-in-ours.txt" },
+ { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" },
+ { 0100644, "241a1005cd9b980732741b74385b891142bcba28", 1, "1b-renamed-in-theirs-edited-in-ours.txt" },
+ { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 2, "1b-renamed-in-theirs-edited-in-ours.txt" },
+ { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" },
+ { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 0, "3a-newname-in-ours-deleted-in-theirs.txt" },
+ { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 0, "3b-newname-in-theirs-deleted-in-ours.txt" },
+ { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 2, "4a-newname-in-ours-added-in-theirs.txt" },
+ { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 3, "4a-newname-in-ours-added-in-theirs.txt" },
+ { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 2, "4b-newname-in-theirs-added-in-ours.txt" },
+ { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 3, "4b-newname-in-theirs-added-in-ours.txt" },
+ { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 2, "5a-newname-in-ours-added-in-theirs.txt" },
+ { 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 3, "5a-newname-in-ours-added-in-theirs.txt" },
+ { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 2, "5b-newname-in-theirs-added-in-ours.txt" },
+ { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 3, "5b-newname-in-theirs-added-in-ours.txt" },
+ { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-ours.txt" },
+ { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-theirs.txt" },
+ { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "7-both-renamed.txt" },
+ { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "7-both-renamed.txt" },
+ };
+
+ cl_git_pass(merge_trees_from_branches(&index, repo,
+ BRANCH_RENAME_OURS, BRANCH_RENAME_THEIRS,
+ &opts));
+
+ cl_assert(merge_test_index(index, merge_index_entries, 32));
+
+ git_index_free(index);
+}
diff --git a/tests/merge/trees/treediff.c b/tests/merge/trees/treediff.c
new file mode 100644
index 000000000..2298a302b
--- /dev/null
+++ b/tests/merge/trees/treediff.c
@@ -0,0 +1,542 @@
+#include "clar_libgit2.h"
+#include "git2/tree.h"
+#include "merge.h"
+#include "../merge_helpers.h"
+#include "diff.h"
+#include "hashsig.h"
+
+static git_repository *repo;
+
+#define TEST_REPO_PATH "merge-resolve"
+
+#define TREE_OID_ANCESTOR "0d52e3a556e189ba0948ae56780918011c1b167d"
+#define TREE_OID_MASTER "1f81433e3161efbf250576c58fede7f6b836f3d3"
+#define TREE_OID_BRANCH "eea9286df54245fea72c5b557291470eb825f38f"
+#define TREE_OID_RENAMES1 "f5f9dd5886a6ee20272be0aafc790cba43b31931"
+#define TREE_OID_RENAMES2 "5fbfbdc04b4eca46f54f4853a3c5a1dce28f5165"
+
+#define TREE_OID_DF_ANCESTOR "b8a3a806d3950e8c0a03a34f234a92eff0e2c68d"
+#define TREE_OID_DF_SIDE1 "ee1d6f164893c1866a323f072eeed36b855656be"
+#define TREE_OID_DF_SIDE2 "6178885b38fe96e825ac0f492c0a941f288b37f6"
+
+#define TREE_OID_RENAME_CONFLICT_ANCESTOR "476dbb3e207313d1d8aaa120c6ad204bf1295e53"
+#define TREE_OID_RENAME_CONFLICT_OURS "c4efe31e9decccc8b2b4d3df9aac2cdfe2995618"
+#define TREE_OID_RENAME_CONFLICT_THEIRS "9e7f4359c469f309b6057febf4c6e80742cbed5b"
+
+void test_merge_trees_treediff__initialize(void)
+{
+ repo = cl_git_sandbox_init(TEST_REPO_PATH);
+}
+
+void test_merge_trees_treediff__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+static void test_find_differences(
+ const char *ancestor_oidstr,
+ const char *ours_oidstr,
+ const char *theirs_oidstr,
+ struct merge_index_conflict_data *treediff_conflict_data,
+ size_t treediff_conflict_data_len)
+{
+ git_merge_diff_list *merge_diff_list = git_merge_diff_list__alloc(repo);
+ git_oid ancestor_oid, ours_oid, theirs_oid;
+ git_tree *ancestor_tree, *ours_tree, *theirs_tree;
+
+ git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
+ opts.flags |= GIT_MERGE_TREE_FIND_RENAMES;
+ opts.target_limit = 1000;
+ opts.rename_threshold = 50;
+
+ opts.metric = git__malloc(sizeof(git_diff_similarity_metric));
+ cl_assert(opts.metric != NULL);
+
+ opts.metric->file_signature = git_diff_find_similar__hashsig_for_file;
+ opts.metric->buffer_signature = git_diff_find_similar__hashsig_for_buf;
+ opts.metric->free_signature = git_diff_find_similar__hashsig_free;
+ opts.metric->similarity = git_diff_find_similar__calc_similarity;
+ opts.metric->payload = (void *)GIT_HASHSIG_SMART_WHITESPACE;
+
+ cl_git_pass(git_oid_fromstr(&ancestor_oid, ancestor_oidstr));
+ cl_git_pass(git_oid_fromstr(&ours_oid, ours_oidstr));
+ cl_git_pass(git_oid_fromstr(&theirs_oid, theirs_oidstr));
+
+ cl_git_pass(git_tree_lookup(&ancestor_tree, repo, &ancestor_oid));
+ cl_git_pass(git_tree_lookup(&ours_tree, repo, &ours_oid));
+ cl_git_pass(git_tree_lookup(&theirs_tree, repo, &theirs_oid));
+
+ cl_git_pass(git_merge_diff_list__find_differences(merge_diff_list, ancestor_tree, ours_tree, theirs_tree));
+ cl_git_pass(git_merge_diff_list__find_renames(repo, merge_diff_list, &opts));
+
+ /*
+ dump_merge_index(merge_index);
+ */
+
+ cl_assert(treediff_conflict_data_len == merge_diff_list->conflicts.length);
+
+ cl_assert(merge_test_merge_conflicts(&merge_diff_list->conflicts, treediff_conflict_data, treediff_conflict_data_len));
+
+ git_tree_free(ancestor_tree);
+ git_tree_free(ours_tree);
+ git_tree_free(theirs_tree);
+
+ git_merge_diff_list__free(merge_diff_list);
+
+ git__free(opts.metric);
+}
+
+void test_merge_trees_treediff__simple(void)
+{
+ struct merge_index_conflict_data treediff_conflict_data[] = {
+ {
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }, GIT_DELTA_ADDED },
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ GIT_MERGE_DIFF_NONE
+ },
+
+ {
+ { { 0100644, "6212c31dab5e482247d7977e4f0dd3601decf13b", 0, "automergeable.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 0, "automergeable.txt" }, GIT_DELTA_MODIFIED },
+ { { 0100644, "058541fc37114bfc1dddf6bd6bffc7fae5c2e6fe", 0, "automergeable.txt" }, GIT_DELTA_MODIFIED },
+ GIT_MERGE_DIFF_BOTH_MODIFIED
+ },
+
+ {
+ { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-branch.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-branch.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "4eb04c9e79e88f6640d01ff5b25ca2a60764f216", 0, "changed-in-branch.txt" }, GIT_DELTA_MODIFIED },
+ GIT_MERGE_DIFF_NONE
+ },
+
+ {
+ { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-master.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" }, GIT_DELTA_MODIFIED },
+ { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-master.txt" }, GIT_DELTA_UNMODIFIED },
+ GIT_MERGE_DIFF_NONE
+ },
+
+ {
+ { { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 0, "conflicting.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 0, "conflicting.txt" }, GIT_DELTA_MODIFIED },
+ { { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 0, "conflicting.txt" }, GIT_DELTA_MODIFIED },
+ GIT_MERGE_DIFF_BOTH_MODIFIED
+ },
+
+ {
+ { { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_DELETED },
+ GIT_MERGE_DIFF_NONE
+ },
+
+ {
+ { { 0100644, "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", 0, "removed-in-master.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_DELETED },
+ { { 0100644, "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", 0, "removed-in-master.txt" }, GIT_DELTA_UNMODIFIED },
+ GIT_MERGE_DIFF_NONE
+ },
+ };
+
+ test_find_differences(TREE_OID_ANCESTOR, TREE_OID_MASTER, TREE_OID_BRANCH, treediff_conflict_data, 7);
+}
+
+void test_merge_trees_treediff__df_conflicts(void)
+{
+ struct merge_index_conflict_data treediff_conflict_data[] = {
+ {
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "49130a28ef567af9a6a6104c38773fedfa5f9742", 0, "dir-10" }, GIT_DELTA_ADDED },
+ { { 0100644, "6c06dcd163587c2cc18be44857e0b71116382aeb", 0, "dir-10" }, GIT_DELTA_ADDED },
+ GIT_MERGE_DIFF_BOTH_ADDED,
+ },
+
+ {
+ { { 0100644, "242591eb280ee9eeb2ce63524b9a8b9bc4cb515d", 0, "dir-10/file.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_DELETED },
+ { { 0, "", 0, "" }, GIT_DELTA_DELETED },
+ GIT_MERGE_DIFF_BOTH_DELETED,
+ },
+
+ {
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "43aafd43bea779ec74317dc361f45ae3f532a505", 0, "dir-6" }, GIT_DELTA_ADDED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0100644, "cf8c5cc8a85a1ff5a4ba51e0bc7cf5665669924d", 0, "dir-6/file.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "cf8c5cc8a85a1ff5a4ba51e0bc7cf5665669924d", 0, "dir-6/file.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_DELETED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "a031a28ae70e33a641ce4b8a8f6317f1ab79dee4", 0, "dir-7" }, GIT_DELTA_ADDED },
+ GIT_MERGE_DIFF_DIRECTORY_FILE,
+ },
+
+ {
+ { { 0100644, "5012fd565b1393bdfda1805d4ec38ce6619e1fd1", 0, "dir-7/file.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "a5563304ddf6caba25cb50323a2ea6f7dbfcadca", 0, "dir-7/file.txt" }, GIT_DELTA_MODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_DELETED },
+ GIT_MERGE_DIFF_DF_CHILD,
+ },
+
+ {
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "e9ad6ec3e38364a3d07feda7c4197d4d845c53b5", 0, "dir-8" }, GIT_DELTA_ADDED },
+ { {0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0100644, "f20c9063fa0bda9a397c96947a7b687305c49753", 0, "dir-8/file.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_DELETED },
+ { { 0100644, "f20c9063fa0bda9a397c96947a7b687305c49753", 0, "dir-8/file.txt" }, GIT_DELTA_UNMODIFIED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "3ef4d30382ca33fdeba9fda895a99e0891ba37aa", 0, "dir-9" }, GIT_DELTA_ADDED },
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ GIT_MERGE_DIFF_DIRECTORY_FILE,
+ },
+
+ {
+ { { 0100644, "fc4c636d6515e9e261f9260dbcf3cc6eca97ea08", 0, "dir-9/file.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_DELETED },
+ { { 0100644, "76ab0e2868197ec158ddd6c78d8a0d2fd73d38f9", 0, "dir-9/file.txt" }, GIT_DELTA_MODIFIED },
+ GIT_MERGE_DIFF_DF_CHILD,
+ },
+
+ {
+ { { 0100644, "1e4ff029aee68d0d69ef9eb6efa6cbf1ec732f99", 0, "file-1" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "1e4ff029aee68d0d69ef9eb6efa6cbf1ec732f99", 0, "file-1" }, GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_DELETED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "5c2411f8075f48a6b2fdb85ebc0d371747c4df15", 0, "file-1/new" }, GIT_DELTA_ADDED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0100644, "a39a620dae5bc8b4e771cd4d251b7d080401a21e", 0, "file-2" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "d963979c237d08b6ba39062ee7bf64c7d34a27f8", 0, "file-2" }, GIT_DELTA_MODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_DELETED },
+ GIT_MERGE_DIFF_DIRECTORY_FILE,
+ },
+
+ {
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "5c341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d", 0, "file-2/new" }, GIT_DELTA_ADDED },
+ GIT_MERGE_DIFF_DF_CHILD,
+ },
+
+ {
+ { { 0100644, "032ebc5ab85d9553bb187d3cd40875ff23a63ed0", 0, "file-3" }, GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_DELETED },
+ { { 0100644, "032ebc5ab85d9553bb187d3cd40875ff23a63ed0", 0, "file-3" }, GIT_DELTA_UNMODIFIED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "9efe7723802d4305142eee177e018fee1572c4f4", 0, "file-3/new" }, GIT_DELTA_ADDED },
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0100644, "bacac9b3493509aa15e1730e1545fc0919d1dae0", 0, "file-4" }, GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_DELETED },
+ { { 0100644, "7663fce0130db092936b137cabd693ec234eb060", 0, "file-4" }, GIT_DELTA_MODIFIED },
+ GIT_MERGE_DIFF_DIRECTORY_FILE,
+ },
+
+ {
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "e49f917b448d1340b31d76e54ba388268fd4c922", 0, "file-4/new" }, GIT_DELTA_ADDED },
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ GIT_MERGE_DIFF_DF_CHILD,
+ },
+
+ {
+ { { 0100644, "ac4045f965119e6998f4340ed0f411decfb3ec05", 0, "file-5" }, GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_DELETED },
+ { { 0, "", 0, "" }, GIT_DELTA_DELETED },
+ GIT_MERGE_DIFF_BOTH_DELETED,
+ },
+
+ {
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "cab2cf23998b40f1af2d9d9a756dc9e285a8df4b", 0, "file-5/new" }, GIT_DELTA_ADDED },
+ { { 0100644, "f5504f36e6f4eb797a56fc5bac6c6c7f32969bf2", 0, "file-5/new" }, GIT_DELTA_ADDED },
+ GIT_MERGE_DIFF_BOTH_ADDED,
+ },
+ };
+
+ test_find_differences(TREE_OID_DF_ANCESTOR, TREE_OID_DF_SIDE1, TREE_OID_DF_SIDE2, treediff_conflict_data, 20);
+}
+
+void test_merge_trees_treediff__strict_renames(void)
+{
+ struct merge_index_conflict_data treediff_conflict_data[] = {
+ {
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }, GIT_DELTA_ADDED },
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0100644, "6212c31dab5e482247d7977e4f0dd3601decf13b", 0, "automergeable.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 0, "automergeable.txt" }, GIT_DELTA_MODIFIED },
+ { { 0100644, "6212c31dab5e482247d7977e4f0dd3601decf13b", 0, "automergeable.txt" }, GIT_DELTA_UNMODIFIED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-master.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" }, GIT_DELTA_MODIFIED },
+ { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-master.txt" }, GIT_DELTA_UNMODIFIED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 0, "conflicting.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 0, "conflicting.txt" }, GIT_DELTA_MODIFIED },
+ { { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 0, "conflicting.txt" }, GIT_DELTA_UNMODIFIED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "renamed-in-branch.txt" }, GIT_DELTA_RENAMED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0100644, "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", 0, "removed-in-master.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_DELETED },
+ { { 0100644, "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", 0, "removed-in-master.txt" }, GIT_DELTA_UNMODIFIED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "renamed.txt" }, GIT_DELTA_ADDED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "copied.txt" }, GIT_DELTA_RENAMED },
+ GIT_MERGE_DIFF_NONE,
+ },
+ };
+
+ test_find_differences(TREE_OID_ANCESTOR, TREE_OID_MASTER, TREE_OID_RENAMES1, treediff_conflict_data, 8);
+}
+
+void test_merge_trees_treediff__rename_conflicts(void)
+{
+ struct merge_index_conflict_data treediff_conflict_data[] = {
+ {
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" }, GIT_DELTA_ADDED },
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-rewritten-in-ours.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 0, "0b-rewritten-in-ours.txt" }, GIT_DELTA_MODIFIED },
+ { { 0100644, "b2d399ae15224e1d58066e3c8df70ce37de7a656", 0, "0b-rewritten-in-ours.txt" }, GIT_DELTA_MODIFIED },
+ GIT_MERGE_DIFF_BOTH_MODIFIED,
+ },
+
+ {
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" }, GIT_DELTA_ADDED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-rewritten-in-theirs.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 0, "0c-rewritten-in-theirs.txt" }, GIT_DELTA_MODIFIED },
+ { { 0100644, "712ebba6669ea847d9829e4f1059d6c830c8b531", 0, "0c-rewritten-in-theirs.txt" }, GIT_DELTA_MODIFIED },
+ GIT_MERGE_DIFF_BOTH_MODIFIED,
+ },
+
+ {
+ { { 0100644, "c3d02eeef75183df7584d8d13ac03053910c1301", 0, "1a-renamed-in-ours-edited-in-theirs.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "c3d02eeef75183df7584d8d13ac03053910c1301", 0, "1a-newname-in-ours-edited-in-theirs.txt" }, GIT_DELTA_RENAMED },
+ { { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-renamed-in-ours-edited-in-theirs.txt" }, GIT_DELTA_MODIFIED },
+ GIT_MERGE_DIFF_RENAMED_MODIFIED,
+ },
+
+ {
+ { { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-renamed-in-ours.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" }, GIT_DELTA_RENAMED },
+ { { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-renamed-in-ours.txt" }, GIT_DELTA_UNMODIFIED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0100644, "241a1005cd9b980732741b74385b891142bcba28", 0, "1b-renamed-in-theirs-edited-in-ours.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-renamed-in-theirs-edited-in-ours.txt" }, GIT_DELTA_MODIFIED },
+ { { 0100644, "241a1005cd9b980732741b74385b891142bcba28", 0, "1b-newname-in-theirs-edited-in-ours.txt" }, GIT_DELTA_RENAMED },
+ GIT_MERGE_DIFF_RENAMED_MODIFIED,
+ },
+
+ {
+ { { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-renamed-in-theirs.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-renamed-in-theirs.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" }, GIT_DELTA_RENAMED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-renamed-in-both.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, GIT_DELTA_RENAMED },
+ { { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" }, GIT_DELTA_RENAMED },
+ GIT_MERGE_DIFF_BOTH_RENAMED,
+ },
+
+ {
+ { { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 0, "3a-renamed-in-ours-deleted-in-theirs.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 0, "3a-newname-in-ours-deleted-in-theirs.txt" }, GIT_DELTA_RENAMED },
+ { { 0, "", 0, "" }, GIT_DELTA_DELETED },
+ GIT_MERGE_DIFF_RENAMED_DELETED,
+ },
+
+ {
+ { { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 0, "3b-renamed-in-theirs-deleted-in-ours.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_DELETED },
+ { { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 0, "3b-newname-in-theirs-deleted-in-ours.txt" }, GIT_DELTA_RENAMED },
+ GIT_MERGE_DIFF_RENAMED_DELETED,
+ },
+
+ {
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 0, "4a-newname-in-ours-added-in-theirs.txt" }, GIT_DELTA_ADDED },
+ GIT_MERGE_DIFF_RENAMED_ADDED,
+ },
+
+ {
+ { { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 0, "4a-renamed-in-ours-added-in-theirs.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 0, "4a-newname-in-ours-added-in-theirs.txt" }, GIT_DELTA_RENAMED },
+ { { 0, "", 0, "" }, GIT_DELTA_DELETED },
+ GIT_MERGE_DIFF_RENAMED_ADDED,
+ },
+
+ {
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 0, "4b-newname-in-theirs-added-in-ours.txt" }, GIT_DELTA_ADDED },
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ GIT_MERGE_DIFF_RENAMED_ADDED,
+ },
+
+ {
+ { { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 0, "4b-renamed-in-theirs-added-in-ours.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_DELETED },
+ { { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 0, "4b-newname-in-theirs-added-in-ours.txt" }, GIT_DELTA_RENAMED },
+ GIT_MERGE_DIFF_RENAMED_ADDED,
+ },
+
+ {
+ { { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "5-both-renamed-1-to-2.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "5-both-renamed-1-to-2-ours.txt" }, GIT_DELTA_RENAMED },
+ { { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "5-both-renamed-1-to-2-theirs.txt" }, GIT_DELTA_RENAMED },
+ GIT_MERGE_DIFF_BOTH_RENAMED_1_TO_2,
+ },
+
+ {
+ { { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "6-both-renamed-side-1.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "6-both-renamed.txt" }, GIT_DELTA_RENAMED },
+ { { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "6-both-renamed-side-1.txt" }, GIT_DELTA_UNMODIFIED },
+ GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1,
+ },
+
+ {
+ { { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "6-both-renamed-side-2.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "6-both-renamed-side-2.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "6-both-renamed.txt" }, GIT_DELTA_RENAMED },
+ GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1,
+ },
+ };
+ test_find_differences(TREE_OID_RENAME_CONFLICT_ANCESTOR,
+ TREE_OID_RENAME_CONFLICT_OURS, TREE_OID_RENAME_CONFLICT_THEIRS, treediff_conflict_data, 18);
+}
+
+void test_merge_trees_treediff__best_renames(void)
+{
+ struct merge_index_conflict_data treediff_conflict_data[] = {
+ {
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" }, GIT_DELTA_ADDED },
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0100644, "6212c31dab5e482247d7977e4f0dd3601decf13b", 0, "automergeable.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 0, "automergeable.txt" }, GIT_DELTA_MODIFIED },
+ { { 0100644, "45299c1ca5e07bba1fd90843056fb559f96b1f5a", 0, "renamed-90.txt" }, GIT_DELTA_RENAMED },
+ GIT_MERGE_DIFF_RENAMED_MODIFIED,
+ },
+
+ {
+ { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-master.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" }, GIT_DELTA_MODIFIED },
+ { { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-master.txt" }, GIT_DELTA_UNMODIFIED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 0, "conflicting.txt" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 0, "conflicting.txt" }, GIT_DELTA_MODIFIED },
+ { { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 0, "conflicting.txt" }, GIT_DELTA_UNMODIFIED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0100644, "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", 0, "removed-in-master.txt" },GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_DELETED },
+ { { 0100644, "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", 0, "removed-in-master.txt" }, GIT_DELTA_UNMODIFIED },
+ GIT_MERGE_DIFF_MODIFIED_DELETED,
+ },
+
+ {
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "5843febcb23480df0b5edb22a21c59c772bb8e29", 0, "renamed-50.txt" }, GIT_DELTA_ADDED },
+ GIT_MERGE_DIFF_NONE,
+ },
+
+ {
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0, "", 0, "" }, GIT_DELTA_UNMODIFIED },
+ { { 0100644, "a77a56a49f8f3ae242e02717f18ebbc60c5cc543", 0, "renamed-75.txt" }, GIT_DELTA_ADDED },
+ GIT_MERGE_DIFF_NONE,
+ },
+ };
+
+ test_find_differences(TREE_OID_ANCESTOR, TREE_OID_MASTER, TREE_OID_RENAMES2, treediff_conflict_data, 7);
+}
diff --git a/tests/merge/trees/trivial.c b/tests/merge/trees/trivial.c
new file mode 100644
index 000000000..62a4574b8
--- /dev/null
+++ b/tests/merge/trees/trivial.c
@@ -0,0 +1,306 @@
+#include "clar_libgit2.h"
+#include "git2/repository.h"
+#include "git2/merge.h"
+#include "merge.h"
+#include "../merge_helpers.h"
+#include "refs.h"
+#include "fileops.h"
+#include "git2/sys/index.h"
+
+static git_repository *repo;
+
+#define TEST_REPO_PATH "merge-resolve"
+#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index"
+
+
+// Fixture setup and teardown
+void test_merge_trees_trivial__initialize(void)
+{
+ repo = cl_git_sandbox_init(TEST_REPO_PATH);
+}
+
+void test_merge_trees_trivial__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+
+static int merge_trivial(git_index **index, const char *ours, const char *theirs)
+{
+ git_commit *our_commit, *their_commit, *ancestor_commit;
+ git_tree *our_tree, *their_tree, *ancestor_tree;
+ git_oid our_oid, their_oid, ancestor_oid;
+ git_buf branch_buf = GIT_BUF_INIT;
+ git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
+
+ git_buf_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, ours);
+ cl_git_pass(git_reference_name_to_id(&our_oid, repo, branch_buf.ptr));
+ cl_git_pass(git_commit_lookup(&our_commit, repo, &our_oid));
+
+ git_buf_clear(&branch_buf);
+ git_buf_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, theirs);
+ cl_git_pass(git_reference_name_to_id(&their_oid, repo, branch_buf.ptr));
+ cl_git_pass(git_commit_lookup(&their_commit, repo, &their_oid));
+
+ cl_git_pass(git_merge_base(&ancestor_oid, repo, git_commit_id(our_commit), git_commit_id(their_commit)));
+ cl_git_pass(git_commit_lookup(&ancestor_commit, repo, &ancestor_oid));
+
+ cl_git_pass(git_commit_tree(&ancestor_tree, ancestor_commit));
+ cl_git_pass(git_commit_tree(&our_tree, our_commit));
+ cl_git_pass(git_commit_tree(&their_tree, their_commit));
+
+ cl_git_pass(git_merge_trees(index, repo, ancestor_tree, our_tree, their_tree, &opts));
+
+ git_buf_free(&branch_buf);
+ git_tree_free(our_tree);
+ git_tree_free(their_tree);
+ git_tree_free(ancestor_tree);
+ git_commit_free(our_commit);
+ git_commit_free(their_commit);
+ git_commit_free(ancestor_commit);
+
+ return 0;
+}
+
+static int merge_trivial_conflict_entrycount(git_index *index)
+{
+ const git_index_entry *entry;
+ int count = 0;
+ size_t i;
+
+ for (i = 0; i < git_index_entrycount(index); i++) {
+ cl_assert(entry = git_index_get_byindex(index, i));
+
+ if (git_index_entry_stage(entry) > 0)
+ count++;
+ }
+
+ return count;
+}
+
+/* 2ALT: ancest:(empty)+, head:*empty*, remote:remote = result:remote */
+void test_merge_trees_trivial__2alt(void)
+{
+ git_index *result;
+ const git_index_entry *entry;
+
+ cl_git_pass(merge_trivial(&result, "trivial-2alt", "trivial-2alt-branch"));
+
+ cl_assert(entry = git_index_get_bypath(result, "new-in-branch.txt", 0));
+ cl_assert(git_index_reuc_entrycount(result) == 0);
+ cl_assert(merge_trivial_conflict_entrycount(result) == 0);
+
+ git_index_free(result);
+}
+
+/* 3ALT: ancest:(empty)+, head:head, remote:*empty* = result:head */
+void test_merge_trees_trivial__3alt(void)
+{
+ git_index *result;
+ const git_index_entry *entry;
+
+ cl_git_pass(merge_trivial(&result, "trivial-3alt", "trivial-3alt-branch"));
+
+ cl_assert(entry = git_index_get_bypath(result, "new-in-3alt.txt", 0));
+ cl_assert(git_index_reuc_entrycount(result) == 0);
+ cl_assert(merge_trivial_conflict_entrycount(result) == 0);
+
+ git_index_free(result);
+}
+
+/* 4: ancest:(empty)^, head:head, remote:remote = result:no merge */
+void test_merge_trees_trivial__4(void)
+{
+ git_index *result;
+ const git_index_entry *entry;
+
+ cl_git_pass(merge_trivial(&result, "trivial-4", "trivial-4-branch"));
+
+ cl_assert((entry = git_index_get_bypath(result, "new-and-different.txt", 0)) == NULL);
+ cl_assert(git_index_reuc_entrycount(result) == 0);
+
+ cl_assert(merge_trivial_conflict_entrycount(result) == 2);
+ cl_assert(entry = git_index_get_bypath(result, "new-and-different.txt", 2));
+ cl_assert(entry = git_index_get_bypath(result, "new-and-different.txt", 3));
+
+ git_index_free(result);
+}
+
+/* 5ALT: ancest:*, head:head, remote:head = result:head */
+void test_merge_trees_trivial__5alt_1(void)
+{
+ git_index *result;
+ const git_index_entry *entry;
+
+ cl_git_pass(merge_trivial(&result, "trivial-5alt-1", "trivial-5alt-1-branch"));
+
+ cl_assert(entry = git_index_get_bypath(result, "new-and-same.txt", 0));
+ cl_assert(git_index_reuc_entrycount(result) == 0);
+ cl_assert(merge_trivial_conflict_entrycount(result) == 0);
+
+ git_index_free(result);
+}
+
+/* 5ALT: ancest:*, head:head, remote:head = result:head */
+void test_merge_trees_trivial__5alt_2(void)
+{
+ git_index *result;
+ const git_index_entry *entry;
+
+ cl_git_pass(merge_trivial(&result, "trivial-5alt-2", "trivial-5alt-2-branch"));
+
+ cl_assert(entry = git_index_get_bypath(result, "modified-to-same.txt", 0));
+ cl_assert(git_index_reuc_entrycount(result) == 0);
+ cl_assert(merge_trivial_conflict_entrycount(result) == 0);
+
+ git_index_free(result);
+}
+
+/* 6: ancest:ancest+, head:(empty), remote:(empty) = result:no merge */
+void test_merge_trees_trivial__6(void)
+{
+ git_index *result;
+ const git_index_entry *entry;
+ const git_index_reuc_entry *reuc;
+
+ cl_git_pass(merge_trivial(&result, "trivial-6", "trivial-6-branch"));
+
+ cl_assert((entry = git_index_get_bypath(result, "removed-in-both.txt", 0)) == NULL);
+ cl_assert(git_index_reuc_entrycount(result) == 1);
+ cl_assert(reuc = git_index_reuc_get_bypath(result, "removed-in-both.txt"));
+
+ cl_assert(merge_trivial_conflict_entrycount(result) == 0);
+
+ git_index_free(result);
+}
+
+/* 8: ancest:ancest^, head:(empty), remote:ancest = result:no merge */
+void test_merge_trees_trivial__8(void)
+{
+ git_index *result;
+ const git_index_entry *entry;
+ const git_index_reuc_entry *reuc;
+
+ cl_git_pass(merge_trivial(&result, "trivial-8", "trivial-8-branch"));
+
+ cl_assert((entry = git_index_get_bypath(result, "removed-in-8.txt", 0)) == NULL);
+
+ cl_assert(git_index_reuc_entrycount(result) == 1);
+ cl_assert(reuc = git_index_reuc_get_bypath(result, "removed-in-8.txt"));
+
+ cl_assert(merge_trivial_conflict_entrycount(result) == 0);
+
+ git_index_free(result);
+}
+
+/* 7: ancest:ancest+, head:(empty), remote:remote = result:no merge */
+void test_merge_trees_trivial__7(void)
+{
+ git_index *result;
+ const git_index_entry *entry;
+
+ cl_git_pass(merge_trivial(&result, "trivial-7", "trivial-7-branch"));
+
+ cl_assert((entry = git_index_get_bypath(result, "removed-in-7.txt", 0)) == NULL);
+ cl_assert(git_index_reuc_entrycount(result) == 0);
+
+ cl_assert(merge_trivial_conflict_entrycount(result) == 2);
+ cl_assert(entry = git_index_get_bypath(result, "removed-in-7.txt", 1));
+ cl_assert(entry = git_index_get_bypath(result, "removed-in-7.txt", 3));
+
+ git_index_free(result);
+}
+
+/* 10: ancest:ancest^, head:ancest, remote:(empty) = result:no merge */
+void test_merge_trees_trivial__10(void)
+{
+ git_index *result;
+ const git_index_entry *entry;
+ const git_index_reuc_entry *reuc;
+
+ cl_git_pass(merge_trivial(&result, "trivial-10", "trivial-10-branch"));
+
+ cl_assert((entry = git_index_get_bypath(result, "removed-in-10-branch.txt", 0)) == NULL);
+
+ cl_assert(git_index_reuc_entrycount(result) == 1);
+ cl_assert(reuc = git_index_reuc_get_bypath(result, "removed-in-10-branch.txt"));
+
+ cl_assert(merge_trivial_conflict_entrycount(result) == 0);
+
+ git_index_free(result);
+}
+
+/* 9: ancest:ancest+, head:head, remote:(empty) = result:no merge */
+void test_merge_trees_trivial__9(void)
+{
+ git_index *result;
+ const git_index_entry *entry;
+
+ cl_git_pass(merge_trivial(&result, "trivial-9", "trivial-9-branch"));
+
+ cl_assert((entry = git_index_get_bypath(result, "removed-in-9-branch.txt", 0)) == NULL);
+ cl_assert(git_index_reuc_entrycount(result) == 0);
+
+ cl_assert(merge_trivial_conflict_entrycount(result) == 2);
+ cl_assert(entry = git_index_get_bypath(result, "removed-in-9-branch.txt", 1));
+ cl_assert(entry = git_index_get_bypath(result, "removed-in-9-branch.txt", 2));
+
+ git_index_free(result);
+}
+
+/* 13: ancest:ancest+, head:head, remote:ancest = result:head */
+void test_merge_trees_trivial__13(void)
+{
+ git_index *result;
+ const git_index_entry *entry;
+ git_oid expected_oid;
+
+ cl_git_pass(merge_trivial(&result, "trivial-13", "trivial-13-branch"));
+
+ cl_assert(entry = git_index_get_bypath(result, "modified-in-13.txt", 0));
+ cl_git_pass(git_oid_fromstr(&expected_oid, "1cff9ec6a47a537380dedfdd17c9e76d74259a2b"));
+ cl_assert(git_oid_cmp(&entry->id, &expected_oid) == 0);
+
+ cl_assert(git_index_reuc_entrycount(result) == 0);
+ cl_assert(merge_trivial_conflict_entrycount(result) == 0);
+
+ git_index_free(result);
+}
+
+/* 14: ancest:ancest+, head:ancest, remote:remote = result:remote */
+void test_merge_trees_trivial__14(void)
+{
+ git_index *result;
+ const git_index_entry *entry;
+ git_oid expected_oid;
+
+ cl_git_pass(merge_trivial(&result, "trivial-14", "trivial-14-branch"));
+
+ cl_assert(entry = git_index_get_bypath(result, "modified-in-14-branch.txt", 0));
+ cl_git_pass(git_oid_fromstr(&expected_oid, "26153a3ff3649b6c2bb652d3f06878c6e0a172f9"));
+ cl_assert(git_oid_cmp(&entry->id, &expected_oid) == 0);
+
+ cl_assert(git_index_reuc_entrycount(result) == 0);
+ cl_assert(merge_trivial_conflict_entrycount(result) == 0);
+
+ git_index_free(result);
+}
+
+/* 11: ancest:ancest+, head:head, remote:remote = result:no merge */
+void test_merge_trees_trivial__11(void)
+{
+ git_index *result;
+ const git_index_entry *entry;
+
+ cl_git_pass(merge_trivial(&result, "trivial-11", "trivial-11-branch"));
+
+ cl_assert((entry = git_index_get_bypath(result, "modified-in-both.txt", 0)) == NULL);
+ cl_assert(git_index_reuc_entrycount(result) == 0);
+
+ cl_assert(merge_trivial_conflict_entrycount(result) == 3);
+ cl_assert(entry = git_index_get_bypath(result, "modified-in-both.txt", 1));
+ cl_assert(entry = git_index_get_bypath(result, "modified-in-both.txt", 2));
+ cl_assert(entry = git_index_get_bypath(result, "modified-in-both.txt", 3));
+
+ git_index_free(result);
+}
diff --git a/tests/merge/workdir/analysis.c b/tests/merge/workdir/analysis.c
new file mode 100644
index 000000000..85918abe4
--- /dev/null
+++ b/tests/merge/workdir/analysis.c
@@ -0,0 +1,140 @@
+#include "clar_libgit2.h"
+#include "git2/repository.h"
+#include "git2/merge.h"
+#include "git2/sys/index.h"
+#include "merge.h"
+#include "../merge_helpers.h"
+#include "refs.h"
+#include "posix.h"
+
+static git_repository *repo;
+static git_index *repo_index;
+
+#define TEST_REPO_PATH "merge-resolve"
+#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index"
+
+#define UPTODATE_BRANCH "master"
+#define PREVIOUS_BRANCH "previous"
+
+#define FASTFORWARD_BRANCH "ff_branch"
+#define FASTFORWARD_ID "fd89f8cffb663ac89095a0f9764902e93ceaca6a"
+
+#define NOFASTFORWARD_BRANCH "branch"
+#define NOFASTFORWARD_ID "7cb63eed597130ba4abb87b3e544b85021905520"
+
+
+// Fixture setup and teardown
+void test_merge_workdir_analysis__initialize(void)
+{
+ repo = cl_git_sandbox_init(TEST_REPO_PATH);
+ git_repository_index(&repo_index, repo);
+}
+
+void test_merge_workdir_analysis__cleanup(void)
+{
+ git_index_free(repo_index);
+ cl_git_sandbox_cleanup();
+}
+
+static void analysis_from_branch(
+ git_merge_analysis_t *merge_analysis,
+ git_merge_preference_t *merge_pref,
+ const char *branchname)
+{
+ git_buf refname = GIT_BUF_INIT;
+ git_reference *their_ref;
+ git_merge_head *their_head;
+
+ git_buf_printf(&refname, "%s%s", GIT_REFS_HEADS_DIR, branchname);
+
+ cl_git_pass(git_reference_lookup(&their_ref, repo, git_buf_cstr(&refname)));
+ cl_git_pass(git_merge_head_from_ref(&their_head, repo, their_ref));
+
+ cl_git_pass(git_merge_analysis(merge_analysis, merge_pref, repo, (const git_merge_head **)&their_head, 1));
+
+ git_buf_free(&refname);
+ git_merge_head_free(their_head);
+ git_reference_free(their_ref);
+}
+
+void test_merge_workdir_analysis__fastforward(void)
+{
+ git_merge_analysis_t merge_analysis;
+ git_merge_preference_t merge_pref;
+
+ analysis_from_branch(&merge_analysis, &merge_pref, FASTFORWARD_BRANCH);
+ cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD, (merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD));
+ cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, (merge_analysis & GIT_MERGE_ANALYSIS_NORMAL));
+}
+
+void test_merge_workdir_analysis__no_fastforward(void)
+{
+ git_merge_analysis_t merge_analysis;
+ git_merge_preference_t merge_pref;
+
+ analysis_from_branch(&merge_analysis, &merge_pref, NOFASTFORWARD_BRANCH);
+ cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, merge_analysis);
+}
+
+void test_merge_workdir_analysis__uptodate(void)
+{
+ git_merge_analysis_t merge_analysis;
+ git_merge_preference_t merge_pref;
+
+ analysis_from_branch(&merge_analysis, &merge_pref, UPTODATE_BRANCH);
+ cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, merge_analysis);
+}
+
+void test_merge_workdir_analysis__uptodate_merging_prev_commit(void)
+{
+ git_merge_analysis_t merge_analysis;
+ git_merge_preference_t merge_pref;
+
+ analysis_from_branch(&merge_analysis, &merge_pref, PREVIOUS_BRANCH);
+ cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, merge_analysis);
+}
+
+void test_merge_workdir_analysis__unborn(void)
+{
+ git_merge_analysis_t merge_analysis;
+ git_merge_preference_t merge_pref;
+ git_buf master = GIT_BUF_INIT;
+
+ git_buf_joinpath(&master, git_repository_path(repo), "refs/heads/master");
+ p_unlink(git_buf_cstr(&master));
+
+ analysis_from_branch(&merge_analysis, &merge_pref, NOFASTFORWARD_BRANCH);
+ cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD, (merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD));
+ cl_assert_equal_i(GIT_MERGE_ANALYSIS_UNBORN, (merge_analysis & GIT_MERGE_ANALYSIS_UNBORN));
+
+ git_buf_free(&master);
+}
+
+void test_merge_workdir_analysis__fastforward_with_config_noff(void)
+{
+ git_config *config;
+ git_merge_analysis_t merge_analysis;
+ git_merge_preference_t merge_pref;
+
+ git_repository_config(&config, repo);
+ git_config_set_string(config, "merge.ff", "false");
+
+ analysis_from_branch(&merge_analysis, &merge_pref, FASTFORWARD_BRANCH);
+ cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD, (merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD));
+ cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, (merge_analysis & GIT_MERGE_ANALYSIS_NORMAL));
+ cl_assert_equal_i(GIT_MERGE_PREFERENCE_NO_FASTFORWARD, (merge_pref & GIT_MERGE_PREFERENCE_NO_FASTFORWARD));
+}
+
+void test_merge_workdir_analysis__no_fastforward_with_config_ffonly(void)
+{
+ git_config *config;
+ git_merge_analysis_t merge_analysis;
+ git_merge_preference_t merge_pref;
+
+ git_repository_config(&config, repo);
+ git_config_set_string(config, "merge.ff", "only");
+
+ analysis_from_branch(&merge_analysis, &merge_pref, NOFASTFORWARD_BRANCH);
+ cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, (merge_analysis & GIT_MERGE_ANALYSIS_NORMAL));
+ cl_assert_equal_i(GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY, (merge_pref & GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY));
+}
diff --git a/tests/merge/workdir/dirty.c b/tests/merge/workdir/dirty.c
new file mode 100644
index 000000000..776e4ea69
--- /dev/null
+++ b/tests/merge/workdir/dirty.c
@@ -0,0 +1,322 @@
+#include "clar_libgit2.h"
+#include "git2/merge.h"
+#include "buffer.h"
+#include "merge.h"
+#include "../merge_helpers.h"
+#include "posix.h"
+
+#define TEST_REPO_PATH "merge-resolve"
+#define MERGE_BRANCH_OID "7cb63eed597130ba4abb87b3e544b85021905520"
+
+#define AUTOMERGEABLE_MERGED_FILE \
+ "this file is changed in master\n" \
+ "this file is automergeable\n" \
+ "this file is automergeable\n" \
+ "this file is automergeable\n" \
+ "this file is automergeable\n" \
+ "this file is automergeable\n" \
+ "this file is automergeable\n" \
+ "this file is automergeable\n" \
+ "this file is changed in branch\n"
+
+#define CHANGED_IN_BRANCH_FILE \
+ "changed in branch\n"
+
+static git_repository *repo;
+static git_index *repo_index;
+
+static char *unaffected[][4] = {
+ { "added-in-master.txt", NULL },
+ { "changed-in-master.txt", NULL },
+ { "unchanged.txt", NULL },
+ { "added-in-master.txt", "changed-in-master.txt", NULL },
+ { "added-in-master.txt", "unchanged.txt", NULL },
+ { "changed-in-master.txt", "unchanged.txt", NULL },
+ { "added-in-master.txt", "changed-in-master.txt", "unchanged.txt", NULL },
+ { "new_file.txt", NULL },
+ { "new_file.txt", "unchanged.txt", NULL },
+ { NULL },
+};
+
+static char *affected[][5] = {
+ { "automergeable.txt", NULL },
+ { "changed-in-branch.txt", NULL },
+ { "conflicting.txt", NULL },
+ { "removed-in-branch.txt", NULL },
+ { "automergeable.txt", "changed-in-branch.txt", NULL },
+ { "automergeable.txt", "conflicting.txt", NULL },
+ { "automergeable.txt", "removed-in-branch.txt", NULL },
+ { "changed-in-branch.txt", "conflicting.txt", NULL },
+ { "changed-in-branch.txt", "removed-in-branch.txt", NULL },
+ { "conflicting.txt", "removed-in-branch.txt", NULL },
+ { "automergeable.txt", "changed-in-branch.txt", "conflicting.txt", NULL },
+ { "automergeable.txt", "changed-in-branch.txt", "removed-in-branch.txt", NULL },
+ { "automergeable.txt", "conflicting.txt", "removed-in-branch.txt", NULL },
+ { "changed-in-branch.txt", "conflicting.txt", "removed-in-branch.txt", NULL },
+ { "automergeable.txt", "changed-in-branch.txt", "conflicting.txt", "removed-in-branch.txt", NULL },
+ { NULL },
+};
+
+static char *result_contents[4][6] = {
+ { "automergeable.txt", AUTOMERGEABLE_MERGED_FILE, NULL, NULL },
+ { "changed-in-branch.txt", CHANGED_IN_BRANCH_FILE, NULL, NULL },
+ { "automergeable.txt", AUTOMERGEABLE_MERGED_FILE, "changed-in-branch.txt", CHANGED_IN_BRANCH_FILE, NULL, NULL },
+ { NULL }
+};
+
+void test_merge_workdir_dirty__initialize(void)
+{
+ repo = cl_git_sandbox_init(TEST_REPO_PATH);
+ git_repository_index(&repo_index, repo);
+}
+
+void test_merge_workdir_dirty__cleanup(void)
+{
+ git_index_free(repo_index);
+ cl_git_sandbox_cleanup();
+}
+
+static void set_core_autocrlf_to(git_repository *repo, bool value)
+{
+ git_config *cfg;
+
+ cl_git_pass(git_repository_config(&cfg, repo));
+ cl_git_pass(git_config_set_bool(cfg, "core.autocrlf", value));
+
+ git_config_free(cfg);
+}
+
+static int merge_branch(void)
+{
+ git_oid their_oids[1];
+ git_merge_head *their_heads[1];
+ git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT;
+ git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
+ int error;
+
+ cl_git_pass(git_oid_fromstr(&their_oids[0], MERGE_BRANCH_OID));
+ cl_git_pass(git_merge_head_from_id(&their_heads[0], repo, &their_oids[0]));
+
+ checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS;
+ error = git_merge(repo, (const git_merge_head **)their_heads, 1, &merge_opts, &checkout_opts);
+
+ git_merge_head_free(their_heads[0]);
+
+ return error;
+}
+
+static void write_files(char *files[])
+{
+ char *filename;
+ git_buf path = GIT_BUF_INIT, content = GIT_BUF_INIT;
+ size_t i;
+
+ for (i = 0, filename = files[i]; filename; filename = files[++i]) {
+ git_buf_clear(&path);
+ git_buf_clear(&content);
+
+ git_buf_printf(&path, "%s/%s", TEST_REPO_PATH, filename);
+ git_buf_printf(&content, "This is a dirty file in the working directory!\n\n"
+ "It will not be staged! Its filename is %s.\n", filename);
+
+ cl_git_mkfile(path.ptr, content.ptr);
+ }
+
+ git_buf_free(&path);
+ git_buf_free(&content);
+}
+
+static void hack_index(char *files[])
+{
+ char *filename;
+ struct stat statbuf;
+ git_buf path = GIT_BUF_INIT;
+ git_index_entry *entry;
+ size_t i;
+
+ /* Update the index to suggest that checkout placed these files on
+ * disk, keeping the object id but updating the cache, which will
+ * emulate a Git implementation's different filter.
+ */
+ for (i = 0, filename = files[i]; filename; filename = files[++i]) {
+ git_buf_clear(&path);
+
+ cl_assert(entry = (git_index_entry *)
+ git_index_get_bypath(repo_index, filename, 0));
+
+ cl_git_pass(git_buf_printf(&path, "%s/%s", TEST_REPO_PATH, filename));
+ cl_git_pass(p_stat(path.ptr, &statbuf));
+
+ entry->ctime.seconds = (git_time_t)statbuf.st_ctime;
+ entry->ctime.nanoseconds = 0;
+ entry->mtime.seconds = (git_time_t)statbuf.st_mtime;
+ entry->mtime.nanoseconds = 0;
+ entry->dev = statbuf.st_dev;
+ entry->ino = statbuf.st_ino;
+ entry->uid = statbuf.st_uid;
+ entry->gid = statbuf.st_gid;
+ entry->file_size = statbuf.st_size;
+ }
+
+ git_buf_free(&path);
+}
+
+static void stage_random_files(char *files[])
+{
+ char *filename;
+ size_t i;
+
+ write_files(files);
+
+ for (i = 0, filename = files[i]; filename; filename = files[++i])
+ cl_git_pass(git_index_add_bypath(repo_index, filename));
+}
+
+static void stage_content(char *content[])
+{
+ git_reference *head;
+ git_object *head_object;
+ git_buf path = GIT_BUF_INIT;
+ char *filename, *text;
+ size_t i;
+
+ cl_git_pass(git_repository_head(&head, repo));
+ cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJ_COMMIT));
+ cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD, NULL, NULL));
+
+ for (i = 0, filename = content[i], text = content[++i];
+ filename && text;
+ filename = content[++i], text = content[++i]) {
+
+ git_buf_clear(&path);
+
+ cl_git_pass(git_buf_printf(&path, "%s/%s", TEST_REPO_PATH, filename));
+
+ cl_git_mkfile(path.ptr, text);
+ cl_git_pass(git_index_add_bypath(repo_index, filename));
+ }
+
+ git_object_free(head_object);
+ git_reference_free(head);
+ git_buf_free(&path);
+}
+
+static int merge_dirty_files(char *dirty_files[])
+{
+ git_reference *head;
+ git_object *head_object;
+ int error;
+
+ cl_git_pass(git_repository_head(&head, repo));
+ cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJ_COMMIT));
+ cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD, NULL, NULL));
+
+ write_files(dirty_files);
+
+ error = merge_branch();
+
+ git_object_free(head_object);
+ git_reference_free(head);
+
+ return error;
+}
+
+static int merge_differently_filtered_files(char *files[])
+{
+ git_reference *head;
+ git_object *head_object;
+ int error;
+
+ cl_git_pass(git_repository_head(&head, repo));
+ cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJ_COMMIT));
+ cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD, NULL, NULL));
+
+ write_files(files);
+ hack_index(files);
+
+ cl_git_pass(git_index_write(repo_index));
+
+ error = merge_branch();
+
+ git_object_free(head_object);
+ git_reference_free(head);
+
+ return error;
+}
+
+static int merge_staged_files(char *staged_files[])
+{
+ stage_random_files(staged_files);
+ return merge_branch();
+}
+
+void test_merge_workdir_dirty__unaffected_dirty_files_allowed(void)
+{
+ char **files;
+ size_t i;
+
+ for (i = 0, files = unaffected[i]; files[0]; files = unaffected[++i])
+ cl_git_pass(merge_dirty_files(files));
+}
+
+void test_merge_workdir_dirty__unstaged_deletes_maintained(void)
+{
+ git_reference *head;
+ git_object *head_object;
+
+ cl_git_pass(git_repository_head(&head, repo));
+ cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJ_COMMIT));
+ cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD, NULL, NULL));
+
+ cl_git_pass(p_unlink("merge-resolve/unchanged.txt"));
+
+ cl_git_pass(merge_branch());
+
+ git_object_free(head_object);
+ git_reference_free(head);
+}
+
+void test_merge_workdir_dirty__affected_dirty_files_disallowed(void)
+{
+ char **files;
+ size_t i;
+
+ for (i = 0, files = affected[i]; files[0]; files = affected[++i])
+ cl_git_fail(merge_dirty_files(files));
+}
+
+void test_merge_workdir_dirty__staged_files_in_index_disallowed(void)
+{
+ char **files;
+ size_t i;
+
+ for (i = 0, files = unaffected[i]; files[0]; files = unaffected[++i])
+ cl_git_fail(merge_staged_files(files));
+
+ for (i = 0, files = affected[i]; files[0]; files = affected[++i])
+ cl_git_fail(merge_staged_files(files));
+}
+
+void test_merge_workdir_dirty__identical_staged_files_allowed(void)
+{
+ char **content;
+ size_t i;
+
+ set_core_autocrlf_to(repo, false);
+
+ for (i = 0, content = result_contents[i]; content[0]; content = result_contents[++i]) {
+ stage_content(content);
+
+ git_index_write(repo_index);
+ cl_git_pass(merge_branch());
+ }
+}
+
+void test_merge_workdir_dirty__honors_cache(void)
+{
+ char **files;
+ size_t i;
+
+ for (i = 0, files = affected[i]; files[0]; files = affected[++i])
+ cl_git_pass(merge_differently_filtered_files(files));
+}
diff --git a/tests/merge/workdir/renames.c b/tests/merge/workdir/renames.c
new file mode 100644
index 000000000..807a88f4c
--- /dev/null
+++ b/tests/merge/workdir/renames.c
@@ -0,0 +1,156 @@
+#include "clar_libgit2.h"
+#include "git2/repository.h"
+#include "git2/merge.h"
+#include "buffer.h"
+#include "merge.h"
+#include "../merge_helpers.h"
+#include "fileops.h"
+#include "refs.h"
+
+static git_repository *repo;
+
+#define TEST_REPO_PATH "merge-resolve"
+
+#define BRANCH_RENAME_OURS "rename_conflict_ours"
+#define BRANCH_RENAME_THEIRS "rename_conflict_theirs"
+
+// Fixture setup and teardown
+void test_merge_workdir_renames__initialize(void)
+{
+ git_config *cfg;
+
+ repo = cl_git_sandbox_init(TEST_REPO_PATH);
+
+ /* Ensure that the user's merge.conflictstyle doesn't interfere */
+ cl_git_pass(git_repository_config(&cfg, repo));
+ cl_git_pass(git_config_set_string(cfg, "merge.conflictstyle", "merge"));
+ git_config_free(cfg);
+}
+
+void test_merge_workdir_renames__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_merge_workdir_renames__renames(void)
+{
+ git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" },
+ { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" },
+ { 0100644, "8aac75de2a34b4d340bf62a6e58197269cb55797", 0, "0b-rewritten-in-ours.txt" },
+ { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" },
+ { 0100644, "7edc726325da726751a4195e434e4377b0f67f9a", 0, "0c-rewritten-in-theirs.txt" },
+ { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" },
+ { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" },
+ { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" },
+ { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" },
+ { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" },
+ { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 0, "3a-newname-in-ours-deleted-in-theirs.txt" },
+ { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 0, "3b-newname-in-theirs-deleted-in-ours.txt" },
+ { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 0, "4a-newname-in-ours-added-in-theirs.txt~HEAD" },
+ { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 0, "4a-newname-in-ours-added-in-theirs.txt~rename_conflict_theirs" },
+ { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 0, "4b-newname-in-theirs-added-in-ours.txt~HEAD" },
+ { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 0, "4b-newname-in-theirs-added-in-ours.txt~rename_conflict_theirs" },
+ { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 0, "5a-newname-in-ours-added-in-theirs.txt~HEAD" },
+ { 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 0, "5a-newname-in-ours-added-in-theirs.txt~rename_conflict_theirs" },
+ { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 0, "5b-newname-in-theirs-added-in-ours.txt~HEAD" },
+ { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 0, "5b-newname-in-theirs-added-in-ours.txt~rename_conflict_theirs" },
+ { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-ours.txt" },
+ { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-theirs.txt" },
+ { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "7-both-renamed.txt~HEAD" },
+ { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "7-both-renamed.txt~rename_conflict_theirs" },
+ };
+
+ merge_opts.flags |= GIT_MERGE_TREE_FIND_RENAMES;
+ merge_opts.rename_threshold = 50;
+
+ cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR BRANCH_RENAME_OURS, GIT_REFS_HEADS_DIR BRANCH_RENAME_THEIRS, &merge_opts, NULL));
+ cl_assert(merge_test_workdir(repo, merge_index_entries, 24));
+}
+
+void test_merge_workdir_renames__ours(void)
+{
+ git_index *index;
+ git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT;
+ git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" },
+ { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" },
+ { 0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e", 0, "0b-rewritten-in-ours.txt" },
+ { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" },
+ { 0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09", 0, "0c-rewritten-in-theirs.txt" },
+ { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" },
+ { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" },
+ { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" },
+ { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" },
+ { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" },
+ { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 0, "3a-newname-in-ours-deleted-in-theirs.txt" },
+ { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 0, "3b-newname-in-theirs-deleted-in-ours.txt" },
+ { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 0, "4a-newname-in-ours-added-in-theirs.txt" },
+ { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 0, "4b-newname-in-theirs-added-in-ours.txt" },
+ { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 0, "5a-newname-in-ours-added-in-theirs.txt" },
+ { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 0, "5b-newname-in-theirs-added-in-ours.txt" },
+ { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 0, "5b-renamed-in-theirs-added-in-ours.txt" },
+ { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-ours.txt" },
+ { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "7-both-renamed-side-2.txt" },
+ { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "7-both-renamed.txt" },
+ };
+
+ merge_opts.flags |= GIT_MERGE_TREE_FIND_RENAMES;
+ merge_opts.rename_threshold = 50;
+ checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS;
+
+ cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR BRANCH_RENAME_OURS, GIT_REFS_HEADS_DIR BRANCH_RENAME_THEIRS, &merge_opts, &checkout_opts));
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_git_pass(git_index_write(index));
+ cl_assert(merge_test_workdir(repo, merge_index_entries, 20));
+
+ git_index_free(index);
+}
+
+void test_merge_workdir_renames__similar(void)
+{
+ git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT;
+
+ /*
+ * Note: this differs slightly from the core git merge result - there, 4a is
+ * tracked as a rename/delete instead of a rename/add and the theirs side
+ * is not placed in workdir in any form.
+ */
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" },
+ { 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" },
+ { 0100644, "8aac75de2a34b4d340bf62a6e58197269cb55797", 0, "0b-rewritten-in-ours.txt" },
+ { 0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31", 0, "0c-duplicated-in-theirs.txt" },
+ { 0100644, "7edc726325da726751a4195e434e4377b0f67f9a", 0, "0c-rewritten-in-theirs.txt" },
+ { 0100644, "0d872f8e871a30208305978ecbf9e66d864f1638", 0, "1a-newname-in-ours-edited-in-theirs.txt" },
+ { 0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb", 0, "1a-newname-in-ours.txt" },
+ { 0100644, "ed9523e62e453e50dd9be1606af19399b96e397a", 0, "1b-newname-in-theirs-edited-in-ours.txt" },
+ { 0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136", 0, "1b-newname-in-theirs.txt" },
+ { 0100644, "178940b450f238a56c0d75b7955cb57b38191982", 0, "2-newname-in-both.txt" },
+ { 0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9", 0, "3a-newname-in-ours-deleted-in-theirs.txt" },
+ { 0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495", 0, "3b-newname-in-theirs-deleted-in-ours.txt" },
+ { 0100644, "227792b52aaa0b238bea00ec7e509b02623f168c", 0, "4a-newname-in-ours-added-in-theirs.txt~HEAD" },
+ { 0100644, "8b5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a", 0, "4a-newname-in-ours-added-in-theirs.txt~rename_conflict_theirs" },
+ { 0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9", 0, "4b-newname-in-theirs-added-in-ours.txt~HEAD" },
+ { 0100644, "98d52d07c0b0bbf2b46548f6aa521295c2cb55db", 0, "4b-newname-in-theirs-added-in-ours.txt~rename_conflict_theirs" },
+ { 0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436", 0, "5a-newname-in-ours-added-in-theirs.txt~HEAD" },
+ { 0100644, "98ba4205fcf31f5dd93c916d35fe3f3b3d0e6714", 0, "5a-newname-in-ours-added-in-theirs.txt~rename_conflict_theirs" },
+ { 0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced", 0, "5b-newname-in-theirs-added-in-ours.txt~HEAD" },
+ { 0100644, "63247125386de9ec90a27ad36169307bf8a11a38", 0, "5b-newname-in-theirs-added-in-ours.txt~rename_conflict_theirs" },
+ { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-ours.txt" },
+ { 0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450", 0, "6-both-renamed-1-to-2-theirs.txt" },
+ { 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 0, "7-both-renamed.txt~HEAD" },
+ { 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 0, "7-both-renamed.txt~rename_conflict_theirs" },
+ };
+
+ merge_opts.flags |= GIT_MERGE_TREE_FIND_RENAMES;
+ merge_opts.rename_threshold = 50;
+
+ cl_git_pass(merge_branches(repo, GIT_REFS_HEADS_DIR BRANCH_RENAME_OURS, GIT_REFS_HEADS_DIR BRANCH_RENAME_THEIRS, &merge_opts, NULL));
+ cl_assert(merge_test_workdir(repo, merge_index_entries, 24));
+}
+
diff --git a/tests/merge/workdir/setup.c b/tests/merge/workdir/setup.c
new file mode 100644
index 000000000..a0028ec6d
--- /dev/null
+++ b/tests/merge/workdir/setup.c
@@ -0,0 +1,1048 @@
+#include "clar_libgit2.h"
+#include "git2/repository.h"
+#include "git2/merge.h"
+#include "merge.h"
+#include "refs.h"
+#include "fileops.h"
+
+static git_repository *repo;
+static git_index *repo_index;
+
+#define TEST_REPO_PATH "merge-resolve"
+#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index"
+
+#define ORIG_HEAD "bd593285fc7fe4ca18ccdbabf027f5d689101452"
+
+#define THEIRS_SIMPLE_BRANCH "branch"
+#define THEIRS_SIMPLE_OID "7cb63eed597130ba4abb87b3e544b85021905520"
+
+#define OCTO1_BRANCH "octo1"
+#define OCTO1_OID "16f825815cfd20a07a75c71554e82d8eede0b061"
+
+#define OCTO2_BRANCH "octo2"
+#define OCTO2_OID "158dc7bedb202f5b26502bf3574faa7f4238d56c"
+
+#define OCTO3_BRANCH "octo3"
+#define OCTO3_OID "50ce7d7d01217679e26c55939eef119e0c93e272"
+
+#define OCTO4_BRANCH "octo4"
+#define OCTO4_OID "54269b3f6ec3d7d4ede24dd350dd5d605495c3ae"
+
+#define OCTO5_BRANCH "octo5"
+#define OCTO5_OID "e4f618a2c3ed0669308735727df5ebf2447f022f"
+
+// Fixture setup and teardown
+void test_merge_workdir_setup__initialize(void)
+{
+ repo = cl_git_sandbox_init(TEST_REPO_PATH);
+ git_repository_index(&repo_index, repo);
+}
+
+void test_merge_workdir_setup__cleanup(void)
+{
+ git_index_free(repo_index);
+ cl_git_sandbox_cleanup();
+}
+
+static bool test_file_contents(const char *filename, const char *expected)
+{
+ git_buf file_path_buf = GIT_BUF_INIT, file_buf = GIT_BUF_INIT;
+ bool equals;
+
+ git_buf_printf(&file_path_buf, "%s/%s", git_repository_path(repo), filename);
+
+ cl_git_pass(git_futils_readbuffer(&file_buf, file_path_buf.ptr));
+ equals = (strcmp(file_buf.ptr, expected) == 0);
+
+ git_buf_free(&file_path_buf);
+ git_buf_free(&file_buf);
+
+ return equals;
+}
+
+static void write_file_contents(const char *filename, const char *output)
+{
+ git_buf file_path_buf = GIT_BUF_INIT;
+
+ git_buf_printf(&file_path_buf, "%s/%s", git_repository_path(repo),
+ filename);
+ cl_git_rewritefile(file_path_buf.ptr, output);
+
+ git_buf_free(&file_path_buf);
+}
+
+/* git merge --no-ff octo1 */
+void test_merge_workdir_setup__one_branch(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_merge_head *our_head, *their_heads[1];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 1));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "'\n"));
+
+ git_reference_free(octo1_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+}
+
+/* git merge --no-ff 16f825815cfd20a07a75c71554e82d8eede0b061 */
+void test_merge_workdir_setup__one_oid(void)
+{
+ git_oid our_oid;
+ git_oid octo1_oid;
+ git_merge_head *our_head, *their_heads[1];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_id(&their_heads[0], repo, &octo1_oid));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 1));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'\n"));
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+}
+
+/* git merge octo1 octo2 */
+void test_merge_workdir_setup__two_branches(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_reference *octo2_ref;
+ git_merge_head *our_head, *their_heads[2];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 2));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO2_BRANCH "'\n"));
+
+ git_reference_free(octo1_ref);
+ git_reference_free(octo2_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+}
+
+/* git merge octo1 octo2 octo3 */
+void test_merge_workdir_setup__three_branches(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_reference *octo2_ref;
+ git_reference *octo3_ref;
+ git_merge_head *our_head, *their_heads[3];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref));
+
+ cl_git_pass(git_reference_lookup(&octo3_ref, repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[2], repo, octo3_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 3));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "', '" OCTO2_BRANCH "' and '" OCTO3_BRANCH "'\n"));
+
+ git_reference_free(octo1_ref);
+ git_reference_free(octo2_ref);
+ git_reference_free(octo3_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+}
+
+/* git merge 16f825815cfd20a07a75c71554e82d8eede0b061 158dc7bedb202f5b26502bf3574faa7f4238d56c 50ce7d7d01217679e26c55939eef119e0c93e272 */
+void test_merge_workdir_setup__three_oids(void)
+{
+ git_oid our_oid;
+ git_oid octo1_oid;
+ git_oid octo2_oid;
+ git_oid octo3_oid;
+ git_merge_head *our_head, *their_heads[3];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_id(&their_heads[0], repo, &octo1_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID));
+ cl_git_pass(git_merge_head_from_id(&their_heads[1], repo, &octo2_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID));
+ cl_git_pass(git_merge_head_from_id(&their_heads[2], repo, &octo3_oid));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 3));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'; commit '" OCTO2_OID "'; commit '" OCTO3_OID "'\n"));
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+}
+
+/* git merge octo1 158dc7bedb202f5b26502bf3574faa7f4238d56c */
+void test_merge_workdir_setup__branches_and_oids_1(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_oid octo2_oid;
+ git_merge_head *our_head, *their_heads[2];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID));
+ cl_git_pass(git_merge_head_from_id(&their_heads[1], repo, &octo2_oid));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 2));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "'; commit '" OCTO2_OID "'\n"));
+
+ git_reference_free(octo1_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+}
+
+/* git merge octo1 158dc7bedb202f5b26502bf3574faa7f4238d56c octo3 54269b3f6ec3d7d4ede24dd350dd5d605495c3ae */
+void test_merge_workdir_setup__branches_and_oids_2(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_oid octo2_oid;
+ git_reference *octo3_ref;
+ git_oid octo4_oid;
+ git_merge_head *our_head, *their_heads[4];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID));
+ cl_git_pass(git_merge_head_from_id(&their_heads[1], repo, &octo2_oid));
+
+ cl_git_pass(git_reference_lookup(&octo3_ref, repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[2], repo, octo3_ref));
+
+ cl_git_pass(git_oid_fromstr(&octo4_oid, OCTO4_OID));
+ cl_git_pass(git_merge_head_from_id(&their_heads[3], repo, &octo4_oid));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 4));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO3_BRANCH "'; commit '" OCTO2_OID "'; commit '" OCTO4_OID "'\n"));
+
+ git_reference_free(octo1_ref);
+ git_reference_free(octo3_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+ git_merge_head_free(their_heads[3]);
+}
+
+/* git merge 16f825815cfd20a07a75c71554e82d8eede0b061 octo2 50ce7d7d01217679e26c55939eef119e0c93e272 octo4 */
+void test_merge_workdir_setup__branches_and_oids_3(void)
+{
+ git_oid our_oid;
+ git_oid octo1_oid;
+ git_reference *octo2_ref;
+ git_oid octo3_oid;
+ git_reference *octo4_ref;
+ git_merge_head *our_head, *their_heads[4];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_id(&their_heads[0], repo, &octo1_oid));
+
+ cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref));
+
+ cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID));
+ cl_git_pass(git_merge_head_from_id(&their_heads[2], repo, &octo3_oid));
+
+ cl_git_pass(git_reference_lookup(&octo4_ref, repo, GIT_REFS_HEADS_DIR OCTO4_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[3], repo, octo4_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 4));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'; branches '" OCTO2_BRANCH "' and '" OCTO4_BRANCH "'; commit '" OCTO3_OID "'\n"));
+
+ git_reference_free(octo2_ref);
+ git_reference_free(octo4_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+ git_merge_head_free(their_heads[3]);
+}
+
+/* git merge 16f825815cfd20a07a75c71554e82d8eede0b061 octo2 50ce7d7d01217679e26c55939eef119e0c93e272 octo4 octo5 */
+void test_merge_workdir_setup__branches_and_oids_4(void)
+{
+ git_oid our_oid;
+ git_oid octo1_oid;
+ git_reference *octo2_ref;
+ git_oid octo3_oid;
+ git_reference *octo4_ref;
+ git_reference *octo5_ref;
+ git_merge_head *our_head, *their_heads[5];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_id(&their_heads[0], repo, &octo1_oid));
+
+ cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref));
+
+ cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID));
+ cl_git_pass(git_merge_head_from_id(&their_heads[2], repo, &octo3_oid));
+
+ cl_git_pass(git_reference_lookup(&octo4_ref, repo, GIT_REFS_HEADS_DIR OCTO4_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[3], repo, octo4_ref));
+
+ cl_git_pass(git_reference_lookup(&octo5_ref, repo, GIT_REFS_HEADS_DIR OCTO5_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[4], repo, octo5_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 5));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n" OCTO5_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'; branches '" OCTO2_BRANCH "', '" OCTO4_BRANCH "' and '" OCTO5_BRANCH "'; commit '" OCTO3_OID "'\n"));
+
+ git_reference_free(octo2_ref);
+ git_reference_free(octo4_ref);
+ git_reference_free(octo5_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+ git_merge_head_free(their_heads[3]);
+ git_merge_head_free(their_heads[4]);
+}
+
+/* git merge octo1 octo1 octo1 */
+void test_merge_workdir_setup__three_same_branches(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_1_ref;
+ git_reference *octo1_2_ref;
+ git_reference *octo1_3_ref;
+ git_merge_head *our_head, *their_heads[3];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_1_ref));
+
+ cl_git_pass(git_reference_lookup(&octo1_2_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo1_2_ref));
+
+ cl_git_pass(git_reference_lookup(&octo1_3_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[2], repo, octo1_3_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 3));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO1_OID "\n" OCTO1_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "', '" OCTO1_BRANCH "' and '" OCTO1_BRANCH "'\n"));
+
+ git_reference_free(octo1_1_ref);
+ git_reference_free(octo1_2_ref);
+ git_reference_free(octo1_3_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+}
+
+/* git merge 16f825815cfd20a07a75c71554e82d8eede0b061 16f825815cfd20a07a75c71554e82d8eede0b061 16f825815cfd20a07a75c71554e82d8eede0b061 */
+void test_merge_workdir_setup__three_same_oids(void)
+{
+ git_oid our_oid;
+ git_oid octo1_1_oid;
+ git_oid octo1_2_oid;
+ git_oid octo1_3_oid;
+ git_merge_head *our_head, *their_heads[3];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_1_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_id(&their_heads[0], repo, &octo1_1_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_2_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_id(&their_heads[1], repo, &octo1_2_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_3_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_id(&their_heads[2], repo, &octo1_3_oid));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 3));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO1_OID "\n" OCTO1_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge commit '" OCTO1_OID "'; commit '" OCTO1_OID "'; commit '" OCTO1_OID "'\n"));
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+}
+
+static int create_remote_tracking_branch(const char *branch_name, const char *oid_str)
+{
+ int error = 0;
+
+ git_buf remotes_path = GIT_BUF_INIT,
+ origin_path = GIT_BUF_INIT,
+ filename = GIT_BUF_INIT,
+ data = GIT_BUF_INIT;
+
+ if ((error = git_buf_puts(&remotes_path, git_repository_path(repo))) < 0 ||
+ (error = git_buf_puts(&remotes_path, GIT_REFS_REMOTES_DIR)) < 0)
+ goto done;
+
+ if (!git_path_exists(git_buf_cstr(&remotes_path)) &&
+ (error = p_mkdir(git_buf_cstr(&remotes_path), 0777)) < 0)
+ goto done;
+
+ if ((error = git_buf_puts(&origin_path, git_buf_cstr(&remotes_path))) < 0 ||
+ (error = git_buf_puts(&origin_path, "origin")) < 0)
+ goto done;
+
+ if (!git_path_exists(git_buf_cstr(&origin_path)) &&
+ (error = p_mkdir(git_buf_cstr(&origin_path), 0777)) < 0)
+ goto done;
+
+ if ((error = git_buf_puts(&filename, git_buf_cstr(&origin_path))) < 0 ||
+ (error = git_buf_puts(&filename, "/")) < 0 ||
+ (error = git_buf_puts(&filename, branch_name)) < 0 ||
+ (error = git_buf_puts(&data, oid_str)) < 0 ||
+ (error = git_buf_puts(&data, "\n")) < 0)
+ goto done;
+
+ cl_git_rewritefile(git_buf_cstr(&filename), git_buf_cstr(&data));
+
+done:
+ git_buf_free(&remotes_path);
+ git_buf_free(&origin_path);
+ git_buf_free(&filename);
+ git_buf_free(&data);
+
+ return error;
+}
+
+/* git merge refs/remotes/origin/octo1 */
+void test_merge_workdir_setup__remote_tracking_one_branch(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_merge_head *our_head, *their_heads[1];
+
+ cl_git_pass(create_remote_tracking_branch(OCTO1_BRANCH, OCTO1_OID));
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 1));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge remote-tracking branch 'refs/remotes/origin/" OCTO1_BRANCH "'\n"));
+
+ git_reference_free(octo1_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+}
+
+/* git merge refs/remotes/origin/octo1 refs/remotes/origin/octo2 */
+void test_merge_workdir_setup__remote_tracking_two_branches(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_reference *octo2_ref;
+ git_merge_head *our_head, *their_heads[2];
+
+ cl_git_pass(create_remote_tracking_branch(OCTO1_BRANCH, OCTO1_OID));
+ cl_git_pass(create_remote_tracking_branch(OCTO2_BRANCH, OCTO2_OID));
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO2_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 2));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge remote-tracking branches 'refs/remotes/origin/" OCTO1_BRANCH "' and 'refs/remotes/origin/" OCTO2_BRANCH "'\n"));
+
+ git_reference_free(octo1_ref);
+ git_reference_free(octo2_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+}
+
+/* git merge refs/remotes/origin/octo1 refs/remotes/origin/octo2 refs/remotes/origin/octo3 */
+void test_merge_workdir_setup__remote_tracking_three_branches(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_reference *octo2_ref;
+ git_reference *octo3_ref;
+ git_merge_head *our_head, *their_heads[3];
+
+ cl_git_pass(create_remote_tracking_branch(OCTO1_BRANCH, OCTO1_OID));
+ cl_git_pass(create_remote_tracking_branch(OCTO2_BRANCH, OCTO2_OID));
+ cl_git_pass(create_remote_tracking_branch(OCTO3_BRANCH, OCTO3_OID));
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO2_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref));
+
+ cl_git_pass(git_reference_lookup(&octo3_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO3_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[2], repo, octo3_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 3));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge remote-tracking branches 'refs/remotes/origin/" OCTO1_BRANCH "', 'refs/remotes/origin/" OCTO2_BRANCH "' and 'refs/remotes/origin/" OCTO3_BRANCH "'\n"));
+
+ git_reference_free(octo1_ref);
+ git_reference_free(octo2_ref);
+ git_reference_free(octo3_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+}
+
+/* git merge octo1 refs/remotes/origin/octo2 */
+void test_merge_workdir_setup__normal_branch_and_remote_tracking_branch(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_reference *octo2_ref;
+ git_merge_head *our_head, *their_heads[2];
+
+ cl_git_pass(create_remote_tracking_branch(OCTO2_BRANCH, OCTO2_OID));
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO2_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 2));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "', remote-tracking branch 'refs/remotes/origin/" OCTO2_BRANCH "'\n"));
+
+ git_reference_free(octo1_ref);
+ git_reference_free(octo2_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+}
+
+/* git merge refs/remotes/origin/octo1 octo2 */
+void test_merge_workdir_setup__remote_tracking_branch_and_normal_branch(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_reference *octo2_ref;
+ git_merge_head *our_head, *their_heads[2];
+
+ cl_git_pass(create_remote_tracking_branch(OCTO1_BRANCH, OCTO1_OID));
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 2));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO2_BRANCH "', remote-tracking branch 'refs/remotes/origin/" OCTO1_BRANCH "'\n"));
+
+ git_reference_free(octo1_ref);
+ git_reference_free(octo2_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+}
+
+/* git merge octo1 refs/remotes/origin/octo2 octo3 refs/remotes/origin/octo4 */
+void test_merge_workdir_setup__two_remote_tracking_branch_and_two_normal_branches(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_reference *octo2_ref;
+ git_reference *octo3_ref;
+ git_reference *octo4_ref;
+ git_merge_head *our_head, *their_heads[4];
+
+ cl_git_pass(create_remote_tracking_branch(OCTO2_BRANCH, OCTO2_OID));
+ cl_git_pass(create_remote_tracking_branch(OCTO4_BRANCH, OCTO4_OID));
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_reference_lookup(&octo2_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO2_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[1], repo, octo2_ref));
+
+ cl_git_pass(git_reference_lookup(&octo3_ref, repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[2], repo, octo3_ref));
+
+ cl_git_pass(git_reference_lookup(&octo4_ref, repo, GIT_REFS_REMOTES_DIR "origin/" OCTO4_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[3], repo, octo4_ref));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 4));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO3_BRANCH "', remote-tracking branches 'refs/remotes/origin/" OCTO2_BRANCH "' and 'refs/remotes/origin/" OCTO4_BRANCH "'\n"));
+
+ git_reference_free(octo1_ref);
+ git_reference_free(octo2_ref);
+ git_reference_free(octo3_ref);
+ git_reference_free(octo4_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+ git_merge_head_free(their_heads[3]);
+}
+
+/* git pull origin branch octo1 */
+void test_merge_workdir_setup__pull_one(void)
+{
+ git_oid our_oid;
+ git_oid octo1_1_oid;
+ git_merge_head *our_head, *their_heads[1];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_1_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.url/repo.git", &octo1_1_oid));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 1));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch 'octo1' of http://remote.url/repo.git\n"));
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+}
+
+/* git pull origin octo1 octo2 */
+void test_merge_workdir_setup__pull_two(void)
+{
+ git_oid our_oid;
+ git_oid octo1_oid;
+ git_oid octo2_oid;
+ git_merge_head *our_head, *their_heads[2];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.url/repo.git", &octo1_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[1], repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH, "http://remote.url/repo.git", &octo2_oid));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 2));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO2_BRANCH "' of http://remote.url/repo.git\n"));
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+}
+
+/* git pull origin octo1 octo2 octo3 */
+void test_merge_workdir_setup__pull_three(void)
+{
+ git_oid our_oid;
+ git_oid octo1_oid;
+ git_oid octo2_oid;
+ git_oid octo3_oid;
+ git_merge_head *our_head, *their_heads[3];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.url/repo.git", &octo1_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[1], repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH, "http://remote.url/repo.git", &octo2_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[2], repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH, "http://remote.url/repo.git", &octo3_oid));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 3));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "', '" OCTO2_BRANCH "' and '" OCTO3_BRANCH "' of http://remote.url/repo.git\n"));
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+}
+
+void test_merge_workdir_setup__three_remotes(void)
+{
+ git_oid our_oid;
+ git_oid octo1_oid;
+ git_oid octo2_oid;
+ git_oid octo3_oid;
+ git_merge_head *our_head, *their_heads[3];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.first/repo.git", &octo1_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[1], repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH, "http://remote.second/repo.git", &octo2_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[2], repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH, "http://remote.third/repo.git", &octo3_oid));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 3));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "' of http://remote.first/repo.git, branch '" OCTO2_BRANCH "' of http://remote.second/repo.git, branch '" OCTO3_BRANCH "' of http://remote.third/repo.git\n"));
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+}
+
+void test_merge_workdir_setup__two_remotes(void)
+{
+ git_oid our_oid;
+ git_oid octo1_oid;
+ git_oid octo2_oid;
+ git_oid octo3_oid;
+ git_oid octo4_oid;
+ git_merge_head *our_head, *their_heads[4];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo1_oid, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.first/repo.git", &octo1_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo2_oid, OCTO2_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[1], repo, GIT_REFS_HEADS_DIR OCTO2_BRANCH, "http://remote.second/repo.git", &octo2_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo3_oid, OCTO3_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[2], repo, GIT_REFS_HEADS_DIR OCTO3_BRANCH, "http://remote.first/repo.git", &octo3_oid));
+
+ cl_git_pass(git_oid_fromstr(&octo4_oid, OCTO4_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&their_heads[3], repo, GIT_REFS_HEADS_DIR OCTO4_BRANCH, "http://remote.second/repo.git", &octo4_oid));
+
+ cl_git_pass(git_merge__setup(repo, our_head, (const git_merge_head **)their_heads, 4));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n" OCTO2_OID "\n" OCTO3_OID "\n" OCTO4_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branches '" OCTO1_BRANCH "' and '" OCTO3_BRANCH "' of http://remote.first/repo.git, branches '" OCTO2_BRANCH "' and '" OCTO4_BRANCH "' of http://remote.second/repo.git\n"));
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+ git_merge_head_free(their_heads[1]);
+ git_merge_head_free(their_heads[2]);
+ git_merge_head_free(their_heads[3]);
+}
+
+void test_merge_workdir_setup__id_from_head(void)
+{
+ git_oid expected_id;
+ const git_oid *id;
+ git_reference *ref;
+ git_merge_head *heads[3];
+
+ cl_git_pass(git_oid_fromstr(&expected_id, OCTO1_OID));
+ cl_git_pass(git_merge_head_from_fetchhead(&heads[0], repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH, "http://remote.url/repo.git", &expected_id));
+ id = git_merge_head_id(heads[0]);
+ cl_assert_equal_i(1, git_oid_equal(id, &expected_id));
+
+ cl_git_pass(git_merge_head_from_id(&heads[1], repo, &expected_id));
+ id = git_merge_head_id(heads[1]);
+ cl_assert_equal_i(1, git_oid_equal(id, &expected_id));
+
+ cl_git_pass(git_reference_lookup(&ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&heads[2], repo, ref));
+ id = git_merge_head_id(heads[2]);
+ cl_assert_equal_i(1, git_oid_equal(id, &expected_id));
+
+ git_reference_free(ref);
+ git_merge_head_free(heads[0]);
+ git_merge_head_free(heads[1]);
+ git_merge_head_free(heads[2]);
+}
+
+struct merge_head_cb_data {
+ const char **oid_str;
+ unsigned int len;
+
+ unsigned int i;
+};
+
+static int merge_head_foreach_cb(const git_oid *oid, void *payload)
+{
+ git_oid expected_oid;
+ struct merge_head_cb_data *cb_data = payload;
+
+ git_oid_fromstr(&expected_oid, cb_data->oid_str[cb_data->i]);
+ cl_assert(git_oid_cmp(&expected_oid, oid) == 0);
+ cb_data->i++;
+ return 0;
+}
+
+void test_merge_workdir_setup__head_notfound(void)
+{
+ int error;
+
+ cl_git_fail((error = git_repository_mergehead_foreach(repo,
+ merge_head_foreach_cb, NULL)));
+ cl_assert(error == GIT_ENOTFOUND);
+}
+
+void test_merge_workdir_setup__head_invalid_oid(void)
+{
+ int error;
+
+ write_file_contents(GIT_MERGE_HEAD_FILE, "invalid-oid\n");
+
+ cl_git_fail((error = git_repository_mergehead_foreach(repo,
+ merge_head_foreach_cb, NULL)));
+ cl_assert(error == -1);
+}
+
+void test_merge_workdir_setup__head_foreach_nonewline(void)
+{
+ int error;
+
+ write_file_contents(GIT_MERGE_HEAD_FILE, THEIRS_SIMPLE_OID);
+
+ cl_git_fail((error = git_repository_mergehead_foreach(repo,
+ merge_head_foreach_cb, NULL)));
+ cl_assert(error == -1);
+}
+
+void test_merge_workdir_setup__head_foreach_one(void)
+{
+ const char *expected = THEIRS_SIMPLE_OID;
+
+ struct merge_head_cb_data cb_data = { &expected, 1 };
+
+ write_file_contents(GIT_MERGE_HEAD_FILE, THEIRS_SIMPLE_OID "\n");
+
+ cl_git_pass(git_repository_mergehead_foreach(repo,
+ merge_head_foreach_cb, &cb_data));
+
+ cl_assert(cb_data.i == cb_data.len);
+}
+
+void test_merge_workdir_setup__head_foreach_octopus(void)
+{
+ const char *expected[] = { THEIRS_SIMPLE_OID,
+ OCTO1_OID, OCTO2_OID, OCTO3_OID, OCTO4_OID, OCTO5_OID };
+
+ struct merge_head_cb_data cb_data = { expected, 6 };
+
+ write_file_contents(GIT_MERGE_HEAD_FILE,
+ THEIRS_SIMPLE_OID "\n"
+ OCTO1_OID "\n"
+ OCTO2_OID "\n"
+ OCTO3_OID "\n"
+ OCTO4_OID "\n"
+ OCTO5_OID "\n");
+
+ cl_git_pass(git_repository_mergehead_foreach(repo,
+ merge_head_foreach_cb, &cb_data));
+
+ cl_assert(cb_data.i == cb_data.len);
+}
+
+void test_merge_workdir_setup__retained_after_success(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_merge_head *our_head, *their_heads[1];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_pass(git_merge(repo, (const git_merge_head **)&their_heads[0], 1, NULL, NULL));
+
+ cl_assert(test_file_contents(GIT_MERGE_HEAD_FILE, OCTO1_OID "\n"));
+ cl_assert(test_file_contents(GIT_ORIG_HEAD_FILE, ORIG_HEAD "\n"));
+ cl_assert(test_file_contents(GIT_MERGE_MODE_FILE, "no-ff"));
+ cl_assert(test_file_contents(GIT_MERGE_MSG_FILE, "Merge branch '" OCTO1_BRANCH "'\n"));
+
+ git_reference_free(octo1_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+}
+
+void test_merge_workdir_setup__removed_after_failure(void)
+{
+ git_oid our_oid;
+ git_reference *octo1_ref;
+ git_merge_head *our_head, *their_heads[1];
+
+ cl_git_pass(git_oid_fromstr(&our_oid, ORIG_HEAD));
+ cl_git_pass(git_merge_head_from_id(&our_head, repo, &our_oid));
+
+ cl_git_pass(git_reference_lookup(&octo1_ref, repo, GIT_REFS_HEADS_DIR OCTO1_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, octo1_ref));
+
+ cl_git_rewritefile("merge-resolve/new-in-octo1.txt",
+ "Conflicting file!\n\nMerge will fail!\n");
+
+ cl_git_fail(git_merge(
+ repo, (const git_merge_head **)&their_heads[0], 1, NULL, NULL));
+
+ cl_assert(!git_path_exists("merge-resolve/" GIT_MERGE_HEAD_FILE));
+ cl_assert(!git_path_exists("merge-resolve/" GIT_ORIG_HEAD_FILE));
+ cl_assert(!git_path_exists("merge-resolve/" GIT_MERGE_MODE_FILE));
+ cl_assert(!git_path_exists("merge-resolve/" GIT_MERGE_MSG_FILE));
+
+ git_reference_free(octo1_ref);
+
+ git_merge_head_free(our_head);
+ git_merge_head_free(their_heads[0]);
+}
diff --git a/tests/merge/workdir/simple.c b/tests/merge/workdir/simple.c
new file mode 100644
index 000000000..327408dc9
--- /dev/null
+++ b/tests/merge/workdir/simple.c
@@ -0,0 +1,627 @@
+#include "clar_libgit2.h"
+#include "git2/repository.h"
+#include "git2/merge.h"
+#include "buffer.h"
+#include "merge.h"
+#include "../merge_helpers.h"
+#include "refs.h"
+#include "fileops.h"
+
+static git_repository *repo;
+static git_index *repo_index;
+
+#define TEST_REPO_PATH "merge-resolve"
+#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index"
+
+#define THEIRS_SIMPLE_BRANCH "branch"
+#define THEIRS_SIMPLE_OID "7cb63eed597130ba4abb87b3e544b85021905520"
+
+#define THEIRS_UNRELATED_BRANCH "unrelated"
+#define THEIRS_UNRELATED_OID "55b4e4687e7a0d9ca367016ed930f385d4022e6f"
+#define THEIRS_UNRELATED_PARENT "d6cf6c7741b3316826af1314042550c97ded1d50"
+
+#define OURS_DIRECTORY_FILE "df_side1"
+#define THEIRS_DIRECTORY_FILE "fc90237dc4891fa6c69827fc465632225e391618"
+
+
+/* Non-conflicting files, index entries are common to every merge operation */
+#define ADDED_IN_MASTER_INDEX_ENTRY \
+ { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, \
+ "added-in-master.txt" }
+#define AUTOMERGEABLE_INDEX_ENTRY \
+ { 0100644, "f2e1550a0c9e53d5811175864a29536642ae3821", 0, \
+ "automergeable.txt" }
+#define CHANGED_IN_BRANCH_INDEX_ENTRY \
+ { 0100644, "4eb04c9e79e88f6640d01ff5b25ca2a60764f216", 0, \
+ "changed-in-branch.txt" }
+#define CHANGED_IN_MASTER_INDEX_ENTRY \
+ { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, \
+ "changed-in-master.txt" }
+#define UNCHANGED_INDEX_ENTRY \
+ { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, \
+ "unchanged.txt" }
+
+/* Unrelated files */
+#define UNRELATED_NEW1 \
+ { 0100644, "ef58fdd8086c243bdc81f99e379acacfd21d32d6", 0, \
+ "new-in-unrelated1.txt" }
+#define UNRELATED_NEW2 \
+ { 0100644, "948ba6e701c1edab0c2d394fb7c5538334129793", 0, \
+ "new-in-unrelated2.txt" }
+
+/* Expected REUC entries */
+#define AUTOMERGEABLE_REUC_ENTRY \
+ { "automergeable.txt", 0100644, 0100644, 0100644, \
+ "6212c31dab5e482247d7977e4f0dd3601decf13b", \
+ "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", \
+ "058541fc37114bfc1dddf6bd6bffc7fae5c2e6fe" }
+#define CONFLICTING_REUC_ENTRY \
+ { "conflicting.txt", 0100644, 0100644, 0100644, \
+ "d427e0b2e138501a3d15cc376077a3631e15bd46", \
+ "4e886e602529caa9ab11d71f86634bd1b6e0de10", \
+ "2bd0a343aeef7a2cf0d158478966a6e587ff3863" }
+#define REMOVED_IN_BRANCH_REUC_ENTRY \
+ { "removed-in-branch.txt", 0100644, 0100644, 0, \
+ "dfe3f22baa1f6fce5447901c3086bae368de6bdd", \
+ "dfe3f22baa1f6fce5447901c3086bae368de6bdd", \
+ "" }
+#define REMOVED_IN_MASTER_REUC_ENTRY \
+ { "removed-in-master.txt", 0100644, 0, 0100644, \
+ "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5", \
+ "", \
+ "5c3b68a71fc4fa5d362fd3875e53137c6a5ab7a5" }
+
+
+// Fixture setup and teardown
+void test_merge_workdir_simple__initialize(void)
+{
+ git_config *cfg;
+
+ repo = cl_git_sandbox_init(TEST_REPO_PATH);
+ git_repository_index(&repo_index, repo);
+
+ /* Ensure that the user's merge.conflictstyle doesn't interfere */
+ cl_git_pass(git_repository_config(&cfg, repo));
+ cl_git_pass(git_config_set_string(cfg, "merge.conflictstyle", "merge"));
+ git_config_free(cfg);
+}
+
+void test_merge_workdir_simple__cleanup(void)
+{
+ git_index_free(repo_index);
+ cl_git_sandbox_cleanup();
+}
+
+static void merge_simple_branch(int merge_file_favor, int addl_checkout_strategy)
+{
+ git_oid their_oids[1];
+ git_merge_head *their_heads[1];
+ git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT;
+ git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
+
+ cl_git_pass(git_oid_fromstr(&their_oids[0], THEIRS_SIMPLE_OID));
+ cl_git_pass(git_merge_head_from_id(&their_heads[0], repo, &their_oids[0]));
+
+ merge_opts.file_favor = merge_file_favor;
+ checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS |
+ addl_checkout_strategy;
+
+ cl_git_pass(git_merge(repo, (const git_merge_head **)their_heads, 1, &merge_opts, &checkout_opts));
+
+ git_merge_head_free(their_heads[0]);
+}
+
+static void set_core_autocrlf_to(git_repository *repo, bool value)
+{
+ git_config *cfg;
+
+ cl_git_pass(git_repository_config(&cfg, repo));
+ cl_git_pass(git_config_set_bool(cfg, "core.autocrlf", value));
+
+ git_config_free(cfg);
+}
+
+void test_merge_workdir_simple__automerge(void)
+{
+ git_index *index;
+ const git_index_entry *entry;
+ git_buf automergeable_buf = GIT_BUF_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ ADDED_IN_MASTER_INDEX_ENTRY,
+ AUTOMERGEABLE_INDEX_ENTRY,
+ CHANGED_IN_BRANCH_INDEX_ENTRY,
+ CHANGED_IN_MASTER_INDEX_ENTRY,
+
+ { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" },
+ { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" },
+ { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" },
+
+ UNCHANGED_INDEX_ENTRY,
+ };
+
+ struct merge_reuc_entry merge_reuc_entries[] = {
+ AUTOMERGEABLE_REUC_ENTRY,
+ REMOVED_IN_BRANCH_REUC_ENTRY,
+ REMOVED_IN_MASTER_REUC_ENTRY
+ };
+
+
+ set_core_autocrlf_to(repo, false);
+
+ merge_simple_branch(0, 0);
+
+ cl_git_pass(git_futils_readbuffer(&automergeable_buf,
+ TEST_REPO_PATH "/automergeable.txt"));
+ cl_assert(strcmp(automergeable_buf.ptr, AUTOMERGEABLE_MERGED_FILE) == 0);
+ git_buf_free(&automergeable_buf);
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 8));
+ cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3));
+
+ git_repository_index(&index, repo);
+
+ cl_assert((entry = git_index_get_bypath(index, "automergeable.txt", 0)) != NULL);
+ cl_assert(entry->file_size == strlen(AUTOMERGEABLE_MERGED_FILE));
+
+ git_index_free(index);
+}
+
+void test_merge_workdir_simple__automerge_crlf(void)
+{
+#ifdef GIT_WIN32
+ git_index *index;
+ const git_index_entry *entry;
+ git_buf automergeable_buf = GIT_BUF_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ ADDED_IN_MASTER_INDEX_ENTRY,
+ AUTOMERGEABLE_INDEX_ENTRY,
+ CHANGED_IN_BRANCH_INDEX_ENTRY,
+ CHANGED_IN_MASTER_INDEX_ENTRY,
+
+ { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" },
+ { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" },
+ { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" },
+
+ UNCHANGED_INDEX_ENTRY,
+ };
+
+ struct merge_reuc_entry merge_reuc_entries[] = {
+ AUTOMERGEABLE_REUC_ENTRY,
+ REMOVED_IN_BRANCH_REUC_ENTRY,
+ REMOVED_IN_MASTER_REUC_ENTRY
+ };
+
+ set_core_autocrlf_to(repo, true);
+
+ merge_simple_branch(0, 0);
+
+ cl_git_pass(git_futils_readbuffer(&automergeable_buf,
+ TEST_REPO_PATH "/automergeable.txt"));
+ cl_assert(strcmp(automergeable_buf.ptr, AUTOMERGEABLE_MERGED_FILE_CRLF) == 0);
+ git_buf_free(&automergeable_buf);
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 8));
+ cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3));
+
+ git_repository_index(&index, repo);
+
+ cl_assert((entry = git_index_get_bypath(index, "automergeable.txt", 0)) != NULL);
+ cl_assert(entry->file_size == strlen(AUTOMERGEABLE_MERGED_FILE_CRLF));
+
+ git_index_free(index);
+#endif /* GIT_WIN32 */
+}
+
+void test_merge_workdir_simple__mergefile(void)
+{
+ git_buf conflicting_buf = GIT_BUF_INIT, mergemsg_buf = GIT_BUF_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ ADDED_IN_MASTER_INDEX_ENTRY,
+ AUTOMERGEABLE_INDEX_ENTRY,
+ CHANGED_IN_BRANCH_INDEX_ENTRY,
+ CHANGED_IN_MASTER_INDEX_ENTRY,
+
+ { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" },
+ { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" },
+ { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" },
+
+ UNCHANGED_INDEX_ENTRY,
+ };
+
+ struct merge_reuc_entry merge_reuc_entries[] = {
+ AUTOMERGEABLE_REUC_ENTRY,
+ REMOVED_IN_BRANCH_REUC_ENTRY,
+ REMOVED_IN_MASTER_REUC_ENTRY
+ };
+
+ merge_simple_branch(0, 0);
+
+ cl_git_pass(git_futils_readbuffer(&conflicting_buf,
+ TEST_REPO_PATH "/conflicting.txt"));
+ cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_MERGE_FILE) == 0);
+ cl_git_pass(git_futils_readbuffer(&mergemsg_buf,
+ TEST_REPO_PATH "/.git/MERGE_MSG"));
+ cl_assert(strcmp(git_buf_cstr(&mergemsg_buf),
+ "Merge commit '7cb63eed597130ba4abb87b3e544b85021905520'\n" \
+ "\n" \
+ "Conflicts:\n" \
+ "\tconflicting.txt\n") == 0);
+ git_buf_free(&conflicting_buf);
+ git_buf_free(&mergemsg_buf);
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 8));
+ cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3));
+}
+
+void test_merge_workdir_simple__diff3(void)
+{
+ git_buf conflicting_buf = GIT_BUF_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ ADDED_IN_MASTER_INDEX_ENTRY,
+ AUTOMERGEABLE_INDEX_ENTRY,
+ CHANGED_IN_BRANCH_INDEX_ENTRY,
+ CHANGED_IN_MASTER_INDEX_ENTRY,
+
+ { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" },
+ { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" },
+ { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" },
+
+ UNCHANGED_INDEX_ENTRY,
+ };
+
+ struct merge_reuc_entry merge_reuc_entries[] = {
+ AUTOMERGEABLE_REUC_ENTRY,
+ REMOVED_IN_BRANCH_REUC_ENTRY,
+ REMOVED_IN_MASTER_REUC_ENTRY
+ };
+
+ merge_simple_branch(0, GIT_CHECKOUT_CONFLICT_STYLE_DIFF3);
+
+ cl_git_pass(git_futils_readbuffer(&conflicting_buf,
+ TEST_REPO_PATH "/conflicting.txt"));
+ cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_DIFF3_FILE) == 0);
+ git_buf_free(&conflicting_buf);
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 8));
+ cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3));
+}
+
+void test_merge_workdir_simple__union(void)
+{
+ git_buf conflicting_buf = GIT_BUF_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ ADDED_IN_MASTER_INDEX_ENTRY,
+ AUTOMERGEABLE_INDEX_ENTRY,
+ CHANGED_IN_BRANCH_INDEX_ENTRY,
+ CHANGED_IN_MASTER_INDEX_ENTRY,
+
+ { 0100644, "72cdb057b340205164478565e91eb71647e66891", 0, "conflicting.txt" },
+
+ UNCHANGED_INDEX_ENTRY,
+ };
+
+ struct merge_reuc_entry merge_reuc_entries[] = {
+ AUTOMERGEABLE_REUC_ENTRY,
+ CONFLICTING_REUC_ENTRY,
+ REMOVED_IN_BRANCH_REUC_ENTRY,
+ REMOVED_IN_MASTER_REUC_ENTRY
+ };
+
+ set_core_autocrlf_to(repo, false);
+
+ merge_simple_branch(GIT_MERGE_FILE_FAVOR_UNION, 0);
+
+ cl_git_pass(git_futils_readbuffer(&conflicting_buf,
+ TEST_REPO_PATH "/conflicting.txt"));
+ cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_UNION_FILE) == 0);
+ git_buf_free(&conflicting_buf);
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 6));
+ cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 4));
+}
+
+void test_merge_workdir_simple__diff3_from_config(void)
+{
+ git_config *config;
+ git_buf conflicting_buf = GIT_BUF_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ ADDED_IN_MASTER_INDEX_ENTRY,
+ AUTOMERGEABLE_INDEX_ENTRY,
+ CHANGED_IN_BRANCH_INDEX_ENTRY,
+ CHANGED_IN_MASTER_INDEX_ENTRY,
+
+ { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" },
+ { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" },
+ { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" },
+
+ UNCHANGED_INDEX_ENTRY,
+ };
+
+ struct merge_reuc_entry merge_reuc_entries[] = {
+ AUTOMERGEABLE_REUC_ENTRY,
+ REMOVED_IN_BRANCH_REUC_ENTRY,
+ REMOVED_IN_MASTER_REUC_ENTRY
+ };
+
+ cl_git_pass(git_repository_config(&config, repo));
+ cl_git_pass(git_config_set_string(config, "merge.conflictstyle", "diff3"));
+
+ merge_simple_branch(0, 0);
+
+ cl_git_pass(git_futils_readbuffer(&conflicting_buf,
+ TEST_REPO_PATH "/conflicting.txt"));
+ cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_DIFF3_FILE) == 0);
+ git_buf_free(&conflicting_buf);
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 8));
+ cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3));
+
+ git_config_free(config);
+}
+
+void test_merge_workdir_simple__merge_overrides_config(void)
+{
+ git_config *config;
+ git_buf conflicting_buf = GIT_BUF_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ ADDED_IN_MASTER_INDEX_ENTRY,
+ AUTOMERGEABLE_INDEX_ENTRY,
+ CHANGED_IN_BRANCH_INDEX_ENTRY,
+ CHANGED_IN_MASTER_INDEX_ENTRY,
+
+ { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" },
+ { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" },
+ { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" },
+
+ UNCHANGED_INDEX_ENTRY,
+ };
+
+ struct merge_reuc_entry merge_reuc_entries[] = {
+ AUTOMERGEABLE_REUC_ENTRY,
+ REMOVED_IN_BRANCH_REUC_ENTRY,
+ REMOVED_IN_MASTER_REUC_ENTRY
+ };
+
+ cl_git_pass(git_repository_config(&config, repo));
+ cl_git_pass(git_config_set_string(config, "merge.conflictstyle", "diff3"));
+
+ merge_simple_branch(0, GIT_CHECKOUT_CONFLICT_STYLE_MERGE);
+
+ cl_git_pass(git_futils_readbuffer(&conflicting_buf,
+ TEST_REPO_PATH "/conflicting.txt"));
+ cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_MERGE_FILE) == 0);
+ git_buf_free(&conflicting_buf);
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 8));
+ cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3));
+
+ git_config_free(config);
+}
+
+void test_merge_workdir_simple__checkout_ours(void)
+{
+ struct merge_index_entry merge_index_entries[] = {
+ ADDED_IN_MASTER_INDEX_ENTRY,
+ AUTOMERGEABLE_INDEX_ENTRY,
+ CHANGED_IN_BRANCH_INDEX_ENTRY,
+ CHANGED_IN_MASTER_INDEX_ENTRY,
+
+ { 0100644, "d427e0b2e138501a3d15cc376077a3631e15bd46", 1, "conflicting.txt" },
+ { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" },
+ { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 3, "conflicting.txt" },
+
+ UNCHANGED_INDEX_ENTRY,
+ };
+
+ struct merge_reuc_entry merge_reuc_entries[] = {
+ AUTOMERGEABLE_REUC_ENTRY,
+ REMOVED_IN_BRANCH_REUC_ENTRY,
+ REMOVED_IN_MASTER_REUC_ENTRY
+ };
+
+ merge_simple_branch(0, GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS);
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 8));
+ cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 3));
+
+ cl_assert(git_path_exists(TEST_REPO_PATH "/conflicting.txt"));
+}
+
+void test_merge_workdir_simple__favor_ours(void)
+{
+ struct merge_index_entry merge_index_entries[] = {
+ ADDED_IN_MASTER_INDEX_ENTRY,
+ AUTOMERGEABLE_INDEX_ENTRY,
+ CHANGED_IN_BRANCH_INDEX_ENTRY,
+ CHANGED_IN_MASTER_INDEX_ENTRY,
+ { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 0, "conflicting.txt" },
+ UNCHANGED_INDEX_ENTRY,
+ };
+
+ struct merge_reuc_entry merge_reuc_entries[] = {
+ AUTOMERGEABLE_REUC_ENTRY,
+ CONFLICTING_REUC_ENTRY,
+ REMOVED_IN_BRANCH_REUC_ENTRY,
+ REMOVED_IN_MASTER_REUC_ENTRY,
+ };
+
+ merge_simple_branch(GIT_MERGE_FILE_FAVOR_OURS, 0);
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 6));
+ cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 4));
+}
+
+void test_merge_workdir_simple__favor_theirs(void)
+{
+ struct merge_index_entry merge_index_entries[] = {
+ ADDED_IN_MASTER_INDEX_ENTRY,
+ AUTOMERGEABLE_INDEX_ENTRY,
+ CHANGED_IN_BRANCH_INDEX_ENTRY,
+ CHANGED_IN_MASTER_INDEX_ENTRY,
+ { 0100644, "2bd0a343aeef7a2cf0d158478966a6e587ff3863", 0, "conflicting.txt" },
+ UNCHANGED_INDEX_ENTRY,
+ };
+
+ struct merge_reuc_entry merge_reuc_entries[] = {
+ AUTOMERGEABLE_REUC_ENTRY,
+ CONFLICTING_REUC_ENTRY,
+ REMOVED_IN_BRANCH_REUC_ENTRY,
+ REMOVED_IN_MASTER_REUC_ENTRY,
+ };
+
+ merge_simple_branch(GIT_MERGE_FILE_FAVOR_THEIRS, 0);
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 6));
+ cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 4));
+}
+
+void test_merge_workdir_simple__directory_file(void)
+{
+ git_reference *head;
+ git_oid their_oids[1], head_commit_id;
+ git_merge_head *their_heads[1];
+ git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT;
+ git_commit *head_commit;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "49130a28ef567af9a6a6104c38773fedfa5f9742", 2, "dir-10" },
+ { 0100644, "6c06dcd163587c2cc18be44857e0b71116382aeb", 3, "dir-10" },
+ { 0100644, "43aafd43bea779ec74317dc361f45ae3f532a505", 0, "dir-6" },
+ { 0100644, "a031a28ae70e33a641ce4b8a8f6317f1ab79dee4", 3, "dir-7" },
+ { 0100644, "5012fd565b1393bdfda1805d4ec38ce6619e1fd1", 1, "dir-7/file.txt" },
+ { 0100644, "a5563304ddf6caba25cb50323a2ea6f7dbfcadca", 2, "dir-7/file.txt" },
+ { 0100644, "e9ad6ec3e38364a3d07feda7c4197d4d845c53b5", 0, "dir-8" },
+ { 0100644, "3ef4d30382ca33fdeba9fda895a99e0891ba37aa", 2, "dir-9" },
+ { 0100644, "fc4c636d6515e9e261f9260dbcf3cc6eca97ea08", 1, "dir-9/file.txt" },
+ { 0100644, "76ab0e2868197ec158ddd6c78d8a0d2fd73d38f9", 3, "dir-9/file.txt" },
+ { 0100644, "5c2411f8075f48a6b2fdb85ebc0d371747c4df15", 0, "file-1/new" },
+ { 0100644, "a39a620dae5bc8b4e771cd4d251b7d080401a21e", 1, "file-2" },
+ { 0100644, "d963979c237d08b6ba39062ee7bf64c7d34a27f8", 2, "file-2" },
+ { 0100644, "5c341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d", 0, "file-2/new" },
+ { 0100644, "9efe7723802d4305142eee177e018fee1572c4f4", 0, "file-3/new" },
+ { 0100644, "bacac9b3493509aa15e1730e1545fc0919d1dae0", 1, "file-4" },
+ { 0100644, "7663fce0130db092936b137cabd693ec234eb060", 3, "file-4" },
+ { 0100644, "e49f917b448d1340b31d76e54ba388268fd4c922", 0, "file-4/new" },
+ { 0100644, "cab2cf23998b40f1af2d9d9a756dc9e285a8df4b", 2, "file-5/new" },
+ { 0100644, "f5504f36e6f4eb797a56fc5bac6c6c7f32969bf2", 3, "file-5/new" },
+ };
+
+ cl_git_pass(git_reference_symbolic_create(&head, repo, GIT_HEAD_FILE, GIT_REFS_HEADS_DIR OURS_DIRECTORY_FILE, 1, NULL, NULL));
+ cl_git_pass(git_reference_name_to_id(&head_commit_id, repo, GIT_HEAD_FILE));
+ cl_git_pass(git_commit_lookup(&head_commit, repo, &head_commit_id));
+ cl_git_pass(git_reset(repo, (git_object *)head_commit, GIT_RESET_HARD, NULL, NULL));
+
+ cl_git_pass(git_oid_fromstr(&their_oids[0], THEIRS_DIRECTORY_FILE));
+ cl_git_pass(git_merge_head_from_id(&their_heads[0], repo, &their_oids[0]));
+
+ merge_opts.file_favor = 0;
+ cl_git_pass(git_merge(repo, (const git_merge_head **)their_heads, 1, &merge_opts, NULL));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 20));
+
+ git_reference_free(head);
+ git_commit_free(head_commit);
+ git_merge_head_free(their_heads[0]);
+}
+
+void test_merge_workdir_simple__unrelated(void)
+{
+ git_oid their_oids[1];
+ git_merge_head *their_heads[1];
+ git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" },
+ { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 0, "automergeable.txt" },
+ { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-branch.txt" },
+ { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" },
+ { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 0, "conflicting.txt" },
+ { 0100644, "ef58fdd8086c243bdc81f99e379acacfd21d32d6", 0, "new-in-unrelated1.txt" },
+ { 0100644, "948ba6e701c1edab0c2d394fb7c5538334129793", 0, "new-in-unrelated2.txt" },
+ { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" },
+ { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" },
+ };
+
+ cl_git_pass(git_oid_fromstr(&their_oids[0], THEIRS_UNRELATED_PARENT));
+ cl_git_pass(git_merge_head_from_id(&their_heads[0], repo, &their_oids[0]));
+
+ merge_opts.file_favor = 0;
+ cl_git_pass(git_merge(repo, (const git_merge_head **)their_heads, 1, &merge_opts, NULL));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 9));
+
+ git_merge_head_free(their_heads[0]);
+}
+
+void test_merge_workdir_simple__unrelated_with_conflicts(void)
+{
+ git_oid their_oids[1];
+ git_merge_head *their_heads[1];
+ git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "233c0919c998ed110a4b6ff36f353aec8b713487", 0, "added-in-master.txt" },
+ { 0100644, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf", 2, "automergeable.txt" },
+ { 0100644, "d07ec190c306ec690bac349e87d01c4358e49bb2", 3, "automergeable.txt" },
+ { 0100644, "ab6c44a2e84492ad4b41bb6bac87353e9d02ac8b", 0, "changed-in-branch.txt" },
+ { 0100644, "11deab00b2d3a6f5a3073988ac050c2d7b6655e2", 0, "changed-in-master.txt" },
+ { 0100644, "4e886e602529caa9ab11d71f86634bd1b6e0de10", 2, "conflicting.txt" },
+ { 0100644, "4b253da36a0ae8bfce63aeabd8c5b58429925594", 3, "conflicting.txt" },
+ { 0100644, "ef58fdd8086c243bdc81f99e379acacfd21d32d6", 0, "new-in-unrelated1.txt" },
+ { 0100644, "948ba6e701c1edab0c2d394fb7c5538334129793", 0, "new-in-unrelated2.txt" },
+ { 0100644, "dfe3f22baa1f6fce5447901c3086bae368de6bdd", 0, "removed-in-branch.txt" },
+ { 0100644, "c8f06f2e3bb2964174677e91f0abead0e43c9e5d", 0, "unchanged.txt" },
+ };
+
+ cl_git_pass(git_oid_fromstr(&their_oids[0], THEIRS_UNRELATED_OID));
+ cl_git_pass(git_merge_head_from_id(&their_heads[0], repo, &their_oids[0]));
+
+ merge_opts.file_favor = 0;
+ cl_git_pass(git_merge(repo, (const git_merge_head **)their_heads, 1, &merge_opts, NULL));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 11));
+
+ git_merge_head_free(their_heads[0]);
+}
+
+void test_merge_workdir_simple__binary(void)
+{
+ git_oid our_oid, their_oid, our_file_oid;
+ git_commit *our_commit;
+ git_merge_head *their_head;
+ const git_index_entry *binary_entry;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "1c51d885170f57a0c4e8c69ff6363d91a5b51f85", 1, "binary" },
+ { 0100644, "23ed141a6ae1e798b2f721afedbe947c119111ba", 2, "binary" },
+ { 0100644, "836b8b82b26cab22eaaed8820877c76d6c8bca19", 3, "binary" },
+ };
+
+ cl_git_pass(git_oid_fromstr(&our_oid, "cc338e4710c9b257106b8d16d82f86458d5beaf1"));
+ cl_git_pass(git_oid_fromstr(&their_oid, "ad01aebfdf2ac13145efafe3f9fcf798882f1730"));
+
+ cl_git_pass(git_commit_lookup(&our_commit, repo, &our_oid));
+ cl_git_pass(git_reset(repo, (git_object *)our_commit, GIT_RESET_HARD, NULL, NULL));
+
+ cl_git_pass(git_merge_head_from_id(&their_head, repo, &their_oid));
+
+ cl_git_pass(git_merge(repo, (const git_merge_head **)&their_head, 1, NULL, NULL));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 3));
+
+ cl_git_pass(git_index_add_bypath(repo_index, "binary"));
+ cl_assert((binary_entry = git_index_get_bypath(repo_index, "binary", 0)) != NULL);
+
+ cl_git_pass(git_oid_fromstr(&our_file_oid, "23ed141a6ae1e798b2f721afedbe947c119111ba"));
+ cl_assert(git_oid_cmp(&binary_entry->id, &our_file_oid) == 0);
+
+ git_merge_head_free(their_head);
+ git_commit_free(our_commit);
+}
diff --git a/tests/merge/workdir/submodules.c b/tests/merge/workdir/submodules.c
new file mode 100644
index 000000000..e093e77ab
--- /dev/null
+++ b/tests/merge/workdir/submodules.c
@@ -0,0 +1,95 @@
+#include "clar_libgit2.h"
+#include "git2/repository.h"
+#include "git2/merge.h"
+#include "buffer.h"
+#include "merge.h"
+#include "../merge_helpers.h"
+
+static git_repository *repo;
+
+#define TEST_REPO_PATH "merge-resolve"
+
+#define SUBMODULE_MAIN_BRANCH "submodules"
+#define SUBMODULE_OTHER_BRANCH "submodules-branch"
+#define SUBMODULE_OTHER2_BRANCH "submodules-branch2"
+
+#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index"
+
+// Fixture setup and teardown
+void test_merge_workdir_submodules__initialize(void)
+{
+ repo = cl_git_sandbox_init(TEST_REPO_PATH);
+}
+
+void test_merge_workdir_submodules__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_merge_workdir_submodules__automerge(void)
+{
+ git_reference *our_ref, *their_ref;
+ git_commit *our_commit;
+ git_merge_head *their_head;
+ git_index *index;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "caff6b7d44973f53e3e0cf31d0d695188b19aec6", 0, ".gitmodules" },
+ { 0100644, "950a663a6a7b2609eed1ed1ba9f41eb1a3192a9f", 0, "file1.txt" },
+ { 0100644, "343e660b9cb4bee5f407c2e33fcb9df24d9407a4", 0, "file2.txt" },
+ { 0160000, "d3d806a4bef96889117fd7ebac0e3cb5ec152932", 1, "submodule" },
+ { 0160000, "297aa6cd028b3336c7802c7a6f49143da4e1602d", 2, "submodule" },
+ { 0160000, "ae39c77c70cb6bad18bb471912460c4e1ba0f586", 3, "submodule" },
+ };
+
+ cl_git_pass(git_reference_lookup(&our_ref, repo, "refs/heads/" SUBMODULE_MAIN_BRANCH));
+ cl_git_pass(git_commit_lookup(&our_commit, repo, git_reference_target(our_ref)));
+ cl_git_pass(git_reset(repo, (git_object *)our_commit, GIT_RESET_HARD, NULL, NULL));
+
+ cl_git_pass(git_reference_lookup(&their_ref, repo, "refs/heads/" SUBMODULE_OTHER_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_head, repo, their_ref));
+
+ cl_git_pass(git_merge(repo, (const git_merge_head **)&their_head, 1, NULL, NULL));
+
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_assert(merge_test_index(index, merge_index_entries, 6));
+
+ git_index_free(index);
+ git_merge_head_free(their_head);
+ git_commit_free(our_commit);
+ git_reference_free(their_ref);
+ git_reference_free(our_ref);
+}
+
+void test_merge_workdir_submodules__take_changed(void)
+{
+ git_reference *our_ref, *their_ref;
+ git_commit *our_commit;
+ git_merge_head *their_head;
+ git_index *index;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "caff6b7d44973f53e3e0cf31d0d695188b19aec6", 0, ".gitmodules" },
+ { 0100644, "b438ff23300b2e0f80b84a6f30140dfa91e71423", 0, "file1.txt" },
+ { 0100644, "f27fbafdfa6693f8f7a5128506fe3e338dbfcad2", 0, "file2.txt" },
+ { 0160000, "297aa6cd028b3336c7802c7a6f49143da4e1602d", 0, "submodule" },
+ };
+
+ cl_git_pass(git_reference_lookup(&our_ref, repo, "refs/heads/" SUBMODULE_MAIN_BRANCH));
+ cl_git_pass(git_commit_lookup(&our_commit, repo, git_reference_target(our_ref)));
+ cl_git_pass(git_reset(repo, (git_object *)our_commit, GIT_RESET_HARD, NULL, NULL));
+
+ cl_git_pass(git_reference_lookup(&their_ref, repo, "refs/heads/" SUBMODULE_OTHER2_BRANCH));
+ cl_git_pass(git_merge_head_from_ref(&their_head, repo, their_ref));
+
+ cl_git_pass(git_merge(repo, (const git_merge_head **)&their_head, 1, NULL, NULL));
+
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_assert(merge_test_index(index, merge_index_entries, 4));
+
+ git_index_free(index);
+ git_merge_head_free(their_head);
+ git_commit_free(our_commit);
+ git_reference_free(their_ref);
+ git_reference_free(our_ref);
+}
diff --git a/tests/merge/workdir/trivial.c b/tests/merge/workdir/trivial.c
new file mode 100644
index 000000000..cc82d990c
--- /dev/null
+++ b/tests/merge/workdir/trivial.c
@@ -0,0 +1,262 @@
+#include "clar_libgit2.h"
+#include "git2/repository.h"
+#include "git2/merge.h"
+#include "git2/sys/index.h"
+#include "merge.h"
+#include "../merge_helpers.h"
+#include "refs.h"
+#include "fileops.h"
+
+static git_repository *repo;
+static git_index *repo_index;
+
+#define TEST_REPO_PATH "merge-resolve"
+#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index"
+
+
+// Fixture setup and teardown
+void test_merge_workdir_trivial__initialize(void)
+{
+ repo = cl_git_sandbox_init(TEST_REPO_PATH);
+ git_repository_index(&repo_index, repo);
+}
+
+void test_merge_workdir_trivial__cleanup(void)
+{
+ git_index_free(repo_index);
+ cl_git_sandbox_cleanup();
+}
+
+
+static int merge_trivial(const char *ours, const char *theirs)
+{
+ git_buf branch_buf = GIT_BUF_INIT;
+ git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_reference *our_ref, *their_ref;
+ git_merge_head *their_heads[1];
+
+ checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ git_buf_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, ours);
+ cl_git_pass(git_reference_symbolic_create(&our_ref, repo, "HEAD", branch_buf.ptr, 1, NULL, NULL));
+
+ cl_git_pass(git_checkout_head(repo, &checkout_opts));
+
+ git_buf_clear(&branch_buf);
+ git_buf_printf(&branch_buf, "%s%s", GIT_REFS_HEADS_DIR, theirs);
+ cl_git_pass(git_reference_lookup(&their_ref, repo, branch_buf.ptr));
+ cl_git_pass(git_merge_head_from_ref(&their_heads[0], repo, their_ref));
+
+ cl_git_pass(git_merge(repo, (const git_merge_head **)their_heads, 1, NULL, NULL));
+
+ git_buf_free(&branch_buf);
+ git_reference_free(our_ref);
+ git_reference_free(their_ref);
+ git_merge_head_free(their_heads[0]);
+
+ return 0;
+}
+
+static size_t merge_trivial_conflict_entrycount(void)
+{
+ const git_index_entry *entry;
+ size_t count = 0;
+ size_t i;
+
+ for (i = 0; i < git_index_entrycount(repo_index); i++) {
+ cl_assert(entry = git_index_get_byindex(repo_index, i));
+
+ if (git_index_entry_stage(entry) > 0)
+ count++;
+ }
+
+ return count;
+}
+
+/* 2ALT: ancest:(empty)+, head:*empty*, remote:remote = result:remote */
+void test_merge_workdir_trivial__2alt(void)
+{
+ const git_index_entry *entry;
+
+ cl_git_pass(merge_trivial("trivial-2alt", "trivial-2alt-branch"));
+
+ cl_assert(entry = git_index_get_bypath(repo_index, "new-in-branch.txt", 0));
+ cl_assert(git_index_reuc_entrycount(repo_index) == 0);
+ cl_assert(merge_trivial_conflict_entrycount() == 0);
+}
+
+/* 3ALT: ancest:(empty)+, head:head, remote:*empty* = result:head */
+void test_merge_workdir_trivial__3alt(void)
+{
+ const git_index_entry *entry;
+
+ cl_git_pass(merge_trivial("trivial-3alt", "trivial-3alt-branch"));
+
+ cl_assert(entry = git_index_get_bypath(repo_index, "new-in-3alt.txt", 0));
+ cl_assert(git_index_reuc_entrycount(repo_index) == 0);
+ cl_assert(merge_trivial_conflict_entrycount() == 0);
+}
+
+/* 4: ancest:(empty)^, head:head, remote:remote = result:no merge */
+void test_merge_workdir_trivial__4(void)
+{
+ const git_index_entry *entry;
+
+ cl_git_pass(merge_trivial("trivial-4", "trivial-4-branch"));
+
+ cl_assert((entry = git_index_get_bypath(repo_index, "new-and-different.txt", 0)) == NULL);
+ cl_assert(git_index_reuc_entrycount(repo_index) == 0);
+
+ cl_assert(merge_trivial_conflict_entrycount() == 2);
+ cl_assert(entry = git_index_get_bypath(repo_index, "new-and-different.txt", 2));
+ cl_assert(entry = git_index_get_bypath(repo_index, "new-and-different.txt", 3));
+}
+
+/* 5ALT: ancest:*, head:head, remote:head = result:head */
+void test_merge_workdir_trivial__5alt_1(void)
+{
+ const git_index_entry *entry;
+
+ cl_git_pass(merge_trivial("trivial-5alt-1", "trivial-5alt-1-branch"));
+
+ cl_assert(entry = git_index_get_bypath(repo_index, "new-and-same.txt", 0));
+ cl_assert(git_index_reuc_entrycount(repo_index) == 0);
+ cl_assert(merge_trivial_conflict_entrycount() == 0);
+}
+
+/* 5ALT: ancest:*, head:head, remote:head = result:head */
+void test_merge_workdir_trivial__5alt_2(void)
+{
+ const git_index_entry *entry;
+
+ cl_git_pass(merge_trivial("trivial-5alt-2", "trivial-5alt-2-branch"));
+
+ cl_assert(entry = git_index_get_bypath(repo_index, "modified-to-same.txt", 0));
+ cl_assert(git_index_reuc_entrycount(repo_index) == 0);
+ cl_assert(merge_trivial_conflict_entrycount() == 0);
+}
+
+/* 6: ancest:ancest+, head:(empty), remote:(empty) = result:no merge */
+void test_merge_workdir_trivial__6(void)
+{
+ const git_index_entry *entry;
+ const git_index_reuc_entry *reuc;
+
+ cl_git_pass(merge_trivial("trivial-6", "trivial-6-branch"));
+
+ cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-both.txt", 0)) == NULL);
+ cl_assert(git_index_reuc_entrycount(repo_index) == 1);
+ cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "removed-in-both.txt"));
+
+ cl_assert(merge_trivial_conflict_entrycount() == 0);
+}
+
+/* 8: ancest:ancest^, head:(empty), remote:ancest = result:no merge */
+void test_merge_workdir_trivial__8(void)
+{
+ const git_index_entry *entry;
+ const git_index_reuc_entry *reuc;
+
+ cl_git_pass(merge_trivial("trivial-8", "trivial-8-branch"));
+
+ cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-8.txt", 0)) == NULL);
+
+ cl_assert(git_index_reuc_entrycount(repo_index) == 1);
+ cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "removed-in-8.txt"));
+
+ cl_assert(merge_trivial_conflict_entrycount() == 0);
+}
+
+/* 7: ancest:ancest+, head:(empty), remote:remote = result:no merge */
+void test_merge_workdir_trivial__7(void)
+{
+ const git_index_entry *entry;
+
+ cl_git_pass(merge_trivial("trivial-7", "trivial-7-branch"));
+
+ cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-7.txt", 0)) == NULL);
+ cl_assert(git_index_reuc_entrycount(repo_index) == 0);
+
+ cl_assert(merge_trivial_conflict_entrycount() == 2);
+ cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-7.txt", 1));
+ cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-7.txt", 3));
+}
+
+/* 10: ancest:ancest^, head:ancest, remote:(empty) = result:no merge */
+void test_merge_workdir_trivial__10(void)
+{
+ const git_index_entry *entry;
+ const git_index_reuc_entry *reuc;
+
+ cl_git_pass(merge_trivial("trivial-10", "trivial-10-branch"));
+
+ cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-10-branch.txt", 0)) == NULL);
+
+ cl_assert(git_index_reuc_entrycount(repo_index) == 1);
+ cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "removed-in-10-branch.txt"));
+
+ cl_assert(merge_trivial_conflict_entrycount() == 0);
+}
+
+/* 9: ancest:ancest+, head:head, remote:(empty) = result:no merge */
+void test_merge_workdir_trivial__9(void)
+{
+ const git_index_entry *entry;
+
+ cl_git_pass(merge_trivial("trivial-9", "trivial-9-branch"));
+
+ cl_assert((entry = git_index_get_bypath(repo_index, "removed-in-9-branch.txt", 0)) == NULL);
+ cl_assert(git_index_reuc_entrycount(repo_index) == 0);
+
+ cl_assert(merge_trivial_conflict_entrycount() == 2);
+ cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-9-branch.txt", 1));
+ cl_assert(entry = git_index_get_bypath(repo_index, "removed-in-9-branch.txt", 2));
+}
+
+/* 13: ancest:ancest+, head:head, remote:ancest = result:head */
+void test_merge_workdir_trivial__13(void)
+{
+ const git_index_entry *entry;
+ git_oid expected_oid;
+
+ cl_git_pass(merge_trivial("trivial-13", "trivial-13-branch"));
+
+ cl_assert(entry = git_index_get_bypath(repo_index, "modified-in-13.txt", 0));
+ cl_git_pass(git_oid_fromstr(&expected_oid, "1cff9ec6a47a537380dedfdd17c9e76d74259a2b"));
+ cl_assert(git_oid_cmp(&entry->id, &expected_oid) == 0);
+
+ cl_assert(git_index_reuc_entrycount(repo_index) == 0);
+ cl_assert(merge_trivial_conflict_entrycount() == 0);
+}
+
+/* 14: ancest:ancest+, head:ancest, remote:remote = result:remote */
+void test_merge_workdir_trivial__14(void)
+{
+ const git_index_entry *entry;
+ git_oid expected_oid;
+
+ cl_git_pass(merge_trivial("trivial-14", "trivial-14-branch"));
+
+ cl_assert(entry = git_index_get_bypath(repo_index, "modified-in-14-branch.txt", 0));
+ cl_git_pass(git_oid_fromstr(&expected_oid, "26153a3ff3649b6c2bb652d3f06878c6e0a172f9"));
+ cl_assert(git_oid_cmp(&entry->id, &expected_oid) == 0);
+
+ cl_assert(git_index_reuc_entrycount(repo_index) == 0);
+ cl_assert(merge_trivial_conflict_entrycount() == 0);
+}
+
+/* 11: ancest:ancest+, head:head, remote:remote = result:no merge */
+void test_merge_workdir_trivial__11(void)
+{
+ const git_index_entry *entry;
+
+ cl_git_pass(merge_trivial("trivial-11", "trivial-11-branch"));
+
+ cl_assert((entry = git_index_get_bypath(repo_index, "modified-in-both.txt", 0)) == NULL);
+ cl_assert(git_index_reuc_entrycount(repo_index) == 0);
+
+ cl_assert(merge_trivial_conflict_entrycount() == 3);
+ cl_assert(entry = git_index_get_bypath(repo_index, "modified-in-both.txt", 1));
+ cl_assert(entry = git_index_get_bypath(repo_index, "modified-in-both.txt", 2));
+ cl_assert(entry = git_index_get_bypath(repo_index, "modified-in-both.txt", 3));
+}
diff --git a/tests/network/cred.c b/tests/network/cred.c
new file mode 100644
index 000000000..6994cc0c3
--- /dev/null
+++ b/tests/network/cred.c
@@ -0,0 +1,50 @@
+#include "clar_libgit2.h"
+
+#include "git2/cred_helpers.h"
+
+void test_network_cred__stock_userpass_validates_args(void)
+{
+ git_cred_userpass_payload payload = {0};
+
+ cl_git_fail(git_cred_userpass(NULL, NULL, NULL, 0, NULL));
+
+ payload.username = "user";
+ cl_git_fail(git_cred_userpass(NULL, NULL, NULL, 0, &payload));
+
+ payload.username = NULL;
+ payload.username = "pass";
+ cl_git_fail(git_cred_userpass(NULL, NULL, NULL, 0, &payload));
+}
+
+void test_network_cred__stock_userpass_validates_that_method_is_allowed(void)
+{
+ git_cred *cred;
+ git_cred_userpass_payload payload = {"user", "pass"};
+
+ cl_git_fail(git_cred_userpass(&cred, NULL, NULL, 0, &payload));
+ cl_git_pass(git_cred_userpass(&cred, NULL, NULL, GIT_CREDTYPE_USERPASS_PLAINTEXT, &payload));
+ cred->free(cred);
+}
+
+void test_network_cred__stock_userpass_properly_handles_username_in_url(void)
+{
+ git_cred *cred;
+ git_cred_userpass_plaintext *plain;
+ git_cred_userpass_payload payload = {"alice", "password"};
+
+ cl_git_pass(git_cred_userpass(&cred, NULL, NULL, GIT_CREDTYPE_USERPASS_PLAINTEXT, &payload));
+ plain = (git_cred_userpass_plaintext*)cred;
+ cl_assert_equal_s(plain->username, "alice");
+ cred->free(cred);
+
+ cl_git_pass(git_cred_userpass(&cred, NULL, "bob", GIT_CREDTYPE_USERPASS_PLAINTEXT, &payload));
+ plain = (git_cred_userpass_plaintext*)cred;
+ cl_assert_equal_s(plain->username, "alice");
+ cred->free(cred);
+
+ payload.username = NULL;
+ cl_git_pass(git_cred_userpass(&cred, NULL, "bob", GIT_CREDTYPE_USERPASS_PLAINTEXT, &payload));
+ plain = (git_cred_userpass_plaintext*)cred;
+ cl_assert_equal_s(plain->username, "bob");
+ cred->free(cred);
+}
diff --git a/tests/network/fetchlocal.c b/tests/network/fetchlocal.c
new file mode 100644
index 000000000..0d23bef48
--- /dev/null
+++ b/tests/network/fetchlocal.c
@@ -0,0 +1,114 @@
+#include "clar_libgit2.h"
+
+#include "buffer.h"
+#include "path.h"
+#include "remote.h"
+
+static int transfer_cb(const git_transfer_progress *stats, void *payload)
+{
+ int *callcount = (int*)payload;
+ GIT_UNUSED(stats);
+ (*callcount)++;
+ return 0;
+}
+
+static void cleanup_local_repo(void *path)
+{
+ cl_fixture_cleanup((char *)path);
+}
+
+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");
+ git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
+
+ callbacks.transfer_progress = transfer_cb;
+ callbacks.payload = &callcount;
+
+ cl_set_cleanup(&cleanup_local_repo, "foo");
+ cl_git_pass(git_repository_init(&repo, "foo", true));
+
+ cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url));
+ git_remote_set_callbacks(origin, &callbacks);
+ cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH));
+ cl_git_pass(git_remote_download(origin));
+ cl_git_pass(git_remote_update_tips(origin, NULL, NULL));
+
+ cl_git_pass(git_reference_list(&refnames, repo));
+ cl_assert_equal_i(19, (int)refnames.count);
+ cl_assert(callcount > 0);
+
+ git_strarray_free(&refnames);
+ git_remote_free(origin);
+ git_repository_free(repo);
+}
+
+static void cleanup_sandbox(void *unused)
+{
+ GIT_UNUSED(unused);
+ cl_git_sandbox_cleanup();
+}
+
+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;
+ git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
+
+ callbacks.transfer_progress = transfer_cb;
+ callbacks.payload = &callcount;
+
+ cl_set_cleanup(&cleanup_sandbox, NULL);
+ cl_git_pass(git_reference_list(&refnames, repo));
+ cl_assert_equal_i(1, (int)refnames.count);
+
+ url = cl_git_fixture_url("testrepo.git");
+ cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url));
+ git_remote_set_callbacks(origin, &callbacks);
+ cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH));
+ cl_git_pass(git_remote_download(origin));
+ cl_git_pass(git_remote_update_tips(origin, NULL, NULL));
+
+ git_strarray_free(&refnames);
+
+ cl_git_pass(git_reference_list(&refnames, repo));
+ cl_assert_equal_i(20, (int)refnames.count); /* 18 remote + 1 local */
+ cl_assert(callcount > 0);
+
+ git_strarray_free(&refnames);
+ git_remote_free(origin);
+}
+
+void test_network_fetchlocal__clone_into_mirror(void)
+{
+ git_buf path = GIT_BUF_INIT;
+ git_repository *repo;
+ git_remote *remote;
+ git_reference *head;
+
+ cl_git_pass(git_repository_init(&repo, "./foo.git", true));
+ cl_git_pass(git_remote_create(&remote, repo, "origin", cl_git_fixture_url("testrepo.git")));
+
+ git_remote_clear_refspecs(remote);
+ cl_git_pass(git_remote_add_fetch(remote, "+refs/*:refs/*"));
+
+ cl_git_pass(git_clone_into(repo, remote, NULL, NULL, NULL));
+
+ cl_git_pass(git_reference_lookup(&head, repo, "HEAD"));
+ cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head));
+ cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head));
+
+ git_remote_free(remote);
+ git_reference_free(head);
+ git_repository_free(repo);
+ git_buf_free(&path);
+ cl_fixture_cleanup("./foo.git");
+}
diff --git a/tests/network/matchhost.c b/tests/network/matchhost.c
new file mode 100644
index 000000000..3100dc21d
--- /dev/null
+++ b/tests/network/matchhost.c
@@ -0,0 +1,13 @@
+#include "clar_libgit2.h"
+#include "netops.h"
+
+void test_network_matchhost__match(void)
+{
+ cl_git_pass(gitno__match_host("*.example.org", "www.example.org"));
+ cl_git_pass(gitno__match_host("*.foo.example.org", "www.foo.example.org"));
+ cl_git_fail(gitno__match_host("*.foo.example.org", "foo.example.org"));
+ cl_git_fail(gitno__match_host("*.foo.example.org", "www.example.org"));
+ cl_git_fail(gitno__match_host("*.example.org", "example.org"));
+ cl_git_fail(gitno__match_host("*.example.org", "www.foo.example.org"));
+ cl_git_fail(gitno__match_host("*.example.org", "blah.www.www.example.org"));
+}
diff --git a/tests/network/refspecs.c b/tests/network/refspecs.c
new file mode 100644
index 000000000..676a1fa99
--- /dev/null
+++ b/tests/network/refspecs.c
@@ -0,0 +1,87 @@
+#include "clar_libgit2.h"
+#include "refspec.h"
+#include "remote.h"
+
+static void assert_refspec(unsigned int direction, const char *input, bool is_expected_to_be_valid)
+{
+ git_refspec refspec;
+ int error;
+
+ error = git_refspec__parse(&refspec, input, direction == GIT_DIRECTION_FETCH);
+ git_refspec__free(&refspec);
+
+ if (is_expected_to_be_valid)
+ cl_assert_equal_i(0, error);
+ else
+ cl_assert_equal_i(GIT_ERROR, error);
+}
+
+void test_network_refspecs__parsing(void)
+{
+ // Ported from https://github.com/git/git/blob/abd2bde78bd994166900290434a2048e660dabed/t/t5511-refspec.sh
+
+ assert_refspec(GIT_DIRECTION_PUSH, "", false);
+ assert_refspec(GIT_DIRECTION_PUSH, ":", true);
+ assert_refspec(GIT_DIRECTION_PUSH, "::", false);
+ assert_refspec(GIT_DIRECTION_PUSH, "+:", true);
+
+ assert_refspec(GIT_DIRECTION_FETCH, "", true);
+ assert_refspec(GIT_DIRECTION_PUSH, ":", true);
+ assert_refspec(GIT_DIRECTION_FETCH, "::", false);
+
+ assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*:refs/remotes/frotz/*", true);
+ assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*:refs/remotes/frotz", false);
+ assert_refspec(GIT_DIRECTION_PUSH, "refs/heads:refs/remotes/frotz/*", false);
+ assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/master:refs/remotes/frotz/xyzzy", true);
+
+ /*
+ * These have invalid LHS, but we do not have a formal "valid sha-1
+ * expression syntax checker" so they are not checked with the current
+ * code. They will be caught downstream anyway, but we may want to
+ * have tighter check later...
+ */
+ //assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/master::refs/remotes/frotz/xyzzy", false);
+ //assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/maste :refs/remotes/frotz/xyzzy", false);
+
+ assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*:refs/remotes/frotz/*", true);
+ assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*:refs/remotes/frotz", false);
+ assert_refspec(GIT_DIRECTION_FETCH, "refs/heads:refs/remotes/frotz/*", false);
+ assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/master:refs/remotes/frotz/xyzzy", true);
+ assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/master::refs/remotes/frotz/xyzzy", false);
+ assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/maste :refs/remotes/frotz/xyzzy", false);
+
+ assert_refspec(GIT_DIRECTION_PUSH, "master~1:refs/remotes/frotz/backup", true);
+ assert_refspec(GIT_DIRECTION_FETCH, "master~1:refs/remotes/frotz/backup", false);
+ assert_refspec(GIT_DIRECTION_PUSH, "HEAD~4:refs/remotes/frotz/new", true);
+ assert_refspec(GIT_DIRECTION_FETCH, "HEAD~4:refs/remotes/frotz/new", false);
+
+ assert_refspec(GIT_DIRECTION_PUSH, "HEAD", true);
+ assert_refspec(GIT_DIRECTION_FETCH, "HEAD", true);
+ assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/ nitfol", false);
+ assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/ nitfol", false);
+
+ assert_refspec(GIT_DIRECTION_PUSH, "HEAD:", false);
+ assert_refspec(GIT_DIRECTION_FETCH, "HEAD:", true);
+ assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/ nitfol:", false);
+ assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/ nitfol:", false);
+
+ assert_refspec(GIT_DIRECTION_PUSH, ":refs/remotes/frotz/deleteme", true);
+ assert_refspec(GIT_DIRECTION_FETCH, ":refs/remotes/frotz/HEAD-to-me", true);
+ assert_refspec(GIT_DIRECTION_PUSH, ":refs/remotes/frotz/delete me", false);
+ assert_refspec(GIT_DIRECTION_FETCH, ":refs/remotes/frotz/HEAD to me", false);
+
+ assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*/for-linus:refs/remotes/mine/*-blah", false);
+ assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*/for-linus:refs/remotes/mine/*-blah", false);
+
+ assert_refspec(GIT_DIRECTION_FETCH, "refs/heads*/for-linus:refs/remotes/mine/*", false);
+ assert_refspec(GIT_DIRECTION_PUSH, "refs/heads*/for-linus:refs/remotes/mine/*", false);
+
+ assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*/*/for-linus:refs/remotes/mine/*", false);
+ assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*/*/for-linus:refs/remotes/mine/*", false);
+
+ assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*/for-linus:refs/remotes/mine/*", true);
+ assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*/for-linus:refs/remotes/mine/*", true);
+
+ assert_refspec(GIT_DIRECTION_FETCH, "master", true);
+ assert_refspec(GIT_DIRECTION_PUSH, "master", true);
+}
diff --git a/tests/network/remote/createthenload.c b/tests/network/remote/createthenload.c
new file mode 100644
index 000000000..ac6cfccd3
--- /dev/null
+++ b/tests/network/remote/createthenload.c
@@ -0,0 +1,37 @@
+#include "clar_libgit2.h"
+
+static git_remote *_remote;
+static git_repository *_repo;
+static git_config *_config;
+static char url[] = "http://github.com/libgit2/libgit2.git";
+
+void test_network_remote_createthenload__initialize(void)
+{
+ cl_fixture_sandbox("testrepo.git");
+
+ cl_git_pass(git_repository_open(&_repo, "testrepo.git"));
+
+ cl_git_pass(git_repository_config(&_config, _repo));
+ cl_git_pass(git_config_set_string(_config, "remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*"));
+ cl_git_pass(git_config_set_string(_config, "remote.origin.url", url));
+ git_config_free(_config);
+
+ cl_git_pass(git_remote_load(&_remote, _repo, "origin"));
+}
+
+void test_network_remote_createthenload__cleanup(void)
+{
+ git_remote_free(_remote);
+ _remote = NULL;
+
+ git_repository_free(_repo);
+ _repo = NULL;
+
+ cl_fixture_cleanup("testrepo.git");
+}
+
+void test_network_remote_createthenload__parsing(void)
+{
+ cl_assert_equal_s(git_remote_name(_remote), "origin");
+ cl_assert_equal_s(git_remote_url(_remote), url);
+}
diff --git a/tests/network/remote/defaultbranch.c b/tests/network/remote/defaultbranch.c
new file mode 100644
index 000000000..fa3a329db
--- /dev/null
+++ b/tests/network/remote/defaultbranch.c
@@ -0,0 +1,50 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "refspec.h"
+#include "remote.h"
+
+static git_remote *g_remote;
+static git_repository *g_repo_a, *g_repo_b;
+
+void test_network_remote_defaultbranch__initialize(void)
+{
+ g_repo_a = cl_git_sandbox_init("testrepo.git");
+ cl_git_pass(git_repository_init(&g_repo_b, "repo-b.git", true));
+ cl_git_pass(git_remote_create(&g_remote, g_repo_b, "origin", git_repository_path(g_repo_a)));
+}
+
+void test_network_remote_defaultbranch__cleanup(void)
+{
+ git_remote_free(g_remote);
+ git_repository_free(g_repo_b);
+
+ cl_git_sandbox_cleanup();
+ cl_fixture_cleanup("repo-b.git");
+}
+
+static void assert_default_branch(const char *should)
+{
+ git_buf name = GIT_BUF_INIT;
+
+ cl_git_pass(git_remote_connect(g_remote, GIT_DIRECTION_FETCH));
+ cl_git_pass(git_remote_default_branch(&name, g_remote));
+ cl_assert_equal_s(should, name.ptr);
+ git_buf_free(&name);
+}
+
+void test_network_remote_defaultbranch__master(void)
+{
+ assert_default_branch("refs/heads/master");
+}
+
+void test_network_remote_defaultbranch__master_does_not_win(void)
+{
+ cl_git_pass(git_repository_set_head(g_repo_a, "refs/heads/not-good", NULL, NULL));
+ assert_default_branch("refs/heads/not-good");
+}
+
+void test_network_remote_defaultbranch__master_on_detached(void)
+{
+ cl_git_pass(git_repository_detach_head(g_repo_a, NULL, NULL));
+ assert_default_branch("refs/heads/master");
+}
diff --git a/tests/network/remote/delete.c b/tests/network/remote/delete.c
new file mode 100644
index 000000000..664f47a43
--- /dev/null
+++ b/tests/network/remote/delete.c
@@ -0,0 +1,61 @@
+#include "clar_libgit2.h"
+#include "config/config_helpers.h"
+
+#include "repository.h"
+
+static git_remote *_remote;
+static git_repository *_repo;
+
+void test_network_remote_delete__initialize(void)
+{
+ _repo = cl_git_sandbox_init("testrepo.git");
+
+ cl_git_pass(git_remote_load(&_remote, _repo, "test"));
+}
+
+void test_network_remote_delete__cleanup(void)
+{
+ git_remote_free(_remote);
+ cl_git_sandbox_cleanup();
+}
+
+void test_network_remote_delete__cannot_delete_an_anonymous_remote(void)
+{
+ git_remote *remote;
+
+ cl_git_pass(git_remote_create_anonymous(&remote, _repo, "git://github.com/libgit2/libgit2", NULL));
+
+ cl_git_fail(git_remote_delete(remote));
+
+ git_remote_free(remote);
+}
+
+void test_network_remote_delete__remove_remote_tracking_branches(void)
+{
+ git_reference *ref;
+
+ cl_git_pass(git_remote_delete(_remote));
+ cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, _repo, "refs/remotes/test/master"));
+}
+
+void test_network_remote_delete__remove_remote_configuration_settings(void)
+{
+ cl_assert(count_config_entries_match(_repo, "remote\\.test\\.+") > 0);
+
+ cl_git_pass(git_remote_delete(_remote));
+
+ cl_assert_equal_i(0, count_config_entries_match(_repo, "remote\\.test\\.+"));
+}
+
+void test_network_remote_delete__remove_branch_upstream_configuration_settings(void)
+{
+ assert_config_entry_existence(_repo, "branch.mergeless.remote", true);
+ assert_config_entry_existence(_repo, "branch.master.remote", true);
+
+ cl_git_pass(git_remote_delete(_remote));
+
+ assert_config_entry_existence(_repo, "branch.mergeless.remote", false);
+ assert_config_entry_existence(_repo, "branch.mergeless.merge", false);
+ assert_config_entry_existence(_repo, "branch.master.remote", false);
+ assert_config_entry_existence(_repo, "branch.master.merge", false);
+}
diff --git a/tests/network/remote/isvalidname.c b/tests/network/remote/isvalidname.c
new file mode 100644
index 000000000..c26fbd0a5
--- /dev/null
+++ b/tests/network/remote/isvalidname.c
@@ -0,0 +1,17 @@
+#include "clar_libgit2.h"
+
+void test_network_remote_isvalidname__can_detect_invalid_formats(void)
+{
+ cl_assert_equal_i(false, git_remote_is_valid_name("/"));
+ cl_assert_equal_i(false, git_remote_is_valid_name("//"));
+ cl_assert_equal_i(false, git_remote_is_valid_name(".lock"));
+ cl_assert_equal_i(false, git_remote_is_valid_name("a.lock"));
+ cl_assert_equal_i(false, git_remote_is_valid_name("/no/leading/slash"));
+ cl_assert_equal_i(false, git_remote_is_valid_name("no/trailing/slash/"));
+}
+
+void test_network_remote_isvalidname__wont_hopefully_choke_on_valid_formats(void)
+{
+ cl_assert_equal_i(true, git_remote_is_valid_name("webmatrix"));
+ cl_assert_equal_i(true, git_remote_is_valid_name("yishaigalatzer/rules"));
+}
diff --git a/tests/network/remote/local.c b/tests/network/remote/local.c
new file mode 100644
index 000000000..75f767980
--- /dev/null
+++ b/tests/network/remote/local.c
@@ -0,0 +1,358 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "path.h"
+#include "posix.h"
+
+static git_repository *repo;
+static git_buf file_path_buf = GIT_BUF_INIT;
+static git_remote *remote;
+
+void test_network_remote_local__initialize(void)
+{
+ cl_git_pass(git_repository_init(&repo, "remotelocal/", 0));
+ cl_assert(repo != NULL);
+}
+
+void test_network_remote_local__cleanup(void)
+{
+ git_buf_free(&file_path_buf);
+
+ git_remote_free(remote);
+ remote = NULL;
+
+ git_repository_free(repo);
+ repo = NULL;
+
+ cl_fixture_cleanup("remotelocal");
+}
+
+static void connect_to_local_repository(const char *local_repository)
+{
+ git_buf_sets(&file_path_buf, cl_git_path_url(local_repository));
+
+ cl_git_pass(git_remote_create_anonymous(&remote, repo, git_buf_cstr(&file_path_buf), NULL));
+ cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH));
+}
+
+void test_network_remote_local__connected(void)
+{
+ connect_to_local_repository(cl_fixture("testrepo.git"));
+ cl_assert(git_remote_connected(remote));
+
+ git_remote_disconnect(remote);
+ cl_assert(!git_remote_connected(remote));
+}
+
+void test_network_remote_local__retrieve_advertised_references(void)
+{
+ const git_remote_head **refs;
+ size_t refs_len;
+
+ connect_to_local_repository(cl_fixture("testrepo.git"));
+
+ cl_git_pass(git_remote_ls(&refs, &refs_len, remote));
+
+ cl_assert_equal_i(refs_len, 28);
+}
+
+void test_network_remote_local__retrieve_advertised_references_after_disconnect(void)
+{
+ const git_remote_head **refs;
+ size_t refs_len;
+
+ connect_to_local_repository(cl_fixture("testrepo.git"));
+ git_remote_disconnect(remote);
+
+ cl_git_pass(git_remote_ls(&refs, &refs_len, remote));
+
+ cl_assert_equal_i(refs_len, 28);
+}
+
+void test_network_remote_local__retrieve_advertised_references_from_spaced_repository(void)
+{
+ const git_remote_head **refs;
+ size_t refs_len;
+
+ cl_fixture_sandbox("testrepo.git");
+ cl_git_pass(p_rename("testrepo.git", "spaced testrepo.git"));
+
+ connect_to_local_repository("spaced testrepo.git");
+
+ cl_git_pass(git_remote_ls(&refs, &refs_len, remote));
+
+ cl_assert_equal_i(refs_len, 28);
+
+ git_remote_free(remote); /* Disconnect from the "spaced repo" before the cleanup */
+ remote = NULL;
+
+ cl_fixture_cleanup("spaced testrepo.git");
+}
+
+void test_network_remote_local__nested_tags_are_completely_peeled(void)
+{
+ const git_remote_head **refs;
+ size_t refs_len, i;
+
+ connect_to_local_repository(cl_fixture("testrepo.git"));
+
+ cl_git_pass(git_remote_ls(&refs, &refs_len, remote));
+
+ for (i = 0; i < refs_len; i++) {
+ if (!strcmp(refs[i]->name, "refs/tags/test^{}"))
+ cl_git_pass(git_oid_streq(&refs[i]->oid, "e90810b8df3e80c413d903f631643c716887138d"));
+ }
+}
+
+void test_network_remote_local__shorthand_fetch_refspec0(void)
+{
+ const char *refspec = "master:remotes/sloppy/master";
+ const char *refspec2 = "master:boh/sloppy/master";
+
+ git_reference *ref;
+
+ connect_to_local_repository(cl_fixture("testrepo.git"));
+ cl_git_pass(git_remote_add_fetch(remote, refspec));
+ cl_git_pass(git_remote_add_fetch(remote, refspec2));
+
+ cl_git_pass(git_remote_download(remote));
+ cl_git_pass(git_remote_update_tips(remote, NULL, NULL));
+
+ cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/sloppy/master"));
+ git_reference_free(ref);
+
+ cl_git_pass(git_reference_lookup(&ref, repo, "refs/heads/boh/sloppy/master"));
+ git_reference_free(ref);
+}
+
+void test_network_remote_local__shorthand_fetch_refspec1(void)
+{
+ const char *refspec = "master";
+ const char *refspec2 = "hard_tag";
+
+ git_reference *ref;
+
+ connect_to_local_repository(cl_fixture("testrepo.git"));
+ git_remote_clear_refspecs(remote);
+ cl_git_pass(git_remote_add_fetch(remote, refspec));
+ cl_git_pass(git_remote_add_fetch(remote, refspec2));
+
+ cl_git_pass(git_remote_download(remote));
+ cl_git_pass(git_remote_update_tips(remote, NULL, NULL));
+
+ cl_git_fail(git_reference_lookup(&ref, repo, "refs/remotes/master"));
+
+ cl_git_fail(git_reference_lookup(&ref, repo, "refs/tags/hard_tag"));
+}
+
+void test_network_remote_local__tagopt(void)
+{
+ git_reference *ref;
+
+ connect_to_local_repository(cl_fixture("testrepo.git"));
+ git_remote_set_autotag(remote, GIT_REMOTE_DOWNLOAD_TAGS_ALL);
+
+ cl_git_pass(git_remote_download(remote));
+ cl_git_pass(git_remote_update_tips(remote, NULL, NULL));
+
+
+ cl_git_fail(git_reference_lookup(&ref, repo, "refs/remotes/master"));
+
+ cl_git_pass(git_reference_lookup(&ref, repo, "refs/tags/hard_tag"));
+ git_reference_free(ref);
+}
+
+void test_network_remote_local__push_to_bare_remote(void)
+{
+ /* Should be able to push to a bare remote */
+ git_remote *localremote;
+ git_push *push;
+
+ /* Get some commits */
+ connect_to_local_repository(cl_fixture("testrepo.git"));
+ cl_git_pass(git_remote_add_fetch(remote, "master:master"));
+ cl_git_pass(git_remote_download(remote));
+ cl_git_pass(git_remote_update_tips(remote, NULL, NULL));
+ git_remote_disconnect(remote);
+
+ /* Set up an empty bare repo to push into */
+ {
+ git_repository *localbarerepo;
+ cl_git_pass(git_repository_init(&localbarerepo, "./localbare.git", 1));
+ git_repository_free(localbarerepo);
+ }
+
+ /* Connect to the bare repo */
+ cl_git_pass(git_remote_create_anonymous(&localremote, repo, "./localbare.git", NULL));
+ cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH));
+
+ /* Try to push */
+ cl_git_pass(git_push_new(&push, localremote));
+ cl_git_pass(git_push_add_refspec(push, "refs/heads/master:"));
+ cl_git_pass(git_push_finish(push));
+ cl_assert(git_push_unpack_ok(push));
+
+ /* Clean up */
+ git_push_free(push);
+ git_remote_free(localremote);
+ cl_fixture_cleanup("localbare.git");
+}
+
+void test_network_remote_local__push_to_bare_remote_with_file_url(void)
+{
+ /* Should be able to push to a bare remote */
+ git_remote *localremote;
+ git_push *push;
+ const char *url;
+
+ /* Get some commits */
+ connect_to_local_repository(cl_fixture("testrepo.git"));
+ cl_git_pass(git_remote_add_fetch(remote, "master:master"));
+ cl_git_pass(git_remote_download(remote));
+ cl_git_pass(git_remote_update_tips(remote, NULL, NULL));
+ git_remote_disconnect(remote);
+
+ /* Set up an empty bare repo to push into */
+ {
+ git_repository *localbarerepo;
+ cl_git_pass(git_repository_init(&localbarerepo, "./localbare.git", 1));
+ git_repository_free(localbarerepo);
+ }
+
+ /* Create a file URL */
+ url = cl_git_path_url("./localbare.git");
+
+ /* Connect to the bare repo */
+ cl_git_pass(git_remote_create_anonymous(&localremote, repo, url, NULL));
+ cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH));
+
+ /* Try to push */
+ cl_git_pass(git_push_new(&push, localremote));
+ cl_git_pass(git_push_add_refspec(push, "refs/heads/master:"));
+ cl_git_pass(git_push_finish(push));
+ cl_assert(git_push_unpack_ok(push));
+
+ /* Clean up */
+ git_push_free(push);
+ git_remote_free(localremote);
+ cl_fixture_cleanup("localbare.git");
+}
+
+
+void test_network_remote_local__push_to_non_bare_remote(void)
+{
+ /* Shouldn't be able to push to a non-bare remote */
+ git_remote *localremote;
+ git_push *push;
+
+ /* Get some commits */
+ connect_to_local_repository(cl_fixture("testrepo.git"));
+ cl_git_pass(git_remote_add_fetch(remote, "master:master"));
+ cl_git_pass(git_remote_download(remote));
+ cl_git_pass(git_remote_update_tips(remote, NULL, NULL));
+ git_remote_disconnect(remote);
+
+ /* Set up an empty non-bare repo to push into */
+ {
+ git_repository *remoterepo = NULL;
+ cl_git_pass(git_repository_init(&remoterepo, "localnonbare", 0));
+ git_repository_free(remoterepo);
+ }
+
+ /* Connect to the bare repo */
+ cl_git_pass(git_remote_create_anonymous(&localremote, repo, "./localnonbare", NULL));
+ cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH));
+
+ /* Try to push */
+ cl_git_pass(git_push_new(&push, localremote));
+ cl_git_pass(git_push_add_refspec(push, "refs/heads/master:"));
+ cl_git_fail_with(git_push_finish(push), GIT_EBAREREPO);
+ cl_assert_equal_i(0, git_push_unpack_ok(push));
+
+ /* Clean up */
+ git_push_free(push);
+ git_remote_free(localremote);
+ cl_fixture_cleanup("localbare.git");
+}
+
+void test_network_remote_local__fetch(void)
+{
+ const char *refspec = "master:remotes/sloppy/master";
+
+ git_reflog *log;
+ const git_reflog_entry *entry;
+ git_signature *sig;
+ git_reference *ref;
+
+ cl_git_pass(git_signature_now(&sig, "Foo Bar", "foo@example.com"));
+
+ connect_to_local_repository(cl_fixture("testrepo.git"));
+ cl_git_pass(git_remote_add_fetch(remote, refspec));
+
+ cl_git_pass(git_remote_fetch(remote, sig, "UPDAAAAAATE!!"));
+
+ cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/sloppy/master"));
+ git_reference_free(ref);
+
+ cl_git_pass(git_reflog_read(&log, repo, "refs/remotes/sloppy/master"));
+ cl_assert_equal_i(1, git_reflog_entrycount(log));
+ entry = git_reflog_entry_byindex(log, 0);
+ cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email);
+ cl_assert_equal_s("UPDAAAAAATE!!", git_reflog_entry_message(entry));
+
+ git_reflog_free(log);
+ git_signature_free(sig);
+}
+
+void test_network_remote_local__reflog(void)
+{
+ const char *refspec = "master:remotes/sloppy/master";
+
+ git_reflog *log;
+ const git_reflog_entry *entry;
+ git_signature *sig;
+
+ cl_git_pass(git_signature_now(&sig, "Foo Bar", "foo@example.com"));
+
+ connect_to_local_repository(cl_fixture("testrepo.git"));
+ cl_git_pass(git_remote_add_fetch(remote, refspec));
+
+ cl_git_pass(git_remote_download(remote));
+ cl_git_pass(git_remote_update_tips(remote, sig, "UPDAAAAAATE!!"));
+
+ cl_git_pass(git_reflog_read(&log, repo, "refs/remotes/sloppy/master"));
+ cl_assert_equal_i(1, git_reflog_entrycount(log));
+ entry = git_reflog_entry_byindex(log, 0);
+ cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email);
+ cl_assert_equal_s("UPDAAAAAATE!!", git_reflog_entry_message(entry));
+
+ git_reflog_free(log);
+ git_signature_free(sig);
+}
+
+void test_network_remote_local__fetch_default_reflog_message(void)
+{
+ const char *refspec = "master:remotes/sloppy/master";
+
+ git_reflog *log;
+ const git_reflog_entry *entry;
+ git_signature *sig;
+ char expected_reflog_msg[1024];
+
+ cl_git_pass(git_signature_now(&sig, "Foo Bar", "foo@example.com"));
+
+ connect_to_local_repository(cl_fixture("testrepo.git"));
+ cl_git_pass(git_remote_add_fetch(remote, refspec));
+
+ cl_git_pass(git_remote_fetch(remote, sig, NULL));
+
+ cl_git_pass(git_reflog_read(&log, repo, "refs/remotes/sloppy/master"));
+ cl_assert_equal_i(1, git_reflog_entrycount(log));
+ entry = git_reflog_entry_byindex(log, 0);
+ cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email);
+
+ sprintf(expected_reflog_msg, "fetch %s", git_remote_url(remote));
+ cl_assert_equal_s(expected_reflog_msg, git_reflog_entry_message(entry));
+
+ git_reflog_free(log);
+ git_signature_free(sig);
+}
diff --git a/tests/network/remote/remotes.c b/tests/network/remote/remotes.c
new file mode 100644
index 000000000..333b52a5b
--- /dev/null
+++ b/tests/network/remote/remotes.c
@@ -0,0 +1,535 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "refspec.h"
+#include "remote.h"
+
+static git_remote *_remote;
+static git_repository *_repo;
+static const git_refspec *_refspec;
+
+void test_network_remote_remotes__initialize(void)
+{
+ _repo = cl_git_sandbox_init("testrepo.git");
+
+ cl_git_pass(git_remote_load(&_remote, _repo, "test"));
+
+ _refspec = git_remote_get_refspec(_remote, 0);
+ cl_assert(_refspec != NULL);
+}
+
+void test_network_remote_remotes__cleanup(void)
+{
+ git_remote_free(_remote);
+ _remote = NULL;
+
+ cl_git_sandbox_cleanup();
+}
+
+void test_network_remote_remotes__parsing(void)
+{
+ git_remote *_remote2 = NULL;
+
+ cl_assert_equal_s(git_remote_name(_remote), "test");
+ cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2");
+ cl_assert(git_remote_pushurl(_remote) == NULL);
+
+ cl_assert_equal_s(git_remote__urlfordirection(_remote, GIT_DIRECTION_FETCH),
+ "git://github.com/libgit2/libgit2");
+ cl_assert_equal_s(git_remote__urlfordirection(_remote, GIT_DIRECTION_PUSH),
+ "git://github.com/libgit2/libgit2");
+
+ cl_git_pass(git_remote_load(&_remote2, _repo, "test_with_pushurl"));
+ cl_assert_equal_s(git_remote_name(_remote2), "test_with_pushurl");
+ cl_assert_equal_s(git_remote_url(_remote2), "git://github.com/libgit2/fetchlibgit2");
+ cl_assert_equal_s(git_remote_pushurl(_remote2), "git://github.com/libgit2/pushlibgit2");
+
+ cl_assert_equal_s(git_remote__urlfordirection(_remote2, GIT_DIRECTION_FETCH),
+ "git://github.com/libgit2/fetchlibgit2");
+ cl_assert_equal_s(git_remote__urlfordirection(_remote2, GIT_DIRECTION_PUSH),
+ "git://github.com/libgit2/pushlibgit2");
+
+ git_remote_free(_remote2);
+}
+
+void test_network_remote_remotes__pushurl(void)
+{
+ cl_git_pass(git_remote_set_pushurl(_remote, "git://github.com/libgit2/notlibgit2"));
+ cl_assert_equal_s(git_remote_pushurl(_remote), "git://github.com/libgit2/notlibgit2");
+
+ cl_git_pass(git_remote_set_pushurl(_remote, NULL));
+ cl_assert(git_remote_pushurl(_remote) == NULL);
+}
+
+void test_network_remote_remotes__error_when_not_found(void)
+{
+ git_remote *r;
+ cl_git_fail_with(git_remote_load(&r, _repo, "does-not-exist"), GIT_ENOTFOUND);
+
+ cl_assert(giterr_last() != NULL);
+ cl_assert(giterr_last()->klass == GITERR_CONFIG);
+}
+
+void test_network_remote_remotes__error_when_no_push_available(void)
+{
+ git_remote *r;
+ git_transport *t;
+ git_push *p;
+
+ cl_git_pass(git_remote_create_anonymous(&r, _repo, cl_fixture("testrepo.git"), NULL));
+
+ cl_git_pass(git_transport_local(&t,r,NULL));
+
+ /* Make sure that push is really not available */
+ t->push = NULL;
+ cl_git_pass(git_remote_set_transport(r, t));
+
+ cl_git_pass(git_remote_connect(r, GIT_DIRECTION_PUSH));
+ cl_git_pass(git_push_new(&p, r));
+ cl_git_pass(git_push_add_refspec(p, "refs/heads/master"));
+ cl_git_fail_with(git_push_finish(p), GIT_ERROR);
+
+ git_push_free(p);
+ git_remote_free(r);
+}
+
+void test_network_remote_remotes__parsing_ssh_remote(void)
+{
+ cl_assert( git_remote_valid_url("git@github.com:libgit2/libgit2.git") );
+}
+
+void test_network_remote_remotes__parsing_local_path_fails_if_path_not_found(void)
+{
+ cl_assert( !git_remote_valid_url("/home/git/repos/libgit2.git") );
+}
+
+void test_network_remote_remotes__supported_transport_methods_are_supported(void)
+{
+ cl_assert( git_remote_supported_url("git://github.com/libgit2/libgit2") );
+}
+
+void test_network_remote_remotes__unsupported_transport_methods_are_unsupported(void)
+{
+#ifndef GIT_SSH
+ cl_assert( !git_remote_supported_url("git@github.com:libgit2/libgit2.git") );
+#endif
+}
+
+void test_network_remote_remotes__refspec_parsing(void)
+{
+ cl_assert_equal_s(git_refspec_src(_refspec), "refs/heads/*");
+ cl_assert_equal_s(git_refspec_dst(_refspec), "refs/remotes/test/*");
+}
+
+void test_network_remote_remotes__add_fetchspec(void)
+{
+ size_t size;
+
+ size = git_remote_refspec_count(_remote);
+
+ cl_git_pass(git_remote_add_fetch(_remote, "refs/*:refs/*"));
+
+ size++;
+ cl_assert_equal_i((int)size, (int)git_remote_refspec_count(_remote));
+
+ _refspec = git_remote_get_refspec(_remote, size - 1);
+ cl_assert_equal_s(git_refspec_src(_refspec), "refs/*");
+ cl_assert_equal_s(git_refspec_dst(_refspec), "refs/*");
+ cl_assert_equal_s(git_refspec_string(_refspec), "refs/*:refs/*");
+ cl_assert_equal_b(_refspec->push, false);
+}
+
+void test_network_remote_remotes__dup(void)
+{
+ git_strarray array;
+ git_remote *dup;
+
+ cl_git_pass(git_remote_dup(&dup, _remote));
+
+ cl_assert_equal_s(git_remote_name(dup), git_remote_name(_remote));
+ cl_assert_equal_s(git_remote_url(dup), git_remote_url(_remote));
+ cl_assert_equal_s(git_remote_pushurl(dup), git_remote_pushurl(_remote));
+
+ cl_git_pass(git_remote_get_fetch_refspecs(&array, _remote));
+ cl_assert_equal_i(1, (int)array.count);
+ cl_assert_equal_s("+refs/heads/*:refs/remotes/test/*", array.strings[0]);
+ git_strarray_free(&array);
+
+ cl_git_pass(git_remote_get_push_refspecs(&array, _remote));
+ cl_assert_equal_i(0, (int)array.count);
+ git_strarray_free(&array);
+
+ git_remote_free(dup);
+}
+
+void test_network_remote_remotes__add_pushspec(void)
+{
+ size_t size;
+
+ size = git_remote_refspec_count(_remote);
+
+ cl_git_pass(git_remote_add_push(_remote, "refs/*:refs/*"));
+ size++;
+ cl_assert_equal_i((int)size, (int)git_remote_refspec_count(_remote));
+
+ _refspec = git_remote_get_refspec(_remote, size - 1);
+ cl_assert_equal_s(git_refspec_src(_refspec), "refs/*");
+ cl_assert_equal_s(git_refspec_dst(_refspec), "refs/*");
+ cl_assert_equal_s(git_refspec_string(_refspec), "refs/*:refs/*");
+
+ cl_assert_equal_b(_refspec->push, true);
+}
+
+void test_network_remote_remotes__save(void)
+{
+ git_strarray array;
+ const char *fetch_refspec1 = "refs/heads/ns1/*:refs/remotes/upstream/ns1/*";
+ const char *fetch_refspec2 = "refs/heads/ns2/*:refs/remotes/upstream/ns2/*";
+ const char *push_refspec1 = "refs/heads/ns1/*:refs/heads/ns1/*";
+ const char *push_refspec2 = "refs/heads/ns2/*:refs/heads/ns2/*";
+
+ git_remote_free(_remote);
+ _remote = NULL;
+
+ /* Set up the remote and save it to config */
+ cl_git_pass(git_remote_create(&_remote, _repo, "upstream", "git://github.com/libgit2/libgit2"));
+ git_remote_clear_refspecs(_remote);
+
+ cl_git_pass(git_remote_add_fetch(_remote, fetch_refspec1));
+ cl_git_pass(git_remote_add_fetch(_remote, fetch_refspec2));
+ cl_git_pass(git_remote_add_push(_remote, push_refspec1));
+ cl_git_pass(git_remote_add_push(_remote, push_refspec2));
+ cl_git_pass(git_remote_set_pushurl(_remote, "git://github.com/libgit2/libgit2_push"));
+ cl_git_pass(git_remote_save(_remote));
+ git_remote_free(_remote);
+ _remote = NULL;
+
+ /* Load it from config and make sure everything matches */
+ cl_git_pass(git_remote_load(&_remote, _repo, "upstream"));
+
+ cl_git_pass(git_remote_get_fetch_refspecs(&array, _remote));
+ cl_assert_equal_i(2, (int)array.count);
+ cl_assert_equal_s(fetch_refspec1, array.strings[0]);
+ cl_assert_equal_s(fetch_refspec2, array.strings[1]);
+ git_strarray_free(&array);
+
+ cl_git_pass(git_remote_get_push_refspecs(&array, _remote));
+ cl_assert_equal_i(2, (int)array.count);
+ cl_assert_equal_s(push_refspec1, array.strings[0]);
+ cl_assert_equal_s(push_refspec2, array.strings[1]);
+ git_strarray_free(&array);
+
+ cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2");
+ cl_assert_equal_s(git_remote_pushurl(_remote), "git://github.com/libgit2/libgit2_push");
+
+ /* remove the pushurl again and see if we can save that too */
+ cl_git_pass(git_remote_set_pushurl(_remote, NULL));
+ cl_git_pass(git_remote_save(_remote));
+ git_remote_free(_remote);
+ _remote = NULL;
+
+ cl_git_pass(git_remote_load(&_remote, _repo, "upstream"));
+ cl_assert(git_remote_pushurl(_remote) == NULL);
+}
+
+void test_network_remote_remotes__fnmatch(void)
+{
+ cl_assert(git_refspec_src_matches(_refspec, "refs/heads/master"));
+ cl_assert(git_refspec_src_matches(_refspec, "refs/heads/multi/level/branch"));
+}
+
+void test_network_remote_remotes__transform(void)
+{
+ git_buf ref = GIT_BUF_INIT;
+
+ cl_git_pass(git_refspec_transform(&ref, _refspec, "refs/heads/master"));
+ cl_assert_equal_s(ref.ptr, "refs/remotes/test/master");
+ git_buf_free(&ref);
+}
+
+void test_network_remote_remotes__transform_destination_to_source(void)
+{
+ git_buf ref = GIT_BUF_INIT;
+
+ cl_git_pass(git_refspec_rtransform(&ref, _refspec, "refs/remotes/test/master"));
+ cl_assert_equal_s(ref.ptr, "refs/heads/master");
+ git_buf_free(&ref);
+}
+
+void test_network_remote_remotes__missing_refspecs(void)
+{
+ git_config *cfg;
+
+ git_remote_free(_remote);
+ _remote = NULL;
+
+ 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_load(&_remote, _repo, "specless"));
+
+ git_config_free(cfg);
+}
+
+void test_network_remote_remotes__list(void)
+{
+ git_strarray list;
+ git_config *cfg;
+
+ cl_git_pass(git_remote_list(&list, _repo));
+ cl_assert(list.count == 5);
+ git_strarray_free(&list);
+
+ cl_git_pass(git_repository_config(&cfg, _repo));
+
+ /* Create a new remote */
+ cl_git_pass(git_config_set_string(cfg, "remote.specless.url", "http://example.com"));
+
+ /* Update a remote (previously without any url/pushurl entry) */
+ cl_git_pass(git_config_set_string(cfg, "remote.no-remote-url.pushurl", "http://example.com"));
+
+ cl_git_pass(git_remote_list(&list, _repo));
+ cl_assert(list.count == 7);
+ git_strarray_free(&list);
+
+ git_config_free(cfg);
+}
+
+void test_network_remote_remotes__loading_a_missing_remote_returns_ENOTFOUND(void)
+{
+ git_remote_free(_remote);
+ _remote = NULL;
+
+ cl_assert_equal_i(GIT_ENOTFOUND, git_remote_load(&_remote, _repo, "just-left-few-minutes-ago"));
+}
+
+void test_network_remote_remotes__loading_with_an_invalid_name_returns_EINVALIDSPEC(void)
+{
+ git_remote_free(_remote);
+ _remote = NULL;
+
+ cl_assert_equal_i(GIT_EINVALIDSPEC, git_remote_load(&_remote, _repo, "Inv@{id"));
+}
+
+/*
+ * $ git remote add addtest http://github.com/libgit2/libgit2
+ *
+ * $ cat .git/config
+ * [...]
+ * [remote "addtest"]
+ * url = http://github.com/libgit2/libgit2
+ * fetch = +refs/heads/\*:refs/remotes/addtest/\*
+ */
+void test_network_remote_remotes__add(void)
+{
+ git_remote_free(_remote);
+ _remote = NULL;
+
+ cl_git_pass(git_remote_create(&_remote, _repo, "addtest", "http://github.com/libgit2/libgit2"));
+ cl_assert_equal_i(GIT_REMOTE_DOWNLOAD_TAGS_AUTO, git_remote_autotag(_remote));
+
+ git_remote_free(_remote);
+ _remote = NULL;
+
+ cl_git_pass(git_remote_load(&_remote, _repo, "addtest"));
+ cl_assert_equal_i(GIT_REMOTE_DOWNLOAD_TAGS_AUTO, git_remote_autotag(_remote));
+
+ _refspec = git_vector_get(&_remote->refspecs, 0);
+ cl_assert_equal_s("refs/heads/*", git_refspec_src(_refspec));
+ cl_assert(git_refspec_force(_refspec) == 1);
+ cl_assert_equal_s("refs/remotes/addtest/*", git_refspec_dst(_refspec));
+ cl_assert_equal_s(git_remote_url(_remote), "http://github.com/libgit2/libgit2");
+}
+
+void test_network_remote_remotes__cannot_add_a_nameless_remote(void)
+{
+ git_remote *remote;
+
+ cl_assert_equal_i(
+ GIT_EINVALIDSPEC,
+ git_remote_create(&remote, _repo, NULL, "git://github.com/libgit2/libgit2"));
+}
+
+void test_network_remote_remotes__cannot_save_an_inmemory_remote(void)
+{
+ git_remote *remote;
+
+ cl_git_pass(git_remote_create_anonymous(&remote, _repo, "git://github.com/libgit2/libgit2", NULL));
+
+ cl_assert_equal_p(NULL, git_remote_name(remote));
+
+ cl_git_fail(git_remote_save(remote));
+ git_remote_free(remote);
+}
+
+void test_network_remote_remotes__cannot_add_a_remote_with_an_invalid_name(void)
+{
+ git_remote *remote = NULL;
+
+ cl_assert_equal_i(
+ GIT_EINVALIDSPEC,
+ git_remote_create(&remote, _repo, "Inv@{id", "git://github.com/libgit2/libgit2"));
+ cl_assert_equal_p(remote, NULL);
+
+ cl_assert_equal_i(
+ GIT_EINVALIDSPEC,
+ git_remote_create(&remote, _repo, "", "git://github.com/libgit2/libgit2"));
+ cl_assert_equal_p(remote, NULL);
+}
+
+void test_network_remote_remotes__tagopt(void)
+{
+ const char *opt;
+ git_config *cfg;
+
+ cl_git_pass(git_repository_config(&cfg, _repo));
+
+ git_remote_set_autotag(_remote, GIT_REMOTE_DOWNLOAD_TAGS_ALL);
+ cl_git_pass(git_remote_save(_remote));
+ cl_git_pass(git_config_get_string(&opt, cfg, "remote.test.tagopt"));
+ cl_assert_equal_s("--tags", opt);
+
+ git_remote_set_autotag(_remote, GIT_REMOTE_DOWNLOAD_TAGS_NONE);
+ cl_git_pass(git_remote_save(_remote));
+ cl_git_pass(git_config_get_string(&opt, cfg, "remote.test.tagopt"));
+ cl_assert_equal_s("--no-tags", opt);
+
+ git_remote_set_autotag(_remote, GIT_REMOTE_DOWNLOAD_TAGS_AUTO);
+ cl_git_pass(git_remote_save(_remote));
+ cl_assert(git_config_get_string(&opt, cfg, "remote.test.tagopt") == GIT_ENOTFOUND);
+
+ git_config_free(cfg);
+}
+
+void test_network_remote_remotes__can_load_with_an_empty_url(void)
+{
+ git_remote *remote = NULL;
+
+ cl_git_pass(git_remote_load(&remote, _repo, "empty-remote-url"));
+
+ cl_assert(remote->url == NULL);
+ cl_assert(remote->pushurl == NULL);
+
+ cl_git_fail(git_remote_connect(remote, GIT_DIRECTION_FETCH));
+
+ cl_assert(giterr_last() != NULL);
+ cl_assert(giterr_last()->klass == GITERR_INVALID);
+
+ git_remote_free(remote);
+}
+
+void test_network_remote_remotes__can_load_with_only_an_empty_pushurl(void)
+{
+ git_remote *remote = NULL;
+
+ cl_git_pass(git_remote_load(&remote, _repo, "empty-remote-pushurl"));
+
+ cl_assert(remote->url == NULL);
+ cl_assert(remote->pushurl == NULL);
+
+ cl_git_fail(git_remote_connect(remote, GIT_DIRECTION_FETCH));
+
+ git_remote_free(remote);
+}
+
+void test_network_remote_remotes__returns_ENOTFOUND_when_neither_url_nor_pushurl(void)
+{
+ git_remote *remote = NULL;
+
+ cl_git_fail_with(
+ git_remote_load(&remote, _repo, "no-remote-url"), GIT_ENOTFOUND);
+}
+
+void test_network_remote_remotes__check_structure_version(void)
+{
+ git_transport transport = GIT_TRANSPORT_INIT;
+ const git_error *err;
+
+ git_remote_free(_remote);
+ _remote = NULL;
+ cl_git_pass(git_remote_create_anonymous(&_remote, _repo, "test-protocol://localhost", NULL));
+
+ transport.version = 0;
+ cl_git_fail(git_remote_set_transport(_remote, &transport));
+ err = giterr_last();
+ cl_assert_equal_i(GITERR_INVALID, err->klass);
+
+ giterr_clear();
+ transport.version = 1024;
+ cl_git_fail(git_remote_set_transport(_remote, &transport));
+ err = giterr_last();
+ cl_assert_equal_i(GITERR_INVALID, err->klass);
+}
+
+void assert_cannot_create_remote(const char *name, int expected_error)
+{
+ git_remote *remote = NULL;
+
+ cl_git_fail_with(
+ git_remote_create(&remote, _repo, name, "git://github.com/libgit2/libgit2"),
+ expected_error);
+
+ cl_assert_equal_p(remote, NULL);
+}
+
+void test_network_remote_remotes__cannot_create_a_remote_which_name_conflicts_with_an_existing_remote(void)
+{
+ assert_cannot_create_remote("test", GIT_EEXISTS);
+}
+
+void test_network_remote_remotes__cannot_create_a_remote_which_name_is_invalid(void)
+{
+ assert_cannot_create_remote("/", GIT_EINVALIDSPEC);
+ assert_cannot_create_remote("//", GIT_EINVALIDSPEC);
+ assert_cannot_create_remote(".lock", GIT_EINVALIDSPEC);
+ assert_cannot_create_remote("a.lock", GIT_EINVALIDSPEC);
+}
+
+void test_network_remote_remote__git_remote_create_with_fetchspec(void)
+{
+ git_remote *remote;
+ git_strarray array;
+
+ cl_git_pass(git_remote_create_with_fetchspec(&remote, _repo, "test-new", "git://github.com/libgit2/libgit2", "+refs/*:refs/*"));
+ git_remote_get_fetch_refspecs(&array, remote);
+ cl_assert_equal_s("+refs/*:refs/*", array.strings[0]);
+ git_remote_free(remote);
+}
+
+static const char *fetch_refspecs[] = {
+ "+refs/heads/*:refs/remotes/origin/*",
+ "refs/tags/*:refs/tags/*",
+ "+refs/pull/*:refs/pull/*",
+};
+
+static const char *push_refspecs[] = {
+ "refs/heads/*:refs/heads/*",
+ "refs/tags/*:refs/tags/*",
+ "refs/notes/*:refs/notes/*",
+};
+
+void test_network_remote_remotes__query_refspecs(void)
+{
+ git_remote *remote;
+ git_strarray array;
+ int i;
+
+ cl_git_pass(git_remote_create_anonymous(&remote, _repo, "git://github.com/libgit2/libgit2", NULL));
+
+ for (i = 0; i < 3; i++) {
+ cl_git_pass(git_remote_add_fetch(remote, fetch_refspecs[i]));
+ cl_git_pass(git_remote_add_push(remote, push_refspecs[i]));
+ }
+
+ cl_git_pass(git_remote_get_fetch_refspecs(&array, remote));
+ for (i = 0; i < 3; i++) {
+ cl_assert_equal_s(fetch_refspecs[i], array.strings[i]);
+ }
+ git_strarray_free(&array);
+
+ cl_git_pass(git_remote_get_push_refspecs(&array, remote));
+ for (i = 0; i < 3; i++) {
+ cl_assert_equal_s(push_refspecs[i], array.strings[i]);
+ }
+ git_strarray_free(&array);
+
+ git_remote_free(remote);
+}
diff --git a/tests/network/remote/rename.c b/tests/network/remote/rename.c
new file mode 100644
index 000000000..1b819a445
--- /dev/null
+++ b/tests/network/remote/rename.c
@@ -0,0 +1,268 @@
+#include "clar_libgit2.h"
+#include "config/config_helpers.h"
+
+#include "repository.h"
+
+static git_remote *_remote;
+static git_repository *_repo;
+
+void test_network_remote_rename__initialize(void)
+{
+ _repo = cl_git_sandbox_init("testrepo.git");
+
+ cl_git_pass(git_remote_load(&_remote, _repo, "test"));
+}
+
+void test_network_remote_rename__cleanup(void)
+{
+ git_remote_free(_remote);
+ _remote = NULL;
+
+ 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_remote_rename__renaming_a_remote_moves_related_configuration_section(void)
+{
+ git_strarray problems = {0};
+
+ 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(&problems, _remote, "just/renamed"));
+ cl_assert_equal_i(0, problems.count);
+ git_strarray_free(&problems);
+
+ assert_config_entry_existence(_repo, "remote.test.fetch", false);
+ assert_config_entry_existence(_repo, "remote.just/renamed.fetch", true);
+}
+
+void test_network_remote_rename__renaming_a_remote_updates_branch_related_configuration_entries(void)
+{
+ git_strarray problems = {0};
+
+ assert_config_entry_value(_repo, "branch.master.remote", "test");
+
+ cl_git_pass(git_remote_rename(&problems, _remote, "just/renamed"));
+ cl_assert_equal_i(0, problems.count);
+ git_strarray_free(&problems);
+
+ assert_config_entry_value(_repo, "branch.master.remote", "just/renamed");
+}
+
+void test_network_remote_rename__renaming_a_remote_updates_default_fetchrefspec(void)
+{
+ git_strarray problems = {0};
+
+ cl_git_pass(git_remote_rename(&problems, _remote, "just/renamed"));
+ cl_assert_equal_i(0, problems.count);
+ git_strarray_free(&problems);
+
+ assert_config_entry_value(_repo, "remote.just/renamed.fetch", "+refs/heads/*:refs/remotes/just/renamed/*");
+}
+
+void test_network_remote_rename__renaming_a_remote_without_a_fetchrefspec_doesnt_create_one(void)
+{
+ git_config *config;
+ git_strarray problems = {0};
+
+ git_remote_free(_remote);
+ cl_git_pass(git_repository_config__weakptr(&config, _repo));
+ cl_git_pass(git_config_delete_entry(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(&problems, _remote, "just/renamed"));
+ cl_assert_equal_i(0, problems.count);
+ git_strarray_free(&problems);
+
+ assert_config_entry_existence(_repo, "remote.just/renamed.fetch", false);
+}
+
+void test_network_remote_rename__renaming_a_remote_notifies_of_non_default_fetchrefspec(void)
+{
+ git_config *config;
+
+ git_strarray problems = {0};
+
+ 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(&problems, _remote, "just/renamed"));
+ cl_assert_equal_i(1, problems.count);
+ cl_assert_equal_s("+refs/*:refs/*", problems.strings[0]);
+ git_strarray_free(&problems);
+
+ assert_config_entry_value(_repo, "remote.just/renamed.fetch", "+refs/*:refs/*");
+
+ git_strarray_free(&problems);
+}
+
+void test_network_remote_rename__new_name_can_contain_dots(void)
+{
+ git_strarray problems = {0};
+
+ cl_git_pass(git_remote_rename(&problems, _remote, "just.renamed"));
+ cl_assert_equal_i(0, problems.count);
+ git_strarray_free(&problems);
+ cl_assert_equal_s("just.renamed", git_remote_name(_remote));
+}
+
+void test_network_remote_rename__new_name_must_conform_to_reference_naming_conventions(void)
+{
+ git_strarray problems = {0};
+
+ cl_assert_equal_i(
+ GIT_EINVALIDSPEC,
+ git_remote_rename(&problems, _remote, "new@{name"));
+}
+
+void test_network_remote_rename__renamed_name_is_persisted(void)
+{
+ git_remote *renamed;
+ git_repository *another_repo;
+ git_strarray problems = {0};
+
+ cl_git_fail(git_remote_load(&renamed, _repo, "just/renamed"));
+
+ cl_git_pass(git_remote_rename(&problems, _remote, "just/renamed"));
+ cl_assert_equal_i(0, problems.count);
+ git_strarray_free(&problems);
+
+ 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_remote_rename__cannot_overwrite_an_existing_remote(void)
+{
+ git_strarray problems = {0};
+
+ cl_assert_equal_i(GIT_EEXISTS, git_remote_rename(&problems, _remote, "test"));
+ cl_assert_equal_i(GIT_EEXISTS, git_remote_rename(&problems, _remote, "test_with_pushurl"));
+}
+
+void test_network_remote_rename__renaming_a_remote_moves_the_underlying_reference(void)
+{
+ git_reference *underlying;
+ git_strarray problems = {0};
+
+ 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(&problems, _remote, "just/renamed"));
+ cl_assert_equal_i(0, problems.count);
+ git_strarray_free(&problems);
+
+ 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);
+}
+
+void test_network_remote_rename__cannot_rename_an_inmemory_remote(void)
+{
+ git_remote *remote;
+ git_strarray problems = {0};
+
+ cl_git_pass(git_remote_create_anonymous(&remote, _repo, "file:///blah", NULL));
+ cl_git_fail(git_remote_rename(&problems, remote, "newname"));
+
+ git_strarray_free(&problems);
+ git_remote_free(remote);
+}
+
+void test_network_remote_rename__overwrite_ref_in_target(void)
+{
+ git_oid id;
+ char idstr[GIT_OID_HEXSZ + 1] = {0};
+ git_remote *remote;
+ git_reference *ref;
+ git_branch_t btype;
+ git_branch_iterator *iter;
+ git_strarray problems = {0};
+
+ cl_git_pass(git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"));
+ cl_git_pass(git_reference_create(&ref, _repo, "refs/remotes/renamed/master", &id, 1, NULL, NULL));
+ git_reference_free(ref);
+
+ cl_git_pass(git_remote_load(&remote, _repo, "test"));
+ cl_git_pass(git_remote_rename(&problems, remote, "renamed"));
+ git_remote_free(remote);
+ cl_assert_equal_i(0, problems.count);
+ git_strarray_free(&problems);
+
+ /* make sure there's only one remote-tracking branch */
+ cl_git_pass(git_branch_iterator_new(&iter, _repo, GIT_BRANCH_REMOTE));
+ cl_git_pass(git_branch_next(&ref, &btype, iter));
+ cl_assert_equal_s("refs/remotes/renamed/master", git_reference_name(ref));
+ git_oid_fmt(idstr, git_reference_target(ref));
+ cl_assert_equal_s("be3563ae3f795b2b4353bcce3a527ad0a4f7f644", idstr);
+ git_reference_free(ref);
+
+ cl_git_fail_with(GIT_ITEROVER, git_branch_next(&ref, &btype, iter));
+ git_branch_iterator_free(iter);
+}
+
+void test_network_remote_rename__symref_head(void)
+{
+ int error;
+ git_remote *remote;
+ git_reference *ref;
+ git_branch_t btype;
+ git_branch_iterator *iter;
+ git_strarray problems = {0};
+ char idstr[GIT_OID_HEXSZ + 1] = {0};
+ git_vector refs;
+
+ cl_git_pass(git_reference_symbolic_create(&ref, _repo, "refs/remotes/test/HEAD", "refs/remotes/test/master", 0, NULL, NULL));
+ git_reference_free(ref);
+
+ cl_git_pass(git_remote_load(&remote, _repo, "test"));
+ cl_git_pass(git_remote_rename(&problems, remote, "renamed"));
+ git_remote_free(remote);
+ cl_assert_equal_i(0, problems.count);
+ git_strarray_free(&problems);
+
+ cl_git_pass(git_vector_init(&refs, 2, (git_vector_cmp) git_reference_cmp));
+ cl_git_pass(git_branch_iterator_new(&iter, _repo, GIT_BRANCH_REMOTE));
+
+ while ((error = git_branch_next(&ref, &btype, iter)) == 0) {
+ cl_git_pass(git_vector_insert(&refs, ref));
+ }
+ cl_assert_equal_i(GIT_ITEROVER, error);
+ git_vector_sort(&refs);
+
+ cl_assert_equal_i(2, refs.length);
+
+ ref = git_vector_get(&refs, 0);
+ cl_assert_equal_s("refs/remotes/renamed/HEAD", git_reference_name(ref));
+ cl_assert_equal_s("refs/remotes/renamed/master", git_reference_symbolic_target(ref));
+ git_reference_free(ref);
+
+ ref = git_vector_get(&refs, 1);
+ cl_assert_equal_s("refs/remotes/renamed/master", git_reference_name(ref));
+ git_oid_fmt(idstr, git_reference_target(ref));
+ cl_assert_equal_s("be3563ae3f795b2b4353bcce3a527ad0a4f7f644", idstr);
+ git_reference_free(ref);
+
+ git_vector_free(&refs);
+
+ cl_git_fail_with(GIT_ITEROVER, git_branch_next(&ref, &btype, iter));
+ git_branch_iterator_free(iter);
+}
diff --git a/tests/network/urlparse.c b/tests/network/urlparse.c
new file mode 100644
index 000000000..2a9c2f69f
--- /dev/null
+++ b/tests/network/urlparse.c
@@ -0,0 +1,193 @@
+#include "clar_libgit2.h"
+#include "netops.h"
+
+static char *host, *port, *path, *user, *pass;
+static gitno_connection_data conndata;
+
+void test_network_urlparse__initialize(void)
+{
+ host = port = path = user = pass = NULL;
+ memset(&conndata, 0, sizeof(conndata));
+}
+
+void test_network_urlparse__cleanup(void)
+{
+#define FREE_AND_NULL(x) if (x) { git__free(x); x = NULL; }
+ FREE_AND_NULL(host);
+ FREE_AND_NULL(port);
+ FREE_AND_NULL(path);
+ FREE_AND_NULL(user);
+ FREE_AND_NULL(pass);
+
+ gitno_connection_data_free_ptrs(&conndata);
+}
+
+void test_network_urlparse__trivial(void)
+{
+ cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass,
+ "http://example.com/resource", "8080"));
+ cl_assert_equal_s(host, "example.com");
+ cl_assert_equal_s(port, "8080");
+ cl_assert_equal_s(path, "/resource");
+ cl_assert_equal_p(user, NULL);
+ cl_assert_equal_p(pass, NULL);
+}
+
+void test_network_urlparse__encoded_password(void)
+{
+ cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass,
+ "https://user:pass%2fis%40bad@hostname.com:1234/", "1"));
+ cl_assert_equal_s(host, "hostname.com");
+ cl_assert_equal_s(port, "1234");
+ cl_assert_equal_s(path, "/");
+ cl_assert_equal_s(user, "user");
+ cl_assert_equal_s(pass, "pass/is@bad");
+}
+
+void test_network_urlparse__user(void)
+{
+ cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass,
+ "https://user@example.com/resource", "8080"));
+ cl_assert_equal_s(host, "example.com");
+ cl_assert_equal_s(port, "8080");
+ cl_assert_equal_s(path, "/resource");
+ cl_assert_equal_s(user, "user");
+ cl_assert_equal_p(pass, NULL);
+}
+
+void test_network_urlparse__user_pass(void)
+{
+ /* user:pass@hostname.tld/resource */
+ cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass,
+ "https://user:pass@example.com/resource", "8080"));
+ cl_assert_equal_s(host, "example.com");
+ cl_assert_equal_s(port, "8080");
+ cl_assert_equal_s(path, "/resource");
+ cl_assert_equal_s(user, "user");
+ cl_assert_equal_s(pass, "pass");
+}
+
+void test_network_urlparse__port(void)
+{
+ /* hostname.tld:port/resource */
+ cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass,
+ "https://example.com:9191/resource", "8080"));
+ cl_assert_equal_s(host, "example.com");
+ cl_assert_equal_s(port, "9191");
+ cl_assert_equal_s(path, "/resource");
+ cl_assert_equal_p(user, NULL);
+ cl_assert_equal_p(pass, NULL);
+}
+
+void test_network_urlparse__user_port(void)
+{
+ /* user@hostname.tld:port/resource */
+ cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass,
+ "https://user@example.com:9191/resource", "8080"));
+ cl_assert_equal_s(host, "example.com");
+ cl_assert_equal_s(port, "9191");
+ cl_assert_equal_s(path, "/resource");
+ cl_assert_equal_s(user, "user");
+ cl_assert_equal_p(pass, NULL);
+}
+
+void test_network_urlparse__user_pass_port(void)
+{
+ /* user:pass@hostname.tld:port/resource */
+ cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass,
+ "https://user:pass@example.com:9191/resource", "8080"));
+ cl_assert_equal_s(host, "example.com");
+ cl_assert_equal_s(port, "9191");
+ cl_assert_equal_s(path, "/resource");
+ cl_assert_equal_s(user, "user");
+ cl_assert_equal_s(pass, "pass");
+}
+
+void test_network_urlparse__connection_data_http(void)
+{
+ cl_git_pass(gitno_connection_data_from_url(&conndata,
+ "http://example.com/foo/bar/baz", "bar/baz"));
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/foo/");
+ cl_assert_equal_p(conndata.user, NULL);
+ cl_assert_equal_p(conndata.pass, NULL);
+ cl_assert_equal_i(conndata.use_ssl, false);
+}
+
+void test_network_urlparse__connection_data_ssl(void)
+{
+ cl_git_pass(gitno_connection_data_from_url(&conndata,
+ "https://example.com/foo/bar/baz", "bar/baz"));
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "443");
+ cl_assert_equal_s(conndata.path, "/foo/");
+ cl_assert_equal_p(conndata.user, NULL);
+ cl_assert_equal_p(conndata.pass, NULL);
+ cl_assert_equal_i(conndata.use_ssl, true);
+}
+
+void test_network_urlparse__encoded_username_password(void)
+{
+ cl_git_pass(gitno_connection_data_from_url(&conndata,
+ "https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz", "bar/baz"));
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "443");
+ cl_assert_equal_s(conndata.path, "/foo/");
+ cl_assert_equal_s(conndata.user, "user/name");
+ cl_assert_equal_s(conndata.pass, "pass@word%zyx%v");
+ cl_assert_equal_i(conndata.use_ssl, true);
+}
+
+void test_network_urlparse__connection_data_cross_host_redirect(void)
+{
+ conndata.host = git__strdup("bar.com");
+ cl_git_fail_with(gitno_connection_data_from_url(&conndata,
+ "https://foo.com/bar/baz", NULL),
+ -1);
+}
+
+void test_network_urlparse__connection_data_http_downgrade(void)
+{
+ conndata.use_ssl = true;
+ cl_git_fail_with(gitno_connection_data_from_url(&conndata,
+ "http://foo.com/bar/baz", NULL),
+ -1);
+}
+
+void test_network_urlparse__connection_data_relative_redirect(void)
+{
+ cl_git_pass(gitno_connection_data_from_url(&conndata,
+ "http://foo.com/bar/baz/biff", NULL));
+ cl_git_pass(gitno_connection_data_from_url(&conndata,
+ "/zap/baz/biff?bam", NULL));
+ cl_assert_equal_s(conndata.host, "foo.com");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam");
+ cl_assert_equal_p(conndata.user, NULL);
+ cl_assert_equal_p(conndata.pass, NULL);
+ cl_assert_equal_i(conndata.use_ssl, false);
+}
+
+void test_network_urlparse__connection_data_relative_redirect_ssl(void)
+{
+ cl_git_pass(gitno_connection_data_from_url(&conndata,
+ "https://foo.com/bar/baz/biff", NULL));
+ cl_git_pass(gitno_connection_data_from_url(&conndata,
+ "/zap/baz/biff?bam", NULL));
+ cl_assert_equal_s(conndata.host, "foo.com");
+ cl_assert_equal_s(conndata.port, "443");
+ cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam");
+ cl_assert_equal_p(conndata.user, NULL);
+ cl_assert_equal_p(conndata.pass, NULL);
+ cl_assert_equal_i(conndata.use_ssl, true);
+}
+
+/* Run this under valgrind */
+void test_network_urlparse__connection_data_cleanup(void)
+{
+ cl_git_pass(gitno_connection_data_from_url(&conndata,
+ "http://foo.com/bar/baz/biff", "baz/biff"));
+ cl_git_pass(gitno_connection_data_from_url(&conndata,
+ "https://foo.com/bar/baz/biff", "baz/biff"));
+}
diff --git a/tests/notes/notes.c b/tests/notes/notes.c
new file mode 100644
index 000000000..e48d9df0e
--- /dev/null
+++ b/tests/notes/notes.c
@@ -0,0 +1,387 @@
+#include "clar_libgit2.h"
+
+static git_repository *_repo;
+static git_signature *_sig;
+
+void test_notes_notes__initialize(void)
+{
+ _repo = cl_git_sandbox_init("testrepo.git");
+ cl_git_pass(git_signature_now(&_sig, "alice", "alice@example.com"));
+}
+
+void test_notes_notes__cleanup(void)
+{
+ git_signature_free(_sig);
+ _sig = NULL;
+
+ cl_git_sandbox_cleanup();
+}
+
+static void assert_note_equal(git_note *note, char *message, git_oid *note_oid) {
+ git_blob *blob;
+
+ cl_assert_equal_s(git_note_message(note), message);
+ cl_assert(!git_oid_cmp(git_note_id(note), note_oid));
+
+ cl_git_pass(git_blob_lookup(&blob, _repo, note_oid));
+ cl_assert_equal_s(git_note_message(note), (const char *)git_blob_rawcontent(blob));
+
+ git_blob_free(blob);
+}
+
+static void create_note(git_oid *note_oid, const char *canonical_namespace, const char *target_sha, const char *message)
+{
+ git_oid oid;
+
+ cl_git_pass(git_oid_fromstr(&oid, target_sha));
+ cl_git_pass(git_note_create(note_oid, _repo, _sig, _sig, canonical_namespace, &oid, message, 0));
+}
+
+static struct {
+ const char *note_sha;
+ const char *annotated_object_sha;
+}
+list_expectations[] = {
+ { "1c73b1f51762155d357bcd1fd4f2c409ef80065b", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045" },
+ { "1c73b1f51762155d357bcd1fd4f2c409ef80065b", "9fd738e8f7967c078dceed8190330fc8648ee56a" },
+ { "257b43746b6b46caa4aa788376c647cce0a33e2b", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750" },
+ { "1ec1c8e03f461f4f5d3f3702172483662e7223f3", "c47800c7266a2be04c571c04d5a6614691ea99bd" },
+ { NULL, NULL }
+};
+
+#define EXPECTATIONS_COUNT (sizeof(list_expectations)/sizeof(list_expectations[0])) - 1
+
+static int note_list_cb(
+ const git_oid *blob_id, const git_oid *annotated_obj_id, void *payload)
+{
+ git_oid expected_note_oid, expected_target_oid;
+
+ unsigned int *count = (unsigned int *)payload;
+
+ cl_assert(*count < EXPECTATIONS_COUNT);
+
+ cl_git_pass(git_oid_fromstr(&expected_note_oid, list_expectations[*count].note_sha));
+ cl_assert(git_oid_cmp(&expected_note_oid, blob_id) == 0);
+
+ cl_git_pass(git_oid_fromstr(&expected_target_oid, list_expectations[*count].annotated_object_sha));
+ cl_assert(git_oid_cmp(&expected_target_oid, annotated_obj_id) == 0);
+
+ (*count)++;
+
+ return 0;
+}
+
+/*
+ * $ git notes --ref i-can-see-dead-notes add -m "I decorate a65f" a65fedf39aefe402d3bb6e24df4d4f5fe4547750
+ * $ git notes --ref i-can-see-dead-notes add -m "I decorate c478" c47800c7266a2be04c571c04d5a6614691ea99bd
+ * $ git notes --ref i-can-see-dead-notes add -m "I decorate 9fd7 and 4a20" 9fd738e8f7967c078dceed8190330fc8648ee56a
+ * $ git notes --ref i-can-see-dead-notes add -m "I decorate 9fd7 and 4a20" 4a202b346bb0fb0db7eff3cffeb3c70babbd2045
+ *
+ * $ git notes --ref i-can-see-dead-notes list
+ * 1c73b1f51762155d357bcd1fd4f2c409ef80065b 4a202b346bb0fb0db7eff3cffeb3c70babbd2045
+ * 1c73b1f51762155d357bcd1fd4f2c409ef80065b 9fd738e8f7967c078dceed8190330fc8648ee56a
+ * 257b43746b6b46caa4aa788376c647cce0a33e2b a65fedf39aefe402d3bb6e24df4d4f5fe4547750
+ * 1ec1c8e03f461f4f5d3f3702172483662e7223f3 c47800c7266a2be04c571c04d5a6614691ea99bd
+ *
+ * $ git ls-tree refs/notes/i-can-see-dead-notes
+ * 100644 blob 1c73b1f51762155d357bcd1fd4f2c409ef80065b 4a202b346bb0fb0db7eff3cffeb3c70babbd2045
+ * 100644 blob 1c73b1f51762155d357bcd1fd4f2c409ef80065b 9fd738e8f7967c078dceed8190330fc8648ee56a
+ * 100644 blob 257b43746b6b46caa4aa788376c647cce0a33e2b a65fedf39aefe402d3bb6e24df4d4f5fe4547750
+ * 100644 blob 1ec1c8e03f461f4f5d3f3702172483662e7223f3 c47800c7266a2be04c571c04d5a6614691ea99bd
+*/
+void test_notes_notes__can_retrieve_a_list_of_notes_for_a_given_namespace(void)
+{
+ git_oid note_oid1, note_oid2, note_oid3, note_oid4;
+ unsigned int retrieved_notes = 0;
+
+ create_note(&note_oid1, "refs/notes/i-can-see-dead-notes", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", "I decorate a65f\n");
+ create_note(&note_oid2, "refs/notes/i-can-see-dead-notes", "c47800c7266a2be04c571c04d5a6614691ea99bd", "I decorate c478\n");
+ create_note(&note_oid3, "refs/notes/i-can-see-dead-notes", "9fd738e8f7967c078dceed8190330fc8648ee56a", "I decorate 9fd7 and 4a20\n");
+ create_note(&note_oid4, "refs/notes/i-can-see-dead-notes", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", "I decorate 9fd7 and 4a20\n");
+
+ cl_git_pass(git_note_foreach
+(_repo, "refs/notes/i-can-see-dead-notes", note_list_cb, &retrieved_notes));
+
+ cl_assert_equal_i(4, retrieved_notes);
+}
+
+static int note_cancel_cb(
+ const git_oid *blob_id, const git_oid *annotated_obj_id, void *payload)
+{
+ unsigned int *count = (unsigned int *)payload;
+
+ GIT_UNUSED(blob_id);
+ GIT_UNUSED(annotated_obj_id);
+
+ (*count)++;
+
+ return (*count > 2);
+}
+
+void test_notes_notes__can_cancel_foreach(void)
+{
+ git_oid note_oid1, note_oid2, note_oid3, note_oid4;
+ unsigned int retrieved_notes = 0;
+
+ create_note(&note_oid1, "refs/notes/i-can-see-dead-notes", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", "I decorate a65f\n");
+ create_note(&note_oid2, "refs/notes/i-can-see-dead-notes", "c47800c7266a2be04c571c04d5a6614691ea99bd", "I decorate c478\n");
+ create_note(&note_oid3, "refs/notes/i-can-see-dead-notes", "9fd738e8f7967c078dceed8190330fc8648ee56a", "I decorate 9fd7 and 4a20\n");
+ create_note(&note_oid4, "refs/notes/i-can-see-dead-notes", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", "I decorate 9fd7 and 4a20\n");
+
+ cl_assert_equal_i(
+ 1,
+ git_note_foreach(_repo, "refs/notes/i-can-see-dead-notes",
+ note_cancel_cb, &retrieved_notes));
+}
+
+void test_notes_notes__retrieving_a_list_of_notes_for_an_unknown_namespace_returns_ENOTFOUND(void)
+{
+ int error;
+ unsigned int retrieved_notes = 0;
+
+ error = git_note_foreach(_repo, "refs/notes/i-am-not", note_list_cb, &retrieved_notes);
+ cl_git_fail(error);
+ cl_assert_equal_i(GIT_ENOTFOUND, error);
+
+ cl_assert_equal_i(0, retrieved_notes);
+}
+
+void test_notes_notes__inserting_a_note_without_passing_a_namespace_uses_the_default_namespace(void)
+{
+ git_oid note_oid, target_oid;
+ git_note *note, *default_namespace_note;
+ const char *default_ref;
+
+ cl_git_pass(git_oid_fromstr(&target_oid, "08b041783f40edfe12bb406c9c9a8a040177c125"));
+ cl_git_pass(git_note_default_ref(&default_ref, _repo));
+
+ create_note(&note_oid, NULL, "08b041783f40edfe12bb406c9c9a8a040177c125", "hello world\n");
+
+ cl_git_pass(git_note_read(&note, _repo, NULL, &target_oid));
+ cl_git_pass(git_note_read(&default_namespace_note, _repo, default_ref, &target_oid));
+
+ assert_note_equal(note, "hello world\n", &note_oid);
+ assert_note_equal(default_namespace_note, "hello world\n", &note_oid);
+
+ git_note_free(note);
+ git_note_free(default_namespace_note);
+}
+
+void test_notes_notes__can_insert_a_note_with_a_custom_namespace(void)
+{
+ git_oid note_oid, target_oid;
+ git_note *note;
+
+ cl_git_pass(git_oid_fromstr(&target_oid, "08b041783f40edfe12bb406c9c9a8a040177c125"));
+
+ create_note(&note_oid, "refs/notes/some/namespace", "08b041783f40edfe12bb406c9c9a8a040177c125", "hello world on a custom namespace\n");
+
+ cl_git_pass(git_note_read(&note, _repo, "refs/notes/some/namespace", &target_oid));
+
+ assert_note_equal(note, "hello world on a custom namespace\n", &note_oid);
+
+ git_note_free(note);
+}
+
+/*
+ * $ git notes --ref fanout list 8496071c1b46c854b31185ea97743be6a8774479
+ * 08b041783f40edfe12bb406c9c9a8a040177c125
+ */
+void test_notes_notes__creating_a_note_on_a_target_which_already_has_one_returns_EEXISTS(void)
+{
+ int error;
+ git_oid note_oid, target_oid;
+
+ cl_git_pass(git_oid_fromstr(&target_oid, "08b041783f40edfe12bb406c9c9a8a040177c125"));
+
+ create_note(&note_oid, NULL, "08b041783f40edfe12bb406c9c9a8a040177c125", "hello world\n");
+ error = git_note_create(&note_oid, _repo, _sig, _sig, NULL, &target_oid, "hello world\n", 0);
+ cl_git_fail(error);
+ cl_assert_equal_i(GIT_EEXISTS, error);
+
+ create_note(&note_oid, "refs/notes/some/namespace", "08b041783f40edfe12bb406c9c9a8a040177c125", "hello world\n");
+ error = git_note_create(&note_oid, _repo, _sig, _sig, "refs/notes/some/namespace", &target_oid, "hello world\n", 0);
+ cl_git_fail(error);
+ cl_assert_equal_i(GIT_EEXISTS, error);
+}
+
+
+void test_notes_notes__creating_a_note_on_a_target_can_overwrite_existing_note(void)
+{
+ git_oid note_oid, target_oid;
+ git_note *note, *namespace_note;
+
+ cl_git_pass(git_oid_fromstr(&target_oid, "08b041783f40edfe12bb406c9c9a8a040177c125"));
+
+ create_note(&note_oid, NULL, "08b041783f40edfe12bb406c9c9a8a040177c125", "hello old world\n");
+ cl_git_pass(git_note_create(&note_oid, _repo, _sig, _sig, NULL, &target_oid, "hello new world\n", 1));
+
+ cl_git_pass(git_note_read(&note, _repo, NULL, &target_oid));
+ assert_note_equal(note, "hello new world\n", &note_oid);
+
+ create_note(&note_oid, "refs/notes/some/namespace", "08b041783f40edfe12bb406c9c9a8a040177c125", "hello old world\n");
+ cl_git_pass(git_note_create(&note_oid, _repo, _sig, _sig, "refs/notes/some/namespace", &target_oid, "hello new ref world\n", 1));
+
+ cl_git_pass(git_note_read(&namespace_note, _repo, "refs/notes/some/namespace", &target_oid));
+ assert_note_equal(namespace_note, "hello new ref world\n", &note_oid);
+
+ git_note_free(note);
+ git_note_free(namespace_note);
+}
+
+static char *messages[] = {
+ "08c041783f40edfe12bb406c9c9a8a040177c125",
+ "96c45fbe09ab7445fc7c60fd8d17f32494399343",
+ "48cc7e38dcfc1ec87e70ec03e08c3e83d7a16aa1",
+ "24c3eaafb681c3df668f9df96f58e7b8c756eb04",
+ "96ca1b6ccc7858ae94684777f85ac0e7447f7040",
+ "7ac2db4378a08bb244a427c357e0082ee0d57ac6",
+ "e6cba23dbf4ef84fe35e884f017f4e24dc228572",
+ "c8cf3462c7d8feba716deeb2ebe6583bd54589e2",
+ "39c16b9834c2d665ac5f68ad91dc5b933bad8549",
+ "f3c582b1397df6a664224ebbaf9d4cc952706597",
+ "29cec67037fe8e89977474988219016ae7f342a6",
+ "36c4cd238bf8e82e27b740e0741b025f2e8c79ab",
+ "f1c45a47c02e01d5a9a326f1d9f7f756373387f8",
+ "4aca84406f5daee34ab513a60717c8d7b1763ead",
+ "84ce167da452552f63ed8407b55d5ece4901845f",
+ NULL
+};
+
+#define MESSAGES_COUNT (sizeof(messages)/sizeof(messages[0])) - 1
+
+/*
+ * $ git ls-tree refs/notes/fanout
+ * 040000 tree 4b22b35d44b5a4f589edf3dc89196399771796ea 84
+ *
+ * $ git ls-tree 4b22b35
+ * 040000 tree d71aab4f9b04b45ce09bcaa636a9be6231474759 96
+ *
+ * $ git ls-tree d71aab4
+ * 100644 blob 08b041783f40edfe12bb406c9c9a8a040177c125 071c1b46c854b31185ea97743be6a8774479
+ */
+void test_notes_notes__can_insert_a_note_in_an_existing_fanout(void)
+{
+ size_t i;
+ git_oid note_oid, target_oid;
+ git_note *_note;
+
+ cl_git_pass(git_oid_fromstr(&target_oid, "08b041783f40edfe12bb406c9c9a8a040177c125"));
+
+ for (i = 0; i < MESSAGES_COUNT; i++) {
+ cl_git_pass(git_note_create(&note_oid, _repo, _sig, _sig, "refs/notes/fanout", &target_oid, messages[i], 0));
+ cl_git_pass(git_note_read(&_note, _repo, "refs/notes/fanout", &target_oid));
+ git_note_free(_note);
+
+ git_oid_cpy(&target_oid, &note_oid);
+ }
+}
+
+/*
+ * $ git notes --ref fanout list 8496071c1b46c854b31185ea97743be6a8774479
+ * 08b041783f40edfe12bb406c9c9a8a040177c125
+ */
+void test_notes_notes__can_read_a_note_in_an_existing_fanout(void)
+{
+ git_oid note_oid, target_oid;
+ git_note *note;
+
+ cl_git_pass(git_oid_fromstr(&target_oid, "8496071c1b46c854b31185ea97743be6a8774479"));
+ cl_git_pass(git_note_read(&note, _repo, "refs/notes/fanout", &target_oid));
+
+ cl_git_pass(git_oid_fromstr(&note_oid, "08b041783f40edfe12bb406c9c9a8a040177c125"));
+ cl_assert(!git_oid_cmp(git_note_id(note), &note_oid));
+
+ git_note_free(note);
+}
+
+void test_notes_notes__can_remove_a_note_in_an_existing_fanout(void)
+{
+ git_oid target_oid;
+ git_note *note;
+
+ cl_git_pass(git_oid_fromstr(&target_oid, "8496071c1b46c854b31185ea97743be6a8774479"));
+ cl_git_pass(git_note_remove(_repo, "refs/notes/fanout", _sig, _sig, &target_oid));
+
+ cl_git_fail(git_note_read(&note, _repo, "refs/notes/fanout", &target_oid));
+}
+
+void test_notes_notes__removing_a_note_which_doesnt_exists_returns_ENOTFOUND(void)
+{
+ int error;
+ git_oid target_oid;
+
+ cl_git_pass(git_oid_fromstr(&target_oid, "8496071c1b46c854b31185ea97743be6a8774479"));
+ cl_git_pass(git_note_remove(_repo, "refs/notes/fanout", _sig, _sig, &target_oid));
+
+ error = git_note_remove(_repo, "refs/notes/fanout", _sig, _sig, &target_oid);
+ cl_git_fail(error);
+ cl_assert_equal_i(GIT_ENOTFOUND, error);
+}
+
+void test_notes_notes__can_iterate_default_namespace(void)
+{
+ git_note_iterator *iter;
+ git_note *note;
+ git_oid note_id, annotated_id;
+ git_oid note_created[2];
+ const char* note_message[] = {
+ "I decorate a65f\n",
+ "I decorate c478\n"
+ };
+ int i, err;
+
+ create_note(&note_created[0], "refs/notes/commits",
+ "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", note_message[0]);
+ create_note(&note_created[1], "refs/notes/commits",
+ "c47800c7266a2be04c571c04d5a6614691ea99bd", note_message[1]);
+
+ cl_git_pass(git_note_iterator_new(&iter, _repo, NULL));
+
+ for (i = 0; (err = git_note_next(&note_id, &annotated_id, iter)) >= 0; ++i) {
+ cl_git_pass(git_note_read(&note, _repo, NULL, &annotated_id));
+ cl_assert_equal_s(git_note_message(note), note_message[i]);
+ git_note_free(note);
+ }
+
+ cl_assert_equal_i(GIT_ITEROVER, err);
+ cl_assert_equal_i(2, i);
+ git_note_iterator_free(iter);
+}
+
+void test_notes_notes__can_iterate_custom_namespace(void)
+{
+ git_note_iterator *iter;
+ git_note *note;
+ git_oid note_id, annotated_id;
+ git_oid note_created[2];
+ const char* note_message[] = {
+ "I decorate a65f\n",
+ "I decorate c478\n"
+ };
+ int i, err;
+
+ create_note(&note_created[0], "refs/notes/beer",
+ "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", note_message[0]);
+ create_note(&note_created[1], "refs/notes/beer",
+ "c47800c7266a2be04c571c04d5a6614691ea99bd", note_message[1]);
+
+ cl_git_pass(git_note_iterator_new(&iter, _repo, "refs/notes/beer"));
+
+ for (i = 0; (err = git_note_next(&note_id, &annotated_id, iter)) >= 0; ++i) {
+ cl_git_pass(git_note_read(&note, _repo, "refs/notes/beer", &annotated_id));
+ cl_assert_equal_s(git_note_message(note), note_message[i]);
+ git_note_free(note);
+ }
+
+ cl_assert_equal_i(GIT_ITEROVER, err);
+ cl_assert_equal_i(2, i);
+ git_note_iterator_free(iter);
+}
+
+void test_notes_notes__empty_iterate(void)
+{
+ git_note_iterator *iter;
+
+ cl_git_fail(git_note_iterator_new(&iter, _repo, "refs/notes/commits"));
+}
diff --git a/tests/notes/notesref.c b/tests/notes/notesref.c
new file mode 100644
index 000000000..a33141979
--- /dev/null
+++ b/tests/notes/notesref.c
@@ -0,0 +1,64 @@
+#include "clar_libgit2.h"
+
+#include "notes.h"
+
+static git_repository *_repo;
+static git_note *_note;
+static git_signature *_sig;
+static git_config *_cfg;
+
+void test_notes_notesref__initialize(void)
+{
+ cl_fixture_sandbox("testrepo.git");
+ cl_git_pass(git_repository_open(&_repo, "testrepo.git"));
+}
+
+void test_notes_notesref__cleanup(void)
+{
+ git_note_free(_note);
+ _note = NULL;
+
+ git_signature_free(_sig);
+ _sig = NULL;
+
+ git_config_free(_cfg);
+ _cfg = NULL;
+
+ git_repository_free(_repo);
+ _repo = NULL;
+
+ cl_fixture_cleanup("testrepo.git");
+}
+
+void test_notes_notesref__config_corenotesref(void)
+{
+ git_oid oid, note_oid;
+ const char *default_ref;
+
+ cl_git_pass(git_signature_now(&_sig, "alice", "alice@example.com"));
+ cl_git_pass(git_oid_fromstr(&oid, "8496071c1b46c854b31185ea97743be6a8774479"));
+
+ cl_git_pass(git_repository_config(&_cfg, _repo));
+
+ cl_git_pass(git_config_set_string(_cfg, "core.notesRef", "refs/notes/mydefaultnotesref"));
+
+ cl_git_pass(git_note_create(&note_oid, _repo, _sig, _sig, NULL, &oid, "test123test\n", 0));
+
+ cl_git_pass(git_note_read(&_note, _repo, NULL, &oid));
+ cl_assert_equal_s("test123test\n", git_note_message(_note));
+ cl_assert(!git_oid_cmp(git_note_id(_note), &note_oid));
+
+ git_note_free(_note);
+
+ cl_git_pass(git_note_read(&_note, _repo, "refs/notes/mydefaultnotesref", &oid));
+ cl_assert_equal_s("test123test\n", git_note_message(_note));
+ cl_assert(!git_oid_cmp(git_note_id(_note), &note_oid));
+
+ cl_git_pass(git_note_default_ref(&default_ref, _repo));
+ cl_assert_equal_s("refs/notes/mydefaultnotesref", default_ref);
+
+ cl_git_pass(git_config_delete_entry(_cfg, "core.notesRef"));
+
+ cl_git_pass(git_note_default_ref(&default_ref, _repo));
+ cl_assert_equal_s(GIT_NOTES_DEFAULT_REF, default_ref);
+}
diff --git a/tests/object/blob/filter.c b/tests/object/blob/filter.c
new file mode 100644
index 000000000..0aaaee6f3
--- /dev/null
+++ b/tests/object/blob/filter.c
@@ -0,0 +1,150 @@
+#include "clar_libgit2.h"
+#include "posix.h"
+#include "blob.h"
+#include "buf_text.h"
+
+static git_repository *g_repo = NULL;
+
+#define CRLF_NUM_TEST_OBJECTS 9
+
+static const char *g_crlf_raw[CRLF_NUM_TEST_OBJECTS] = {
+ "",
+ "foo\nbar\n",
+ "foo\rbar\r",
+ "foo\r\nbar\r\n",
+ "foo\nbar\rboth\r\nreversed\n\ragain\nproblems\r",
+ "123\n\000\001\002\003\004abc\255\254\253\r\n",
+ "\xEF\xBB\xBFThis is UTF-8\n",
+ "\xEF\xBB\xBF\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\r\n\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\r\n",
+ "\xFE\xFF\x00T\x00h\x00i\x00s\x00!"
+};
+
+static git_off_t g_crlf_raw_len[CRLF_NUM_TEST_OBJECTS] = {
+ -1, -1, -1, -1, -1, 17, -1, -1, 12
+};
+
+static git_oid g_crlf_oids[CRLF_NUM_TEST_OBJECTS];
+
+static git_buf g_crlf_filtered[CRLF_NUM_TEST_OBJECTS] = {
+ { "", 0, 0 },
+ { "foo\nbar\n", 0, 8 },
+ { "foo\rbar\r", 0, 8 },
+ { "foo\nbar\n", 0, 8 },
+ { "foo\nbar\rboth\nreversed\n\ragain\nproblems\r", 0, 38 },
+ { "123\n\000\001\002\003\004abc\255\254\253\n", 0, 16 },
+ { "\xEF\xBB\xBFThis is UTF-8\n", 0, 17 },
+ { "\xEF\xBB\xBF\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\n\xE3\x81\xBB\xE3\x81\x92\xE3\x81\xBB\xE3\x81\x92\n", 0, 29 },
+ { "\xFE\xFF\x00T\x00h\x00i\x00s\x00!", 0, 12 }
+};
+
+static git_buf_text_stats g_crlf_filtered_stats[CRLF_NUM_TEST_OBJECTS] = {
+ { 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 2, 0, 6, 0 },
+ { 0, 0, 2, 0, 0, 6, 0 },
+ { 0, 0, 2, 2, 2, 6, 0 },
+ { 0, 0, 4, 4, 1, 31, 0 },
+ { 0, 1, 1, 2, 1, 9, 5 },
+ { GIT_BOM_UTF8, 0, 0, 1, 0, 16, 0 },
+ { GIT_BOM_UTF8, 0, 2, 2, 2, 27, 0 },
+ { GIT_BOM_UTF16_BE, 5, 0, 0, 0, 7, 5 },
+};
+
+void test_object_blob_filter__initialize(void)
+{
+ int i;
+
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+
+ for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) {
+ if (g_crlf_raw_len[i] < 0)
+ g_crlf_raw_len[i] = strlen(g_crlf_raw[i]);
+
+ cl_git_pass(git_blob_create_frombuffer(
+ &g_crlf_oids[i], g_repo, g_crlf_raw[i], (size_t)g_crlf_raw_len[i]));
+ }
+}
+
+void test_object_blob_filter__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_object_blob_filter__unfiltered(void)
+{
+ int i;
+ git_blob *blob;
+
+ for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) {
+ size_t raw_len = (size_t)g_crlf_raw_len[i];
+
+ cl_git_pass(git_blob_lookup(&blob, g_repo, &g_crlf_oids[i]));
+
+ cl_assert_equal_sz(raw_len, (size_t)git_blob_rawsize(blob));
+ cl_assert_equal_i(
+ 0, memcmp(g_crlf_raw[i], git_blob_rawcontent(blob), raw_len));
+
+ git_blob_free(blob);
+ }
+}
+
+void test_object_blob_filter__stats(void)
+{
+ int i;
+ git_blob *blob;
+ git_buf buf = GIT_BUF_INIT;
+ git_buf_text_stats stats;
+
+ for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) {
+ cl_git_pass(git_blob_lookup(&blob, g_repo, &g_crlf_oids[i]));
+ cl_git_pass(git_blob__getbuf(&buf, blob));
+ git_buf_text_gather_stats(&stats, &buf, false);
+ cl_assert_equal_i(
+ 0, memcmp(&g_crlf_filtered_stats[i], &stats, sizeof(stats)));
+ git_blob_free(blob);
+ }
+
+ git_buf_free(&buf);
+}
+
+void test_object_blob_filter__to_odb(void)
+{
+ git_filter_list *fl = NULL;
+ git_config *cfg;
+ int i;
+ git_blob *blob;
+ git_buf out = GIT_BUF_INIT, zeroed;
+
+ cl_git_pass(git_repository_config(&cfg, g_repo));
+ cl_assert(cfg);
+
+ git_attr_cache_flush(g_repo);
+ cl_git_append2file("empty_standard_repo/.gitattributes", "*.txt text\n");
+
+ cl_git_pass(git_filter_list_load(
+ &fl, g_repo, NULL, "filename.txt", GIT_FILTER_TO_ODB, 0));
+ cl_assert(fl != NULL);
+
+ for (i = 0; i < CRLF_NUM_TEST_OBJECTS; i++) {
+ cl_git_pass(git_blob_lookup(&blob, g_repo, &g_crlf_oids[i]));
+
+ /* try once with allocated blob */
+ cl_git_pass(git_filter_list_apply_to_blob(&out, fl, blob));
+ cl_assert_equal_sz(g_crlf_filtered[i].size, out.size);
+ cl_assert_equal_i(
+ 0, memcmp(out.ptr, g_crlf_filtered[i].ptr, out.size));
+
+ /* try again with zeroed blob */
+ memset(&zeroed, 0, sizeof(zeroed));
+ cl_git_pass(git_filter_list_apply_to_blob(&zeroed, fl, blob));
+ cl_assert_equal_sz(g_crlf_filtered[i].size, zeroed.size);
+ cl_assert_equal_i(
+ 0, memcmp(zeroed.ptr, g_crlf_filtered[i].ptr, zeroed.size));
+ git_buf_free(&zeroed);
+
+ git_blob_free(blob);
+ }
+
+ git_filter_list_free(fl);
+ git_buf_free(&out);
+ git_config_free(cfg);
+}
diff --git a/tests/object/blob/fromchunks.c b/tests/object/blob/fromchunks.c
new file mode 100644
index 000000000..b61cabfe1
--- /dev/null
+++ b/tests/object/blob/fromchunks.c
@@ -0,0 +1,156 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "posix.h"
+#include "path.h"
+#include "fileops.h"
+
+static git_repository *repo;
+static char textual_content[] = "libgit2\n\r\n\0";
+
+void test_object_blob_fromchunks__initialize(void)
+{
+ repo = cl_git_sandbox_init("testrepo.git");
+}
+
+void test_object_blob_fromchunks__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+static int text_chunked_source_cb(char *content, size_t max_length, void *payload)
+{
+ int *count;
+
+ GIT_UNUSED(max_length);
+
+ count = (int *)payload;
+ (*count)--;
+
+ if (*count == 0)
+ return 0;
+
+ strcpy(content, textual_content);
+ return (int)strlen(textual_content);
+}
+
+void test_object_blob_fromchunks__can_create_a_blob_from_a_in_memory_chunk_provider(void)
+{
+ git_oid expected_oid, oid;
+ git_object *blob;
+ int howmany = 7;
+
+ cl_git_pass(git_oid_fromstr(&expected_oid, "321cbdf08803c744082332332838df6bd160f8f9"));
+
+ cl_git_fail_with(
+ git_object_lookup(&blob, repo, &expected_oid, GIT_OBJ_ANY),
+ GIT_ENOTFOUND);
+
+ cl_git_pass(git_blob_create_fromchunks(&oid, repo, NULL, text_chunked_source_cb, &howmany));
+
+ cl_git_pass(git_object_lookup(&blob, repo, &expected_oid, GIT_OBJ_ANY));
+ cl_assert(git_oid_cmp(&expected_oid, git_object_id(blob)) == 0);
+
+ git_object_free(blob);
+}
+
+void test_object_blob_fromchunks__doesnot_overwrite_an_already_existing_object(void)
+{
+ git_buf path = GIT_BUF_INIT;
+ git_buf content = GIT_BUF_INIT;
+ git_oid expected_oid, oid;
+ int howmany = 7;
+
+ cl_git_pass(git_oid_fromstr(&expected_oid, "321cbdf08803c744082332332838df6bd160f8f9"));
+
+ cl_git_pass(git_blob_create_fromchunks(&oid, repo, NULL, text_chunked_source_cb, &howmany));
+
+ /* Let's replace the content of the blob file storage with something else... */
+ cl_git_pass(git_buf_joinpath(&path, git_repository_path(repo), "objects/32/1cbdf08803c744082332332838df6bd160f8f9"));
+ cl_git_pass(p_unlink(git_buf_cstr(&path)));
+ cl_git_mkfile(git_buf_cstr(&path), "boom");
+
+ /* ...request a creation of the same blob... */
+ howmany = 7;
+ cl_git_pass(git_blob_create_fromchunks(&oid, repo, NULL, text_chunked_source_cb, &howmany));
+
+ /* ...and ensure the content of the faked blob file hasn't been altered */
+ cl_git_pass(git_futils_readbuffer(&content, git_buf_cstr(&path)));
+ cl_assert(!git__strcmp("boom", git_buf_cstr(&content)));
+
+ git_buf_free(&path);
+ git_buf_free(&content);
+}
+
+#define GITATTR "* text=auto\n" \
+ "*.txt text\n" \
+ "*.data binary\n"
+
+static void write_attributes(git_repository *repo)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ cl_git_pass(git_buf_joinpath(&buf, git_repository_path(repo), "info"));
+ cl_git_pass(git_buf_joinpath(&buf, git_buf_cstr(&buf), "attributes"));
+
+ cl_git_pass(git_futils_mkpath2file(git_buf_cstr(&buf), 0777));
+ cl_git_rewritefile(git_buf_cstr(&buf), GITATTR);
+
+ git_buf_free(&buf);
+}
+
+static void assert_named_chunked_blob(const char *expected_sha, const char *fake_name)
+{
+ git_oid expected_oid, oid;
+ int howmany = 7;
+
+ cl_git_pass(git_oid_fromstr(&expected_oid, expected_sha));
+
+ cl_git_pass(git_blob_create_fromchunks(&oid, repo, fake_name, text_chunked_source_cb, &howmany));
+ cl_assert(git_oid_cmp(&expected_oid, &oid) == 0);
+}
+
+void test_object_blob_fromchunks__creating_a_blob_from_chunks_honors_the_attributes_directives(void)
+{
+ write_attributes(repo);
+
+ assert_named_chunked_blob("321cbdf08803c744082332332838df6bd160f8f9", "dummy.data");
+ assert_named_chunked_blob("e9671e138a780833cb689753570fd10a55be84fb", "dummy.txt");
+ assert_named_chunked_blob("e9671e138a780833cb689753570fd10a55be84fb", "dummy.dunno");
+}
+
+static int failing_chunked_source_cb(
+ char *content, size_t max_length, void *payload)
+{
+ int *count = (int *)payload;
+
+ GIT_UNUSED(max_length);
+
+ (*count)--;
+ if (*count == 0)
+ return -1234;
+
+ strcpy(content, textual_content);
+ return (int)strlen(textual_content);
+}
+
+void test_object_blob_fromchunks__can_stop_with_error(void)
+{
+ git_oid expected_oid, oid;
+ git_object *blob;
+ int howmany = 7;
+
+ cl_git_pass(git_oid_fromstr(
+ &expected_oid, "321cbdf08803c744082332332838df6bd160f8f9"));
+
+ cl_git_fail_with(
+ git_object_lookup(&blob, repo, &expected_oid, GIT_OBJ_ANY),
+ GIT_ENOTFOUND);
+
+ cl_git_fail_with(git_blob_create_fromchunks(
+ &oid, repo, NULL, failing_chunked_source_cb, &howmany), -1234);
+
+ cl_git_fail_with(
+ git_object_lookup(&blob, repo, &expected_oid, GIT_OBJ_ANY),
+ GIT_ENOTFOUND);
+}
+
diff --git a/tests/object/blob/write.c b/tests/object/blob/write.c
new file mode 100644
index 000000000..203bc67c1
--- /dev/null
+++ b/tests/object/blob/write.c
@@ -0,0 +1,69 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "posix.h"
+#include "path.h"
+#include "fileops.h"
+
+static git_repository *repo;
+
+#define WORKDIR "empty_standard_repo"
+#define BARE_REPO "testrepo.git"
+#define ELSEWHERE "elsewhere"
+
+typedef int (*blob_creator_fn)(
+ git_oid *,
+ git_repository *,
+ const char *);
+
+void test_object_blob_write__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+static void assert_blob_creation(const char *path_to_file, const char *blob_from_path, blob_creator_fn creator)
+{
+ git_oid oid;
+ cl_git_mkfile(path_to_file, "1..2...3... Can you hear me?\n");
+
+ cl_must_pass(creator(&oid, repo, blob_from_path));
+ cl_assert(git_oid_streq(&oid, "da5e4f20c91c81b44a7e298f3d3fb3fe2f178e32") == 0);
+}
+
+void test_object_blob_write__can_create_a_blob_in_a_standard_repo_from_a_file_located_in_the_working_directory(void)
+{
+ repo = cl_git_sandbox_init(WORKDIR);
+
+ assert_blob_creation(WORKDIR "/test.txt", "test.txt", &git_blob_create_fromworkdir);
+}
+
+void test_object_blob_write__can_create_a_blob_in_a_standard_repo_from_a_absolute_filepath_pointing_outside_of_the_working_directory(void)
+{
+ git_buf full_path = GIT_BUF_INIT;
+
+ repo = cl_git_sandbox_init(WORKDIR);
+
+ cl_must_pass(p_mkdir(ELSEWHERE, 0777));
+ cl_must_pass(git_path_prettify_dir(&full_path, ELSEWHERE, NULL));
+ cl_must_pass(git_buf_puts(&full_path, "test.txt"));
+
+ 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_RMDIR_REMOVE_FILES));
+}
+
+void test_object_blob_write__can_create_a_blob_in_a_bare_repo_from_a_absolute_filepath(void)
+{
+ git_buf full_path = GIT_BUF_INIT;
+
+ repo = cl_git_sandbox_init(BARE_REPO);
+
+ cl_must_pass(p_mkdir(ELSEWHERE, 0777));
+ cl_must_pass(git_path_prettify_dir(&full_path, ELSEWHERE, NULL));
+ cl_must_pass(git_buf_puts(&full_path, "test.txt"));
+
+ 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_RMDIR_REMOVE_FILES));
+}
diff --git a/tests/object/cache.c b/tests/object/cache.c
new file mode 100644
index 000000000..bdf12da7a
--- /dev/null
+++ b/tests/object/cache.c
@@ -0,0 +1,287 @@
+#include "clar_libgit2.h"
+#include "repository.h"
+
+static git_repository *g_repo;
+
+void test_object_cache__initialize(void)
+{
+ g_repo = NULL;
+}
+
+void test_object_cache__cleanup(void)
+{
+ git_repository_free(g_repo);
+ g_repo = NULL;
+
+ git_libgit2_opts(GIT_OPT_SET_CACHE_OBJECT_LIMIT, (int)GIT_OBJ_BLOB, (size_t)0);
+}
+
+static struct {
+ git_otype type;
+ const char *sha;
+} g_data[] = {
+ /* HEAD */
+ { GIT_OBJ_BLOB, "a8233120f6ad708f843d861ce2b7228ec4e3dec6" }, /* README */
+ { GIT_OBJ_BLOB, "3697d64be941a53d4ae8f6a271e4e3fa56b022cc" }, /* branch_file.txt */
+ { GIT_OBJ_BLOB, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd" }, /* new.txt */
+
+ /* refs/heads/subtrees */
+ { GIT_OBJ_BLOB, "1385f264afb75a56a5bec74243be9b367ba4ca08" }, /* README */
+ { GIT_OBJ_TREE, "f1425cef211cc08caa31e7b545ffb232acb098c3" }, /* ab */
+ { GIT_OBJ_BLOB, "d6c93164c249c8000205dd4ec5cbca1b516d487f" }, /* ab/4.txt */
+ { GIT_OBJ_TREE, "9a03079b8a8ee85a0bee58bf9be3da8b62414ed4" }, /* ab/c */
+ { GIT_OBJ_BLOB, "270b8ea76056d5cad83af921837702d3e3c2924d" }, /* ab/c/3.txt */
+ { GIT_OBJ_TREE, "b6361fc6a97178d8fc8639fdeed71c775ab52593" }, /* ab/de */
+ { GIT_OBJ_BLOB, "e7b4ad382349ff96dd8199000580b9b1e2042eb0" }, /* ab/de/2.txt */
+ { GIT_OBJ_TREE, "3259a6bd5b57fb9c1281bb7ed3167b50f224cb54" }, /* ab/de/fgh */
+ { GIT_OBJ_BLOB, "1f67fc4386b2d171e0d21be1c447e12660561f9b" }, /* ab/de/fgh/1.txt */
+ { GIT_OBJ_BLOB, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057" }, /* branch_file.txt */
+ { GIT_OBJ_BLOB, "fa49b077972391ad58037050f2a75f74e3671e92" }, /* new.txt */
+
+ /* refs/heads/chomped */
+ { GIT_OBJ_BLOB, "0266163a49e280c4f5ed1e08facd36a2bd716bcf" }, /* readme.txt */
+
+ { 0, NULL },
+ { 0, NULL }
+};
+
+void test_object_cache__cache_everything(void)
+{
+ int i, start;
+ git_oid oid;
+ git_odb_object *odb_obj;
+ git_object *obj;
+ git_odb *odb;
+
+ git_libgit2_opts(
+ GIT_OPT_SET_CACHE_OBJECT_LIMIT, (int)GIT_OBJ_BLOB, (size_t)32767);
+
+ cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git")));
+ cl_git_pass(git_repository_odb(&odb, g_repo));
+
+ start = (int)git_cache_size(&g_repo->objects);
+
+ for (i = 0; g_data[i].sha != NULL; ++i) {
+ int count = (int)git_cache_size(&g_repo->objects);
+
+ cl_git_pass(git_oid_fromstr(&oid, g_data[i].sha));
+
+ /* alternate between loading raw and parsed objects */
+ if ((i & 1) == 0) {
+ cl_git_pass(git_odb_read(&odb_obj, odb, &oid));
+ cl_assert(g_data[i].type == git_odb_object_type(odb_obj));
+ git_odb_object_free(odb_obj);
+ } else {
+ cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+ cl_assert(g_data[i].type == git_object_type(obj));
+ git_object_free(obj);
+ }
+
+ cl_assert_equal_i(count + 1, (int)git_cache_size(&g_repo->objects));
+ }
+
+ cl_assert_equal_i(i, (int)git_cache_size(&g_repo->objects) - start);
+
+ git_odb_free(odb);
+
+ for (i = 0; g_data[i].sha != NULL; ++i) {
+ int count = (int)git_cache_size(&g_repo->objects);
+
+ cl_git_pass(git_oid_fromstr(&oid, g_data[i].sha));
+ cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+ cl_assert(g_data[i].type == git_object_type(obj));
+ git_object_free(obj);
+
+ cl_assert_equal_i(count, (int)git_cache_size(&g_repo->objects));
+ }
+}
+
+void test_object_cache__cache_no_blobs(void)
+{
+ int i, start, nonblobs = 0;
+ git_oid oid;
+ git_odb_object *odb_obj;
+ git_object *obj;
+ git_odb *odb;
+
+ git_libgit2_opts(GIT_OPT_SET_CACHE_OBJECT_LIMIT, (int)GIT_OBJ_BLOB, (size_t)0);
+
+ cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git")));
+ cl_git_pass(git_repository_odb(&odb, g_repo));
+
+ start = (int)git_cache_size(&g_repo->objects);
+
+ for (i = 0; g_data[i].sha != NULL; ++i) {
+ int count = (int)git_cache_size(&g_repo->objects);
+
+ cl_git_pass(git_oid_fromstr(&oid, g_data[i].sha));
+
+ /* alternate between loading raw and parsed objects */
+ if ((i & 1) == 0) {
+ cl_git_pass(git_odb_read(&odb_obj, odb, &oid));
+ cl_assert(g_data[i].type == git_odb_object_type(odb_obj));
+ git_odb_object_free(odb_obj);
+ } else {
+ cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+ cl_assert(g_data[i].type == git_object_type(obj));
+ git_object_free(obj);
+ }
+
+ if (g_data[i].type == GIT_OBJ_BLOB)
+ cl_assert_equal_i(count, (int)git_cache_size(&g_repo->objects));
+ else {
+ cl_assert_equal_i(count + 1, (int)git_cache_size(&g_repo->objects));
+ nonblobs++;
+ }
+ }
+
+ cl_assert_equal_i(nonblobs, (int)git_cache_size(&g_repo->objects) - start);
+
+ git_odb_free(odb);
+}
+
+static void *cache_parsed(void *arg)
+{
+ int i;
+ git_oid oid;
+ git_object *obj;
+
+ for (i = ((int *)arg)[1]; g_data[i].sha != NULL; i += 2) {
+ cl_git_pass(git_oid_fromstr(&oid, g_data[i].sha));
+ cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+ cl_assert(g_data[i].type == git_object_type(obj));
+ git_object_free(obj);
+ }
+
+ for (i = 0; i < ((int *)arg)[1]; i += 2) {
+ cl_git_pass(git_oid_fromstr(&oid, g_data[i].sha));
+ cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+ cl_assert(g_data[i].type == git_object_type(obj));
+ git_object_free(obj);
+ }
+
+ return arg;
+}
+
+static void *cache_raw(void *arg)
+{
+ int i;
+ git_oid oid;
+ git_odb *odb;
+ git_odb_object *odb_obj;
+
+ cl_git_pass(git_repository_odb(&odb, g_repo));
+
+ for (i = ((int *)arg)[1]; g_data[i].sha != NULL; i += 2) {
+ cl_git_pass(git_oid_fromstr(&oid, g_data[i].sha));
+ cl_git_pass(git_odb_read(&odb_obj, odb, &oid));
+ cl_assert(g_data[i].type == git_odb_object_type(odb_obj));
+ git_odb_object_free(odb_obj);
+ }
+
+ for (i = 0; i < ((int *)arg)[1]; i += 2) {
+ cl_git_pass(git_oid_fromstr(&oid, g_data[i].sha));
+ cl_git_pass(git_odb_read(&odb_obj, odb, &oid));
+ cl_assert(g_data[i].type == git_odb_object_type(odb_obj));
+ git_odb_object_free(odb_obj);
+ }
+
+ git_odb_free(odb);
+
+ return arg;
+}
+
+#define REPEAT 20
+#define THREADCOUNT 50
+
+void test_object_cache__threadmania(void)
+{
+ int try, th, max_i;
+ void *data;
+ void *(*fn)(void *);
+
+#ifdef GIT_THREADS
+ git_thread t[THREADCOUNT];
+#endif
+
+ for (max_i = 0; g_data[max_i].sha != NULL; ++max_i)
+ /* count up */;
+
+ for (try = 0; try < REPEAT; ++try) {
+
+ cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git")));
+
+ for (th = 0; th < THREADCOUNT; ++th) {
+ data = git__malloc(2 * sizeof(int));
+
+ ((int *)data)[0] = th;
+ ((int *)data)[1] = th % max_i;
+
+ fn = (th & 1) ? cache_parsed : cache_raw;
+
+#ifdef GIT_THREADS
+ cl_git_pass(git_thread_create(&t[th], NULL, fn, data));
+#else
+ cl_assert(fn(data) == data);
+ git__free(data);
+#endif
+ }
+
+#ifdef GIT_THREADS
+ for (th = 0; th < THREADCOUNT; ++th) {
+ cl_git_pass(git_thread_join(&t[th], &data));
+ cl_assert_equal_i(th, ((int *)data)[0]);
+ git__free(data);
+ }
+#endif
+
+ git_repository_free(g_repo);
+ g_repo = NULL;
+ }
+}
+
+static void *cache_quick(void *arg)
+{
+ git_oid oid;
+ git_object *obj;
+
+ cl_git_pass(git_oid_fromstr(&oid, g_data[4].sha));
+ cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+ cl_assert(g_data[4].type == git_object_type(obj));
+ git_object_free(obj);
+
+ return arg;
+}
+
+void test_object_cache__fast_thread_rush(void)
+{
+ int try, th, data[THREADCOUNT*2];
+#ifdef GIT_THREADS
+ git_thread t[THREADCOUNT*2];
+#endif
+
+ for (try = 0; try < REPEAT; ++try) {
+ cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git")));
+
+ for (th = 0; th < THREADCOUNT*2; ++th) {
+ data[th] = th;
+#ifdef GIT_THREADS
+ cl_git_pass(
+ git_thread_create(&t[th], NULL, cache_quick, &data[th]));
+#else
+ cl_assert(cache_quick(&data[th]) == &data[th]);
+#endif
+ }
+
+#ifdef GIT_THREADS
+ for (th = 0; th < THREADCOUNT*2; ++th) {
+ void *rval;
+ cl_git_pass(git_thread_join(&t[th], &rval));
+ cl_assert_equal_i(th, *((int *)rval));
+ }
+#endif
+
+ git_repository_free(g_repo);
+ g_repo = NULL;
+ }
+}
diff --git a/tests/object/commit/commitstagedfile.c b/tests/object/commit/commitstagedfile.c
new file mode 100644
index 000000000..5b48519b8
--- /dev/null
+++ b/tests/object/commit/commitstagedfile.c
@@ -0,0 +1,219 @@
+#include "clar_libgit2.h"
+#include "posix.h"
+
+static git_repository *repo;
+
+void test_object_commit_commitstagedfile__initialize(void)
+{
+ cl_fixture("treebuilder");
+ cl_git_pass(git_repository_init(&repo, "treebuilder/", 0));
+ cl_assert(repo != NULL);
+}
+
+void test_object_commit_commitstagedfile__cleanup(void)
+{
+ git_repository_free(repo);
+ repo = NULL;
+
+ cl_fixture_cleanup("treebuilder");
+}
+
+void test_object_commit_commitstagedfile__generate_predictable_object_ids(void)
+{
+ git_index *index;
+ const git_index_entry *entry;
+ git_oid expected_blob_oid, tree_oid, expected_tree_oid, commit_oid, expected_commit_oid;
+ git_signature *signature;
+ git_tree *tree;
+ git_buf buffer;
+
+ /*
+ * The test below replicates the following git scenario
+ *
+ * $ echo "test" > test.txt
+ * $ git hash-object test.txt
+ * 9daeafb9864cf43055ae93beb0afd6c7d144bfa4
+ *
+ * $ git add .
+ * $ git commit -m "Initial commit"
+ *
+ * $ git log
+ * commit 1fe3126578fc4eca68c193e4a3a0a14a0704624d
+ * Author: nulltoken <emeric.fermas@gmail.com>
+ * Date: Wed Dec 14 08:29:03 2011 +0100
+ *
+ * Initial commit
+ *
+ * $ git show 1fe3 --format=raw
+ * commit 1fe3126578fc4eca68c193e4a3a0a14a0704624d
+ * tree 2b297e643c551e76cfa1f93810c50811382f9117
+ * author nulltoken <emeric.fermas@gmail.com> 1323847743 +0100
+ * committer nulltoken <emeric.fermas@gmail.com> 1323847743 +0100
+ *
+ * Initial commit
+ *
+ * diff --git a/test.txt b/test.txt
+ * new file mode 100644
+ * index 0000000..9daeafb
+ * --- /dev/null
+ * +++ b/test.txt
+ * @@ -0,0 +1 @@
+ * +test
+ *
+ * $ git ls-tree 2b297
+ * 100644 blob 9daeafb9864cf43055ae93beb0afd6c7d144bfa4 test.txt
+ */
+
+ cl_git_pass(git_oid_fromstr(&expected_commit_oid, "1fe3126578fc4eca68c193e4a3a0a14a0704624d"));
+ cl_git_pass(git_oid_fromstr(&expected_tree_oid, "2b297e643c551e76cfa1f93810c50811382f9117"));
+ cl_git_pass(git_oid_fromstr(&expected_blob_oid, "9daeafb9864cf43055ae93beb0afd6c7d144bfa4"));
+
+ /*
+ * Add a new file to the index
+ */
+ cl_git_mkfile("treebuilder/test.txt", "test\n");
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_git_pass(git_index_add_bypath(index, "test.txt"));
+
+ entry = git_index_get_byindex(index, 0);
+
+ cl_assert(git_oid_cmp(&expected_blob_oid, &entry->id) == 0);
+
+ /*
+ * Information about index entry should match test file
+ */
+ {
+ struct stat st;
+ cl_must_pass(p_lstat("treebuilder/test.txt", &st));
+ cl_assert(entry->file_size == st.st_size);
+#ifndef _WIN32
+ /*
+ * Windows doesn't populate these fields, and the signage is
+ * wrong in the Windows version of the struct, so lets avoid
+ * the "comparing signed and unsigned" compilation warning in
+ * that case.
+ */
+ cl_assert(entry->uid == st.st_uid);
+ cl_assert(entry->gid == st.st_gid);
+#endif
+ }
+
+ /*
+ * Build the tree from the index
+ */
+ cl_git_pass(git_index_write_tree(&tree_oid, index));
+
+ cl_assert(git_oid_cmp(&expected_tree_oid, &tree_oid) == 0);
+
+ /*
+ * Commit the staged file
+ */
+ cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60));
+ cl_git_pass(git_tree_lookup(&tree, repo, &tree_oid));
+
+ memset(&buffer, 0, sizeof(git_buf));
+ cl_git_pass(git_message_prettify(&buffer, "Initial commit", 0, '#'));
+
+ cl_git_pass(git_commit_create_v(
+ &commit_oid,
+ repo,
+ "HEAD",
+ signature,
+ signature,
+ NULL,
+ buffer.ptr,
+ tree,
+ 0));
+
+ cl_assert(git_oid_cmp(&expected_commit_oid, &commit_oid) == 0);
+
+ git_buf_free(&buffer);
+ git_signature_free(signature);
+ git_tree_free(tree);
+ git_index_free(index);
+}
+
+static void assert_commit_tree_has_n_entries(git_commit *c, int count)
+{
+ git_tree *tree;
+ cl_git_pass(git_commit_tree(&tree, c));
+ cl_assert_equal_i(count, git_tree_entrycount(tree));
+ git_tree_free(tree);
+}
+
+static void assert_commit_is_head_(git_commit *c, const char *file, int line)
+{
+ git_commit *head;
+ cl_git_pass(git_revparse_single((git_object **)&head, repo, "HEAD"));
+ clar__assert(git_oid_equal(git_commit_id(c), git_commit_id(head)), file, line, "Commit is not the HEAD", NULL, 1);
+ git_commit_free(head);
+}
+#define assert_commit_is_head(C) assert_commit_is_head_((C),__FILE__,__LINE__)
+
+void test_object_commit_commitstagedfile__amend_commit(void)
+{
+ git_index *index;
+ git_oid old_oid, new_oid, tree_oid;
+ git_commit *old_commit, *new_commit;
+ git_tree *tree;
+
+ /* make a commit */
+
+ cl_git_mkfile("treebuilder/myfile", "This is a file\n");
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_git_pass(git_index_add_bypath(index, "myfile"));
+ cl_repo_commit_from_index(&old_oid, repo, NULL, 0, "first commit");
+
+ cl_git_pass(git_commit_lookup(&old_commit, repo, &old_oid));
+
+ cl_assert_equal_i(0, git_commit_parentcount(old_commit));
+ assert_commit_tree_has_n_entries(old_commit, 1);
+ assert_commit_is_head(old_commit);
+
+ /* let's amend the message of the HEAD commit */
+
+ cl_git_pass(git_commit_amend(
+ &new_oid, old_commit, "HEAD", NULL, NULL, NULL, "Initial commit", NULL));
+
+ /* fail because the commit isn't the tip of the branch anymore */
+ cl_git_fail(git_commit_amend(
+ &new_oid, old_commit, "HEAD", NULL, NULL, NULL, "Initial commit", NULL));
+
+ cl_git_pass(git_commit_lookup(&new_commit, repo, &new_oid));
+
+ cl_assert_equal_i(0, git_commit_parentcount(new_commit));
+ assert_commit_tree_has_n_entries(new_commit, 1);
+ assert_commit_is_head(new_commit);
+
+ git_commit_free(old_commit);
+
+ old_commit = new_commit;
+
+ /* let's amend the tree of that last commit */
+
+ cl_git_mkfile("treebuilder/anotherfile", "This is another file\n");
+ cl_git_pass(git_index_add_bypath(index, "anotherfile"));
+ cl_git_pass(git_index_write_tree(&tree_oid, index));
+ cl_git_pass(git_tree_lookup(&tree, repo, &tree_oid));
+ cl_assert_equal_i(2, git_tree_entrycount(tree));
+
+ /* fail to amend on a ref which does not exist */
+ cl_git_fail_with(GIT_ENOTFOUND, git_commit_amend(
+ &new_oid, old_commit, "refs/heads/nope", NULL, NULL, NULL, "Initial commit", tree));
+
+ cl_git_pass(git_commit_amend(
+ &new_oid, old_commit, "HEAD", NULL, NULL, NULL, "Initial commit", tree));
+ git_tree_free(tree);
+
+ cl_git_pass(git_commit_lookup(&new_commit, repo, &new_oid));
+
+ cl_assert_equal_i(0, git_commit_parentcount(new_commit));
+ assert_commit_tree_has_n_entries(new_commit, 2);
+ assert_commit_is_head(new_commit);
+
+ /* cleanup */
+
+ git_commit_free(old_commit);
+ git_commit_free(new_commit);
+ git_index_free(index);
+}
diff --git a/tests/object/lookup.c b/tests/object/lookup.c
new file mode 100644
index 000000000..cfa6d4678
--- /dev/null
+++ b/tests/object/lookup.c
@@ -0,0 +1,65 @@
+#include "clar_libgit2.h"
+
+#include "repository.h"
+
+static git_repository *g_repo;
+
+void test_object_lookup__initialize(void)
+{
+ cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git")));
+}
+
+void test_object_lookup__cleanup(void)
+{
+ git_repository_free(g_repo);
+ g_repo = NULL;
+}
+
+void test_object_lookup__lookup_wrong_type_returns_enotfound(void)
+{
+ const char *commit = "e90810b8df3e80c413d903f631643c716887138d";
+ git_oid oid;
+ git_object *object;
+
+ cl_git_pass(git_oid_fromstr(&oid, commit));
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_object_lookup(&object, g_repo, &oid, GIT_OBJ_TAG));
+}
+
+void test_object_lookup__lookup_nonexisting_returns_enotfound(void)
+{
+ const char *unknown = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
+ git_oid oid;
+ git_object *object;
+
+ cl_git_pass(git_oid_fromstr(&oid, unknown));
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_object_lookup(&object, g_repo, &oid, GIT_OBJ_ANY));
+}
+
+void test_object_lookup__lookup_wrong_type_by_abbreviated_id_returns_enotfound(void)
+{
+ const char *commit = "e90810b";
+ git_oid oid;
+ git_object *object;
+
+ cl_git_pass(git_oid_fromstrn(&oid, commit, strlen(commit)));
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_object_lookup_prefix(&object, g_repo, &oid, strlen(commit), GIT_OBJ_TAG));
+}
+
+void test_object_lookup__lookup_wrong_type_eventually_returns_enotfound(void)
+{
+ const char *commit = "e90810b8df3e80c413d903f631643c716887138d";
+ git_oid oid;
+ git_object *object;
+
+ cl_git_pass(git_oid_fromstr(&oid, commit));
+
+ cl_git_pass(git_object_lookup(&object, g_repo, &oid, GIT_OBJ_COMMIT));
+ git_object_free(object);
+
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_object_lookup(&object, g_repo, &oid, GIT_OBJ_TAG));
+}
+
diff --git a/tests/object/lookupbypath.c b/tests/object/lookupbypath.c
new file mode 100644
index 000000000..31aac7647
--- /dev/null
+++ b/tests/object/lookupbypath.c
@@ -0,0 +1,83 @@
+#include "clar_libgit2.h"
+
+#include "repository.h"
+
+static git_repository *g_repo;
+static git_tree *g_root_tree;
+static git_commit *g_head_commit;
+static git_object *g_expectedobject,
+ *g_actualobject;
+
+void test_object_lookupbypath__initialize(void)
+{
+ git_reference *head;
+ git_tree_entry *tree_entry;
+
+ cl_git_pass(git_repository_open(&g_repo, cl_fixture("attr/.gitted")));
+
+ cl_git_pass(git_repository_head(&head, g_repo));
+ cl_git_pass(git_reference_peel((git_object**)&g_head_commit, head, GIT_OBJ_COMMIT));
+ cl_git_pass(git_commit_tree(&g_root_tree, g_head_commit));
+ cl_git_pass(git_tree_entry_bypath(&tree_entry, g_root_tree, "subdir/subdir_test2.txt"));
+ cl_git_pass(git_object_lookup(&g_expectedobject, g_repo, git_tree_entry_id(tree_entry),
+ GIT_OBJ_ANY));
+
+ git_tree_entry_free(tree_entry);
+ git_reference_free(head);
+
+ g_actualobject = NULL;
+}
+void test_object_lookupbypath__cleanup(void)
+{
+ git_object_free(g_actualobject);
+ git_object_free(g_expectedobject);
+ git_tree_free(g_root_tree);
+ git_commit_free(g_head_commit);
+ g_expectedobject = NULL;
+ git_repository_free(g_repo);
+ g_repo = NULL;
+}
+
+void test_object_lookupbypath__errors(void)
+{
+ cl_assert_equal_i(GIT_EINVALIDSPEC,
+ git_object_lookup_bypath(&g_actualobject, (git_object*)g_root_tree,
+ "subdir/subdir_test2.txt", GIT_OBJ_TREE)); // It's not a tree
+ cl_assert_equal_i(GIT_ENOTFOUND,
+ git_object_lookup_bypath(&g_actualobject, (git_object*)g_root_tree,
+ "file/doesnt/exist", GIT_OBJ_ANY));
+}
+
+void test_object_lookupbypath__from_root_tree(void)
+{
+ cl_git_pass(git_object_lookup_bypath(&g_actualobject, (git_object*)g_root_tree,
+ "subdir/subdir_test2.txt", GIT_OBJ_BLOB));
+ cl_assert_equal_i(0, git_oid_cmp(git_object_id(g_expectedobject),
+ git_object_id(g_actualobject)));
+}
+
+void test_object_lookupbypath__from_head_commit(void)
+{
+ cl_git_pass(git_object_lookup_bypath(&g_actualobject, (git_object*)g_head_commit,
+ "subdir/subdir_test2.txt", GIT_OBJ_BLOB));
+ cl_assert_equal_i(0, git_oid_cmp(git_object_id(g_expectedobject),
+ git_object_id(g_actualobject)));
+}
+
+void test_object_lookupbypath__from_subdir_tree(void)
+{
+ git_tree_entry *entry = NULL;
+ git_tree *tree = NULL;
+
+ cl_git_pass(git_tree_entry_bypath(&entry, g_root_tree, "subdir"));
+ cl_git_pass(git_tree_lookup(&tree, g_repo, git_tree_entry_id(entry)));
+
+ cl_git_pass(git_object_lookup_bypath(&g_actualobject, (git_object*)tree,
+ "subdir_test2.txt", GIT_OBJ_BLOB));
+ cl_assert_equal_i(0, git_oid_cmp(git_object_id(g_expectedobject),
+ git_object_id(g_actualobject)));
+
+ git_tree_entry_free(entry);
+ git_tree_free(tree);
+}
+
diff --git a/tests/object/message.c b/tests/object/message.c
new file mode 100644
index 000000000..40d8e7297
--- /dev/null
+++ b/tests/object/message.c
@@ -0,0 +1,199 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "message.h"
+
+static void assert_message_prettifying(char *expected_output, char *input, int strip_comments)
+{
+ git_buf prettified_message = GIT_BUF_INIT;
+
+ git_message_prettify(&prettified_message, input, strip_comments, '#');
+ cl_assert_equal_s(expected_output, git_buf_cstr(&prettified_message));
+
+ git_buf_free(&prettified_message);
+}
+
+#define t40 "A quick brown fox jumps over the lazy do"
+#define s40 " "
+#define sss s40 s40 s40 s40 s40 s40 s40 s40 s40 s40 // # 400
+#define ttt t40 t40 t40 t40 t40 t40 t40 t40 t40 t40 // # 400
+
+/* Ported from git.git */
+/* see https://github.com/git/git/blob/master/t/t0030-stripspace.sh */
+void test_object_message__long_lines_without_spaces_should_be_unchanged(void)
+{
+ assert_message_prettifying(ttt "\n", ttt, 0);
+ assert_message_prettifying(ttt ttt "\n", ttt ttt, 0);
+ assert_message_prettifying(ttt ttt ttt "\n", ttt ttt ttt, 0);
+ assert_message_prettifying(ttt ttt ttt ttt "\n", ttt ttt ttt ttt, 0);
+}
+
+void test_object_message__lines_with_spaces_at_the_beginning_should_be_unchanged(void)
+{
+ assert_message_prettifying(sss ttt "\n", sss ttt, 0);
+ assert_message_prettifying(sss sss ttt "\n", sss sss ttt, 0);
+ assert_message_prettifying(sss sss sss ttt "\n", sss sss sss ttt, 0);
+}
+
+void test_object_message__lines_with_intermediate_spaces_should_be_unchanged(void)
+{
+ assert_message_prettifying(ttt sss ttt "\n", ttt sss ttt, 0);
+ assert_message_prettifying(ttt sss sss ttt "\n", ttt sss sss ttt, 0);
+}
+
+void test_object_message__consecutive_blank_lines_should_be_unified(void)
+{
+ assert_message_prettifying(ttt "\n\n" ttt "\n", ttt "\n\n\n\n\n" ttt "\n", 0);
+ assert_message_prettifying(ttt ttt "\n\n" ttt "\n", ttt ttt "\n\n\n\n\n" ttt "\n", 0);
+ assert_message_prettifying(ttt ttt ttt "\n\n" ttt "\n", ttt ttt ttt "\n\n\n\n\n" ttt "\n", 0);
+
+ assert_message_prettifying(ttt "\n\n" ttt ttt "\n", ttt "\n\n\n\n\n" ttt ttt "\n", 0);
+ assert_message_prettifying(ttt "\n\n" ttt ttt ttt "\n", ttt "\n\n\n\n\n" ttt ttt ttt "\n", 0);
+
+ assert_message_prettifying(ttt "\n\n" ttt "\n", ttt "\n\t\n \n\n \t\t\n" ttt "\n", 0);
+ assert_message_prettifying(ttt ttt "\n\n" ttt "\n", ttt ttt "\n\t\n \n\n \t\t\n" ttt "\n", 0);
+ assert_message_prettifying(ttt ttt ttt "\n\n" ttt "\n", ttt ttt ttt "\n\t\n \n\n \t\t\n" ttt "\n", 0);
+
+ assert_message_prettifying(ttt "\n\n" ttt ttt "\n", ttt "\n\t\n \n\n \t\t\n" ttt ttt "\n", 0);
+ assert_message_prettifying(ttt "\n\n" ttt ttt ttt "\n", ttt "\n\t\n \n\n \t\t\n" ttt ttt ttt "\n", 0);
+}
+
+void test_object_message__only_consecutive_blank_lines_should_be_completely_removed(void)
+{
+ assert_message_prettifying("", "\n", 0);
+ assert_message_prettifying("", "\n\n\n", 0);
+ assert_message_prettifying("", sss "\n" sss "\n" sss "\n", 0);
+ assert_message_prettifying("", sss sss "\n" sss "\n\n", 0);
+}
+
+void test_object_message__consecutive_blank_lines_at_the_beginning_should_be_removed(void)
+{
+ assert_message_prettifying(ttt "\n", "\n" ttt "\n", 0);
+ assert_message_prettifying(ttt "\n", "\n\n\n" ttt "\n", 0);
+ assert_message_prettifying(ttt ttt "\n", "\n\n\n" ttt ttt "\n", 0);
+ assert_message_prettifying(ttt ttt ttt "\n", "\n\n\n" ttt ttt ttt "\n", 0);
+ assert_message_prettifying(ttt ttt ttt ttt "\n", "\n\n\n" ttt ttt ttt ttt "\n", 0);
+ assert_message_prettifying(ttt "\n", sss "\n" sss "\n" sss "\n" ttt "\n", 0);
+ assert_message_prettifying(ttt "\n", "\n" sss "\n" sss sss "\n" ttt "\n", 0);
+ assert_message_prettifying(ttt "\n", sss sss "\n" sss "\n\n" ttt "\n", 0);
+ assert_message_prettifying(ttt "\n", sss sss sss "\n\n\n" ttt "\n", 0);
+ assert_message_prettifying(ttt "\n", "\n" sss sss sss "\n\n" ttt "\n", 0);
+ assert_message_prettifying(ttt "\n", "\n\n" sss sss sss "\n" ttt "\n", 0);
+}
+
+void test_object_message__consecutive_blank_lines_at_the_end_should_be_removed(void)
+{
+ assert_message_prettifying(ttt "\n", ttt "\n\n", 0);
+ assert_message_prettifying(ttt "\n", ttt "\n\n\n\n", 0);
+ assert_message_prettifying(ttt ttt "\n", ttt ttt "\n\n\n\n", 0);
+ assert_message_prettifying(ttt ttt ttt "\n", ttt ttt ttt "\n\n\n\n", 0);
+ assert_message_prettifying(ttt ttt ttt ttt "\n", ttt ttt ttt ttt "\n\n\n\n", 0);
+ assert_message_prettifying(ttt "\n", ttt "\n" sss "\n" sss "\n" sss "\n", 0);
+ assert_message_prettifying(ttt "\n", ttt "\n\n" sss "\n" sss sss "\n", 0);
+ assert_message_prettifying(ttt "\n", ttt "\n" sss sss "\n" sss "\n\n", 0);
+ assert_message_prettifying(ttt "\n", ttt "\n" sss sss sss "\n\n\n", 0);
+ assert_message_prettifying(ttt "\n", ttt "\n\n" sss sss sss "\n\n", 0);
+ assert_message_prettifying(ttt "\n", ttt "\n\n\n" sss sss sss "\n\n", 0);
+}
+
+void test_object_message__text_without_newline_at_end_should_end_with_newline(void)
+{
+ assert_message_prettifying(ttt "\n", ttt, 0);
+ assert_message_prettifying(ttt ttt "\n", ttt ttt, 0);
+ assert_message_prettifying(ttt ttt ttt "\n", ttt ttt ttt, 0);
+ assert_message_prettifying(ttt ttt ttt ttt "\n", ttt ttt ttt ttt, 0);
+}
+
+void test_object_message__text_plus_spaces_without_newline_should_not_show_spaces_and_end_with_newline(void)
+{
+ assert_message_prettifying(ttt "\n", ttt sss, 0);
+ assert_message_prettifying(ttt ttt "\n", ttt ttt sss, 0);
+ assert_message_prettifying(ttt ttt ttt "\n", ttt ttt ttt sss, 0);
+ assert_message_prettifying(ttt "\n", ttt sss sss, 0);
+ assert_message_prettifying(ttt ttt "\n", ttt ttt sss sss, 0);
+ assert_message_prettifying(ttt "\n", ttt sss sss sss, 0);
+}
+
+void test_object_message__text_plus_spaces_ending_with_newline_should_be_cleaned_and_newline_must_remain(void){
+ assert_message_prettifying(ttt "\n", ttt sss "\n", 0);
+ assert_message_prettifying(ttt "\n", ttt sss sss "\n", 0);
+ assert_message_prettifying(ttt "\n", ttt sss sss sss "\n", 0);
+ assert_message_prettifying(ttt ttt "\n", ttt ttt sss "\n", 0);
+ assert_message_prettifying(ttt ttt "\n", ttt ttt sss sss "\n", 0);
+ assert_message_prettifying(ttt ttt ttt "\n", ttt ttt ttt sss "\n", 0);
+}
+
+void test_object_message__spaces_with_newline_at_end_should_be_replaced_with_empty_string(void)
+{
+ assert_message_prettifying("", sss "\n", 0);
+ assert_message_prettifying("", sss sss "\n", 0);
+ assert_message_prettifying("", sss sss sss "\n", 0);
+ assert_message_prettifying("", sss sss sss sss "\n", 0);
+}
+
+void test_object_message__spaces_without_newline_at_end_should_be_replaced_with_empty_string(void)
+{
+ assert_message_prettifying("", "", 0);
+ assert_message_prettifying("", sss sss, 0);
+ assert_message_prettifying("", sss sss sss, 0);
+ assert_message_prettifying("", sss sss sss sss, 0);
+}
+
+void test_object_message__consecutive_text_lines_should_be_unchanged(void)
+{
+ assert_message_prettifying(ttt ttt "\n" ttt "\n", ttt ttt "\n" ttt "\n", 0);
+ assert_message_prettifying(ttt "\n" ttt ttt "\n" ttt "\n", ttt "\n" ttt ttt "\n" ttt "\n", 0);
+ assert_message_prettifying(ttt "\n" ttt "\n" ttt "\n" ttt ttt "\n", ttt "\n" ttt "\n" ttt "\n" ttt ttt "\n", 0);
+ assert_message_prettifying(ttt "\n" ttt "\n\n" ttt ttt "\n" ttt "\n", ttt "\n" ttt "\n\n" ttt ttt "\n" ttt "\n", 0);
+ assert_message_prettifying(ttt ttt "\n\n" ttt "\n" ttt ttt "\n", ttt ttt "\n\n" ttt "\n" ttt ttt "\n", 0);
+ assert_message_prettifying(ttt "\n" ttt ttt "\n\n" ttt "\n", ttt "\n" ttt ttt "\n\n" ttt "\n", 0);
+}
+
+void test_object_message__strip_comments(void)
+{
+ assert_message_prettifying("", "# comment", 1);
+ assert_message_prettifying("", "# comment\n", 1);
+ assert_message_prettifying("", "# comment \n", 1);
+
+ assert_message_prettifying(ttt "\n", ttt "\n" "# comment\n", 1);
+ assert_message_prettifying(ttt "\n", "# comment\n" ttt "\n", 1);
+ assert_message_prettifying(ttt "\n" ttt "\n", ttt "\n" "# comment\n" ttt "\n", 1);
+}
+
+void test_object_message__keep_comments(void)
+{
+ assert_message_prettifying("# comment\n", "# comment", 0);
+ assert_message_prettifying("# comment\n", "# comment\n", 0);
+ assert_message_prettifying("# comment\n", "# comment \n", 0);
+
+ assert_message_prettifying(ttt "\n" "# comment\n", ttt "\n" "# comment\n", 0);
+ 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)
+{
+ git_buf buffer;
+
+ memset(&buffer, 0, sizeof(buffer));
+ cl_git_pass(git_message_prettify(&buffer, "", 0, '#'));
+ cl_assert_equal_s(buffer.ptr, "");
+ git_buf_free(&buffer);
+ cl_git_pass(git_message_prettify(&buffer, "", 1, '#'));
+ cl_assert_equal_s(buffer.ptr, "");
+ git_buf_free(&buffer);
+
+ cl_git_pass(git_message_prettify(&buffer, "Short", 0, '#'));
+ cl_assert_equal_s("Short\n", buffer.ptr);
+ git_buf_free(&buffer);
+ cl_git_pass(git_message_prettify(&buffer, "Short", 1, '#'));
+ cl_assert_equal_s("Short\n", buffer.ptr);
+ git_buf_free(&buffer);
+
+ cl_git_pass(git_message_prettify(&buffer, "This is longer\nAnd multiline\n# with some comments still in\n", 0, '#'));
+ cl_assert_equal_s(buffer.ptr, "This is longer\nAnd multiline\n# with some comments still in\n");
+ git_buf_free(&buffer);
+
+ cl_git_pass(git_message_prettify(&buffer, "This is longer\nAnd multiline\n# with some comments still in\n", 1, '#'));
+ cl_assert_equal_s(buffer.ptr, "This is longer\nAnd multiline\n");
+ git_buf_free(&buffer);
+}
diff --git a/tests/object/peel.c b/tests/object/peel.c
new file mode 100644
index 000000000..b6c9c7a3b
--- /dev/null
+++ b/tests/object/peel.c
@@ -0,0 +1,105 @@
+#include "clar_libgit2.h"
+
+static git_repository *g_repo;
+
+void test_object_peel__initialize(void)
+{
+ cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git")));
+}
+
+void test_object_peel__cleanup(void)
+{
+ git_repository_free(g_repo);
+ g_repo = NULL;
+}
+
+static void assert_peel(
+ const char *sha,
+ git_otype requested_type,
+ const char* expected_sha,
+ git_otype expected_type)
+{
+ git_oid oid, expected_oid;
+ git_object *obj;
+ git_object *peeled;
+
+ cl_git_pass(git_oid_fromstr(&oid, sha));
+ cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+
+ cl_git_pass(git_object_peel(&peeled, obj, requested_type));
+
+ cl_git_pass(git_oid_fromstr(&expected_oid, expected_sha));
+ cl_assert_equal_i(0, git_oid_cmp(&expected_oid, git_object_id(peeled)));
+
+ cl_assert_equal_i(expected_type, git_object_type(peeled));
+
+ git_object_free(peeled);
+ git_object_free(obj);
+}
+
+static void assert_peel_error(int error, const char *sha, git_otype requested_type)
+{
+ git_oid oid;
+ git_object *obj;
+ git_object *peeled;
+
+ cl_git_pass(git_oid_fromstr(&oid, sha));
+ cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+
+ cl_assert_equal_i(error, git_object_peel(&peeled, obj, requested_type));
+
+ git_object_free(obj);
+}
+
+void test_object_peel__peeling_an_object_into_its_own_type_returns_another_instance_of_it(void)
+{
+ assert_peel("e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_COMMIT,
+ "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_COMMIT);
+ assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_TAG,
+ "7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_TAG);
+ assert_peel("53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_TREE,
+ "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_TREE);
+ assert_peel("0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJ_BLOB,
+ "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJ_BLOB);
+}
+
+void test_object_peel__can_peel_a_tag(void)
+{
+ assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_COMMIT,
+ "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_COMMIT);
+ assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_TREE,
+ "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_TREE);
+}
+
+void test_object_peel__can_peel_a_commit(void)
+{
+ assert_peel("e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_TREE,
+ "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_TREE);
+}
+
+void test_object_peel__cannot_peel_a_tree(void)
+{
+ assert_peel_error(GIT_EAMBIGUOUS, "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_BLOB);
+}
+
+void test_object_peel__cannot_peel_a_blob(void)
+{
+ assert_peel_error(GIT_ENOTFOUND, "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJ_COMMIT);
+}
+
+void test_object_peel__target_any_object_for_type_change(void)
+{
+ /* tag to commit */
+ assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_ANY,
+ "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_COMMIT);
+
+ /* commit to tree */
+ assert_peel("e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_ANY,
+ "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_TREE);
+
+ /* fail to peel tree */
+ assert_peel_error(GIT_EAMBIGUOUS, "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_ANY);
+
+ /* fail to peel blob */
+ assert_peel_error(GIT_ENOTFOUND, "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJ_ANY);
+}
diff --git a/tests/object/raw/chars.c b/tests/object/raw/chars.c
new file mode 100644
index 000000000..206bf7119
--- /dev/null
+++ b/tests/object/raw/chars.c
@@ -0,0 +1,41 @@
+
+#include "clar_libgit2.h"
+
+#include "odb.h"
+
+void test_object_raw_chars__find_invalid_chars_in_oid(void)
+{
+ git_oid out;
+ unsigned char exp[] = {
+ 0x16, 0xa6, 0x77, 0x70, 0xb7,
+ 0xd8, 0xd7, 0x23, 0x17, 0xc4,
+ 0xb7, 0x75, 0x21, 0x3c, 0x23,
+ 0xa8, 0xbd, 0x74, 0xf5, 0xe0,
+ };
+ char in[41] = "16a67770b7d8d72317c4b775213c23a8bd74f5e0";
+ unsigned int i;
+
+ for (i = 0; i < 256; i++) {
+ in[38] = (char)i;
+ if (git__fromhex(i) >= 0) {
+ exp[19] = (unsigned char)(git__fromhex(i) << 4);
+ cl_git_pass(git_oid_fromstr(&out, in));
+ cl_assert(memcmp(out.id, exp, sizeof(out.id)) == 0);
+ } else {
+ cl_git_fail(git_oid_fromstr(&out, in));
+ }
+ }
+}
+
+void test_object_raw_chars__build_valid_oid_from_raw_bytes(void)
+{
+ git_oid out;
+ unsigned char exp[] = {
+ 0x16, 0xa6, 0x77, 0x70, 0xb7,
+ 0xd8, 0xd7, 0x23, 0x17, 0xc4,
+ 0xb7, 0x75, 0x21, 0x3c, 0x23,
+ 0xa8, 0xbd, 0x74, 0xf5, 0xe0,
+ };
+ git_oid_fromraw(&out, exp);
+ cl_git_pass(memcmp(out.id, exp, sizeof(out.id)));
+}
diff --git a/tests/object/raw/compare.c b/tests/object/raw/compare.c
new file mode 100644
index 000000000..1c9ce4b81
--- /dev/null
+++ b/tests/object/raw/compare.c
@@ -0,0 +1,124 @@
+
+#include "clar_libgit2.h"
+
+#include "odb.h"
+
+void test_object_raw_compare__succeed_on_copy_oid(void)
+{
+ git_oid a, b;
+ unsigned char exp[] = {
+ 0x16, 0xa6, 0x77, 0x70, 0xb7,
+ 0xd8, 0xd7, 0x23, 0x17, 0xc4,
+ 0xb7, 0x75, 0x21, 0x3c, 0x23,
+ 0xa8, 0xbd, 0x74, 0xf5, 0xe0,
+ };
+ memset(&b, 0, sizeof(b));
+ git_oid_fromraw(&a, exp);
+ git_oid_cpy(&b, &a);
+ cl_git_pass(memcmp(a.id, exp, sizeof(a.id)));
+}
+
+void test_object_raw_compare__succeed_on_oid_comparison_lesser(void)
+{
+ git_oid a, b;
+ unsigned char a_in[] = {
+ 0x16, 0xa6, 0x77, 0x70, 0xb7,
+ 0xd8, 0xd7, 0x23, 0x17, 0xc4,
+ 0xb7, 0x75, 0x21, 0x3c, 0x23,
+ 0xa8, 0xbd, 0x74, 0xf5, 0xe0,
+ };
+ unsigned char b_in[] = {
+ 0x16, 0xa6, 0x77, 0x70, 0xb7,
+ 0xd8, 0xd7, 0x23, 0x17, 0xc4,
+ 0xb7, 0x75, 0x21, 0x3c, 0x23,
+ 0xa8, 0xbd, 0x74, 0xf5, 0xf0,
+ };
+ git_oid_fromraw(&a, a_in);
+ git_oid_fromraw(&b, b_in);
+ cl_assert(git_oid_cmp(&a, &b) < 0);
+}
+
+void test_object_raw_compare__succeed_on_oid_comparison_equal(void)
+{
+ git_oid a, b;
+ unsigned char a_in[] = {
+ 0x16, 0xa6, 0x77, 0x70, 0xb7,
+ 0xd8, 0xd7, 0x23, 0x17, 0xc4,
+ 0xb7, 0x75, 0x21, 0x3c, 0x23,
+ 0xa8, 0xbd, 0x74, 0xf5, 0xe0,
+ };
+ git_oid_fromraw(&a, a_in);
+ git_oid_fromraw(&b, a_in);
+ cl_assert(git_oid_cmp(&a, &b) == 0);
+}
+
+void test_object_raw_compare__succeed_on_oid_comparison_greater(void)
+{
+ git_oid a, b;
+ unsigned char a_in[] = {
+ 0x16, 0xa6, 0x77, 0x70, 0xb7,
+ 0xd8, 0xd7, 0x23, 0x17, 0xc4,
+ 0xb7, 0x75, 0x21, 0x3c, 0x23,
+ 0xa8, 0xbd, 0x74, 0xf5, 0xe0,
+ };
+ unsigned char b_in[] = {
+ 0x16, 0xa6, 0x77, 0x70, 0xb7,
+ 0xd8, 0xd7, 0x23, 0x17, 0xc4,
+ 0xb7, 0x75, 0x21, 0x3c, 0x23,
+ 0xa8, 0xbd, 0x74, 0xf5, 0xd0,
+ };
+ git_oid_fromraw(&a, a_in);
+ git_oid_fromraw(&b, b_in);
+ cl_assert(git_oid_cmp(&a, &b) > 0);
+}
+
+void test_object_raw_compare__compare_fmt_oids(void)
+{
+ const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0";
+ git_oid in;
+ char out[GIT_OID_HEXSZ + 1];
+
+ cl_git_pass(git_oid_fromstr(&in, exp));
+
+ /* Format doesn't touch the last byte */
+ out[GIT_OID_HEXSZ] = 'Z';
+ git_oid_fmt(out, &in);
+ cl_assert(out[GIT_OID_HEXSZ] == 'Z');
+
+ /* Format produced the right result */
+ out[GIT_OID_HEXSZ] = '\0';
+ cl_assert_equal_s(exp, out);
+}
+
+void test_object_raw_compare__compare_allocfmt_oids(void)
+{
+ const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0";
+ git_oid in;
+ char *out;
+
+ cl_git_pass(git_oid_fromstr(&in, exp));
+
+ out = git_oid_allocfmt(&in);
+ cl_assert(out);
+ cl_assert_equal_s(exp, out);
+ git__free(out);
+}
+
+void test_object_raw_compare__compare_pathfmt_oids(void)
+{
+ const char *exp1 = "16a0123456789abcdef4b775213c23a8bd74f5e0";
+ const char *exp2 = "16/a0123456789abcdef4b775213c23a8bd74f5e0";
+ git_oid in;
+ char out[GIT_OID_HEXSZ + 2];
+
+ cl_git_pass(git_oid_fromstr(&in, exp1));
+
+ /* Format doesn't touch the last byte */
+ out[GIT_OID_HEXSZ + 1] = 'Z';
+ git_oid_pathfmt(out, &in);
+ cl_assert(out[GIT_OID_HEXSZ + 1] == 'Z');
+
+ /* Format produced the right result */
+ out[GIT_OID_HEXSZ + 1] = '\0';
+ cl_assert_equal_s(exp2, out);
+}
diff --git a/tests/object/raw/convert.c b/tests/object/raw/convert.c
new file mode 100644
index 000000000..88b1380a4
--- /dev/null
+++ b/tests/object/raw/convert.c
@@ -0,0 +1,112 @@
+
+#include "clar_libgit2.h"
+
+#include "odb.h"
+
+void test_object_raw_convert__succeed_on_oid_to_string_conversion(void)
+{
+ const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0";
+ git_oid in;
+ char out[GIT_OID_HEXSZ + 1];
+ char *str;
+ int i;
+
+ cl_git_pass(git_oid_fromstr(&in, exp));
+
+ /* NULL buffer pointer, returns static empty string */
+ str = git_oid_tostr(NULL, sizeof(out), &in);
+ cl_assert(str && *str == '\0' && str != out);
+
+ /* zero buffer size, returns static empty string */
+ str = git_oid_tostr(out, 0, &in);
+ cl_assert(str && *str == '\0' && str != out);
+
+ /* NULL oid pointer, sets existing buffer to empty string */
+ str = git_oid_tostr(out, sizeof(out), NULL);
+ cl_assert(str && *str == '\0' && str == out);
+
+ /* n == 1, returns out as an empty string */
+ str = git_oid_tostr(out, 1, &in);
+ cl_assert(str && *str == '\0' && str == out);
+
+ for (i = 1; i < GIT_OID_HEXSZ; i++) {
+ out[i+1] = 'Z';
+ str = git_oid_tostr(out, i+1, &in);
+ /* returns out containing c-string */
+ cl_assert(str && str == out);
+ /* must be '\0' terminated */
+ cl_assert(*(str+i) == '\0');
+ /* must not touch bytes past end of string */
+ cl_assert(*(str+(i+1)) == 'Z');
+ /* i == n-1 charaters of string */
+ cl_git_pass(strncmp(exp, out, i));
+ }
+
+ /* returns out as hex formatted c-string */
+ str = git_oid_tostr(out, sizeof(out), &in);
+ cl_assert(str && str == out && *(str+GIT_OID_HEXSZ) == '\0');
+ cl_assert_equal_s(exp, out);
+}
+
+void test_object_raw_convert__succeed_on_oid_to_string_conversion_big(void)
+{
+ const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0";
+ git_oid in;
+ char big[GIT_OID_HEXSZ + 1 + 3]; /* note + 4 => big buffer */
+ char *str;
+
+ cl_git_pass(git_oid_fromstr(&in, exp));
+
+ /* place some tail material */
+ big[GIT_OID_HEXSZ+0] = 'W'; /* should be '\0' afterwards */
+ big[GIT_OID_HEXSZ+1] = 'X'; /* should remain untouched */
+ big[GIT_OID_HEXSZ+2] = 'Y'; /* ditto */
+ big[GIT_OID_HEXSZ+3] = 'Z'; /* ditto */
+
+ /* returns big as hex formatted c-string */
+ str = git_oid_tostr(big, sizeof(big), &in);
+ cl_assert(str && str == big && *(str+GIT_OID_HEXSZ) == '\0');
+ cl_assert_equal_s(exp, big);
+
+ /* check tail material is untouched */
+ cl_assert(str && str == big && *(str+GIT_OID_HEXSZ+1) == 'X');
+ cl_assert(str && str == big && *(str+GIT_OID_HEXSZ+2) == 'Y');
+ cl_assert(str && str == big && *(str+GIT_OID_HEXSZ+3) == 'Z');
+}
+
+static void check_partial_oid(
+ char *buffer, size_t count, const git_oid *oid, const char *expected)
+{
+ git_oid_nfmt(buffer, count, oid);
+ buffer[count] = '\0';
+ cl_assert_equal_s(expected, buffer);
+}
+
+void test_object_raw_convert__convert_oid_partially(void)
+{
+ const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0";
+ git_oid in;
+ char big[GIT_OID_HEXSZ + 1 + 3]; /* note + 4 => big buffer */
+
+ cl_git_pass(git_oid_fromstr(&in, exp));
+
+ git_oid_nfmt(big, sizeof(big), &in);
+ cl_assert_equal_s(exp, big);
+
+ git_oid_nfmt(big, GIT_OID_HEXSZ + 1, &in);
+ cl_assert_equal_s(exp, big);
+
+ check_partial_oid(big, 1, &in, "1");
+ check_partial_oid(big, 2, &in, "16");
+ check_partial_oid(big, 3, &in, "16a");
+ check_partial_oid(big, 4, &in, "16a0");
+ check_partial_oid(big, 5, &in, "16a01");
+
+ check_partial_oid(big, GIT_OID_HEXSZ, &in, exp);
+ check_partial_oid(
+ big, GIT_OID_HEXSZ - 1, &in, "16a0123456789abcdef4b775213c23a8bd74f5e");
+ check_partial_oid(
+ big, GIT_OID_HEXSZ - 2, &in, "16a0123456789abcdef4b775213c23a8bd74f5");
+ check_partial_oid(
+ big, GIT_OID_HEXSZ - 3, &in, "16a0123456789abcdef4b775213c23a8bd74f");
+}
diff --git a/tests/t01-data.h b/tests/object/raw/data.h
index 268269d69..cf23819f1 100644
--- a/tests/t01-data.h
+++ b/tests/object/raw/data.h
@@ -95,8 +95,11 @@ static unsigned char tag_data[] = {
0x2e, 0x30, 0x2e, 0x31, 0x0a,
};
+/*
+ * Dummy data
+ */
static unsigned char zero_data[] = {
- 0x00 /* dummy data */
+ 0x00,
};
static unsigned char one_data[] = {
@@ -258,7 +261,7 @@ static unsigned char some_data[] = {
};
/*
- * Sha1 IDS
+ * SHA1 Hashes
*/
static char *commit_id = "3d7f8a6af076c8c3f20071a8935cdbe8228594d1";
static char *tree_id = "dff2da90b254e1beb889d1f1f1288be1803782df";
@@ -269,7 +272,7 @@ static char *two_id = "78981922613b2afb6025042ff6bd878ac1994e85";
static char *some_id = "fd8430bc864cfcd5f10e5590f8a447e01b942bfe";
/*
- * In memory objects
+ * In-memory objects
*/
static git_rawobj tree_obj = {
tree_data,
@@ -318,5 +321,3 @@ static git_rawobj junk_obj = {
0,
GIT_OBJ_BAD
};
-
-
diff --git a/tests/object/raw/fromstr.c b/tests/object/raw/fromstr.c
new file mode 100644
index 000000000..8c11c105f
--- /dev/null
+++ b/tests/object/raw/fromstr.c
@@ -0,0 +1,30 @@
+
+#include "clar_libgit2.h"
+
+#include "odb.h"
+
+void test_object_raw_fromstr__fail_on_invalid_oid_string(void)
+{
+ git_oid out;
+ cl_git_fail(git_oid_fromstr(&out, ""));
+ cl_git_fail(git_oid_fromstr(&out, "moo"));
+ cl_git_fail(git_oid_fromstr(&out, "16a67770b7d8d72317c4b775213c23a8bd74f5ez"));
+}
+
+void test_object_raw_fromstr__succeed_on_valid_oid_string(void)
+{
+ git_oid out;
+ unsigned char exp[] = {
+ 0x16, 0xa6, 0x77, 0x70, 0xb7,
+ 0xd8, 0xd7, 0x23, 0x17, 0xc4,
+ 0xb7, 0x75, 0x21, 0x3c, 0x23,
+ 0xa8, 0xbd, 0x74, 0xf5, 0xe0,
+ };
+
+ cl_git_pass(git_oid_fromstr(&out, "16a67770b7d8d72317c4b775213c23a8bd74f5e0"));
+ cl_git_pass(memcmp(out.id, exp, sizeof(out.id)));
+
+ cl_git_pass(git_oid_fromstr(&out, "16A67770B7D8D72317C4b775213C23A8BD74F5E0"));
+ cl_git_pass(memcmp(out.id, exp, sizeof(out.id)));
+
+}
diff --git a/tests/object/raw/hash.c b/tests/object/raw/hash.c
new file mode 100644
index 000000000..ede31e145
--- /dev/null
+++ b/tests/object/raw/hash.c
@@ -0,0 +1,166 @@
+
+#include "clar_libgit2.h"
+
+#include "odb.h"
+#include "hash.h"
+
+#include "data.h"
+
+static void hash_object_pass(git_oid *oid, git_rawobj *obj)
+{
+ cl_git_pass(git_odb_hash(oid, obj->data, obj->len, obj->type));
+}
+static void hash_object_fail(git_oid *oid, git_rawobj *obj)
+{
+ cl_git_fail(git_odb_hash(oid, obj->data, obj->len, obj->type));
+}
+
+static char *hello_id = "22596363b3de40b06f981fb85d82312e8c0ed511";
+static char *hello_text = "hello world\n";
+
+static char *bye_id = "ce08fe4884650f067bd5703b6a59a8b3b3c99a09";
+static char *bye_text = "bye world\n";
+
+void test_object_raw_hash__hash_by_blocks(void)
+{
+ git_hash_ctx ctx;
+ git_oid id1, id2;
+
+ cl_git_pass(git_hash_ctx_init(&ctx));
+
+ /* should already be init'd */
+ 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 */
+ 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_ctx_cleanup(&ctx);
+}
+
+void test_object_raw_hash__hash_buffer_in_single_call(void)
+{
+ git_oid id1, id2;
+
+ cl_git_pass(git_oid_fromstr(&id1, hello_id));
+ git_hash_buf(&id2, hello_text, strlen(hello_text));
+ cl_assert(git_oid_cmp(&id1, &id2) == 0);
+}
+
+void test_object_raw_hash__hash_vector(void)
+{
+ git_oid id1, id2;
+ git_buf_vec vec[2];
+
+ cl_git_pass(git_oid_fromstr(&id1, hello_id));
+
+ vec[0].data = hello_text;
+ vec[0].len = 4;
+ vec[1].data = hello_text+4;
+ vec[1].len = strlen(hello_text)-4;
+
+ git_hash_vec(&id2, vec, 2);
+
+ cl_assert(git_oid_cmp(&id1, &id2) == 0);
+}
+
+void test_object_raw_hash__hash_junk_data(void)
+{
+ git_oid id, id_zero;
+
+ cl_git_pass(git_oid_fromstr(&id_zero, zero_id));
+
+ /* invalid types: */
+ junk_obj.data = some_data;
+ hash_object_fail(&id, &junk_obj);
+
+ junk_obj.type = GIT_OBJ__EXT1;
+ hash_object_fail(&id, &junk_obj);
+
+ junk_obj.type = GIT_OBJ__EXT2;
+ hash_object_fail(&id, &junk_obj);
+
+ junk_obj.type = GIT_OBJ_OFS_DELTA;
+ hash_object_fail(&id, &junk_obj);
+
+ junk_obj.type = GIT_OBJ_REF_DELTA;
+ hash_object_fail(&id, &junk_obj);
+
+ /* data can be NULL only if len is zero: */
+ junk_obj.type = GIT_OBJ_BLOB;
+ junk_obj.data = NULL;
+ hash_object_pass(&id, &junk_obj);
+ cl_assert(git_oid_cmp(&id, &id_zero) == 0);
+
+ junk_obj.len = 1;
+ hash_object_fail(&id, &junk_obj);
+}
+
+void test_object_raw_hash__hash_commit_object(void)
+{
+ git_oid id1, id2;
+
+ cl_git_pass(git_oid_fromstr(&id1, commit_id));
+ hash_object_pass(&id2, &commit_obj);
+ cl_assert(git_oid_cmp(&id1, &id2) == 0);
+}
+
+void test_object_raw_hash__hash_tree_object(void)
+{
+ git_oid id1, id2;
+
+ cl_git_pass(git_oid_fromstr(&id1, tree_id));
+ hash_object_pass(&id2, &tree_obj);
+ cl_assert(git_oid_cmp(&id1, &id2) == 0);
+}
+
+void test_object_raw_hash__hash_tag_object(void)
+{
+ git_oid id1, id2;
+
+ cl_git_pass(git_oid_fromstr(&id1, tag_id));
+ hash_object_pass(&id2, &tag_obj);
+ cl_assert(git_oid_cmp(&id1, &id2) == 0);
+}
+
+void test_object_raw_hash__hash_zero_length_object(void)
+{
+ git_oid id1, id2;
+
+ cl_git_pass(git_oid_fromstr(&id1, zero_id));
+ hash_object_pass(&id2, &zero_obj);
+ cl_assert(git_oid_cmp(&id1, &id2) == 0);
+}
+
+void test_object_raw_hash__hash_one_byte_object(void)
+{
+ git_oid id1, id2;
+
+ cl_git_pass(git_oid_fromstr(&id1, one_id));
+ hash_object_pass(&id2, &one_obj);
+ cl_assert(git_oid_cmp(&id1, &id2) == 0);
+}
+
+void test_object_raw_hash__hash_two_byte_object(void)
+{
+ git_oid id1, id2;
+
+ cl_git_pass(git_oid_fromstr(&id1, two_id));
+ hash_object_pass(&id2, &two_obj);
+ cl_assert(git_oid_cmp(&id1, &id2) == 0);
+}
+
+void test_object_raw_hash__hash_multi_byte_object(void)
+{
+ git_oid id1, id2;
+
+ cl_git_pass(git_oid_fromstr(&id1, some_id));
+ hash_object_pass(&id2, &some_obj);
+ cl_assert(git_oid_cmp(&id1, &id2) == 0);
+}
diff --git a/tests/object/raw/short.c b/tests/object/raw/short.c
new file mode 100644
index 000000000..813cd86b6
--- /dev/null
+++ b/tests/object/raw/short.c
@@ -0,0 +1,137 @@
+
+#include "clar_libgit2.h"
+
+#include "odb.h"
+#include "hash.h"
+
+void test_object_raw_short__oid_shortener_no_duplicates(void)
+{
+ git_oid_shorten *os;
+ int min_len;
+
+ os = git_oid_shorten_new(0);
+ cl_assert(os != NULL);
+
+ git_oid_shorten_add(os, "22596363b3de40b06f981fb85d82312e8c0ed511");
+ git_oid_shorten_add(os, "ce08fe4884650f067bd5703b6a59a8b3b3c99a09");
+ git_oid_shorten_add(os, "16a0123456789abcdef4b775213c23a8bd74f5e0");
+ min_len = git_oid_shorten_add(os, "ce08fe4884650f067bd5703b6a59a8b3b3c99a09");
+
+ cl_assert(min_len == GIT_OID_HEXSZ + 1);
+
+ git_oid_shorten_free(os);
+}
+
+static int insert_sequential_oids(
+ char ***out, git_oid_shorten *os, int n, int fail)
+{
+ int i, min_len = 0;
+ char numbuf[16];
+ git_oid oid;
+ char **oids = git__calloc(n, sizeof(char *));
+ cl_assert(oids != NULL);
+
+ for (i = 0; i < n; ++i) {
+ p_snprintf(numbuf, sizeof(numbuf), "%u", (unsigned int)i);
+ git_hash_buf(&oid, numbuf, strlen(numbuf));
+
+ oids[i] = git__malloc(GIT_OID_HEXSZ + 1);
+ cl_assert(oids[i]);
+ git_oid_nfmt(oids[i], GIT_OID_HEXSZ + 1, &oid);
+
+ min_len = git_oid_shorten_add(os, oids[i]);
+
+ /* After "fail", we expect git_oid_shorten_add to fail */
+ if (fail >= 0 && i >= fail)
+ cl_assert(min_len < 0);
+ else
+ cl_assert(min_len >= 0);
+ }
+
+ *out = oids;
+
+ return min_len;
+}
+
+static void free_oids(int n, char **oids)
+{
+ int i;
+
+ for (i = 0; i < n; ++i) {
+ git__free(oids[i]);
+ }
+ git__free(oids);
+}
+
+void test_object_raw_short__oid_shortener_stresstest_git_oid_shorten(void)
+{
+#define MAX_OIDS 1000
+
+ git_oid_shorten *os;
+ size_t i, j;
+ int min_len = 0, found_collision;
+ char **oids;
+
+ os = git_oid_shorten_new(0);
+ cl_assert(os != NULL);
+
+ /*
+ * Insert in the shortener 1000 unique SHA1 ids
+ */
+ min_len = insert_sequential_oids(&oids, os, MAX_OIDS, MAX_OIDS);
+ cl_assert(min_len > 0);
+
+ /*
+ * Compare the first `min_char - 1` characters of each
+ * SHA1 OID. If the minimizer worked, we should find at
+ * least one collision
+ */
+ found_collision = 0;
+ for (i = 0; i < MAX_OIDS; ++i) {
+ for (j = i + 1; j < MAX_OIDS; ++j) {
+ if (memcmp(oids[i], oids[j], min_len - 1) == 0)
+ found_collision = 1;
+ }
+ }
+ cl_assert_equal_b(true, found_collision);
+
+ /*
+ * Compare the first `min_char` characters of each
+ * SHA1 OID. If the minimizer worked, every single preffix
+ * should be unique.
+ */
+ found_collision = 0;
+ for (i = 0; i < MAX_OIDS; ++i) {
+ for (j = i + 1; j < MAX_OIDS; ++j) {
+ if (memcmp(oids[i], oids[j], min_len) == 0)
+ found_collision = 1;
+ }
+ }
+ cl_assert_equal_b(false, found_collision);
+
+ /* cleanup */
+ free_oids(MAX_OIDS, oids);
+ git_oid_shorten_free(os);
+
+#undef MAX_OIDS
+}
+
+void test_object_raw_short__oid_shortener_too_much_oids(void)
+{
+ /* The magic number of oids at which an oid_shortener will fail.
+ * This was experimentally established. */
+#define MAX_OIDS 24556
+
+ git_oid_shorten *os;
+ char **oids;
+
+ os = git_oid_shorten_new(0);
+ cl_assert(os != NULL);
+
+ cl_assert(insert_sequential_oids(&oids, os, MAX_OIDS, MAX_OIDS - 1) < 0);
+
+ free_oids(MAX_OIDS, oids);
+ git_oid_shorten_free(os);
+
+#undef MAX_OIDS
+}
diff --git a/tests/object/raw/size.c b/tests/object/raw/size.c
new file mode 100644
index 000000000..930c6de23
--- /dev/null
+++ b/tests/object/raw/size.c
@@ -0,0 +1,13 @@
+
+#include "clar_libgit2.h"
+
+#include "odb.h"
+
+void test_object_raw_size__validate_oid_size(void)
+{
+ git_oid out;
+ cl_assert(20 == GIT_OID_RAWSZ);
+ cl_assert(40 == GIT_OID_HEXSZ);
+ cl_assert(sizeof(out) == GIT_OID_RAWSZ);
+ cl_assert(sizeof(out.id) == GIT_OID_RAWSZ);
+}
diff --git a/tests/object/raw/type2string.c b/tests/object/raw/type2string.c
new file mode 100644
index 000000000..a3585487f
--- /dev/null
+++ b/tests/object/raw/type2string.c
@@ -0,0 +1,54 @@
+
+#include "clar_libgit2.h"
+
+#include "odb.h"
+#include "hash.h"
+
+void test_object_raw_type2string__convert_type_to_string(void)
+{
+ cl_assert_equal_s(git_object_type2string(GIT_OBJ_BAD), "");
+ cl_assert_equal_s(git_object_type2string(GIT_OBJ__EXT1), "");
+ cl_assert_equal_s(git_object_type2string(GIT_OBJ_COMMIT), "commit");
+ cl_assert_equal_s(git_object_type2string(GIT_OBJ_TREE), "tree");
+ cl_assert_equal_s(git_object_type2string(GIT_OBJ_BLOB), "blob");
+ cl_assert_equal_s(git_object_type2string(GIT_OBJ_TAG), "tag");
+ cl_assert_equal_s(git_object_type2string(GIT_OBJ__EXT2), "");
+ cl_assert_equal_s(git_object_type2string(GIT_OBJ_OFS_DELTA), "OFS_DELTA");
+ cl_assert_equal_s(git_object_type2string(GIT_OBJ_REF_DELTA), "REF_DELTA");
+
+ cl_assert_equal_s(git_object_type2string(-2), "");
+ cl_assert_equal_s(git_object_type2string(8), "");
+ cl_assert_equal_s(git_object_type2string(1234), "");
+}
+
+void test_object_raw_type2string__convert_string_to_type(void)
+{
+ cl_assert(git_object_string2type(NULL) == GIT_OBJ_BAD);
+ cl_assert(git_object_string2type("") == GIT_OBJ_BAD);
+ cl_assert(git_object_string2type("commit") == GIT_OBJ_COMMIT);
+ cl_assert(git_object_string2type("tree") == GIT_OBJ_TREE);
+ cl_assert(git_object_string2type("blob") == GIT_OBJ_BLOB);
+ cl_assert(git_object_string2type("tag") == GIT_OBJ_TAG);
+ cl_assert(git_object_string2type("OFS_DELTA") == GIT_OBJ_OFS_DELTA);
+ cl_assert(git_object_string2type("REF_DELTA") == GIT_OBJ_REF_DELTA);
+
+ cl_assert(git_object_string2type("CoMmIt") == GIT_OBJ_BAD);
+ cl_assert(git_object_string2type("hohoho") == GIT_OBJ_BAD);
+}
+
+void test_object_raw_type2string__check_type_is_loose(void)
+{
+ cl_assert(git_object_typeisloose(GIT_OBJ_BAD) == 0);
+ cl_assert(git_object_typeisloose(GIT_OBJ__EXT1) == 0);
+ cl_assert(git_object_typeisloose(GIT_OBJ_COMMIT) == 1);
+ cl_assert(git_object_typeisloose(GIT_OBJ_TREE) == 1);
+ cl_assert(git_object_typeisloose(GIT_OBJ_BLOB) == 1);
+ cl_assert(git_object_typeisloose(GIT_OBJ_TAG) == 1);
+ cl_assert(git_object_typeisloose(GIT_OBJ__EXT2) == 0);
+ cl_assert(git_object_typeisloose(GIT_OBJ_OFS_DELTA) == 0);
+ cl_assert(git_object_typeisloose(GIT_OBJ_REF_DELTA) == 0);
+
+ cl_assert(git_object_typeisloose(-2) == 0);
+ cl_assert(git_object_typeisloose(8) == 0);
+ cl_assert(git_object_typeisloose(1234) == 0);
+}
diff --git a/tests/object/raw/write.c b/tests/object/raw/write.c
new file mode 100644
index 000000000..273f08f2c
--- /dev/null
+++ b/tests/object/raw/write.c
@@ -0,0 +1,462 @@
+#include "clar_libgit2.h"
+#include "git2/odb_backend.h"
+
+#include "fileops.h"
+#include "odb.h"
+
+typedef struct object_data {
+ char *id; /* object id (sha1) */
+ char *dir; /* object store (fan-out) directory name */
+ char *file; /* object store filename */
+} object_data;
+
+static const char *odb_dir = "test-objects";
+
+void test_body(object_data *d, git_rawobj *o);
+
+
+
+// Helpers
+static void remove_object_files(object_data *d)
+{
+ cl_git_pass(p_unlink(d->file));
+ cl_git_pass(p_rmdir(d->dir));
+ cl_assert(errno != ENOTEMPTY);
+ cl_git_pass(p_rmdir(odb_dir) < 0);
+}
+
+static void streaming_write(git_oid *oid, git_odb *odb, git_rawobj *raw)
+{
+ git_odb_stream *stream;
+ int error;
+
+ cl_git_pass(git_odb_open_wstream(&stream, odb, raw->len, raw->type));
+ git_odb_stream_write(stream, raw->data, raw->len);
+ error = git_odb_stream_finalize_write(oid, stream);
+ git_odb_stream_free(stream);
+ cl_git_pass(error);
+}
+
+static void check_object_files(object_data *d)
+{
+ cl_assert(git_path_exists(d->dir));
+ cl_assert(git_path_exists(d->file));
+}
+
+static void cmp_objects(git_rawobj *o1, git_rawobj *o2)
+{
+ cl_assert(o1->type == o2->type);
+ cl_assert(o1->len == o2->len);
+ if (o1->len > 0)
+ cl_assert(memcmp(o1->data, o2->data, o1->len) == 0);
+}
+
+static void make_odb_dir(void)
+{
+ cl_git_pass(p_mkdir(odb_dir, GIT_OBJECT_DIR_MODE));
+}
+
+
+// Standard test form
+void test_body(object_data *d, git_rawobj *o)
+{
+ git_odb *db;
+ git_oid id1, id2;
+ git_odb_object *obj;
+ git_rawobj tmp;
+
+ make_odb_dir();
+ cl_git_pass(git_odb_open(&db, odb_dir));
+ cl_git_pass(git_oid_fromstr(&id1, d->id));
+
+ streaming_write(&id2, db, o);
+ cl_assert(git_oid_cmp(&id1, &id2) == 0);
+ check_object_files(d);
+
+ cl_git_pass(git_odb_read(&obj, db, &id1));
+
+ tmp.data = obj->buffer;
+ tmp.len = obj->cached.size;
+ tmp.type = obj->cached.type;
+
+ cmp_objects(&tmp, o);
+
+ git_odb_object_free(obj);
+ git_odb_free(db);
+ remove_object_files(d);
+}
+
+
+void test_object_raw_write__loose_object(void)
+{
+ object_data commit = {
+ "3d7f8a6af076c8c3f20071a8935cdbe8228594d1",
+ "test-objects/3d",
+ "test-objects/3d/7f8a6af076c8c3f20071a8935cdbe8228594d1",
+ };
+
+ unsigned char commit_data[] = {
+ 0x74, 0x72, 0x65, 0x65, 0x20, 0x64, 0x66, 0x66,
+ 0x32, 0x64, 0x61, 0x39, 0x30, 0x62, 0x32, 0x35,
+ 0x34, 0x65, 0x31, 0x62, 0x65, 0x62, 0x38, 0x38,
+ 0x39, 0x64, 0x31, 0x66, 0x31, 0x66, 0x31, 0x32,
+ 0x38, 0x38, 0x62, 0x65, 0x31, 0x38, 0x30, 0x33,
+ 0x37, 0x38, 0x32, 0x64, 0x66, 0x0a, 0x61, 0x75,
+ 0x74, 0x68, 0x6f, 0x72, 0x20, 0x41, 0x20, 0x55,
+ 0x20, 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61,
+ 0x75, 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78,
+ 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x3e, 0x20, 0x31, 0x32, 0x32, 0x37, 0x38,
+ 0x31, 0x34, 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30,
+ 0x30, 0x30, 0x30, 0x0a, 0x63, 0x6f, 0x6d, 0x6d,
+ 0x69, 0x74, 0x74, 0x65, 0x72, 0x20, 0x43, 0x20,
+ 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72,
+ 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74,
+ 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d,
+ 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e,
+ 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, 0x31, 0x34,
+ 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, 0x30, 0x30,
+ 0x30, 0x0a, 0x0a, 0x41, 0x20, 0x6f, 0x6e, 0x65,
+ 0x2d, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x63, 0x6f,
+ 0x6d, 0x6d, 0x69, 0x74, 0x20, 0x73, 0x75, 0x6d,
+ 0x6d, 0x61, 0x72, 0x79, 0x0a, 0x0a, 0x54, 0x68,
+ 0x65, 0x20, 0x62, 0x6f, 0x64, 0x79, 0x20, 0x6f,
+ 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f,
+ 0x6d, 0x6d, 0x69, 0x74, 0x20, 0x6d, 0x65, 0x73,
+ 0x73, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x63, 0x6f,
+ 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67,
+ 0x20, 0x66, 0x75, 0x72, 0x74, 0x68, 0x65, 0x72,
+ 0x20, 0x65, 0x78, 0x70, 0x6c, 0x61, 0x6e, 0x61,
+ 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x6f, 0x66, 0x20,
+ 0x74, 0x68, 0x65, 0x20, 0x70, 0x75, 0x72, 0x70,
+ 0x6f, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74,
+ 0x68, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67,
+ 0x65, 0x73, 0x20, 0x69, 0x6e, 0x74, 0x72, 0x6f,
+ 0x64, 0x75, 0x63, 0x65, 0x64, 0x20, 0x62, 0x79,
+ 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6d,
+ 0x6d, 0x69, 0x74, 0x2e, 0x0a, 0x0a, 0x53, 0x69,
+ 0x67, 0x6e, 0x65, 0x64, 0x2d, 0x6f, 0x66, 0x2d,
+ 0x62, 0x79, 0x3a, 0x20, 0x41, 0x20, 0x55, 0x20,
+ 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61, 0x75,
+ 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78, 0x61,
+ 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x3e, 0x0a,
+ };
+
+ git_rawobj commit_obj = {
+ commit_data,
+ sizeof(commit_data),
+ GIT_OBJ_COMMIT
+ };
+
+ test_body(&commit, &commit_obj);
+}
+
+void test_object_raw_write__loose_tree(void)
+{
+ static object_data tree = {
+ "dff2da90b254e1beb889d1f1f1288be1803782df",
+ "test-objects/df",
+ "test-objects/df/f2da90b254e1beb889d1f1f1288be1803782df",
+ };
+
+ static unsigned char tree_data[] = {
+ 0x31, 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x6f,
+ 0x6e, 0x65, 0x00, 0x8b, 0x13, 0x78, 0x91, 0x79,
+ 0x1f, 0xe9, 0x69, 0x27, 0xad, 0x78, 0xe6, 0x4b,
+ 0x0a, 0xad, 0x7b, 0xde, 0xd0, 0x8b, 0xdc, 0x31,
+ 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x73, 0x6f,
+ 0x6d, 0x65, 0x00, 0xfd, 0x84, 0x30, 0xbc, 0x86,
+ 0x4c, 0xfc, 0xd5, 0xf1, 0x0e, 0x55, 0x90, 0xf8,
+ 0xa4, 0x47, 0xe0, 0x1b, 0x94, 0x2b, 0xfe, 0x31,
+ 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x74, 0x77,
+ 0x6f, 0x00, 0x78, 0x98, 0x19, 0x22, 0x61, 0x3b,
+ 0x2a, 0xfb, 0x60, 0x25, 0x04, 0x2f, 0xf6, 0xbd,
+ 0x87, 0x8a, 0xc1, 0x99, 0x4e, 0x85, 0x31, 0x30,
+ 0x30, 0x36, 0x34, 0x34, 0x20, 0x7a, 0x65, 0x72,
+ 0x6f, 0x00, 0xe6, 0x9d, 0xe2, 0x9b, 0xb2, 0xd1,
+ 0xd6, 0x43, 0x4b, 0x8b, 0x29, 0xae, 0x77, 0x5a,
+ 0xd8, 0xc2, 0xe4, 0x8c, 0x53, 0x91,
+ };
+
+ static git_rawobj tree_obj = {
+ tree_data,
+ sizeof(tree_data),
+ GIT_OBJ_TREE
+ };
+
+ test_body(&tree, &tree_obj);
+}
+
+void test_object_raw_write__loose_tag(void)
+{
+ static object_data tag = {
+ "09d373e1dfdc16b129ceec6dd649739911541e05",
+ "test-objects/09",
+ "test-objects/09/d373e1dfdc16b129ceec6dd649739911541e05",
+ };
+
+ static unsigned char tag_data[] = {
+ 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x33,
+ 0x64, 0x37, 0x66, 0x38, 0x61, 0x36, 0x61, 0x66,
+ 0x30, 0x37, 0x36, 0x63, 0x38, 0x63, 0x33, 0x66,
+ 0x32, 0x30, 0x30, 0x37, 0x31, 0x61, 0x38, 0x39,
+ 0x33, 0x35, 0x63, 0x64, 0x62, 0x65, 0x38, 0x32,
+ 0x32, 0x38, 0x35, 0x39, 0x34, 0x64, 0x31, 0x0a,
+ 0x74, 0x79, 0x70, 0x65, 0x20, 0x63, 0x6f, 0x6d,
+ 0x6d, 0x69, 0x74, 0x0a, 0x74, 0x61, 0x67, 0x20,
+ 0x76, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x0a, 0x74,
+ 0x61, 0x67, 0x67, 0x65, 0x72, 0x20, 0x43, 0x20,
+ 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72,
+ 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74,
+ 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d,
+ 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e,
+ 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, 0x31, 0x34,
+ 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, 0x30, 0x30,
+ 0x30, 0x0a, 0x0a, 0x54, 0x68, 0x69, 0x73, 0x20,
+ 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74,
+ 0x61, 0x67, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63,
+ 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x72, 0x65,
+ 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x76, 0x30,
+ 0x2e, 0x30, 0x2e, 0x31, 0x0a,
+ };
+
+ static git_rawobj tag_obj = {
+ tag_data,
+ sizeof(tag_data),
+ GIT_OBJ_TAG
+ };
+
+
+ test_body(&tag, &tag_obj);
+}
+
+void test_object_raw_write__zero_length(void)
+{
+ static object_data zero = {
+ "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
+ "test-objects/e6",
+ "test-objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391",
+ };
+
+ static unsigned char zero_data[] = {
+ 0x00 /* dummy data */
+ };
+
+ static git_rawobj zero_obj = {
+ zero_data,
+ 0,
+ GIT_OBJ_BLOB
+ };
+
+ test_body(&zero, &zero_obj);
+}
+
+void test_object_raw_write__one_byte(void)
+{
+ static object_data one = {
+ "8b137891791fe96927ad78e64b0aad7bded08bdc",
+ "test-objects/8b",
+ "test-objects/8b/137891791fe96927ad78e64b0aad7bded08bdc",
+ };
+
+ static unsigned char one_data[] = {
+ 0x0a,
+ };
+
+ static git_rawobj one_obj = {
+ one_data,
+ sizeof(one_data),
+ GIT_OBJ_BLOB
+ };
+
+ test_body(&one, &one_obj);
+}
+
+void test_object_raw_write__two_byte(void)
+{
+ static object_data two = {
+ "78981922613b2afb6025042ff6bd878ac1994e85",
+ "test-objects/78",
+ "test-objects/78/981922613b2afb6025042ff6bd878ac1994e85",
+ };
+
+ static unsigned char two_data[] = {
+ 0x61, 0x0a,
+ };
+
+ static git_rawobj two_obj = {
+ two_data,
+ sizeof(two_data),
+ GIT_OBJ_BLOB
+ };
+
+ test_body(&two, &two_obj);
+}
+
+void test_object_raw_write__several_bytes(void)
+{
+ static object_data some = {
+ "fd8430bc864cfcd5f10e5590f8a447e01b942bfe",
+ "test-objects/fd",
+ "test-objects/fd/8430bc864cfcd5f10e5590f8a447e01b942bfe",
+ };
+
+ static unsigned char some_data[] = {
+ 0x2f, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x54, 0x68,
+ 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20,
+ 0x69, 0x73, 0x20, 0x66, 0x72, 0x65, 0x65, 0x20,
+ 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65,
+ 0x3b, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x63, 0x61,
+ 0x6e, 0x20, 0x72, 0x65, 0x64, 0x69, 0x73, 0x74,
+ 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x69,
+ 0x74, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72,
+ 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x0a,
+ 0x20, 0x2a, 0x20, 0x69, 0x74, 0x20, 0x75, 0x6e,
+ 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20,
+ 0x74, 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66,
+ 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4e, 0x55,
+ 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c,
+ 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20,
+ 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x2c,
+ 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
+ 0x20, 0x32, 0x2c, 0x0a, 0x20, 0x2a, 0x20, 0x61,
+ 0x73, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73,
+ 0x68, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74,
+ 0x68, 0x65, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20,
+ 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65,
+ 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x20, 0x2a, 0x0a,
+ 0x20, 0x2a, 0x20, 0x49, 0x6e, 0x20, 0x61, 0x64,
+ 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74,
+ 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x65,
+ 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e,
+ 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65,
+ 0x20, 0x47, 0x4e, 0x55, 0x20, 0x47, 0x65, 0x6e,
+ 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50, 0x75, 0x62,
+ 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69, 0x63, 0x65,
+ 0x6e, 0x73, 0x65, 0x2c, 0x0a, 0x20, 0x2a, 0x20,
+ 0x74, 0x68, 0x65, 0x20, 0x61, 0x75, 0x74, 0x68,
+ 0x6f, 0x72, 0x73, 0x20, 0x67, 0x69, 0x76, 0x65,
+ 0x20, 0x79, 0x6f, 0x75, 0x20, 0x75, 0x6e, 0x6c,
+ 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x20, 0x70,
+ 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f,
+ 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x6c, 0x69, 0x6e,
+ 0x6b, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f,
+ 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x0a, 0x20,
+ 0x2a, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
+ 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69,
+ 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x69,
+ 0x6e, 0x74, 0x6f, 0x20, 0x63, 0x6f, 0x6d, 0x62,
+ 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73,
+ 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x74,
+ 0x68, 0x65, 0x72, 0x20, 0x70, 0x72, 0x6f, 0x67,
+ 0x72, 0x61, 0x6d, 0x73, 0x2c, 0x0a, 0x20, 0x2a,
+ 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x6f, 0x20,
+ 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75,
+ 0x74, 0x65, 0x20, 0x74, 0x68, 0x6f, 0x73, 0x65,
+ 0x20, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x61,
+ 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x77, 0x69,
+ 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x61, 0x6e,
+ 0x79, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, 0x69,
+ 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x2a,
+ 0x20, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x20,
+ 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65,
+ 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20,
+ 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c,
+ 0x65, 0x2e, 0x20, 0x20, 0x28, 0x54, 0x68, 0x65,
+ 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c,
+ 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20,
+ 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a,
+ 0x20, 0x2a, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72,
+ 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20,
+ 0x64, 0x6f, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x79,
+ 0x20, 0x69, 0x6e, 0x20, 0x6f, 0x74, 0x68, 0x65,
+ 0x72, 0x20, 0x72, 0x65, 0x73, 0x70, 0x65, 0x63,
+ 0x74, 0x73, 0x3b, 0x20, 0x66, 0x6f, 0x72, 0x20,
+ 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2c,
+ 0x20, 0x74, 0x68, 0x65, 0x79, 0x20, 0x63, 0x6f,
+ 0x76, 0x65, 0x72, 0x0a, 0x20, 0x2a, 0x20, 0x6d,
+ 0x6f, 0x64, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74,
+ 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2c,
+ 0x20, 0x61, 0x6e, 0x64, 0x20, 0x64, 0x69, 0x73,
+ 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f,
+ 0x6e, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x6e,
+ 0x6f, 0x74, 0x20, 0x6c, 0x69, 0x6e, 0x6b, 0x65,
+ 0x64, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x0a, 0x20,
+ 0x2a, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x6d, 0x62,
+ 0x69, 0x6e, 0x65, 0x64, 0x20, 0x65, 0x78, 0x65,
+ 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2e,
+ 0x29, 0x0a, 0x20, 0x2a, 0x0a, 0x20, 0x2a, 0x20,
+ 0x54, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c,
+ 0x65, 0x20, 0x69, 0x73, 0x20, 0x64, 0x69, 0x73,
+ 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64,
+ 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20,
+ 0x68, 0x6f, 0x70, 0x65, 0x20, 0x74, 0x68, 0x61,
+ 0x74, 0x20, 0x69, 0x74, 0x20, 0x77, 0x69, 0x6c,
+ 0x6c, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65,
+ 0x66, 0x75, 0x6c, 0x2c, 0x20, 0x62, 0x75, 0x74,
+ 0x0a, 0x20, 0x2a, 0x20, 0x57, 0x49, 0x54, 0x48,
+ 0x4f, 0x55, 0x54, 0x20, 0x41, 0x4e, 0x59, 0x20,
+ 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x59,
+ 0x3b, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75,
+ 0x74, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x20, 0x74,
+ 0x68, 0x65, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x69,
+ 0x65, 0x64, 0x20, 0x77, 0x61, 0x72, 0x72, 0x61,
+ 0x6e, 0x74, 0x79, 0x20, 0x6f, 0x66, 0x0a, 0x20,
+ 0x2a, 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, 0x41,
+ 0x4e, 0x54, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54,
+ 0x59, 0x20, 0x6f, 0x72, 0x20, 0x46, 0x49, 0x54,
+ 0x4e, 0x45, 0x53, 0x53, 0x20, 0x46, 0x4f, 0x52,
+ 0x20, 0x41, 0x20, 0x50, 0x41, 0x52, 0x54, 0x49,
+ 0x43, 0x55, 0x4c, 0x41, 0x52, 0x20, 0x50, 0x55,
+ 0x52, 0x50, 0x4f, 0x53, 0x45, 0x2e, 0x20, 0x20,
+ 0x53, 0x65, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20,
+ 0x47, 0x4e, 0x55, 0x0a, 0x20, 0x2a, 0x20, 0x47,
+ 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50,
+ 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69,
+ 0x63, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x66, 0x6f,
+ 0x72, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x64,
+ 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x0a,
+ 0x20, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x59, 0x6f,
+ 0x75, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64,
+ 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x72, 0x65,
+ 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x20, 0x61,
+ 0x20, 0x63, 0x6f, 0x70, 0x79, 0x20, 0x6f, 0x66,
+ 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4e, 0x55,
+ 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c,
+ 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20,
+ 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a,
+ 0x20, 0x2a, 0x20, 0x61, 0x6c, 0x6f, 0x6e, 0x67,
+ 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68,
+ 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72,
+ 0x61, 0x6d, 0x3b, 0x20, 0x73, 0x65, 0x65, 0x20,
+ 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65,
+ 0x20, 0x43, 0x4f, 0x50, 0x59, 0x49, 0x4e, 0x47,
+ 0x2e, 0x20, 0x20, 0x49, 0x66, 0x20, 0x6e, 0x6f,
+ 0x74, 0x2c, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65,
+ 0x20, 0x74, 0x6f, 0x0a, 0x20, 0x2a, 0x20, 0x74,
+ 0x68, 0x65, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20,
+ 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65,
+ 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x35, 0x31, 0x20,
+ 0x46, 0x72, 0x61, 0x6e, 0x6b, 0x6c, 0x69, 0x6e,
+ 0x20, 0x53, 0x74, 0x72, 0x65, 0x65, 0x74, 0x2c,
+ 0x20, 0x46, 0x69, 0x66, 0x74, 0x68, 0x20, 0x46,
+ 0x6c, 0x6f, 0x6f, 0x72, 0x2c, 0x0a, 0x20, 0x2a,
+ 0x20, 0x42, 0x6f, 0x73, 0x74, 0x6f, 0x6e, 0x2c,
+ 0x20, 0x4d, 0x41, 0x20, 0x30, 0x32, 0x31, 0x31,
+ 0x30, 0x2d, 0x31, 0x33, 0x30, 0x31, 0x2c, 0x20,
+ 0x55, 0x53, 0x41, 0x2e, 0x0a, 0x20, 0x2a, 0x2f,
+ 0x0a,
+ };
+
+ static git_rawobj some_obj = {
+ some_data,
+ sizeof(some_data),
+ GIT_OBJ_BLOB
+ };
+
+ test_body(&some, &some_obj);
+}
diff --git a/tests/object/shortid.c b/tests/object/shortid.c
new file mode 100644
index 000000000..d854cb78e
--- /dev/null
+++ b/tests/object/shortid.c
@@ -0,0 +1,51 @@
+#include "clar_libgit2.h"
+
+git_repository *_repo;
+
+void test_object_shortid__initialize(void)
+{
+ cl_git_pass(git_repository_open(&_repo, cl_fixture("duplicate.git")));
+}
+
+void test_object_shortid__cleanup(void)
+{
+ git_repository_free(_repo);
+ _repo = NULL;
+}
+
+void test_object_shortid__select(void)
+{
+ git_oid full;
+ git_object *obj;
+ git_buf shorty = {0};
+
+ git_oid_fromstr(&full, "ce013625030ba8dba906f756967f9e9ca394464a");
+ cl_git_pass(git_object_lookup(&obj, _repo, &full, GIT_OBJ_ANY));
+ cl_git_pass(git_object_short_id(&shorty, obj));
+ cl_assert_equal_i(7, shorty.size);
+ cl_assert_equal_s("ce01362", shorty.ptr);
+ git_object_free(obj);
+
+ git_oid_fromstr(&full, "038d718da6a1ebbc6a7780a96ed75a70cc2ad6e2");
+ cl_git_pass(git_object_lookup(&obj, _repo, &full, GIT_OBJ_ANY));
+ cl_git_pass(git_object_short_id(&shorty, obj));
+ cl_assert_equal_i(7, shorty.size);
+ cl_assert_equal_s("038d718", shorty.ptr);
+ git_object_free(obj);
+
+ git_oid_fromstr(&full, "dea509d097ce692e167dfc6a48a7a280cc5e877e");
+ cl_git_pass(git_object_lookup(&obj, _repo, &full, GIT_OBJ_ANY));
+ cl_git_pass(git_object_short_id(&shorty, obj));
+ cl_assert_equal_i(9, shorty.size);
+ cl_assert_equal_s("dea509d09", shorty.ptr);
+ git_object_free(obj);
+
+ git_oid_fromstr(&full, "dea509d0b3cb8ee0650f6ca210bc83f4678851ba");
+ cl_git_pass(git_object_lookup(&obj, _repo, &full, GIT_OBJ_ANY));
+ cl_git_pass(git_object_short_id(&shorty, obj));
+ cl_assert_equal_i(9, shorty.size);
+ cl_assert_equal_s("dea509d0b", shorty.ptr);
+ git_object_free(obj);
+
+ git_buf_free(&shorty);
+}
diff --git a/tests/object/tag/list.c b/tests/object/tag/list.c
new file mode 100644
index 000000000..6d5a24347
--- /dev/null
+++ b/tests/object/tag/list.c
@@ -0,0 +1,115 @@
+#include "clar_libgit2.h"
+
+#include "tag.h"
+
+static git_repository *g_repo;
+
+#define MAX_USED_TAGS 6
+
+struct pattern_match_t
+{
+ const char* pattern;
+ const size_t expected_matches;
+ const char* expected_results[MAX_USED_TAGS];
+};
+
+// Helpers
+static void ensure_tag_pattern_match(git_repository *repo,
+ const struct pattern_match_t* data)
+{
+ int already_found[MAX_USED_TAGS] = { 0 };
+ git_strarray tag_list;
+ int error = 0;
+ size_t sucessfully_found = 0;
+ size_t i, j;
+
+ cl_assert(data->expected_matches <= MAX_USED_TAGS);
+
+ if ((error = git_tag_list_match(&tag_list, data->pattern, repo)) < 0)
+ goto exit;
+
+ if (tag_list.count != data->expected_matches)
+ {
+ error = GIT_ERROR;
+ goto exit;
+ }
+
+ // we have to be prepared that tags come in any order.
+ for (i = 0; i < tag_list.count; i++)
+ {
+ for (j = 0; j < data->expected_matches; j++)
+ {
+ if (!already_found[j] && !strcmp(data->expected_results[j], tag_list.strings[i]))
+ {
+ already_found[j] = 1;
+ sucessfully_found++;
+ break;
+ }
+ }
+ }
+ cl_assert_equal_i((int)sucessfully_found, (int)data->expected_matches);
+
+exit:
+ git_strarray_free(&tag_list);
+ cl_git_pass(error);
+}
+
+// Fixture setup and teardown
+void test_object_tag_list__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo");
+}
+
+void test_object_tag_list__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_object_tag_list__list_all(void)
+{
+ // list all tag names from the repository
+ git_strarray tag_list;
+
+ cl_git_pass(git_tag_list(&tag_list, g_repo));
+
+ cl_assert_equal_i((int)tag_list.count, 6);
+
+ git_strarray_free(&tag_list);
+}
+
+static const struct pattern_match_t matches[] = {
+ // All tags, including a packed one and two namespaced ones.
+ { "", 6, { "e90810b", "point_to_blob", "test", "packed-tag", "foo/bar", "foo/foo/bar" } },
+
+ // beginning with
+ { "t*", 1, { "test" } },
+
+ // ending with
+ { "*b", 2, { "e90810b", "point_to_blob" } },
+
+ // exact match
+ { "e", 0 },
+ { "e90810b", 1, { "e90810b" } },
+
+ // either or
+ { "e90810[ab]", 1, { "e90810b" } },
+
+ // glob in the middle
+ { "foo/*/bar", 1, { "foo/foo/bar" } },
+
+ // The matching of '*' is based on plain string matching analog to the regular expression ".*"
+ // => a '/' in the tag name has no special meaning.
+ // Compare to `git tag -l "*bar"`
+ { "*bar", 2, { "foo/bar", "foo/foo/bar" } },
+
+ // End of list
+ { NULL }
+};
+
+void test_object_tag_list__list_by_pattern(void)
+{
+ // list all tag names from the repository matching a specified pattern
+ size_t i = 0;
+ while (matches[i].pattern)
+ ensure_tag_pattern_match(g_repo, &matches[i++]);
+}
diff --git a/tests/object/tag/peel.c b/tests/object/tag/peel.c
new file mode 100644
index 000000000..e2cd8d6a8
--- /dev/null
+++ b/tests/object/tag/peel.c
@@ -0,0 +1,61 @@
+#include "clar_libgit2.h"
+#include "tag.h"
+
+static git_repository *repo;
+static git_tag *tag;
+static git_object *target;
+
+void test_object_tag_peel__initialize(void)
+{
+ cl_fixture_sandbox("testrepo.git");
+ cl_git_pass(git_repository_open(&repo, "testrepo.git"));
+}
+
+void test_object_tag_peel__cleanup(void)
+{
+ git_tag_free(tag);
+ tag = NULL;
+
+ git_object_free(target);
+ target = NULL;
+
+ git_repository_free(repo);
+ repo = NULL;
+
+ cl_fixture_cleanup("testrepo.git");
+}
+
+static void retrieve_tag_from_oid(git_tag **tag_out, git_repository *repo, const char *sha)
+{
+ git_oid oid;
+
+ cl_git_pass(git_oid_fromstr(&oid, sha));
+ cl_git_pass(git_tag_lookup(tag_out, repo, &oid));
+}
+
+void test_object_tag_peel__can_peel_to_a_commit(void)
+{
+ retrieve_tag_from_oid(&tag, repo, "7b4384978d2493e851f9cca7858815fac9b10980");
+
+ cl_git_pass(git_tag_peel(&target, tag));
+ cl_assert(git_object_type(target) == GIT_OBJ_COMMIT);
+ cl_git_pass(git_oid_streq(git_object_id(target), "e90810b8df3e80c413d903f631643c716887138d"));
+}
+
+void test_object_tag_peel__can_peel_several_nested_tags_to_a_commit(void)
+{
+ retrieve_tag_from_oid(&tag, repo, "b25fa35b38051e4ae45d4222e795f9df2e43f1d1");
+
+ cl_git_pass(git_tag_peel(&target, tag));
+ cl_assert(git_object_type(target) == GIT_OBJ_COMMIT);
+ cl_git_pass(git_oid_streq(git_object_id(target), "e90810b8df3e80c413d903f631643c716887138d"));
+}
+
+void test_object_tag_peel__can_peel_to_a_non_commit(void)
+{
+ retrieve_tag_from_oid(&tag, repo, "521d87c1ec3aef9824daf6d96cc0ae3710766d91");
+
+ cl_git_pass(git_tag_peel(&target, tag));
+ cl_assert(git_object_type(target) == GIT_OBJ_BLOB);
+ cl_git_pass(git_oid_streq(git_object_id(target), "1385f264afb75a56a5bec74243be9b367ba4ca08"));
+}
diff --git a/tests/object/tag/read.c b/tests/object/tag/read.c
new file mode 100644
index 000000000..c9787a413
--- /dev/null
+++ b/tests/object/tag/read.c
@@ -0,0 +1,142 @@
+#include "clar_libgit2.h"
+
+#include "tag.h"
+
+static const char *tag1_id = "b25fa35b38051e4ae45d4222e795f9df2e43f1d1";
+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 const char *taggerless = "4a23e2e65ad4e31c4c9db7dc746650bfad082679";
+
+static git_repository *g_repo;
+
+// Fixture setup and teardown
+void test_object_tag_read__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo");
+}
+
+void test_object_tag_read__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+
+void test_object_tag_read__parse(void)
+{
+ // read and parse a tag from the repository
+ git_tag *tag1, *tag2;
+ git_commit *commit;
+ git_oid id1, id2, id_commit;
+
+ git_oid_fromstr(&id1, tag1_id);
+ git_oid_fromstr(&id2, tag2_id);
+ git_oid_fromstr(&id_commit, tagged_commit);
+
+ cl_git_pass(git_tag_lookup(&tag1, g_repo, &id1));
+
+ cl_assert_equal_s(git_tag_name(tag1), "test");
+ cl_assert(git_tag_target_type(tag1) == GIT_OBJ_TAG);
+
+ cl_git_pass(git_tag_target((git_object **)&tag2, tag1));
+ cl_assert(tag2 != NULL);
+
+ cl_assert(git_oid_cmp(&id2, git_tag_id(tag2)) == 0);
+
+ cl_git_pass(git_tag_target((git_object **)&commit, tag2));
+ cl_assert(commit != NULL);
+
+ cl_assert(git_oid_cmp(&id_commit, git_commit_id(commit)) == 0);
+
+ git_tag_free(tag1);
+ git_tag_free(tag2);
+ git_commit_free(commit);
+}
+
+void test_object_tag_read__parse_without_tagger(void)
+{
+ // read and parse a tag without a tagger field
+ git_repository *bad_tag_repo;
+ git_tag *bad_tag;
+ git_commit *commit;
+ git_oid id, id_commit;
+
+ // TODO: This is a little messy
+ cl_git_pass(git_repository_open(&bad_tag_repo, cl_fixture("bad_tag.git")));
+
+ git_oid_fromstr(&id, bad_tag_id);
+ git_oid_fromstr(&id_commit, badly_tagged_commit);
+
+ cl_git_pass(git_tag_lookup(&bad_tag, bad_tag_repo, &id));
+ cl_assert(bad_tag != NULL);
+
+ cl_assert_equal_s(git_tag_name(bad_tag), "e90810b");
+ cl_assert(git_oid_cmp(&id, git_tag_id(bad_tag)) == 0);
+ cl_assert(bad_tag->tagger == NULL);
+
+ cl_git_pass(git_tag_target((git_object **)&commit, bad_tag));
+ cl_assert(commit != NULL);
+
+ cl_assert(git_oid_cmp(&id_commit, git_commit_id(commit)) == 0);
+
+
+ git_tag_free(bad_tag);
+ 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);
+}
+
+void test_object_tag_read__without_tagger_nor_message(void)
+{
+ git_tag *tag;
+ git_oid id;
+ git_repository *repo;
+
+ cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
+
+ cl_git_pass(git_oid_fromstr(&id, taggerless));
+
+ cl_git_pass(git_tag_lookup(&tag, repo, &id));
+
+ cl_assert_equal_s(git_tag_name(tag), "taggerless");
+ cl_assert(git_tag_target_type(tag) == GIT_OBJ_COMMIT);
+
+ cl_assert(tag->message == NULL);
+ cl_assert(tag->tagger == NULL);
+
+ git_tag_free(tag);
+ git_repository_free(repo);
+}
diff --git a/tests/object/tag/write.c b/tests/object/tag/write.c
new file mode 100644
index 000000000..68e4b6c61
--- /dev/null
+++ b/tests/object/tag/write.c
@@ -0,0 +1,260 @@
+#include "clar_libgit2.h"
+
+static const char* tagger_name = "Vicent Marti";
+static const char* tagger_email = "vicent@github.com";
+static const char* tagger_message = "This is my tag.\n\nThere are many tags, but this one is mine\n";
+
+static const char *tag2_id = "7b4384978d2493e851f9cca7858815fac9b10980";
+static const char *tagged_commit = "e90810b8df3e80c413d903f631643c716887138d";
+
+static git_repository *g_repo;
+
+// Fixture setup and teardown
+void test_object_tag_write__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo");
+}
+
+void test_object_tag_write__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_object_tag_write__basic(void)
+{
+ // write a tag to the repository and read it again
+ git_tag *tag;
+ git_oid target_id, tag_id;
+ git_signature *tagger;
+ const git_signature *tagger1;
+ git_reference *ref_tag;
+ git_object *target;
+
+ git_oid_fromstr(&target_id, tagged_commit);
+ cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJ_COMMIT));
+
+ /* create signature */
+ cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60));
+
+ cl_git_pass(
+ git_tag_create(&tag_id, g_repo,
+ "the-tag", target, tagger, tagger_message, 0)
+ );
+
+ git_object_free(target);
+ git_signature_free(tagger);
+
+ cl_git_pass(git_tag_lookup(&tag, g_repo, &tag_id));
+ cl_assert(git_oid_cmp(git_tag_target_id(tag), &target_id) == 0);
+
+ /* Check attributes were set correctly */
+ tagger1 = git_tag_tagger(tag);
+ cl_assert(tagger1 != NULL);
+ cl_assert_equal_s(tagger1->name, tagger_name);
+ cl_assert_equal_s(tagger1->email, tagger_email);
+ cl_assert(tagger1->when.time == 123456789);
+ cl_assert(tagger1->when.offset == 60);
+
+ cl_assert_equal_s(git_tag_message(tag), tagger_message);
+
+ cl_git_pass(git_reference_lookup(&ref_tag, g_repo, "refs/tags/the-tag"));
+ cl_assert(git_oid_cmp(git_reference_target(ref_tag), &tag_id) == 0);
+ cl_git_pass(git_reference_delete(ref_tag));
+ git_reference_free(ref_tag);
+
+ git_tag_free(tag);
+}
+
+void test_object_tag_write__overwrite(void)
+{
+ // Attempt to write a tag bearing the same name than an already existing tag
+ git_oid target_id, tag_id;
+ git_signature *tagger;
+ git_object *target;
+
+ git_oid_fromstr(&target_id, tagged_commit);
+ cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJ_COMMIT));
+
+ /* create signature */
+ cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60));
+
+ cl_assert_equal_i(GIT_EEXISTS, git_tag_create(
+ &tag_id, /* out id */
+ g_repo,
+ "e90810b",
+ target,
+ tagger,
+ tagger_message,
+ 0));
+
+ git_object_free(target);
+ git_signature_free(tagger);
+}
+
+void test_object_tag_write__replace(void)
+{
+ // Replace an already existing tag
+ git_oid target_id, tag_id, old_tag_id;
+ git_signature *tagger;
+ git_reference *ref_tag;
+ git_object *target;
+
+ git_oid_fromstr(&target_id, tagged_commit);
+ cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJ_COMMIT));
+
+ cl_git_pass(git_reference_lookup(&ref_tag, g_repo, "refs/tags/e90810b"));
+ git_oid_cpy(&old_tag_id, git_reference_target(ref_tag));
+ git_reference_free(ref_tag);
+
+ /* create signature */
+ cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60));
+
+ cl_git_pass(git_tag_create(
+ &tag_id, /* out id */
+ g_repo,
+ "e90810b",
+ target,
+ tagger,
+ tagger_message,
+ 1));
+
+ git_object_free(target);
+ git_signature_free(tagger);
+
+ cl_git_pass(git_reference_lookup(&ref_tag, g_repo, "refs/tags/e90810b"));
+ cl_assert(git_oid_cmp(git_reference_target(ref_tag), &tag_id) == 0);
+ cl_assert(git_oid_cmp(git_reference_target(ref_tag), &old_tag_id) != 0);
+
+ git_reference_free(ref_tag);
+}
+
+void test_object_tag_write__lightweight(void)
+{
+ // write a lightweight tag to the repository and read it again
+ git_oid target_id, object_id;
+ git_reference *ref_tag;
+ git_object *target;
+
+ git_oid_fromstr(&target_id, tagged_commit);
+ cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJ_COMMIT));
+
+ cl_git_pass(git_tag_create_lightweight(
+ &object_id,
+ g_repo,
+ "light-tag",
+ target,
+ 0));
+
+ git_object_free(target);
+
+ cl_assert(git_oid_cmp(&object_id, &target_id) == 0);
+
+ cl_git_pass(git_reference_lookup(&ref_tag, g_repo, "refs/tags/light-tag"));
+ cl_assert(git_oid_cmp(git_reference_target(ref_tag), &target_id) == 0);
+
+ cl_git_pass(git_tag_delete(g_repo, "light-tag"));
+
+ git_reference_free(ref_tag);
+}
+
+void test_object_tag_write__lightweight_over_existing(void)
+{
+ // Attempt to write a lightweight tag bearing the same name than an already existing tag
+ git_oid target_id, object_id, existing_object_id;
+ git_object *target;
+
+ git_oid_fromstr(&target_id, tagged_commit);
+ cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJ_COMMIT));
+
+ cl_assert_equal_i(GIT_EEXISTS, git_tag_create_lightweight(
+ &object_id,
+ g_repo,
+ "e90810b",
+ target,
+ 0));
+
+ git_oid_fromstr(&existing_object_id, tag2_id);
+ cl_assert(git_oid_cmp(&object_id, &existing_object_id) == 0);
+
+ git_object_free(target);
+}
+
+void test_object_tag_write__delete(void)
+{
+ // Delete an already existing tag
+ git_reference *ref_tag;
+
+ cl_git_pass(git_tag_delete(g_repo, "e90810b"));
+
+ cl_git_fail(git_reference_lookup(&ref_tag, g_repo, "refs/tags/e90810b"));
+
+ git_reference_free(ref_tag);
+}
+
+void test_object_tag_write__creating_with_an_invalid_name_returns_EINVALIDSPEC(void)
+{
+ git_oid target_id, tag_id;
+ git_signature *tagger;
+ git_object *target;
+
+ git_oid_fromstr(&target_id, tagged_commit);
+ cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJ_COMMIT));
+
+ cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60));
+
+ cl_assert_equal_i(GIT_EINVALIDSPEC,
+ git_tag_create(&tag_id, g_repo,
+ "Inv@{id", target, tagger, tagger_message, 0)
+ );
+
+ cl_assert_equal_i(GIT_EINVALIDSPEC,
+ git_tag_create_lightweight(&tag_id, g_repo,
+ "Inv@{id", target, 0)
+ );
+
+ git_object_free(target);
+ git_signature_free(tagger);
+}
+
+void test_object_tag_write__deleting_with_an_invalid_name_returns_EINVALIDSPEC(void)
+{
+ cl_assert_equal_i(GIT_EINVALIDSPEC, git_tag_delete(g_repo, "Inv@{id"));
+}
+
+void create_annotation(git_oid *tag_id, const char *name)
+{
+ git_object *target;
+ git_oid target_id;
+ git_signature *tagger;
+
+ cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60));
+
+ git_oid_fromstr(&target_id, tagged_commit);
+ cl_git_pass(git_object_lookup(&target, g_repo, &target_id, GIT_OBJ_COMMIT));
+
+ cl_git_pass(git_tag_annotation_create(tag_id, g_repo, name, target, tagger, "boom!"));
+ git_object_free(target);
+ git_signature_free(tagger);
+}
+
+void test_object_tag_write__creating_an_annotation_stores_the_new_object_in_the_odb(void)
+{
+ git_oid tag_id;
+ git_tag *tag;
+
+ create_annotation(&tag_id, "new_tag");
+
+ cl_git_pass(git_tag_lookup(&tag, g_repo, &tag_id));
+ cl_assert_equal_s("new_tag", git_tag_name(tag));
+
+ git_tag_free(tag);
+}
+
+void test_object_tag_write__creating_an_annotation_does_not_create_a_reference(void)
+{
+ git_oid tag_id;
+ git_reference *tag_ref;
+
+ create_annotation(&tag_id, "new_tag");
+ cl_git_fail_with(git_reference_lookup(&tag_ref, g_repo, "refs/tags/new_tag"), GIT_ENOTFOUND);
+}
diff --git a/tests/object/tree/attributes.c b/tests/object/tree/attributes.c
new file mode 100644
index 000000000..85216cd1b
--- /dev/null
+++ b/tests/object/tree/attributes.c
@@ -0,0 +1,115 @@
+#include "clar_libgit2.h"
+#include "tree.h"
+
+static const char *blob_oid = "3d0970ec547fc41ef8a5882dde99c6adce65b021";
+static const char *tree_oid = "1b05fdaa881ee45b48cbaa5e9b037d667a47745e";
+
+void test_object_tree_attributes__ensure_correctness_of_attributes_on_insertion(void)
+{
+ git_treebuilder *builder;
+ git_oid oid;
+
+ cl_git_pass(git_oid_fromstr(&oid, blob_oid));
+
+ cl_git_pass(git_treebuilder_create(&builder, NULL));
+
+ cl_git_fail(git_treebuilder_insert(NULL, builder, "one.txt", &oid, (git_filemode_t)0777777));
+ cl_git_fail(git_treebuilder_insert(NULL, builder, "one.txt", &oid, (git_filemode_t)0100666));
+ cl_git_fail(git_treebuilder_insert(NULL, builder, "one.txt", &oid, (git_filemode_t)0000001));
+
+ git_treebuilder_free(builder);
+}
+
+void test_object_tree_attributes__group_writable_tree_entries_created_with_an_antique_git_version_can_still_be_accessed(void)
+{
+ git_repository *repo;
+ git_oid tid;
+ git_tree *tree;
+ const git_tree_entry *entry;
+
+ cl_git_pass(git_repository_open(&repo, cl_fixture("deprecated-mode.git")));
+
+ cl_git_pass(git_oid_fromstr(&tid, tree_oid));
+ cl_git_pass(git_tree_lookup(&tree, repo, &tid));
+
+ entry = git_tree_entry_byname(tree, "old_mode.txt");
+ cl_assert_equal_i(
+ GIT_FILEMODE_BLOB,
+ git_tree_entry_filemode(entry));
+
+ git_tree_free(tree);
+ git_repository_free(repo);
+}
+
+void test_object_tree_attributes__treebuilder_reject_invalid_filemode(void)
+{
+ git_treebuilder *builder;
+ git_oid bid;
+ const git_tree_entry *entry;
+
+ cl_git_pass(git_oid_fromstr(&bid, blob_oid));
+ cl_git_pass(git_treebuilder_create(&builder, NULL));
+
+ cl_git_fail(git_treebuilder_insert(
+ &entry,
+ builder,
+ "normalized.txt",
+ &bid,
+ GIT_FILEMODE_BLOB_GROUP_WRITABLE));
+
+ git_treebuilder_free(builder);
+}
+
+void test_object_tree_attributes__normalize_attributes_when_creating_a_tree_from_an_existing_one(void)
+{
+ git_repository *repo;
+ git_treebuilder *builder;
+ git_oid tid, tid2;
+ git_tree *tree;
+ const git_tree_entry *entry;
+
+ repo = cl_git_sandbox_init("deprecated-mode.git");
+
+ cl_git_pass(git_oid_fromstr(&tid, tree_oid));
+ cl_git_pass(git_tree_lookup(&tree, repo, &tid));
+
+ cl_git_pass(git_treebuilder_create(&builder, tree));
+
+ entry = git_treebuilder_get(builder, "old_mode.txt");
+ cl_assert_equal_i(
+ GIT_FILEMODE_BLOB,
+ git_tree_entry_filemode(entry));
+
+ cl_git_pass(git_treebuilder_write(&tid2, repo, builder));
+ git_treebuilder_free(builder);
+ git_tree_free(tree);
+
+ cl_git_pass(git_tree_lookup(&tree, repo, &tid2));
+ entry = git_tree_entry_byname(tree, "old_mode.txt");
+ cl_assert_equal_i(
+ GIT_FILEMODE_BLOB,
+ git_tree_entry_filemode(entry));
+
+ git_tree_free(tree);
+ cl_git_sandbox_cleanup();
+}
+
+void test_object_tree_attributes__normalize_600(void)
+{
+ git_oid id;
+ git_tree *tree;
+ git_repository *repo;
+ const git_tree_entry *entry;
+
+ repo = cl_git_sandbox_init("deprecated-mode.git");
+
+ git_oid_fromstr(&id, "0810fb7818088ff5ac41ee49199b51473b1bd6c7");
+ cl_git_pass(git_tree_lookup(&tree, repo, &id));
+
+ entry = git_tree_entry_byname(tree, "ListaTeste.xml");
+ cl_assert_equal_i(git_tree_entry_filemode(entry), GIT_FILEMODE_BLOB);
+ cl_assert_equal_i(git_tree_entry_filemode_raw(entry), 0100600);
+
+ git_tree_free(tree);
+ cl_git_sandbox_cleanup();
+}
diff --git a/tests/object/tree/duplicateentries.c b/tests/object/tree/duplicateentries.c
new file mode 100644
index 000000000..1b752acbb
--- /dev/null
+++ b/tests/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, (int)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.id, "a8233120f6ad708f843d861ce2b7228ec4e3dec6");
+
+ our_entry.path = "duplicate";
+ our_entry.mode = GIT_FILEMODE_BLOB;
+ ancestor_entry.flags |= (2 << GIT_IDXENTRY_STAGESHIFT);
+ git_oid_fromstr(&our_entry.id, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057");
+
+ their_entry.path = "duplicate";
+ their_entry.mode = GIT_FILEMODE_BLOB;
+ ancestor_entry.flags |= (3 << GIT_IDXENTRY_STAGESHIFT);
+ git_oid_fromstr(&their_entry.id, "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/object/tree/frompath.c b/tests/object/tree/frompath.c
new file mode 100644
index 000000000..86ca47e94
--- /dev/null
+++ b/tests/object/tree/frompath.c
@@ -0,0 +1,68 @@
+#include "clar_libgit2.h"
+
+static git_repository *repo;
+static git_tree *tree;
+
+void test_object_tree_frompath__initialize(void)
+{
+ git_oid id;
+ const char *tree_with_subtrees_oid = "ae90f12eea699729ed24555e40b9fd669da12a12";
+
+ cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
+ cl_assert(repo != NULL);
+
+ cl_git_pass(git_oid_fromstr(&id, tree_with_subtrees_oid));
+ cl_git_pass(git_tree_lookup(&tree, repo, &id));
+ cl_assert(tree != NULL);
+}
+
+void test_object_tree_frompath__cleanup(void)
+{
+ git_tree_free(tree);
+ tree = NULL;
+
+ git_repository_free(repo);
+ repo = NULL;
+}
+
+static void assert_tree_from_path(
+ git_tree *root,
+ const char *path,
+ const char *expected_entry_name)
+{
+ git_tree_entry *entry;
+
+ cl_git_pass(git_tree_entry_bypath(&entry, root, path));
+ cl_assert_equal_s(git_tree_entry_name(entry), expected_entry_name);
+ git_tree_entry_free(entry);
+}
+
+void test_object_tree_frompath__retrieve_tree_from_path_to_treeentry(void)
+{
+ git_tree_entry *e;
+
+ assert_tree_from_path(tree, "README", "README");
+ assert_tree_from_path(tree, "ab/de/fgh/1.txt", "1.txt");
+ assert_tree_from_path(tree, "ab/de/fgh", "fgh");
+ assert_tree_from_path(tree, "ab/de/fgh/", "fgh");
+ assert_tree_from_path(tree, "ab/de", "de");
+ assert_tree_from_path(tree, "ab/", "ab");
+ assert_tree_from_path(tree, "ab/de/", "de");
+
+ cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "i-do-not-exist.txt"));
+ cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "README/"));
+ cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "ab/de/fgh/i-do-not-exist.txt"));
+ cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "nope/de/fgh/1.txt"));
+ cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "ab/me-neither/fgh/2.txt"));
+ cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "ab/me-neither/fgh/2.txt/"));
+}
+
+void test_object_tree_frompath__fail_when_processing_an_invalid_path(void)
+{
+ git_tree_entry *e;
+
+ cl_must_fail(git_tree_entry_bypath(&e, tree, "/"));
+ cl_must_fail(git_tree_entry_bypath(&e, tree, "/ab"));
+ cl_must_fail(git_tree_entry_bypath(&e, tree, "/ab/de"));
+ cl_must_fail(git_tree_entry_bypath(&e, tree, "ab//de"));
+}
diff --git a/tests/object/tree/read.c b/tests/object/tree/read.c
new file mode 100644
index 000000000..59a809bf1
--- /dev/null
+++ b/tests/object/tree/read.c
@@ -0,0 +1,75 @@
+#include "clar_libgit2.h"
+
+#include "tree.h"
+
+static const char *tree_oid = "1810dff58d8a660512d4832e740f692884338ccd";
+
+static git_repository *g_repo;
+
+// Fixture setup and teardown
+void test_object_tree_read__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo");
+}
+
+void test_object_tree_read__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+
+
+void test_object_tree_read__loaded(void)
+{
+ // acces randomly the entries on a loaded tree
+ git_oid id;
+ git_tree *tree;
+
+ git_oid_fromstr(&id, tree_oid);
+
+ cl_git_pass(git_tree_lookup(&tree, g_repo, &id));
+
+ cl_assert(git_tree_entry_byname(tree, "README") != NULL);
+ cl_assert(git_tree_entry_byname(tree, "NOTEXISTS") == NULL);
+ cl_assert(git_tree_entry_byname(tree, "") == NULL);
+ cl_assert(git_tree_entry_byindex(tree, 0) != NULL);
+ cl_assert(git_tree_entry_byindex(tree, 2) != NULL);
+ cl_assert(git_tree_entry_byindex(tree, 3) == NULL);
+ cl_assert(git_tree_entry_byindex(tree, (unsigned int)-1) == NULL);
+
+ git_tree_free(tree);
+}
+
+void test_object_tree_read__two(void)
+{
+ // read a tree from the repository
+ git_oid id;
+ git_tree *tree;
+ const git_tree_entry *entry;
+ git_object *obj;
+
+ git_oid_fromstr(&id, tree_oid);
+
+ cl_git_pass(git_tree_lookup(&tree, g_repo, &id));
+
+ cl_assert(git_tree_entrycount(tree) == 3);
+
+ /* GH-86: git_object_lookup() should also check the type if the object comes from the cache */
+ cl_assert(git_object_lookup(&obj, g_repo, &id, GIT_OBJ_TREE) == 0);
+ cl_assert(obj != NULL);
+ git_object_free(obj);
+ obj = NULL;
+ cl_git_fail(git_object_lookup(&obj, g_repo, &id, GIT_OBJ_BLOB));
+ cl_assert(obj == NULL);
+
+ entry = git_tree_entry_byname(tree, "README");
+ cl_assert(entry != NULL);
+
+ cl_assert_equal_s(git_tree_entry_name(entry), "README");
+
+ cl_git_pass(git_tree_entry_to_object(&obj, g_repo, entry));
+ cl_assert(obj != NULL);
+
+ git_object_free(obj);
+ git_tree_free(tree);
+}
diff --git a/tests/object/tree/walk.c b/tests/object/tree/walk.c
new file mode 100644
index 000000000..f8005e579
--- /dev/null
+++ b/tests/object/tree/walk.c
@@ -0,0 +1,177 @@
+#include "clar_libgit2.h"
+#include "tree.h"
+
+static const char *tree_oid = "1810dff58d8a660512d4832e740f692884338ccd";
+static git_repository *g_repo;
+
+void test_object_tree_walk__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo");
+}
+
+void test_object_tree_walk__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+static int treewalk_count_cb(
+ const char *root, const git_tree_entry *entry, void *payload)
+{
+ int *count = payload;
+
+ GIT_UNUSED(root);
+ GIT_UNUSED(entry);
+
+ (*count) += 1;
+
+ return 0;
+}
+
+void test_object_tree_walk__0(void)
+{
+ git_oid id;
+ git_tree *tree;
+ int ct;
+
+ git_oid_fromstr(&id, tree_oid);
+
+ cl_git_pass(git_tree_lookup(&tree, g_repo, &id));
+
+ ct = 0;
+ cl_git_pass(git_tree_walk(tree, GIT_TREEWALK_PRE, treewalk_count_cb, &ct));
+ cl_assert_equal_i(3, ct);
+
+ ct = 0;
+ cl_git_pass(git_tree_walk(tree, GIT_TREEWALK_POST, treewalk_count_cb, &ct));
+ cl_assert_equal_i(3, ct);
+
+ git_tree_free(tree);
+}
+
+
+static int treewalk_stop_cb(
+ const char *root, const git_tree_entry *entry, void *payload)
+{
+ int *count = payload;
+
+ GIT_UNUSED(root);
+ GIT_UNUSED(entry);
+
+ (*count) += 1;
+
+ return (*count == 2) ? -123 : 0;
+}
+
+static int treewalk_stop_immediately_cb(
+ const char *root, const git_tree_entry *entry, void *payload)
+{
+ GIT_UNUSED(root);
+ GIT_UNUSED(entry);
+ GIT_UNUSED(payload);
+ return -100;
+}
+
+void test_object_tree_walk__1(void)
+{
+ git_oid id;
+ git_tree *tree;
+ int ct;
+
+ git_oid_fromstr(&id, tree_oid);
+
+ cl_git_pass(git_tree_lookup(&tree, g_repo, &id));
+
+ ct = 0;
+ cl_assert_equal_i(
+ -123, git_tree_walk(tree, GIT_TREEWALK_PRE, treewalk_stop_cb, &ct));
+ cl_assert_equal_i(2, ct);
+
+ ct = 0;
+ cl_assert_equal_i(
+ -123, git_tree_walk(tree, GIT_TREEWALK_POST, treewalk_stop_cb, &ct));
+ cl_assert_equal_i(2, ct);
+
+ cl_assert_equal_i(
+ -100, git_tree_walk(
+ tree, GIT_TREEWALK_PRE, treewalk_stop_immediately_cb, NULL));
+
+ cl_assert_equal_i(
+ -100, git_tree_walk(
+ tree, GIT_TREEWALK_POST, treewalk_stop_immediately_cb, NULL));
+
+ git_tree_free(tree);
+}
+
+
+struct treewalk_skip_data {
+ int files;
+ int dirs;
+ const char *skip;
+ const char *stop;
+};
+
+static int treewalk_skip_de_cb(
+ const char *root, const git_tree_entry *entry, void *payload)
+{
+ struct treewalk_skip_data *data = payload;
+ const char *name = git_tree_entry_name(entry);
+
+ GIT_UNUSED(root);
+
+ if (git_tree_entry_type(entry) == GIT_OBJ_TREE)
+ data->dirs++;
+ else
+ data->files++;
+
+ if (data->skip && !strcmp(name, data->skip))
+ return 1;
+ else if (data->stop && !strcmp(name, data->stop))
+ return -1;
+ else
+ return 0;
+}
+
+void test_object_tree_walk__2(void)
+{
+ git_oid id;
+ git_tree *tree;
+ struct treewalk_skip_data data;
+
+ /* look up a deep tree */
+ git_oid_fromstr(&id, "ae90f12eea699729ed24555e40b9fd669da12a12");
+ cl_git_pass(git_tree_lookup(&tree, g_repo, &id));
+
+ memset(&data, 0, sizeof(data));
+ data.skip = "de";
+
+ cl_assert_equal_i(0, git_tree_walk(
+ tree, GIT_TREEWALK_PRE, treewalk_skip_de_cb, &data));
+ cl_assert_equal_i(5, data.files);
+ cl_assert_equal_i(3, data.dirs);
+
+ memset(&data, 0, sizeof(data));
+ data.stop = "3.txt";
+
+ cl_assert_equal_i(-1, git_tree_walk(
+ tree, GIT_TREEWALK_PRE, treewalk_skip_de_cb, &data));
+ cl_assert_equal_i(3, data.files);
+ cl_assert_equal_i(2, data.dirs);
+
+ memset(&data, 0, sizeof(data));
+ data.skip = "new.txt";
+
+ cl_assert_equal_i(0, git_tree_walk(
+ tree, GIT_TREEWALK_PRE, treewalk_skip_de_cb, &data));
+ cl_assert_equal_i(7, data.files);
+ cl_assert_equal_i(4, data.dirs);
+
+ memset(&data, 0, sizeof(data));
+ data.stop = "new.txt";
+
+ cl_assert_equal_i(-1, git_tree_walk(
+ tree, GIT_TREEWALK_PRE, treewalk_skip_de_cb, &data));
+ cl_assert_equal_i(7, data.files);
+ cl_assert_equal_i(4, data.dirs);
+
+ git_tree_free(tree);
+}
diff --git a/tests/object/tree/write.c b/tests/object/tree/write.c
new file mode 100644
index 000000000..45356e807
--- /dev/null
+++ b/tests/object/tree/write.c
@@ -0,0 +1,398 @@
+#include "clar_libgit2.h"
+
+#include "tree.h"
+
+static const char *blob_oid = "fa49b077972391ad58037050f2a75f74e3671e92";
+static const char *first_tree = "181037049a54a1eb5fab404658a3a250b44335d7";
+static const char *second_tree = "f60079018b664e4e79329a7ef9559c8d9e0378d1";
+static const char *third_tree = "eb86d8b81d6adbd5290a935d6c9976882de98488";
+
+static git_repository *g_repo;
+
+/* Fixture setup and teardown */
+void test_object_tree_write__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo");
+}
+
+void test_object_tree_write__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_object_tree_write__from_memory(void)
+{
+ /* write a tree from a memory */
+ git_treebuilder *builder;
+ git_tree *tree;
+ git_oid id, bid, rid, id2;
+
+ git_oid_fromstr(&id, first_tree);
+ git_oid_fromstr(&id2, second_tree);
+ git_oid_fromstr(&bid, blob_oid);
+
+ /* create a second tree from first tree using `git_treebuilder_insert`
+ * on REPOSITORY_FOLDER.
+ */
+ cl_git_pass(git_tree_lookup(&tree, g_repo, &id));
+ cl_git_pass(git_treebuilder_create(&builder, tree));
+
+ 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, ".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));
+
+ cl_git_pass(git_treebuilder_insert(
+ NULL, builder, "new.txt", &bid, GIT_FILEMODE_BLOB));
+
+ cl_git_pass(git_treebuilder_write(&rid, g_repo, builder));
+
+ cl_assert(git_oid_cmp(&rid, &id2) == 0);
+
+ git_treebuilder_free(builder);
+ git_tree_free(tree);
+}
+
+void test_object_tree_write__subtree(void)
+{
+ /* write a hierarchical tree from a memory */
+ git_treebuilder *builder;
+ git_tree *tree;
+ git_oid id, bid, subtree_id, id2, id3;
+ git_oid id_hiearar;
+
+ git_oid_fromstr(&id, first_tree);
+ git_oid_fromstr(&id2, second_tree);
+ git_oid_fromstr(&id3, third_tree);
+ git_oid_fromstr(&bid, blob_oid);
+
+ /* create subtree */
+ cl_git_pass(git_treebuilder_create(&builder, NULL));
+ cl_git_pass(git_treebuilder_insert(
+ NULL, builder, "new.txt", &bid, GIT_FILEMODE_BLOB)); /* -V536 */
+ cl_git_pass(git_treebuilder_write(&subtree_id, g_repo, builder));
+ git_treebuilder_free(builder);
+
+ /* create parent tree */
+ cl_git_pass(git_tree_lookup(&tree, g_repo, &id));
+ cl_git_pass(git_treebuilder_create(&builder, tree));
+ cl_git_pass(git_treebuilder_insert(
+ NULL, builder, "new", &subtree_id, GIT_FILEMODE_TREE)); /* -V536 */
+ cl_git_pass(git_treebuilder_write(&id_hiearar, g_repo, builder));
+ git_treebuilder_free(builder);
+ git_tree_free(tree);
+
+ cl_assert(git_oid_cmp(&id_hiearar, &id3) == 0);
+
+ /* check data is correct */
+ cl_git_pass(git_tree_lookup(&tree, g_repo, &id_hiearar));
+ cl_assert(2 == git_tree_entrycount(tree));
+ git_tree_free(tree);
+}
+
+/*
+ * And the Lord said: Is this tree properly sorted?
+ */
+void test_object_tree_write__sorted_subtrees(void)
+{
+ git_treebuilder *builder;
+ unsigned int i;
+ int position_c = -1, position_cake = -1, position_config = -1;
+
+ struct {
+ unsigned int attr;
+ const char *filename;
+ } entries[] = {
+ { GIT_FILEMODE_BLOB, ".gitattributes" },
+ { GIT_FILEMODE_BLOB, ".gitignore" },
+ { GIT_FILEMODE_BLOB, ".htaccess" },
+ { GIT_FILEMODE_BLOB, "Capfile" },
+ { GIT_FILEMODE_BLOB, "Makefile"},
+ { GIT_FILEMODE_BLOB, "README"},
+ { GIT_FILEMODE_TREE, "app"},
+ { GIT_FILEMODE_TREE, "cake"},
+ { GIT_FILEMODE_TREE, "config"},
+ { GIT_FILEMODE_BLOB, "c"},
+ { GIT_FILEMODE_BLOB, "git_test.txt"},
+ { GIT_FILEMODE_BLOB, "htaccess.htaccess"},
+ { GIT_FILEMODE_BLOB, "index.php"},
+ { GIT_FILEMODE_TREE, "plugins"},
+ { GIT_FILEMODE_TREE, "schemas"},
+ { GIT_FILEMODE_TREE, "ssl-certs"},
+ { GIT_FILEMODE_TREE, "vendors"}
+ };
+
+ git_oid blank_oid, tree_oid;
+
+ memset(&blank_oid, 0x0, sizeof(blank_oid));
+
+ cl_git_pass(git_treebuilder_create(&builder, NULL));
+
+ for (i = 0; i < ARRAY_SIZE(entries); ++i) {
+ cl_git_pass(git_treebuilder_insert(NULL,
+ builder, entries[i].filename, &blank_oid, entries[i].attr));
+ }
+
+ cl_git_pass(git_treebuilder_write(&tree_oid, g_repo, builder));
+
+ for (i = 0; i < builder->entries.length; ++i) {
+ git_tree_entry *entry = git_vector_get(&builder->entries, i);
+
+ if (strcmp(entry->filename, "c") == 0)
+ position_c = i;
+
+ if (strcmp(entry->filename, "cake") == 0)
+ position_cake = i;
+
+ if (strcmp(entry->filename, "config") == 0)
+ position_config = i;
+ }
+
+ cl_assert(position_c != -1);
+ cl_assert(position_cake != -1);
+ cl_assert(position_config != -1);
+
+ cl_assert(position_c < position_cake);
+ cl_assert(position_cake < position_config);
+
+ git_treebuilder_free(builder);
+}
+
+static struct {
+ unsigned int attr;
+ const char *filename;
+} _entries[] = {
+ { GIT_FILEMODE_BLOB, "aardvark" },
+ { GIT_FILEMODE_BLOB, ".first" },
+ { GIT_FILEMODE_BLOB, "apple" },
+ { GIT_FILEMODE_BLOB, "last"},
+ { GIT_FILEMODE_BLOB, "apple_after"},
+ { GIT_FILEMODE_BLOB, "after_aardvark"},
+ { 0, NULL },
+};
+
+void test_object_tree_write__removing_and_re_adding_in_treebuilder(void)
+{
+ git_treebuilder *builder;
+ int i, aardvark_i, apple_i, apple_after_i, apple_extra_i, last_i;
+ git_oid blank_oid, tree_oid;
+ git_tree *tree;
+
+ memset(&blank_oid, 0x0, sizeof(blank_oid));
+
+ cl_git_pass(git_treebuilder_create(&builder, NULL));
+
+ cl_assert_equal_i(0, (int)git_treebuilder_entrycount(builder));
+
+ for (i = 0; _entries[i].filename; ++i)
+ cl_git_pass(git_treebuilder_insert(NULL,
+ builder, _entries[i].filename, &blank_oid, _entries[i].attr));
+
+ cl_assert_equal_i(6, (int)git_treebuilder_entrycount(builder));
+
+ cl_git_pass(git_treebuilder_remove(builder, "apple"));
+ cl_assert_equal_i(5, (int)git_treebuilder_entrycount(builder));
+
+ cl_git_pass(git_treebuilder_remove(builder, "apple_after"));
+ cl_assert_equal_i(4, (int)git_treebuilder_entrycount(builder));
+
+ cl_git_pass(git_treebuilder_insert(
+ NULL, builder, "before_last", &blank_oid, GIT_FILEMODE_BLOB));
+ cl_assert_equal_i(5, (int)git_treebuilder_entrycount(builder));
+
+ /* reinsert apple_after */
+ cl_git_pass(git_treebuilder_insert(
+ NULL, builder, "apple_after", &blank_oid, GIT_FILEMODE_BLOB));
+ cl_assert_equal_i(6, (int)git_treebuilder_entrycount(builder));
+
+ cl_git_pass(git_treebuilder_remove(builder, "last"));
+ cl_assert_equal_i(5, (int)git_treebuilder_entrycount(builder));
+
+ /* reinsert last */
+ cl_git_pass(git_treebuilder_insert(
+ NULL, builder, "last", &blank_oid, GIT_FILEMODE_BLOB));
+ cl_assert_equal_i(6, (int)git_treebuilder_entrycount(builder));
+
+ cl_git_pass(git_treebuilder_insert(
+ NULL, builder, "apple_extra", &blank_oid, GIT_FILEMODE_BLOB));
+ cl_assert_equal_i(7, (int)git_treebuilder_entrycount(builder));
+
+ cl_git_pass(git_treebuilder_write(&tree_oid, g_repo, builder));
+
+ git_treebuilder_free(builder);
+
+ cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_oid));
+
+ cl_assert_equal_i(7, (int)git_tree_entrycount(tree));
+
+ cl_assert(git_tree_entry_byname(tree, ".first") != NULL);
+ cl_assert(git_tree_entry_byname(tree, "apple") == NULL);
+ cl_assert(git_tree_entry_byname(tree, "apple_after") != NULL);
+ cl_assert(git_tree_entry_byname(tree, "apple_extra") != NULL);
+ cl_assert(git_tree_entry_byname(tree, "last") != NULL);
+
+ aardvark_i = apple_i = apple_after_i = apple_extra_i = last_i = -1;
+
+ for (i = 0; i < 7; ++i) {
+ const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
+
+ if (!strcmp(entry->filename, "aardvark"))
+ aardvark_i = i;
+ else if (!strcmp(entry->filename, "apple"))
+ apple_i = i;
+ else if (!strcmp(entry->filename, "apple_after"))
+ apple_after_i = i;
+ else if (!strcmp(entry->filename, "apple_extra"))
+ apple_extra_i = i;
+ else if (!strcmp(entry->filename, "last"))
+ last_i = i;
+ }
+
+ cl_assert_equal_i(-1, apple_i);
+ cl_assert_equal_i(6, last_i);
+ cl_assert(aardvark_i < apple_after_i);
+ cl_assert(apple_after_i < apple_extra_i);
+
+ git_tree_free(tree);
+}
+
+static int treebuilder_filter_prefixed(
+ const git_tree_entry *entry, void *payload)
+{
+ return !git__prefixcmp(git_tree_entry_name(entry), payload);
+}
+
+void test_object_tree_write__filtering(void)
+{
+ git_treebuilder *builder;
+ int i;
+ git_oid blank_oid, tree_oid;
+ git_tree *tree;
+
+ memset(&blank_oid, 0x0, sizeof(blank_oid));
+
+ cl_git_pass(git_treebuilder_create(&builder, NULL));
+
+ for (i = 0; _entries[i].filename; ++i)
+ cl_git_pass(git_treebuilder_insert(NULL,
+ builder, _entries[i].filename, &blank_oid, _entries[i].attr));
+
+ cl_assert_equal_i(6, (int)git_treebuilder_entrycount(builder));
+
+ cl_assert(git_treebuilder_get(builder, "apple") != NULL);
+ cl_assert(git_treebuilder_get(builder, "aardvark") != NULL);
+ cl_assert(git_treebuilder_get(builder, "last") != NULL);
+
+ git_treebuilder_filter(builder, treebuilder_filter_prefixed, "apple");
+
+ cl_assert_equal_i(4, (int)git_treebuilder_entrycount(builder));
+
+ cl_assert(git_treebuilder_get(builder, "apple") == NULL);
+ cl_assert(git_treebuilder_get(builder, "aardvark") != NULL);
+ cl_assert(git_treebuilder_get(builder, "last") != NULL);
+
+ git_treebuilder_filter(builder, treebuilder_filter_prefixed, "a");
+
+ cl_assert_equal_i(2, (int)git_treebuilder_entrycount(builder));
+
+ cl_assert(git_treebuilder_get(builder, "aardvark") == NULL);
+ cl_assert(git_treebuilder_get(builder, "last") != NULL);
+
+ cl_git_pass(git_treebuilder_write(&tree_oid, g_repo, builder));
+
+ git_treebuilder_free(builder);
+
+ cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_oid));
+
+ cl_assert_equal_i(2, (int)git_tree_entrycount(tree));
+
+ git_tree_free(tree);
+}
+
+void test_object_tree_write__cruel_paths(void)
+{
+ static const char *the_paths[] = {
+ "C:\\",
+ " : * ? \" \n < > |",
+ "a\\b",
+ "\\\\b\a",
+ ":\\",
+ "COM1",
+ "foo.aux",
+ REP1024("1234"), /* 4096 char string */
+ REP1024("12345678"), /* 8192 char string */
+ "\xC5\xAA\x6E\xC4\xAD\x63\xC5\x8D\x64\x65\xCC\xBD", /* ŪnÄ­cÅde̽ */
+ NULL
+ };
+ git_treebuilder *builder;
+ git_tree *tree;
+ git_oid id, bid, subid;
+ const char **scan;
+ int count = 0, i, j;
+ git_tree_entry *te;
+
+ git_oid_fromstr(&bid, blob_oid);
+
+ /* create tree */
+ cl_git_pass(git_treebuilder_create(&builder, NULL));
+ for (scan = the_paths; *scan; ++scan) {
+ cl_git_pass(git_treebuilder_insert(
+ NULL, builder, *scan, &bid, GIT_FILEMODE_BLOB));
+ count++;
+ }
+ cl_git_pass(git_treebuilder_write(&id, g_repo, builder));
+ git_treebuilder_free(builder);
+
+ /* check data is correct */
+ cl_git_pass(git_tree_lookup(&tree, g_repo, &id));
+
+ cl_assert_equal_i(count, git_tree_entrycount(tree));
+
+ for (scan = the_paths; *scan; ++scan) {
+ const git_tree_entry *cte = git_tree_entry_byname(tree, *scan);
+ cl_assert(cte != NULL);
+ cl_assert_equal_s(*scan, git_tree_entry_name(cte));
+ }
+ for (scan = the_paths; *scan; ++scan) {
+ cl_git_pass(git_tree_entry_bypath(&te, tree, *scan));
+ cl_assert_equal_s(*scan, git_tree_entry_name(te));
+ git_tree_entry_free(te);
+ }
+
+ git_tree_free(tree);
+
+ /* let's try longer paths */
+ cl_git_pass(git_treebuilder_create(&builder, NULL));
+ for (scan = the_paths; *scan; ++scan) {
+ cl_git_pass(git_treebuilder_insert(
+ NULL, builder, *scan, &id, GIT_FILEMODE_TREE));
+ }
+ cl_git_pass(git_treebuilder_write(&subid, g_repo, builder));
+ git_treebuilder_free(builder);
+
+ /* check data is correct */
+ cl_git_pass(git_tree_lookup(&tree, g_repo, &subid));
+
+ cl_assert_equal_i(count, git_tree_entrycount(tree));
+
+ for (i = 0; i < count; ++i) {
+ for (j = 0; j < count; ++j) {
+ git_buf b = GIT_BUF_INIT;
+ cl_git_pass(git_buf_joinpath(&b, the_paths[i], the_paths[j]));
+ cl_git_pass(git_tree_entry_bypath(&te, tree, b.ptr));
+ cl_assert_equal_s(the_paths[j], git_tree_entry_name(te));
+ git_tree_entry_free(te);
+ git_buf_free(&b);
+ }
+ }
+
+ git_tree_free(tree);
+}
diff --git a/tests/odb/alternates.c b/tests/odb/alternates.c
new file mode 100644
index 000000000..c75f6feaa
--- /dev/null
+++ b/tests/odb/alternates.c
@@ -0,0 +1,80 @@
+#include "clar_libgit2.h"
+#include "odb.h"
+#include "filebuf.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)
+{
+ size_t i;
+
+ git_buf_free(&destpath);
+ git_buf_free(&filepath);
+
+ for (i = 0; i < ARRAY_SIZE(paths); i++)
+ cl_fixture_cleanup(paths[i]);
+}
+
+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, 0666));
+ git_filebuf_printf(&file, "%s\n", git_buf_cstr(&destpath));
+ cl_git_pass(git_filebuf_commit(&file));
+
+ 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/odb/backend/nobackend.c b/tests/odb/backend/nobackend.c
new file mode 100644
index 000000000..783641e8f
--- /dev/null
+++ b/tests/odb/backend/nobackend.c
@@ -0,0 +1,46 @@
+#include "clar_libgit2.h"
+#include "repository.h"
+#include "git2/sys/repository.h"
+
+static git_repository *_repo;
+
+void test_odb_backend_nobackend__initialize(void)
+{
+ git_config *config;
+ git_odb *odb;
+ git_refdb *refdb;
+
+ cl_git_pass(git_repository_new(&_repo));
+ cl_git_pass(git_config_new(&config));
+ cl_git_pass(git_odb_new(&odb));
+ cl_git_pass(git_refdb_new(&refdb, _repo));
+
+ git_repository_set_config(_repo, config);
+ git_repository_set_odb(_repo, odb);
+ git_repository_set_refdb(_repo, refdb);
+
+ /* The set increases the refcount and we don't want them anymore */
+ git_config_free(config);
+ git_odb_free(odb);
+ git_refdb_free(refdb);
+}
+
+void test_odb_backend_nobackend__cleanup(void)
+{
+ git_repository_free(_repo);
+}
+
+void test_odb_backend_nobackend__write_fails_gracefully(void)
+{
+ git_oid id;
+ git_odb *odb;
+ const git_error *err;
+
+ git_repository_odb(&odb, _repo);
+ cl_git_fail(git_odb_write(&id, odb, "Hello world!\n", 13, GIT_OBJ_BLOB));
+
+ err = giterr_last();
+ cl_assert_equal_s(err->message, "Cannot write object - unsupported in the loaded odb backends");
+
+ git_odb_free(odb);
+}
diff --git a/tests/odb/backend/nonrefreshing.c b/tests/odb/backend/nonrefreshing.c
new file mode 100644
index 000000000..b43529479
--- /dev/null
+++ b/tests/odb/backend/nonrefreshing.c
@@ -0,0 +1,274 @@
+#include "clar_libgit2.h"
+#include "git2/sys/odb_backend.h"
+#include "repository.h"
+
+typedef struct fake_backend {
+ git_odb_backend parent;
+
+ git_error_code error_code;
+
+ int exists_calls;
+ int read_calls;
+ int read_header_calls;
+ int read_prefix_calls;
+} fake_backend;
+
+static git_repository *_repo;
+static fake_backend *_fake;
+static git_oid _oid;
+
+static int fake_backend__exists(git_odb_backend *backend, const git_oid *oid)
+{
+ fake_backend *fake;
+
+ GIT_UNUSED(oid);
+
+ fake = (fake_backend *)backend;
+
+ fake->exists_calls++;
+
+ return (fake->error_code == GIT_OK);
+}
+
+static int fake_backend__read(
+ void **buffer_p, size_t *len_p, git_otype *type_p,
+ git_odb_backend *backend, const git_oid *oid)
+{
+ fake_backend *fake;
+
+ GIT_UNUSED(buffer_p);
+ GIT_UNUSED(len_p);
+ GIT_UNUSED(type_p);
+ GIT_UNUSED(oid);
+
+ fake = (fake_backend *)backend;
+
+ fake->read_calls++;
+
+ *len_p = 0;
+ *buffer_p = NULL;
+ *type_p = GIT_OBJ_BLOB;
+
+ return fake->error_code;
+}
+
+static int fake_backend__read_header(
+ size_t *len_p, git_otype *type_p,
+ git_odb_backend *backend, const git_oid *oid)
+{
+ fake_backend *fake;
+
+ GIT_UNUSED(len_p);
+ GIT_UNUSED(type_p);
+ GIT_UNUSED(oid);
+
+ fake = (fake_backend *)backend;
+
+ fake->read_header_calls++;
+
+ *len_p = 0;
+ *type_p = GIT_OBJ_BLOB;
+
+ return fake->error_code;
+}
+
+static int fake_backend__read_prefix(
+ git_oid *out_oid, void **buffer_p, size_t *len_p, git_otype *type_p,
+ git_odb_backend *backend, const git_oid *short_oid, size_t len)
+{
+ fake_backend *fake;
+
+ GIT_UNUSED(out_oid);
+ GIT_UNUSED(buffer_p);
+ GIT_UNUSED(len_p);
+ GIT_UNUSED(type_p);
+ GIT_UNUSED(short_oid);
+ GIT_UNUSED(len);
+
+ fake = (fake_backend *)backend;
+
+ fake->read_prefix_calls++;
+
+ *len_p = 0;
+ *buffer_p = NULL;
+ *type_p = GIT_OBJ_BLOB;
+
+ return fake->error_code;
+}
+
+static void fake_backend__free(git_odb_backend *_backend)
+{
+ fake_backend *backend;
+
+ backend = (fake_backend *)_backend;
+
+ git__free(backend);
+}
+
+static int build_fake_backend(
+ git_odb_backend **out,
+ git_error_code error_code)
+{
+ fake_backend *backend;
+
+ backend = git__calloc(1, sizeof(fake_backend));
+ GITERR_CHECK_ALLOC(backend);
+
+ backend->parent.version = GIT_ODB_BACKEND_VERSION;
+
+ backend->parent.refresh = NULL;
+ backend->error_code = error_code;
+
+ backend->parent.read = fake_backend__read;
+ backend->parent.read_prefix = fake_backend__read_prefix;
+ backend->parent.read_header = fake_backend__read_header;
+ backend->parent.exists = fake_backend__exists;
+ backend->parent.free = &fake_backend__free;
+
+ *out = (git_odb_backend *)backend;
+
+ return 0;
+}
+
+static void setup_repository_and_backend(git_error_code error_code)
+{
+ git_odb *odb = NULL;
+ git_odb_backend *backend = NULL;
+
+ _repo = cl_git_sandbox_init("testrepo.git");
+
+ cl_git_pass(build_fake_backend(&backend, error_code));
+
+ cl_git_pass(git_repository_odb__weakptr(&odb, _repo));
+ cl_git_pass(git_odb_add_backend(odb, backend, 10));
+
+ _fake = (fake_backend *)backend;
+
+ cl_git_pass(git_oid_fromstr(&_oid, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
+}
+
+void test_odb_backend_nonrefreshing__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_odb_backend_nonrefreshing__exists_is_invoked_once_on_failure(void)
+{
+ git_odb *odb;
+
+ setup_repository_and_backend(GIT_ENOTFOUND);
+
+ cl_git_pass(git_repository_odb__weakptr(&odb, _repo));
+ cl_assert_equal_b(false, git_odb_exists(odb, &_oid));
+
+ cl_assert_equal_i(1, _fake->exists_calls);
+}
+
+void test_odb_backend_nonrefreshing__read_is_invoked_once_on_failure(void)
+{
+ git_object *obj;
+
+ setup_repository_and_backend(GIT_ENOTFOUND);
+
+ cl_git_fail_with(
+ git_object_lookup(&obj, _repo, &_oid, GIT_OBJ_ANY),
+ GIT_ENOTFOUND);
+
+ cl_assert_equal_i(1, _fake->read_calls);
+}
+
+void test_odb_backend_nonrefreshing__readprefix_is_invoked_once_on_failure(void)
+{
+ git_object *obj;
+
+ setup_repository_and_backend(GIT_ENOTFOUND);
+
+ cl_git_fail_with(
+ git_object_lookup_prefix(&obj, _repo, &_oid, 7, GIT_OBJ_ANY),
+ GIT_ENOTFOUND);
+
+ cl_assert_equal_i(1, _fake->read_prefix_calls);
+}
+
+void test_odb_backend_nonrefreshing__readheader_is_invoked_once_on_failure(void)
+{
+ git_odb *odb;
+ size_t len;
+ git_otype type;
+
+ setup_repository_and_backend(GIT_ENOTFOUND);
+
+ cl_git_pass(git_repository_odb__weakptr(&odb, _repo));
+
+ cl_git_fail_with(
+ git_odb_read_header(&len, &type, odb, &_oid),
+ GIT_ENOTFOUND);
+
+ cl_assert_equal_i(1, _fake->read_header_calls);
+}
+
+void test_odb_backend_nonrefreshing__exists_is_invoked_once_on_success(void)
+{
+ git_odb *odb;
+
+ setup_repository_and_backend(GIT_OK);
+
+ cl_git_pass(git_repository_odb__weakptr(&odb, _repo));
+ cl_assert_equal_b(true, git_odb_exists(odb, &_oid));
+
+ cl_assert_equal_i(1, _fake->exists_calls);
+}
+
+void test_odb_backend_nonrefreshing__read_is_invoked_once_on_success(void)
+{
+ git_object *obj;
+
+ setup_repository_and_backend(GIT_OK);
+
+ cl_git_pass(git_object_lookup(&obj, _repo, &_oid, GIT_OBJ_ANY));
+
+ cl_assert_equal_i(1, _fake->read_calls);
+
+ git_object_free(obj);
+}
+
+void test_odb_backend_nonrefreshing__readprefix_is_invoked_once_on_success(void)
+{
+ git_object *obj;
+
+ setup_repository_and_backend(GIT_OK);
+
+ cl_git_pass(git_object_lookup_prefix(&obj, _repo, &_oid, 7, GIT_OBJ_ANY));
+
+ cl_assert_equal_i(1, _fake->read_prefix_calls);
+
+ git_object_free(obj);
+}
+
+void test_odb_backend_nonrefreshing__readheader_is_invoked_once_on_success(void)
+{
+ git_odb *odb;
+ size_t len;
+ git_otype type;
+
+ setup_repository_and_backend(GIT_OK);
+
+ cl_git_pass(git_repository_odb__weakptr(&odb, _repo));
+
+ cl_git_pass(git_odb_read_header(&len, &type, odb, &_oid));
+
+ cl_assert_equal_i(1, _fake->read_header_calls);
+}
+
+void test_odb_backend_nonrefreshing__read_is_invoked_once_when_revparsing_a_full_oid(void)
+{
+ git_object *obj;
+
+ setup_repository_and_backend(GIT_ENOTFOUND);
+
+ cl_git_fail_with(
+ git_revparse_single(&obj, _repo, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"),
+ GIT_ENOTFOUND);
+
+ cl_assert_equal_i(1, _fake->read_calls);
+}
diff --git a/tests/odb/foreach.c b/tests/odb/foreach.c
new file mode 100644
index 000000000..56daf7574
--- /dev/null
+++ b/tests/odb/foreach.c
@@ -0,0 +1,106 @@
+#include "clar_libgit2.h"
+#include "odb.h"
+#include "git2/odb_backend.h"
+#include "pack.h"
+#include "buffer.h"
+
+static git_odb *_odb;
+static git_repository *_repo;
+
+void test_odb_foreach__cleanup(void)
+{
+ git_odb_free(_odb);
+ git_repository_free(_repo);
+
+ _odb = NULL;
+ _repo = NULL;
+}
+
+static int foreach_cb(const git_oid *oid, void *data)
+{
+ int *nobj = data;
+ (*nobj)++;
+
+ GIT_UNUSED(oid);
+
+ return 0;
+}
+
+/*
+ * $ git --git-dir tests/resources/testrepo.git count-objects --verbose
+ * count: 47
+ * size: 4
+ * in-pack: 1640
+ * packs: 3
+ * size-pack: 425
+ * prune-packable: 0
+ * garbage: 0
+ */
+void test_odb_foreach__foreach(void)
+{
+ int nobj = 0;
+
+ cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git")));
+ git_repository_odb(&_odb, _repo);
+
+ cl_git_pass(git_odb_foreach(_odb, foreach_cb, &nobj));
+ cl_assert_equal_i(47 + 1640, nobj); /* count + in-pack */
+}
+
+void test_odb_foreach__one_pack(void)
+{
+ git_odb_backend *backend = NULL;
+ int nobj = 0;
+
+ cl_git_pass(git_odb_new(&_odb));
+ cl_git_pass(git_odb_backend_one_pack(&backend, cl_fixture("testrepo.git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx")));
+ cl_git_pass(git_odb_add_backend(_odb, backend, 1));
+ _repo = NULL;
+
+ cl_git_pass(git_odb_foreach(_odb, foreach_cb, &nobj));
+ cl_assert(nobj == 1628);
+}
+
+static int foreach_stop_cb(const git_oid *oid, void *data)
+{
+ int *nobj = data;
+ (*nobj)++;
+
+ GIT_UNUSED(oid);
+
+ return (*nobj == 1000) ? -321 : 0;
+}
+
+void test_odb_foreach__interrupt_foreach(void)
+{
+ int nobj = 0;
+
+ cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git")));
+ git_repository_odb(&_odb, _repo);
+
+ cl_assert_equal_i(-321, git_odb_foreach(_odb, foreach_stop_cb, &nobj));
+ cl_assert(nobj == 1000);
+}
+
+void test_odb_foreach__files_in_objects_dir(void)
+{
+ git_repository *repo;
+ git_odb *odb;
+ git_buf buf = GIT_BUF_INIT;
+ size_t nobj = 0;
+
+ cl_fixture_sandbox("testrepo.git");
+ cl_git_pass(git_repository_open(&repo, "testrepo.git"));
+
+ cl_git_pass(git_buf_printf(&buf, "%s/objects/somefile", git_repository_path(repo)));
+ cl_git_mkfile(buf.ptr, "");
+ git_buf_free(&buf);
+
+ cl_git_pass(git_repository_odb(&odb, repo));
+ cl_git_pass(git_odb_foreach(odb, foreach_cb, &nobj));
+ cl_assert_equal_i(47 + 1640, nobj); /* count + in-pack */
+
+ git_odb_free(odb);
+ git_repository_free(repo);
+ cl_fixture_cleanup("testrepo.git");
+}
diff --git a/tests/odb/loose.c b/tests/odb/loose.c
new file mode 100644
index 000000000..c91927c4a
--- /dev/null
+++ b/tests/odb/loose.c
@@ -0,0 +1,152 @@
+#include "clar_libgit2.h"
+#include "odb.h"
+#include "git2/odb_backend.h"
+#include "posix.h"
+#include "loose_data.h"
+
+#ifdef __ANDROID_API__
+# define S_IREAD S_IRUSR
+# define S_IWRITE S_IWUSR
+#endif
+
+static void write_object_files(object_data *d)
+{
+ int fd;
+
+ if (p_mkdir(d->dir, GIT_OBJECT_DIR_MODE) < 0)
+ cl_assert(errno == EEXIST);
+
+ cl_assert((fd = p_creat(d->file, S_IREAD | S_IWRITE)) >= 0);
+ cl_must_pass(p_write(fd, d->bytes, d->blen));
+
+ p_close(fd);
+}
+
+static void cmp_objects(git_rawobj *o, object_data *d)
+{
+ cl_assert(o->type == git_object_string2type(d->type));
+ cl_assert(o->len == d->dlen);
+
+ if (o->len > 0)
+ cl_assert(memcmp(o->data, d->data, o->len) == 0);
+}
+
+static void test_read_object(object_data *data)
+{
+ git_oid id;
+ git_odb_object *obj;
+ git_odb *odb;
+ git_rawobj tmp;
+
+ write_object_files(data);
+
+ cl_git_pass(git_odb_open(&odb, "test-objects"));
+ cl_git_pass(git_oid_fromstr(&id, data->id));
+ cl_git_pass(git_odb_read(&obj, odb, &id));
+
+ tmp.data = obj->buffer;
+ tmp.len = obj->cached.size;
+ tmp.type = obj->cached.type;
+
+ cmp_objects(&tmp, data);
+
+ git_odb_object_free(obj);
+ git_odb_free(odb);
+}
+
+void test_odb_loose__initialize(void)
+{
+ cl_must_pass(p_mkdir("test-objects", GIT_OBJECT_DIR_MODE));
+}
+
+void test_odb_loose__cleanup(void)
+{
+ cl_fixture_cleanup("test-objects");
+}
+
+void test_odb_loose__exists(void)
+{
+ git_oid id, id2;
+ git_odb *odb;
+
+ write_object_files(&one);
+ cl_git_pass(git_odb_open(&odb, "test-objects"));
+
+ cl_git_pass(git_oid_fromstr(&id, one.id));
+ cl_assert(git_odb_exists(odb, &id));
+
+ cl_git_pass(git_oid_fromstrp(&id, "8b137891"));
+ cl_git_pass(git_odb_exists_prefix(&id2, odb, &id, 8));
+ cl_assert_equal_i(0, git_oid_streq(&id2, one.id));
+
+ /* Test for a missing object */
+ cl_git_pass(git_oid_fromstr(&id, "8b137891791fe96927ad78e64b0aad7bded08baa"));
+ cl_assert(!git_odb_exists(odb, &id));
+
+ cl_git_pass(git_oid_fromstrp(&id, "8b13789a"));
+ cl_assert_equal_i(GIT_ENOTFOUND, git_odb_exists_prefix(&id2, odb, &id, 8));
+
+ git_odb_free(odb);
+}
+
+void test_odb_loose__simple_reads(void)
+{
+ test_read_object(&commit);
+ test_read_object(&tree);
+ test_read_object(&tag);
+ test_read_object(&zero);
+ test_read_object(&one);
+ test_read_object(&two);
+ test_read_object(&some);
+}
+
+void test_write_object_permission(
+ mode_t dir_mode, mode_t file_mode,
+ mode_t expected_dir_mode, mode_t expected_file_mode)
+{
+ git_odb *odb;
+ git_odb_backend *backend;
+ git_oid oid;
+ struct stat statbuf;
+ mode_t mask, os_mask;
+
+ /* Windows does not return group/user bits from stat,
+ * files are never executable.
+ */
+#ifdef GIT_WIN32
+ os_mask = 0600;
+#else
+ os_mask = 0777;
+#endif
+
+ mask = p_umask(0);
+ p_umask(mask);
+
+ cl_git_pass(git_odb_new(&odb));
+ cl_git_pass(git_odb_backend_loose(&backend, "test-objects", -1, 0, dir_mode, file_mode));
+ cl_git_pass(git_odb_add_backend(odb, backend, 1));
+ cl_git_pass(git_odb_write(&oid, odb, "Test data\n", 10, GIT_OBJ_BLOB));
+
+ cl_git_pass(p_stat("test-objects/67", &statbuf));
+ cl_assert_equal_i(statbuf.st_mode & os_mask, (expected_dir_mode & ~mask) & os_mask);
+
+ cl_git_pass(p_stat("test-objects/67/b808feb36201507a77f85e6d898f0a2836e4a5", &statbuf));
+ cl_assert_equal_i(statbuf.st_mode & os_mask, (expected_file_mode & ~mask) & os_mask);
+
+ git_odb_free(odb);
+}
+
+void test_odb_loose__permissions_standard(void)
+{
+ test_write_object_permission(0, 0, GIT_OBJECT_DIR_MODE, GIT_OBJECT_FILE_MODE);
+}
+
+void test_odb_loose_permissions_readonly(void)
+{
+ test_write_object_permission(0777, 0444, 0777, 0444);
+}
+
+void test_odb_loose__permissions_readwrite(void)
+{
+ test_write_object_permission(0777, 0666, 0777, 0666);
+}
diff --git a/tests/t02-data.h b/tests/odb/loose_data.h
index 705a2d7af..c10c9bc7f 100644
--- a/tests/t02-data.h
+++ b/tests/odb/loose_data.h
@@ -1,5 +1,13 @@
-
-static char *odb_dir = "test-objects";
+typedef struct object_data {
+ unsigned char *bytes; /* (compressed) bytes stored in object store */
+ size_t blen; /* length of data in object store */
+ char *id; /* object id (sha1) */
+ char *type; /* object type */
+ char *dir; /* object store (fan-out) directory name */
+ char *file; /* object store filename */
+ unsigned char *data; /* (uncompressed) object data */
+ size_t dlen; /* length of (uncompressed) object data */
+} object_data;
/* one == 8b137891791fe96927ad78e64b0aad7bded08bdc */
static unsigned char one_bytes[] = {
@@ -512,4 +520,3 @@ static object_data some = {
some_data,
sizeof(some_data),
};
-
diff --git a/tests/odb/mixed.c b/tests/odb/mixed.c
new file mode 100644
index 000000000..ceba4ec81
--- /dev/null
+++ b/tests/odb/mixed.c
@@ -0,0 +1,110 @@
+#include "clar_libgit2.h"
+#include "odb.h"
+
+static git_odb *_odb;
+
+void test_odb_mixed__initialize(void)
+{
+ cl_git_pass(git_odb_open(&_odb, cl_fixture("duplicate.git/objects")));
+}
+
+void test_odb_mixed__cleanup(void)
+{
+ git_odb_free(_odb);
+ _odb = NULL;
+}
+
+void test_odb_mixed__dup_oid(void) {
+ const char hex[] = "ce013625030ba8dba906f756967f9e9ca394464a";
+ const char short_hex[] = "ce01362";
+ git_oid oid;
+ git_odb_object *obj;
+
+ cl_git_pass(git_oid_fromstr(&oid, hex));
+ cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, GIT_OID_HEXSZ));
+ git_odb_object_free(obj);
+
+ cl_git_pass(git_odb_exists_prefix(NULL, _odb, &oid, GIT_OID_HEXSZ));
+
+ cl_git_pass(git_oid_fromstrn(&oid, short_hex, sizeof(short_hex) - 1));
+ cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, sizeof(short_hex) - 1));
+ git_odb_object_free(obj);
+
+ cl_git_pass(git_odb_exists_prefix(NULL, _odb, &oid, sizeof(short_hex) - 1));
+}
+
+/* some known sha collisions of file content:
+ * 'aabqhq' and 'aaazvc' with prefix 'dea509d0' (+ '9' and + 'b')
+ * 'aaeufo' and 'aaaohs' with prefix '81b5bff5' (+ 'f' and + 'b')
+ * 'aafewy' and 'aaepta' with prefix '739e3c4c'
+ * 'aahsyn' and 'aadrjg' with prefix '0ddeaded' (+ '9' and + 'e')
+ */
+
+void test_odb_mixed__dup_oid_prefix_0(void) {
+ char hex[10];
+ git_oid oid, found;
+ git_odb_object *obj;
+
+ /* ambiguous in the same pack file */
+
+ strncpy(hex, "dea509d0", sizeof(hex));
+ cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex)));
+ cl_assert_equal_i(
+ GIT_EAMBIGUOUS, git_odb_read_prefix(&obj, _odb, &oid, strlen(hex)));
+ cl_assert_equal_i(
+ GIT_EAMBIGUOUS, git_odb_exists_prefix(&found, _odb, &oid, strlen(hex)));
+
+ strncpy(hex, "dea509d09", sizeof(hex));
+ cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex)));
+ cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex)));
+ cl_git_pass(git_odb_exists_prefix(&found, _odb, &oid, strlen(hex)));
+ cl_assert(git_oid_equal(&found, git_odb_object_id(obj)));
+ git_odb_object_free(obj);
+
+ strncpy(hex, "dea509d0b", sizeof(hex));
+ cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex)));
+ cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex)));
+ git_odb_object_free(obj);
+
+ /* ambiguous in different pack files */
+
+ strncpy(hex, "81b5bff5", sizeof(hex));
+ cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex)));
+ cl_assert_equal_i(
+ GIT_EAMBIGUOUS, git_odb_read_prefix(&obj, _odb, &oid, strlen(hex)));
+ cl_assert_equal_i(
+ GIT_EAMBIGUOUS, git_odb_exists_prefix(&found, _odb, &oid, strlen(hex)));
+
+ strncpy(hex, "81b5bff5b", sizeof(hex));
+ cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex)));
+ cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex)));
+ cl_git_pass(git_odb_exists_prefix(&found, _odb, &oid, strlen(hex)));
+ cl_assert(git_oid_equal(&found, git_odb_object_id(obj)));
+ git_odb_object_free(obj);
+
+ strncpy(hex, "81b5bff5f", sizeof(hex));
+ cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex)));
+ cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex)));
+ git_odb_object_free(obj);
+
+ /* ambiguous in pack file and loose */
+
+ strncpy(hex, "0ddeaded", sizeof(hex));
+ cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex)));
+ cl_assert_equal_i(
+ GIT_EAMBIGUOUS, git_odb_read_prefix(&obj, _odb, &oid, strlen(hex)));
+ cl_assert_equal_i(
+ GIT_EAMBIGUOUS, git_odb_exists_prefix(&found, _odb, &oid, strlen(hex)));
+
+ strncpy(hex, "0ddeaded9", sizeof(hex));
+ cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex)));
+ cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex)));
+ cl_git_pass(git_odb_exists_prefix(&found, _odb, &oid, strlen(hex)));
+ cl_assert(git_oid_equal(&found, git_odb_object_id(obj)));
+ git_odb_object_free(obj);
+
+ strncpy(hex, "0ddeadede", sizeof(hex));
+ cl_git_pass(git_oid_fromstrn(&oid, hex, strlen(hex)));
+ cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex)));
+ git_odb_object_free(obj);
+}
diff --git a/tests/t02-oids.h b/tests/odb/pack_data.h
index 1a5ed5df0..e6371beb1 100644
--- a/tests/t02-oids.h
+++ b/tests/odb/pack_data.h
@@ -149,4 +149,3 @@ static const char *loose_objects[] = {
"a4a7dce85cf63874e984719f4fdd239f5145052f",
"4a202b346bb0fb0db7eff3cffeb3c70babbd2045"
};
-
diff --git a/tests/odb/pack_data_one.h b/tests/odb/pack_data_one.h
new file mode 100644
index 000000000..13570ba78
--- /dev/null
+++ b/tests/odb/pack_data_one.h
@@ -0,0 +1,19 @@
+/* Just a few to make sure it's working, the rest is tested already */
+static const char *packed_objects_one[] = {
+ "9fcf811e00fa469688943a9152c16d4ee90fb9a9",
+ "a93f42a5b5e9de40fa645a9ff1e276a021c9542b",
+ "12bf5f3e3470d90db177ccf1b5e8126409377fc6",
+ "ed1ea164cdbe3c4b200fb4fa19861ea90eaee222",
+ "dfae6ed8f6dd8acc3b40a31811ea316239223559",
+ "aefe66d192771201e369fde830530f4475beec30",
+ "775e4b4c1296e9e3104f2a36ca9cf9356a130959",
+ "412ec4e4a6a7419bc1be00561fe474e54cb499fe",
+ "236e7579fed7763be77209efb8708960982f3cb3",
+ "09fe9364461cf60dd1c46b0e9545b1e47bb1a297",
+ "d76d8a6390d1cf32138d98a91b1eb7e0275a12f5",
+ "d0fdf2dcff2f548952eec536ccc6d266550041bc",
+ "a20d733a9fa79fa5b4cbb9639864f93325ec27a6",
+ "785d3fe8e7db5ade2c2242fecd46c32a7f4dc59f",
+ "4d8d0fd9cb6045075385701c3f933ec13345e9c4",
+ "0cfd861bd547b6520d1fc2e190e8359e0a9c9b90"
+};
diff --git a/tests/odb/packed.c b/tests/odb/packed.c
new file mode 100644
index 000000000..b4f549b58
--- /dev/null
+++ b/tests/odb/packed.c
@@ -0,0 +1,79 @@
+#include "clar_libgit2.h"
+#include "odb.h"
+#include "pack_data.h"
+
+static git_odb *_odb;
+
+void test_odb_packed__initialize(void)
+{
+ cl_git_pass(git_odb_open(&_odb, cl_fixture("testrepo.git/objects")));
+}
+
+void test_odb_packed__cleanup(void)
+{
+ git_odb_free(_odb);
+ _odb = NULL;
+}
+
+void test_odb_packed__mass_read(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(packed_objects); ++i) {
+ git_oid id;
+ git_odb_object *obj;
+
+ cl_git_pass(git_oid_fromstr(&id, packed_objects[i]));
+ cl_assert(git_odb_exists(_odb, &id) == 1);
+ cl_git_pass(git_odb_read(&obj, _odb, &id));
+
+ git_odb_object_free(obj);
+ }
+}
+
+void test_odb_packed__read_header_0(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(packed_objects); ++i) {
+ git_oid id;
+ git_odb_object *obj;
+ size_t len;
+ git_otype type;
+
+ cl_git_pass(git_oid_fromstr(&id, packed_objects[i]));
+
+ cl_git_pass(git_odb_read(&obj, _odb, &id));
+ cl_git_pass(git_odb_read_header(&len, &type, _odb, &id));
+
+ cl_assert(obj->cached.size == len);
+ cl_assert(obj->cached.type == type);
+
+ git_odb_object_free(obj);
+ }
+}
+
+void test_odb_packed__read_header_1(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(loose_objects); ++i) {
+ git_oid id;
+ git_odb_object *obj;
+ size_t len;
+ git_otype type;
+
+ cl_git_pass(git_oid_fromstr(&id, loose_objects[i]));
+
+ cl_assert(git_odb_exists(_odb, &id) == 1);
+
+ cl_git_pass(git_odb_read(&obj, _odb, &id));
+ cl_git_pass(git_odb_read_header(&len, &type, _odb, &id));
+
+ cl_assert(obj->cached.size == len);
+ cl_assert(obj->cached.type == type);
+
+ git_odb_object_free(obj);
+ }
+}
+
diff --git a/tests/odb/packed_one.c b/tests/odb/packed_one.c
new file mode 100644
index 000000000..0c6ed387b
--- /dev/null
+++ b/tests/odb/packed_one.c
@@ -0,0 +1,60 @@
+#include "clar_libgit2.h"
+#include "git2/odb_backend.h"
+
+#include "pack_data_one.h"
+#include "pack.h"
+
+static git_odb *_odb;
+
+void test_odb_packed_one__initialize(void)
+{
+ git_odb_backend *backend = NULL;
+
+ cl_git_pass(git_odb_new(&_odb));
+ cl_git_pass(git_odb_backend_one_pack(&backend, cl_fixture("testrepo.git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx")));
+ cl_git_pass(git_odb_add_backend(_odb, backend, 1));
+}
+
+void test_odb_packed_one__cleanup(void)
+{
+ git_odb_free(_odb);
+ _odb = NULL;
+}
+
+void test_odb_packed_one__mass_read(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(packed_objects_one); ++i) {
+ git_oid id;
+ git_odb_object *obj;
+
+ cl_git_pass(git_oid_fromstr(&id, packed_objects_one[i]));
+ cl_assert(git_odb_exists(_odb, &id) == 1);
+ cl_git_pass(git_odb_read(&obj, _odb, &id));
+
+ git_odb_object_free(obj);
+ }
+}
+
+void test_odb_packed_one__read_header_0(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(packed_objects_one); ++i) {
+ git_oid id;
+ git_odb_object *obj;
+ size_t len;
+ git_otype type;
+
+ cl_git_pass(git_oid_fromstr(&id, packed_objects_one[i]));
+
+ cl_git_pass(git_odb_read(&obj, _odb, &id));
+ cl_git_pass(git_odb_read_header(&len, &type, _odb, &id));
+
+ cl_assert(obj->cached.size == len);
+ cl_assert(obj->cached.type == type);
+
+ git_odb_object_free(obj);
+ }
+}
diff --git a/tests/odb/sorting.c b/tests/odb/sorting.c
new file mode 100644
index 000000000..147a160c8
--- /dev/null
+++ b/tests/odb/sorting.c
@@ -0,0 +1,69 @@
+#include "clar_libgit2.h"
+#include "git2/sys/odb_backend.h"
+
+typedef struct {
+ git_odb_backend base;
+ size_t position;
+} fake_backend;
+
+static git_odb_backend *new_backend(size_t position)
+{
+ fake_backend *b;
+
+ b = git__calloc(1, sizeof(fake_backend));
+ if (b == NULL)
+ return NULL;
+
+ b->base.version = GIT_ODB_BACKEND_VERSION;
+ b->position = position;
+ return (git_odb_backend *)b;
+}
+
+static void check_backend_sorting(git_odb *odb)
+{
+ size_t i, max_i = git_odb_num_backends(odb);
+ fake_backend *internal;
+
+ for (i = 0; i < max_i; ++i) {
+ cl_git_pass(git_odb_get_backend((git_odb_backend **)&internal, odb, i));
+ cl_assert(internal != NULL);
+ cl_assert_equal_sz(i, internal->position);
+ }
+}
+
+static git_odb *_odb;
+
+void test_odb_sorting__initialize(void)
+{
+ cl_git_pass(git_odb_new(&_odb));
+}
+
+void test_odb_sorting__cleanup(void)
+{
+ git_odb_free(_odb);
+ _odb = NULL;
+}
+
+void test_odb_sorting__basic_backends_sorting(void)
+{
+ cl_git_pass(git_odb_add_backend(_odb, new_backend(0), 5));
+ cl_git_pass(git_odb_add_backend(_odb, new_backend(2), 3));
+ cl_git_pass(git_odb_add_backend(_odb, new_backend(1), 4));
+ cl_git_pass(git_odb_add_backend(_odb, new_backend(3), 1));
+
+ check_backend_sorting(_odb);
+}
+
+void test_odb_sorting__alternate_backends_sorting(void)
+{
+ cl_git_pass(git_odb_add_backend(_odb, new_backend(0), 5));
+ cl_git_pass(git_odb_add_backend(_odb, new_backend(2), 3));
+ cl_git_pass(git_odb_add_backend(_odb, new_backend(1), 4));
+ cl_git_pass(git_odb_add_backend(_odb, new_backend(3), 1));
+ cl_git_pass(git_odb_add_alternate(_odb, new_backend(4), 5));
+ cl_git_pass(git_odb_add_alternate(_odb, new_backend(6), 3));
+ cl_git_pass(git_odb_add_alternate(_odb, new_backend(5), 4));
+ cl_git_pass(git_odb_add_alternate(_odb, new_backend(7), 1));
+
+ check_backend_sorting(_odb);
+}
diff --git a/tests/odb/streamwrite.c b/tests/odb/streamwrite.c
new file mode 100644
index 000000000..591a20040
--- /dev/null
+++ b/tests/odb/streamwrite.c
@@ -0,0 +1,56 @@
+#include "clar_libgit2.h"
+#include "git2/odb_backend.h"
+
+static git_repository *repo;
+static git_odb *odb;
+static git_odb_stream *stream;
+
+void test_odb_streamwrite__initialize(void)
+{
+ repo = cl_git_sandbox_init("testrepo.git");
+ cl_git_pass(git_repository_odb(&odb, repo));
+
+ cl_git_pass(git_odb_open_wstream(&stream, odb, 14, GIT_OBJ_BLOB));
+ cl_assert_equal_sz(14, stream->declared_size);
+}
+
+void test_odb_streamwrite__cleanup(void)
+{
+ git_odb_stream_free(stream);
+ git_odb_free(odb);
+ cl_git_sandbox_cleanup();
+}
+
+void test_odb_streamwrite__can_accept_chunks(void)
+{
+ git_oid oid;
+
+ cl_git_pass(git_odb_stream_write(stream, "deadbeef", 8));
+ cl_assert_equal_sz(8, stream->received_bytes);
+
+ cl_git_pass(git_odb_stream_write(stream, "deadbeef", 6));
+ cl_assert_equal_sz(8 + 6, stream->received_bytes);
+
+ cl_git_pass(git_odb_stream_finalize_write(&oid, stream));
+}
+
+void test_odb_streamwrite__can_detect_missing_bytes(void)
+{
+ git_oid oid;
+
+ cl_git_pass(git_odb_stream_write(stream, "deadbeef", 8));
+ cl_assert_equal_sz(8, stream->received_bytes);
+
+ cl_git_pass(git_odb_stream_write(stream, "deadbeef", 4));
+ cl_assert_equal_sz(8 + 4, stream->received_bytes);
+
+ cl_git_fail(git_odb_stream_finalize_write(&oid, stream));
+}
+
+void test_odb_streamwrite__can_detect_additional_bytes(void)
+{
+ cl_git_pass(git_odb_stream_write(stream, "deadbeef", 8));
+ cl_assert_equal_sz(8, stream->received_bytes);
+
+ cl_git_fail(git_odb_stream_write(stream, "deadbeef", 7));
+}
diff --git a/tests/online/clone.c b/tests/online/clone.c
new file mode 100644
index 000000000..8a2a64f95
--- /dev/null
+++ b/tests/online/clone.c
@@ -0,0 +1,319 @@
+#include "clar_libgit2.h"
+
+#include "git2/clone.h"
+#include "git2/cred_helpers.h"
+#include "remote.h"
+#include "fileops.h"
+#include "refs.h"
+
+#define LIVE_REPO_URL "http://github.com/libgit2/TestGitRepository"
+#define LIVE_EMPTYREPO_URL "http://github.com/libgit2/TestEmptyRepository"
+#define BB_REPO_URL "https://libgit2@bitbucket.org/libgit2/testgitrepository.git"
+#define BB_REPO_URL_WITH_PASS "https://libgit2:libgit2@bitbucket.org/libgit2/testgitrepository.git"
+#define BB_REPO_URL_WITH_WRONG_PASS "https://libgit2:wrong@bitbucket.org/libgit2/testgitrepository.git"
+#define ASSEMBLA_REPO_URL "https://libgit2:_Libgit2@git.assembla.com/libgit2-test-repos.git"
+
+static git_repository *g_repo;
+static git_clone_options g_options;
+
+void test_online_clone__initialize(void)
+{
+ git_checkout_options dummy_opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_remote_callbacks dummy_callbacks = GIT_REMOTE_CALLBACKS_INIT;
+
+ g_repo = NULL;
+
+ memset(&g_options, 0, sizeof(git_clone_options));
+ g_options.version = GIT_CLONE_OPTIONS_VERSION;
+ g_options.checkout_opts = dummy_opts;
+ g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
+ g_options.remote_callbacks = dummy_callbacks;
+}
+
+void test_online_clone__cleanup(void)
+{
+ if (g_repo) {
+ git_repository_free(g_repo);
+ g_repo = NULL;
+ }
+ cl_fixture_cleanup("./foo");
+}
+
+void test_online_clone__network_full(void)
+{
+ git_remote *origin;
+
+ cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
+ cl_assert(!git_repository_is_bare(g_repo));
+ cl_git_pass(git_remote_load(&origin, g_repo, "origin"));
+
+ cl_assert_equal_i(GIT_REMOTE_DOWNLOAD_TAGS_AUTO, origin->download_tags);
+
+ git_remote_free(origin);
+}
+
+void test_online_clone__network_bare(void)
+{
+ git_remote *origin;
+
+ g_options.bare = true;
+
+ cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
+ cl_assert(git_repository_is_bare(g_repo));
+ cl_git_pass(git_remote_load(&origin, g_repo, "origin"));
+
+ git_remote_free(origin);
+}
+
+void test_online_clone__empty_repository(void)
+{
+ git_reference *head;
+
+ cl_git_pass(git_clone(&g_repo, LIVE_EMPTYREPO_URL, "./foo", &g_options));
+
+ cl_assert_equal_i(true, git_repository_is_empty(g_repo));
+ cl_assert_equal_i(true, git_repository_head_unborn(g_repo));
+
+ cl_git_pass(git_reference_lookup(&head, g_repo, GIT_HEAD_FILE));
+ cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head));
+ cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head));
+
+ git_reference_free(head);
+}
+
+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 int fetch_progress(const git_transfer_progress *stats, void *payload)
+{
+ bool *was_called = (bool*)payload;
+ GIT_UNUSED(stats);
+ (*was_called) = true;
+ return 0;
+}
+
+void test_online_clone__can_checkout_a_cloned_repo(void)
+{
+ git_buf path = GIT_BUF_INIT;
+ git_reference *head;
+ bool checkout_progress_cb_was_called = false,
+ fetch_progress_cb_was_called = false;
+
+ g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+ g_options.checkout_opts.progress_cb = &checkout_progress;
+ g_options.checkout_opts.progress_payload = &checkout_progress_cb_was_called;
+ g_options.remote_callbacks.transfer_progress = &fetch_progress;
+ g_options.remote_callbacks.payload = &fetch_progress_cb_was_called;
+
+ cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
+
+ 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)));
+
+ cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD"));
+ cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head));
+ cl_assert_equal_s("refs/heads/master", git_reference_symbolic_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);
+}
+
+void test_online_clone__clone_into(void)
+{
+ git_buf path = GIT_BUF_INIT;
+ git_remote *remote;
+ git_reference *head;
+ git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
+ git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
+
+ bool checkout_progress_cb_was_called = false,
+ fetch_progress_cb_was_called = false;
+
+ checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+ checkout_opts.progress_cb = &checkout_progress;
+ checkout_opts.progress_payload = &checkout_progress_cb_was_called;
+
+ cl_git_pass(git_repository_init(&g_repo, "./foo", false));
+ cl_git_pass(git_remote_create(&remote, g_repo, "origin", LIVE_REPO_URL));
+
+ callbacks.transfer_progress = &fetch_progress;
+ callbacks.payload = &fetch_progress_cb_was_called;
+ git_remote_set_callbacks(remote, &callbacks);
+
+ cl_git_pass(git_clone_into(g_repo, remote, &checkout_opts, NULL, NULL));
+
+ 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)));
+
+ cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD"));
+ cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head));
+ cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head));
+
+ cl_assert_equal_i(true, checkout_progress_cb_was_called);
+ cl_assert_equal_i(true, fetch_progress_cb_was_called);
+
+ git_remote_free(remote);
+ git_reference_free(head);
+ git_buf_free(&path);
+}
+
+void test_online_clone__clone_mirror(void)
+{
+ git_buf path = GIT_BUF_INIT;
+ git_remote *remote;
+ git_reference *head;
+ git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
+
+ bool fetch_progress_cb_was_called = false;
+
+ cl_git_pass(git_repository_init(&g_repo, "./foo.git", true));
+ cl_git_pass(git_remote_create(&remote, g_repo, "origin", LIVE_REPO_URL));
+
+ callbacks.transfer_progress = &fetch_progress;
+ callbacks.payload = &fetch_progress_cb_was_called;
+ git_remote_set_callbacks(remote, &callbacks);
+
+ git_remote_clear_refspecs(remote);
+ cl_git_pass(git_remote_add_fetch(remote, "+refs/*:refs/*"));
+
+ cl_git_pass(git_clone_into(g_repo, remote, NULL, NULL, NULL));
+
+ cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD"));
+ cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head));
+ cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head));
+
+ cl_assert_equal_i(true, fetch_progress_cb_was_called);
+
+ git_remote_free(remote);
+ git_reference_free(head);
+ git_buf_free(&path);
+ git_repository_free(g_repo);
+ g_repo = NULL;
+
+ cl_fixture_cleanup("./foo.git");
+}
+
+static int update_tips(const char *refname, const git_oid *a, const git_oid *b, void *payload)
+{
+ int *callcount = (int*)payload;
+ GIT_UNUSED(refname); GIT_UNUSED(a); GIT_UNUSED(b);
+ *callcount = *callcount + 1;
+ return 0;
+}
+
+void test_online_clone__custom_remote_callbacks(void)
+{
+ int callcount = 0;
+
+ g_options.remote_callbacks.update_tips = update_tips;
+ g_options.remote_callbacks.payload = &callcount;
+
+ cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
+ cl_assert(callcount > 0);
+}
+
+static int cred_failure_cb(
+ git_cred **cred,
+ const char *url,
+ const char *username_from_url,
+ unsigned int allowed_types,
+ void *data)
+{
+ GIT_UNUSED(cred); GIT_UNUSED(url); GIT_UNUSED(username_from_url);
+ GIT_UNUSED(allowed_types); GIT_UNUSED(data);
+ return -172;
+}
+
+void test_online_clone__cred_callback_failure_return_code_is_tunnelled(void)
+{
+ const char *remote_url = cl_getenv("GITTEST_REMOTE_URL");
+ const char *remote_user = cl_getenv("GITTEST_REMOTE_USER");
+
+ if (!remote_url || !remote_user)
+ clar__skip();
+
+ g_options.remote_callbacks.credentials = cred_failure_cb;
+
+ /* TODO: this should expect -172. */
+ cl_git_fail_with(git_clone(&g_repo, remote_url, "./foo", &g_options), -1);
+}
+
+void test_online_clone__credentials(void)
+{
+ /* Remote URL environment variable must be set. User and password are optional. */
+ const char *remote_url = cl_getenv("GITTEST_REMOTE_URL");
+ git_cred_userpass_payload user_pass = {
+ cl_getenv("GITTEST_REMOTE_USER"),
+ cl_getenv("GITTEST_REMOTE_PASS")
+ };
+
+ if (!remote_url) return;
+
+ g_options.remote_callbacks.credentials = git_cred_userpass;
+ g_options.remote_callbacks.payload = &user_pass;
+
+ cl_git_pass(git_clone(&g_repo, remote_url, "./foo", &g_options));
+ git_repository_free(g_repo); g_repo = NULL;
+ cl_fixture_cleanup("./foo");
+}
+
+void test_online_clone__bitbucket_style(void)
+{
+ git_cred_userpass_payload user_pass = {
+ "libgit2", "libgit2"
+ };
+
+ g_options.remote_callbacks.credentials = git_cred_userpass;
+ g_options.remote_callbacks.payload = &user_pass;
+
+ cl_git_pass(git_clone(&g_repo, BB_REPO_URL, "./foo", &g_options));
+ git_repository_free(g_repo); g_repo = NULL;
+ cl_fixture_cleanup("./foo");
+
+ /* User and pass from URL */
+ user_pass.password = "wrong";
+ cl_git_pass(git_clone(&g_repo, BB_REPO_URL_WITH_PASS, "./foo", &g_options));
+ git_repository_free(g_repo); g_repo = NULL;
+ cl_fixture_cleanup("./foo");
+
+ /* Wrong password in URL, fall back to user_pass */
+ user_pass.password = "libgit2";
+ cl_git_pass(git_clone(&g_repo, BB_REPO_URL_WITH_WRONG_PASS, "./foo", &g_options));
+ git_repository_free(g_repo); g_repo = NULL;
+ cl_fixture_cleanup("./foo");
+}
+
+void test_online_clone__assembla_style(void)
+{
+ cl_git_pass(git_clone(&g_repo, ASSEMBLA_REPO_URL, "./foo", NULL));
+}
+
+static int cancel_at_half(const git_transfer_progress *stats, void *payload)
+{
+ GIT_UNUSED(payload);
+
+ if (stats->received_objects > (stats->total_objects/2))
+ return 4321;
+ return 0;
+}
+
+void test_online_clone__can_cancel(void)
+{
+ g_options.remote_callbacks.transfer_progress = cancel_at_half;
+
+ cl_git_fail_with(
+ git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options), 4321);
+}
+
+
+
+
+
+
diff --git a/tests/online/fetch.c b/tests/online/fetch.c
new file mode 100644
index 000000000..f03a6faa6
--- /dev/null
+++ b/tests/online/fetch.c
@@ -0,0 +1,204 @@
+#include "clar_libgit2.h"
+
+static git_repository *_repo;
+static int counter;
+
+void test_online_fetch__initialize(void)
+{
+ cl_git_pass(git_repository_init(&_repo, "./fetch", 0));
+}
+
+void test_online_fetch__cleanup(void)
+{
+ git_repository_free(_repo);
+ _repo = NULL;
+
+ cl_fixture_cleanup("./fetch");
+}
+
+static int update_tips(const char *refname, const git_oid *a, const git_oid *b, void *data)
+{
+ GIT_UNUSED(refname); GIT_UNUSED(a); GIT_UNUSED(b); GIT_UNUSED(data);
+
+ ++counter;
+
+ return 0;
+}
+
+static int progress(const git_transfer_progress *stats, void *payload)
+{
+ size_t *bytes_received = (size_t *)payload;
+ *bytes_received = stats->received_bytes;
+ return 0;
+}
+
+static void do_fetch(const char *url, git_remote_autotag_option_t flag, int n)
+{
+ git_remote *remote;
+ git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
+ size_t bytes_received = 0;
+
+ callbacks.transfer_progress = progress;
+ callbacks.update_tips = update_tips;
+ callbacks.payload = &bytes_received;
+ counter = 0;
+
+ cl_git_pass(git_remote_create(&remote, _repo, "test", url));
+ git_remote_set_callbacks(remote, &callbacks);
+ git_remote_set_autotag(remote, flag);
+ cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH));
+ cl_git_pass(git_remote_download(remote));
+ cl_git_pass(git_remote_update_tips(remote, NULL, NULL));
+ git_remote_disconnect(remote);
+ cl_assert_equal_i(counter, n);
+ cl_assert(bytes_received > 0);
+
+ git_remote_free(remote);
+}
+
+void test_online_fetch__default_git(void)
+{
+ do_fetch("git://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 5);
+}
+
+void test_online_fetch__default_http(void)
+{
+ do_fetch("http://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 5);
+}
+
+void test_online_fetch__default_https(void)
+{
+ do_fetch("https://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 5);
+}
+
+void test_online_fetch__no_tags_git(void)
+{
+ do_fetch("git://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_NONE, 3);
+}
+
+void test_online_fetch__no_tags_http(void)
+{
+ do_fetch("http://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_NONE, 3);
+}
+
+void test_online_fetch__fetch_twice(void)
+{
+ git_remote *remote;
+ cl_git_pass(git_remote_create(&remote, _repo, "test", "git://github.com/libgit2/TestGitRepository.git"));
+ cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH));
+ cl_git_pass(git_remote_download(remote));
+ git_remote_disconnect(remote);
+
+ git_remote_connect(remote, GIT_DIRECTION_FETCH);
+ cl_git_pass(git_remote_download(remote));
+ git_remote_disconnect(remote);
+
+ git_remote_free(remote);
+}
+
+static int transferProgressCallback(const git_transfer_progress *stats, void *payload)
+{
+ bool *invoked = (bool *)payload;
+
+ GIT_UNUSED(stats);
+ *invoked = true;
+ return 0;
+}
+
+void test_online_fetch__doesnt_retrieve_a_pack_when_the_repository_is_up_to_date(void)
+{
+ git_repository *_repository;
+ bool invoked = false;
+ git_remote *remote;
+ git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
+ git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
+ opts.bare = true;
+
+ cl_git_pass(git_clone(&_repository, "https://github.com/libgit2/TestGitRepository.git",
+ "./fetch/lg2", &opts));
+ git_repository_free(_repository);
+
+ cl_git_pass(git_repository_open(&_repository, "./fetch/lg2"));
+
+ cl_git_pass(git_remote_load(&remote, _repository, "origin"));
+ cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH));
+
+ cl_assert_equal_i(false, invoked);
+
+ callbacks.transfer_progress = &transferProgressCallback;
+ callbacks.payload = &invoked;
+ git_remote_set_callbacks(remote, &callbacks);
+ cl_git_pass(git_remote_download(remote));
+
+ cl_assert_equal_i(false, invoked);
+
+ cl_git_pass(git_remote_update_tips(remote, NULL, NULL));
+ git_remote_disconnect(remote);
+
+ git_remote_free(remote);
+ git_repository_free(_repository);
+}
+
+static int cancel_at_half(const git_transfer_progress *stats, void *payload)
+{
+ GIT_UNUSED(payload);
+
+ if (stats->received_objects > (stats->total_objects/2))
+ return -4321;
+ return 0;
+}
+
+void test_online_fetch__can_cancel(void)
+{
+ git_remote *remote;
+ size_t bytes_received = 0;
+ git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
+
+ cl_git_pass(git_remote_create(&remote, _repo, "test",
+ "http://github.com/libgit2/TestGitRepository.git"));
+
+ callbacks.transfer_progress = cancel_at_half;
+ callbacks.payload = &bytes_received;
+ git_remote_set_callbacks(remote, &callbacks);
+
+ cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH));
+ cl_git_fail_with(git_remote_download(remote), -4321);
+ git_remote_disconnect(remote);
+ git_remote_free(remote);
+}
+
+void test_online_fetch__ls_disconnected(void)
+{
+ const git_remote_head **refs;
+ size_t refs_len_before, refs_len_after;
+ git_remote *remote;
+
+ cl_git_pass(git_remote_create(&remote, _repo, "test",
+ "http://github.com/libgit2/TestGitRepository.git"));
+ cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH));
+ cl_git_pass(git_remote_ls(&refs, &refs_len_before, remote));
+ git_remote_disconnect(remote);
+ cl_git_pass(git_remote_ls(&refs, &refs_len_after, remote));
+
+ cl_assert_equal_i(refs_len_before, refs_len_after);
+
+ git_remote_free(remote);
+}
+
+void test_online_fetch__remote_symrefs(void)
+{
+ const git_remote_head **refs;
+ size_t refs_len;
+ git_remote *remote;
+
+ cl_git_pass(git_remote_create(&remote, _repo, "test",
+ "http://github.com/libgit2/TestGitRepository.git"));
+ cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH));
+ git_remote_disconnect(remote);
+ cl_git_pass(git_remote_ls(&refs, &refs_len, remote));
+
+ cl_assert_equal_s("HEAD", refs[0]->name);
+ cl_assert_equal_s("refs/heads/master", refs[0]->symref_target);
+
+ git_remote_free(remote);
+}
diff --git a/tests/online/fetchhead.c b/tests/online/fetchhead.c
new file mode 100644
index 000000000..0b3f20db1
--- /dev/null
+++ b/tests/online/fetchhead.c
@@ -0,0 +1,91 @@
+#include "clar_libgit2.h"
+
+#include "fileops.h"
+#include "fetchhead.h"
+#include "../fetchhead/fetchhead_data.h"
+#include "git2/clone.h"
+
+#define LIVE_REPO_URL "git://github.com/libgit2/TestGitRepository"
+
+static git_repository *g_repo;
+static git_clone_options g_options;
+
+void test_online_fetchhead__initialize(void)
+{
+ git_remote_callbacks dummy_callbacks = GIT_REMOTE_CALLBACKS_INIT;
+ g_repo = NULL;
+
+ memset(&g_options, 0, sizeof(git_clone_options));
+ g_options.version = GIT_CLONE_OPTIONS_VERSION;
+ g_options.remote_callbacks = dummy_callbacks;
+}
+
+void test_online_fetchhead__cleanup(void)
+{
+ if (g_repo) {
+ git_repository_free(g_repo);
+ g_repo = NULL;
+ }
+
+ cl_fixture_cleanup("./foo");
+}
+
+static void fetchhead_test_clone(void)
+{
+ cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
+}
+
+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_clear_refspecs(remote);
+ git_remote_add_fetch(remote, fetchspec);
+ }
+
+ cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH));
+ cl_git_pass(git_remote_download(remote));
+ cl_git_pass(git_remote_update_tips(remote, NULL, NULL));
+ git_remote_disconnect(remote);
+ git_remote_free(remote);
+
+ cl_git_pass(git_futils_readbuffer(&fetchhead_buf, "./foo/.git/FETCH_HEAD"));
+
+ equals = (strcmp(fetchhead_buf.ptr, expected_fetchhead) == 0);
+
+ git_buf_free(&fetchhead_buf);
+
+ cl_assert(equals);
+}
+
+void test_online_fetchhead__wildcard_spec(void)
+{
+ fetchhead_test_clone();
+ fetchhead_test_fetch(NULL, FETCH_HEAD_WILDCARD_DATA);
+}
+
+void test_online_fetchhead__explicit_spec(void)
+{
+ fetchhead_test_clone();
+ fetchhead_test_fetch("refs/heads/first-merge:refs/remotes/origin/first-merge", FETCH_HEAD_EXPLICIT_DATA);
+}
+
+void test_online_fetchhead__no_merges(void)
+{
+ git_config *config;
+
+ fetchhead_test_clone();
+
+ cl_git_pass(git_repository_config(&config, g_repo));
+ cl_git_pass(git_config_delete_entry(config, "branch.master.remote"));
+ cl_git_pass(git_config_delete_entry(config, "branch.master.merge"));
+ git_config_free(config);
+
+ fetchhead_test_fetch(NULL, FETCH_HEAD_NO_MERGE_DATA);
+}
diff --git a/tests/online/push.c b/tests/online/push.c
new file mode 100644
index 000000000..6da27bb96
--- /dev/null
+++ b/tests/online/push.c
@@ -0,0 +1,868 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "posix.h"
+#include "vector.h"
+#include "../submodule/submodule_helpers.h"
+#include "push_util.h"
+#include "refspec.h"
+#include "remote.h"
+
+static git_repository *_repo;
+
+static char *_remote_url;
+
+static char *_remote_ssh_key;
+static char *_remote_ssh_pubkey;
+static char *_remote_ssh_passphrase;
+
+static char *_remote_user;
+static char *_remote_pass;
+
+static char *_remote_default;
+
+static int cred_acquire_cb(git_cred **, const char *, const char *, unsigned int, void *);
+
+static git_remote *_remote;
+static record_callbacks_data _record_cbs_data = {{ 0 }};
+static git_remote_callbacks _record_cbs = RECORD_CALLBACKS_INIT(&_record_cbs_data);
+
+static git_oid _oid_b6;
+static git_oid _oid_b5;
+static git_oid _oid_b4;
+static git_oid _oid_b3;
+static git_oid _oid_b2;
+static git_oid _oid_b1;
+
+static git_oid _tag_commit;
+static git_oid _tag_tree;
+static git_oid _tag_blob;
+static git_oid _tag_lightweight;
+static git_oid _tag_tag;
+
+static int cred_acquire_cb(
+ git_cred **cred,
+ const char *url,
+ const char *user_from_url,
+ unsigned int allowed_types,
+ void *payload)
+{
+ GIT_UNUSED(url);
+ GIT_UNUSED(user_from_url);
+ GIT_UNUSED(payload);
+
+ if (GIT_CREDTYPE_DEFAULT & allowed_types) {
+ if (!_remote_default) {
+ printf("GITTEST_REMOTE_DEFAULT must be set to use NTLM/Negotiate credentials\n");
+ return -1;
+ }
+
+ return git_cred_default_new(cred);
+ }
+
+ if (GIT_CREDTYPE_SSH_KEY & allowed_types) {
+ if (!_remote_user || !_remote_ssh_pubkey || !_remote_ssh_key || !_remote_ssh_passphrase) {
+ printf("GITTEST_REMOTE_USER, GITTEST_REMOTE_SSH_PUBKEY, GITTEST_REMOTE_SSH_KEY and GITTEST_REMOTE_SSH_PASSPHRASE must be set\n");
+ return -1;
+ }
+
+ return git_cred_ssh_key_new(cred, _remote_user, _remote_ssh_pubkey, _remote_ssh_key, _remote_ssh_passphrase);
+ }
+
+ if (GIT_CREDTYPE_USERPASS_PLAINTEXT & allowed_types) {
+ if (!_remote_user || !_remote_pass) {
+ printf("GITTEST_REMOTE_USER and GITTEST_REMOTE_PASS must be set\n");
+ return -1;
+ }
+
+ return git_cred_userpass_plaintext_new(cred, _remote_user, _remote_pass);
+ }
+
+ return -1;
+}
+
+/* the results of a push status. when used for expected values, msg may be NULL
+ * to indicate that it should not be matched. */
+typedef struct {
+ const char *ref;
+ int success;
+ const char *msg;
+} push_status;
+
+/**
+ * git_push_status_foreach callback that records status entries.
+ * @param data (git_vector *) of push_status instances
+ */
+static int record_push_status_cb(const char *ref, const char *msg, void *data)
+{
+ git_vector *statuses = (git_vector *)data;
+ push_status *s;
+
+ cl_assert(s = git__malloc(sizeof(*s)));
+ s->ref = ref;
+ s->success = (msg == NULL);
+ s->msg = msg;
+
+ git_vector_insert(statuses, s);
+
+ return 0;
+}
+
+static void do_verify_push_status(git_push *push, const push_status expected[], const size_t expected_len)
+{
+ git_vector actual = GIT_VECTOR_INIT;
+ push_status *iter;
+ bool failed = false;
+ size_t i;
+
+ git_push_status_foreach(push, record_push_status_cb, &actual);
+
+ if (expected_len != actual.length)
+ failed = true;
+ else
+ git_vector_foreach(&actual, i, iter)
+ if (strcmp(expected[i].ref, iter->ref) ||
+ (expected[i].success != iter->success) ||
+ (expected[i].msg && (!iter->msg || strcmp(expected[i].msg, iter->msg)))) {
+ failed = true;
+ break;
+ }
+
+ if (failed) {
+ git_buf msg = GIT_BUF_INIT;
+
+ git_buf_puts(&msg, "Expected and actual push statuses differ:\nEXPECTED:\n");
+
+ for(i = 0; i < expected_len; i++) {
+ git_buf_printf(&msg, "%s: %s\n",
+ expected[i].ref,
+ expected[i].success ? "success" : "failed");
+ }
+
+ git_buf_puts(&msg, "\nACTUAL:\n");
+
+ git_vector_foreach(&actual, i, iter) {
+ if (iter->success)
+ git_buf_printf(&msg, "%s: success\n", iter->ref);
+ else
+ git_buf_printf(&msg, "%s: failed with message: %s", iter->ref, iter->msg);
+ }
+
+ cl_fail(git_buf_cstr(&msg));
+
+ git_buf_free(&msg);
+ }
+
+ git_vector_foreach(&actual, i, iter)
+ git__free(iter);
+
+ git_vector_free(&actual);
+}
+
+/**
+ * Verifies that after git_push_finish(), refs on a remote have the expected
+ * names, oids, and order.
+ *
+ * @param remote remote to verify
+ * @param expected_refs expected remote refs after push
+ * @param expected_refs_len length of expected_refs
+ */
+static void verify_refs(git_remote *remote, expected_ref expected_refs[], size_t expected_refs_len)
+{
+ const git_remote_head **actual_refs;
+ size_t actual_refs_len;
+
+ git_remote_ls(&actual_refs, &actual_refs_len, remote);
+ verify_remote_refs(actual_refs, actual_refs_len, expected_refs, expected_refs_len);
+}
+
+/**
+ * Verifies that after git_push_update_tips(), remote tracking branches have the expected
+ * names and oids.
+ *
+ * @param remote remote to verify
+ * @param expected_refs expected remote refs after push
+ * @param expected_refs_len length of expected_refs
+ */
+static void verify_tracking_branches(git_remote *remote, expected_ref expected_refs[], size_t expected_refs_len)
+{
+ git_refspec *fetch_spec;
+ size_t i, j;
+ git_buf msg = GIT_BUF_INIT;
+ git_buf ref_name = GIT_BUF_INIT;
+ git_vector actual_refs = GIT_VECTOR_INIT;
+ git_branch_iterator *iter;
+ char *actual_ref;
+ git_oid oid;
+ int failed = 0, error;
+ git_branch_t branch_type;
+ git_reference *ref;
+
+ /* Get current remote branches */
+ cl_git_pass(git_branch_iterator_new(&iter, remote->repo, GIT_BRANCH_REMOTE));
+
+ while ((error = git_branch_next(&ref, &branch_type, iter)) == 0) {
+ cl_assert_equal_i(branch_type, GIT_BRANCH_REMOTE);
+
+ cl_git_pass(git_vector_insert(&actual_refs, git__strdup(git_reference_name(ref))));
+ }
+
+ cl_assert_equal_i(error, GIT_ITEROVER);
+ git_branch_iterator_free(iter);
+
+ /* Loop through expected refs, make sure they exist */
+ for (i = 0; i < expected_refs_len; i++) {
+
+ /* Convert remote reference name into tracking branch name.
+ * If the spec is not under refs/heads/, then skip.
+ */
+ fetch_spec = git_remote__matching_refspec(remote, expected_refs[i].name);
+ if (!fetch_spec)
+ continue;
+
+ cl_git_pass(git_refspec_transform(&ref_name, fetch_spec, expected_refs[i].name));
+
+ /* Find matching remote branch */
+ git_vector_foreach(&actual_refs, j, actual_ref) {
+ if (!strcmp(git_buf_cstr(&ref_name), actual_ref))
+ break;
+ }
+
+ if (j == actual_refs.length) {
+ git_buf_printf(&msg, "Did not find expected tracking branch '%s'.", git_buf_cstr(&ref_name));
+ failed = 1;
+ goto failed;
+ }
+
+ /* Make sure tracking branch is at expected commit ID */
+ cl_git_pass(git_reference_name_to_id(&oid, remote->repo, actual_ref));
+
+ if (git_oid_cmp(expected_refs[i].oid, &oid) != 0) {
+ git_buf_puts(&msg, "Tracking branch commit does not match expected ID.");
+ failed = 1;
+ goto failed;
+ }
+
+ git__free(actual_ref);
+ cl_git_pass(git_vector_remove(&actual_refs, j));
+ }
+
+ /* Make sure there are no extra branches */
+ if (actual_refs.length > 0) {
+ git_buf_puts(&msg, "Unexpected remote tracking branches exist.");
+ failed = 1;
+ goto failed;
+ }
+
+failed:
+ if (failed)
+ cl_fail(git_buf_cstr(&msg));
+
+ git_vector_foreach(&actual_refs, i, actual_ref)
+ git__free(actual_ref);
+
+ git_vector_free(&actual_refs);
+ git_buf_free(&msg);
+ git_buf_free(&ref_name);
+}
+
+static void verify_update_tips_callback(git_remote *remote, expected_ref expected_refs[], size_t expected_refs_len)
+{
+ git_refspec *fetch_spec;
+ git_buf msg = GIT_BUF_INIT;
+ git_buf ref_name = GIT_BUF_INIT;
+ updated_tip *tip = NULL;
+ size_t i, j;
+ int failed = 0;
+
+ for (i = 0; i < expected_refs_len; ++i) {
+ /* Convert remote reference name into tracking branch name.
+ * If the spec is not under refs/heads/, then skip.
+ */
+ fetch_spec = git_remote__matching_refspec(remote, expected_refs[i].name);
+ if (!fetch_spec)
+ continue;
+
+ cl_git_pass(git_refspec_transform(&ref_name, fetch_spec, expected_refs[i].name));
+
+ /* Find matching update_tip entry */
+ git_vector_foreach(&_record_cbs_data.updated_tips, j, tip) {
+ if (!strcmp(git_buf_cstr(&ref_name), tip->name))
+ break;
+ }
+
+ if (j == _record_cbs_data.updated_tips.length) {
+ git_buf_printf(&msg, "Did not find expected updated tip entry for branch '%s'.", git_buf_cstr(&ref_name));
+ failed = 1;
+ goto failed;
+ }
+
+ if (git_oid_cmp(expected_refs[i].oid, tip->new_oid) != 0) {
+ git_buf_printf(&msg, "Updated tip ID does not match expected ID");
+ failed = 1;
+ goto failed;
+ }
+ }
+
+failed:
+ if (failed)
+ cl_fail(git_buf_cstr(&msg));
+
+ git_buf_free(&ref_name);
+ git_buf_free(&msg);
+}
+
+void test_online_push__initialize(void)
+{
+ git_vector delete_specs = GIT_VECTOR_INIT;
+ const git_remote_head **heads;
+ size_t i, heads_len;
+ char *curr_del_spec;
+
+ _repo = cl_git_sandbox_init("push_src");
+
+ cl_fixture_sandbox("testrepo.git");
+ cl_rename("push_src/submodule/.gitted", "push_src/submodule/.git");
+
+ rewrite_gitmodules(git_repository_workdir(_repo));
+
+ /* git log --format=oneline --decorate --graph
+ * *-. 951bbbb90e2259a4c8950db78946784fb53fcbce (HEAD, b6) merge b3, b4, and b5 to b6
+ * |\ \
+ * | | * fa38b91f199934685819bea316186d8b008c52a2 (b5) added submodule named 'submodule' pointing to '../testrepo.git'
+ * | * | 27b7ce66243eb1403862d05f958c002312df173d (b4) edited fold\b.txt
+ * | |/
+ * * | d9b63a88223d8367516f50bd131a5f7349b7f3e4 (b3) edited a.txt
+ * |/
+ * * a78705c3b2725f931d3ee05348d83cc26700f247 (b2, b1) added fold and fold/b.txt
+ * * 5c0bb3d1b9449d1cc69d7519fd05166f01840915 added a.txt
+ */
+ git_oid_fromstr(&_oid_b6, "951bbbb90e2259a4c8950db78946784fb53fcbce");
+ git_oid_fromstr(&_oid_b5, "fa38b91f199934685819bea316186d8b008c52a2");
+ git_oid_fromstr(&_oid_b4, "27b7ce66243eb1403862d05f958c002312df173d");
+ git_oid_fromstr(&_oid_b3, "d9b63a88223d8367516f50bd131a5f7349b7f3e4");
+ git_oid_fromstr(&_oid_b2, "a78705c3b2725f931d3ee05348d83cc26700f247");
+ git_oid_fromstr(&_oid_b1, "a78705c3b2725f931d3ee05348d83cc26700f247");
+
+ git_oid_fromstr(&_tag_commit, "805c54522e614f29f70d2413a0470247d8b424ac");
+ git_oid_fromstr(&_tag_tree, "ff83aa4c5e5d28e3bcba2f5c6e2adc61286a4e5e");
+ git_oid_fromstr(&_tag_blob, "b483ae7ba66decee9aee971f501221dea84b1498");
+ git_oid_fromstr(&_tag_lightweight, "951bbbb90e2259a4c8950db78946784fb53fcbce");
+ git_oid_fromstr(&_tag_tag, "eea4f2705eeec2db3813f2430829afce99cd00b5");
+
+ /* Remote URL environment variable must be set. User and password are optional. */
+ _remote_url = cl_getenv("GITTEST_REMOTE_URL");
+ _remote_user = cl_getenv("GITTEST_REMOTE_USER");
+ _remote_pass = cl_getenv("GITTEST_REMOTE_PASS");
+ _remote_ssh_key = cl_getenv("GITTEST_REMOTE_SSH_KEY");
+ _remote_ssh_pubkey = cl_getenv("GITTEST_REMOTE_SSH_PUBKEY");
+ _remote_ssh_passphrase = cl_getenv("GITTEST_REMOTE_SSH_PASSPHRASE");
+ _remote_default = cl_getenv("GITTEST_REMOTE_DEFAULT");
+ _remote = NULL;
+
+ /* Skip the test if we're missing the remote URL */
+ if (!_remote_url)
+ cl_skip();
+
+ cl_git_pass(git_remote_create(&_remote, _repo, "test", _remote_url));
+
+ record_callbacks_data_clear(&_record_cbs_data);
+ git_remote_set_callbacks(_remote, &_record_cbs);
+
+ cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH));
+
+ /* Clean up previously pushed branches. Fails if receive.denyDeletes is
+ * set on the remote. Also, on Git 1.7.0 and newer, you must run
+ * 'git config receive.denyDeleteCurrent ignore' in the remote repo in
+ * order to delete the remote branch pointed to by HEAD (usually master).
+ * See: https://raw.github.com/git/git/master/Documentation/RelNotes/1.7.0.txt
+ */
+ cl_git_pass(git_remote_ls(&heads, &heads_len, _remote));
+ cl_git_pass(create_deletion_refspecs(&delete_specs, heads, heads_len));
+ if (delete_specs.length) {
+ git_push *push;
+
+ cl_git_pass(git_push_new(&push, _remote));
+
+ git_vector_foreach(&delete_specs, i, curr_del_spec) {
+ git_push_add_refspec(push, curr_del_spec);
+ git__free(curr_del_spec);
+ }
+
+ cl_git_pass(git_push_finish(push));
+ git_push_free(push);
+ }
+
+ git_remote_disconnect(_remote);
+ git_vector_free(&delete_specs);
+
+ /* Now that we've deleted everything, fetch from the remote */
+ cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_FETCH));
+ cl_git_pass(git_remote_download(_remote));
+ cl_git_pass(git_remote_update_tips(_remote, NULL, NULL));
+ git_remote_disconnect(_remote);
+}
+
+void test_online_push__cleanup(void)
+{
+ if (_remote)
+ git_remote_free(_remote);
+ _remote = NULL;
+
+ /* Freed by cl_git_sandbox_cleanup */
+ _repo = NULL;
+
+ record_callbacks_data_clear(&_record_cbs_data);
+
+ cl_fixture_cleanup("testrepo.git");
+ cl_git_sandbox_cleanup();
+}
+
+static int push_pack_progress_cb(
+ int stage, unsigned int current, unsigned int total, void* payload)
+{
+ int *calls = (int *)payload;
+ GIT_UNUSED(stage); GIT_UNUSED(current); GIT_UNUSED(total);
+ if (*calls < 0)
+ return *calls;
+ (*calls)++;
+ return 0;
+}
+
+static int push_transfer_progress_cb(
+ unsigned int current, unsigned int total, size_t bytes, void* payload)
+{
+ int *calls = (int *)payload;
+ GIT_UNUSED(current); GIT_UNUSED(total); GIT_UNUSED(bytes);
+ if (*calls < 0)
+ return *calls;
+ (*calls)++;
+ return 0;
+}
+
+/**
+ * Calls push and relists refs on remote to verify success.
+ *
+ * @param refspecs refspecs to push
+ * @param refspecs_len length of refspecs
+ * @param expected_refs expected remote refs after push
+ * @param expected_refs_len length of expected_refs
+ * @param expected_ret expected return value from git_push_finish()
+ * @param check_progress_cb Check that the push progress callbacks are called
+ */
+static void do_push(
+ const char *refspecs[], size_t refspecs_len,
+ push_status expected_statuses[], size_t expected_statuses_len,
+ expected_ref expected_refs[], size_t expected_refs_len,
+ int expected_ret, int check_progress_cb, int check_update_tips_cb)
+{
+ git_push *push;
+ git_push_options opts = GIT_PUSH_OPTIONS_INIT;
+ size_t i;
+ int pack_progress_calls = 0, transfer_progress_calls = 0;
+ git_signature *pusher;
+
+ if (_remote) {
+ /* Auto-detect the number of threads to use */
+ opts.pb_parallelism = 0;
+
+ cl_git_pass(git_signature_now(&pusher, "Foo Bar", "foo@example.com"));
+ cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH));
+
+ cl_git_pass(git_push_new(&push, _remote));
+ cl_git_pass(git_push_set_options(push, &opts));
+
+ if (check_progress_cb) {
+ /* if EUSER, then abort in transfer */
+ if (expected_ret == GIT_EUSER)
+ transfer_progress_calls = GIT_EUSER;
+
+ cl_git_pass(
+ git_push_set_callbacks(
+ push, push_pack_progress_cb, &pack_progress_calls,
+ push_transfer_progress_cb, &transfer_progress_calls));
+ }
+
+ for (i = 0; i < refspecs_len; i++)
+ cl_git_pass(git_push_add_refspec(push, refspecs[i]));
+
+ if (expected_ret < 0) {
+ cl_git_fail_with(git_push_finish(push), expected_ret);
+ cl_assert_equal_i(0, git_push_unpack_ok(push));
+ } else {
+ cl_git_pass(git_push_finish(push));
+ cl_assert_equal_i(1, git_push_unpack_ok(push));
+ }
+
+ if (check_progress_cb && !expected_ret) {
+ cl_assert(pack_progress_calls > 0);
+ cl_assert(transfer_progress_calls > 0);
+ }
+
+ do_verify_push_status(push, expected_statuses, expected_statuses_len);
+
+ verify_refs(_remote, expected_refs, expected_refs_len);
+
+ cl_git_pass(git_push_update_tips(push, pusher, "test push"));
+ verify_tracking_branches(_remote, expected_refs, expected_refs_len);
+
+ if (check_update_tips_cb)
+ verify_update_tips_callback(_remote, expected_refs, expected_refs_len);
+
+ git_push_free(push);
+
+ git_remote_disconnect(_remote);
+ git_signature_free(pusher);
+ }
+
+}
+
+/* Call push_finish() without ever calling git_push_add_refspec() */
+void test_online_push__noop(void)
+{
+ do_push(NULL, 0, NULL, 0, NULL, 0, 0, 0, 1);
+}
+
+void test_online_push__b1(void)
+{
+ const char *specs[] = { "refs/heads/b1:refs/heads/b1" };
+ push_status exp_stats[] = { { "refs/heads/b1", 1 } };
+ expected_ref exp_refs[] = { { "refs/heads/b1", &_oid_b1 } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
+}
+
+void test_online_push__b2(void)
+{
+ const char *specs[] = { "refs/heads/b2:refs/heads/b2" };
+ push_status exp_stats[] = { { "refs/heads/b2", 1 } };
+ expected_ref exp_refs[] = { { "refs/heads/b2", &_oid_b2 } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
+}
+
+void test_online_push__b3(void)
+{
+ const char *specs[] = { "refs/heads/b3:refs/heads/b3" };
+ push_status exp_stats[] = { { "refs/heads/b3", 1 } };
+ expected_ref exp_refs[] = { { "refs/heads/b3", &_oid_b3 } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
+}
+
+void test_online_push__b4(void)
+{
+ const char *specs[] = { "refs/heads/b4:refs/heads/b4" };
+ push_status exp_stats[] = { { "refs/heads/b4", 1 } };
+ expected_ref exp_refs[] = { { "refs/heads/b4", &_oid_b4 } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
+}
+
+void test_online_push__b5(void)
+{
+ const char *specs[] = { "refs/heads/b5:refs/heads/b5" };
+ push_status exp_stats[] = { { "refs/heads/b5", 1 } };
+ expected_ref exp_refs[] = { { "refs/heads/b5", &_oid_b5 } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
+}
+
+void test_online_push__b5_cancel(void)
+{
+ const char *specs[] = { "refs/heads/b5:refs/heads/b5" };
+ do_push(specs, ARRAY_SIZE(specs), NULL, 0, NULL, 0, GIT_EUSER, 1, 1);
+}
+
+void test_online_push__multi(void)
+{
+ git_reflog *log;
+ const git_reflog_entry *entry;
+
+ const char *specs[] = {
+ "refs/heads/b1:refs/heads/b1",
+ "refs/heads/b2:refs/heads/b2",
+ "refs/heads/b3:refs/heads/b3",
+ "refs/heads/b4:refs/heads/b4",
+ "refs/heads/b5:refs/heads/b5"
+ };
+ push_status exp_stats[] = {
+ { "refs/heads/b1", 1 },
+ { "refs/heads/b2", 1 },
+ { "refs/heads/b3", 1 },
+ { "refs/heads/b4", 1 },
+ { "refs/heads/b5", 1 }
+ };
+ expected_ref exp_refs[] = {
+ { "refs/heads/b1", &_oid_b1 },
+ { "refs/heads/b2", &_oid_b2 },
+ { "refs/heads/b3", &_oid_b3 },
+ { "refs/heads/b4", &_oid_b4 },
+ { "refs/heads/b5", &_oid_b5 }
+ };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
+
+ cl_git_pass(git_reflog_read(&log, _repo, "refs/remotes/test/b1"));
+ entry = git_reflog_entry_byindex(log, 0);
+ if (entry) {
+ cl_assert_equal_s("test push", git_reflog_entry_message(entry));
+ cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email);
+ }
+
+ git_reflog_free(log);
+}
+
+void test_online_push__implicit_tgt(void)
+{
+ const char *specs1[] = { "refs/heads/b1:" };
+ push_status exp_stats1[] = { { "refs/heads/b1", 1 } };
+ expected_ref exp_refs1[] = { { "refs/heads/b1", &_oid_b1 } };
+
+ const char *specs2[] = { "refs/heads/b2:" };
+ push_status exp_stats2[] = { { "refs/heads/b2", 1 } };
+ expected_ref exp_refs2[] = {
+ { "refs/heads/b1", &_oid_b1 },
+ { "refs/heads/b2", &_oid_b2 }
+ };
+
+ do_push(specs1, ARRAY_SIZE(specs1),
+ exp_stats1, ARRAY_SIZE(exp_stats1),
+ exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1);
+ do_push(specs2, ARRAY_SIZE(specs2),
+ exp_stats2, ARRAY_SIZE(exp_stats2),
+ exp_refs2, ARRAY_SIZE(exp_refs2), 0, 0, 0);
+}
+
+void test_online_push__fast_fwd(void)
+{
+ /* Fast forward b1 in tgt from _oid_b1 to _oid_b6. */
+
+ const char *specs_init[] = { "refs/heads/b1:refs/heads/b1" };
+ push_status exp_stats_init[] = { { "refs/heads/b1", 1 } };
+ expected_ref exp_refs_init[] = { { "refs/heads/b1", &_oid_b1 } };
+
+ const char *specs_ff[] = { "refs/heads/b6:refs/heads/b1" };
+ push_status exp_stats_ff[] = { { "refs/heads/b1", 1 } };
+ expected_ref exp_refs_ff[] = { { "refs/heads/b1", &_oid_b6 } };
+
+ /* Do a force push to reset b1 in target back to _oid_b1 */
+ const char *specs_reset[] = { "+refs/heads/b1:refs/heads/b1" };
+ /* Force should have no effect on a fast forward push */
+ const char *specs_ff_force[] = { "+refs/heads/b6:refs/heads/b1" };
+
+ do_push(specs_init, ARRAY_SIZE(specs_init),
+ exp_stats_init, ARRAY_SIZE(exp_stats_init),
+ exp_refs_init, ARRAY_SIZE(exp_refs_init), 0, 1, 1);
+
+ do_push(specs_ff, ARRAY_SIZE(specs_ff),
+ exp_stats_ff, ARRAY_SIZE(exp_stats_ff),
+ exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0, 0, 0);
+
+ do_push(specs_reset, ARRAY_SIZE(specs_reset),
+ exp_stats_init, ARRAY_SIZE(exp_stats_init),
+ exp_refs_init, ARRAY_SIZE(exp_refs_init), 0, 0, 0);
+
+ do_push(specs_ff_force, ARRAY_SIZE(specs_ff_force),
+ exp_stats_ff, ARRAY_SIZE(exp_stats_ff),
+ exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0, 0, 0);
+}
+
+void test_online_push__tag_commit(void)
+{
+ const char *specs[] = { "refs/tags/tag-commit:refs/tags/tag-commit" };
+ push_status exp_stats[] = { { "refs/tags/tag-commit", 1 } };
+ expected_ref exp_refs[] = { { "refs/tags/tag-commit", &_tag_commit } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
+}
+
+void test_online_push__tag_tree(void)
+{
+ const char *specs[] = { "refs/tags/tag-tree:refs/tags/tag-tree" };
+ push_status exp_stats[] = { { "refs/tags/tag-tree", 1 } };
+ expected_ref exp_refs[] = { { "refs/tags/tag-tree", &_tag_tree } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
+}
+
+void test_online_push__tag_blob(void)
+{
+ const char *specs[] = { "refs/tags/tag-blob:refs/tags/tag-blob" };
+ push_status exp_stats[] = { { "refs/tags/tag-blob", 1 } };
+ expected_ref exp_refs[] = { { "refs/tags/tag-blob", &_tag_blob } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
+}
+
+void test_online_push__tag_lightweight(void)
+{
+ const char *specs[] = { "refs/tags/tag-lightweight:refs/tags/tag-lightweight" };
+ push_status exp_stats[] = { { "refs/tags/tag-lightweight", 1 } };
+ expected_ref exp_refs[] = { { "refs/tags/tag-lightweight", &_tag_lightweight } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
+}
+
+void test_online_push__tag_to_tag(void)
+{
+ const char *specs[] = { "refs/tags/tag-tag:refs/tags/tag-tag" };
+ push_status exp_stats[] = { { "refs/tags/tag-tag", 1 } };
+ expected_ref exp_refs[] = { { "refs/tags/tag-tag", &_tag_tag } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 0, 0);
+}
+
+void test_online_push__force(void)
+{
+ const char *specs1[] = {"refs/heads/b3:refs/heads/tgt"};
+ push_status exp_stats1[] = { { "refs/heads/tgt", 1 } };
+ expected_ref exp_refs1[] = { { "refs/heads/tgt", &_oid_b3 } };
+
+ const char *specs2[] = {"refs/heads/b4:refs/heads/tgt"};
+
+ const char *specs2_force[] = {"+refs/heads/b4:refs/heads/tgt"};
+ push_status exp_stats2_force[] = { { "refs/heads/tgt", 1 } };
+ expected_ref exp_refs2_force[] = { { "refs/heads/tgt", &_oid_b4 } };
+
+ do_push(specs1, ARRAY_SIZE(specs1),
+ exp_stats1, ARRAY_SIZE(exp_stats1),
+ exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1);
+
+ do_push(specs2, ARRAY_SIZE(specs2),
+ NULL, 0,
+ exp_refs1, ARRAY_SIZE(exp_refs1), GIT_ENONFASTFORWARD, 0, 0);
+
+ /* Non-fast-forward update with force should pass. */
+ record_callbacks_data_clear(&_record_cbs_data);
+ do_push(specs2_force, ARRAY_SIZE(specs2_force),
+ exp_stats2_force, ARRAY_SIZE(exp_stats2_force),
+ exp_refs2_force, ARRAY_SIZE(exp_refs2_force), 0, 1, 1);
+}
+
+void test_online_push__delete(void)
+{
+ const char *specs1[] = {
+ "refs/heads/b1:refs/heads/tgt1",
+ "refs/heads/b1:refs/heads/tgt2"
+ };
+ push_status exp_stats1[] = {
+ { "refs/heads/tgt1", 1 },
+ { "refs/heads/tgt2", 1 }
+ };
+ expected_ref exp_refs1[] = {
+ { "refs/heads/tgt1", &_oid_b1 },
+ { "refs/heads/tgt2", &_oid_b1 }
+ };
+
+ const char *specs_del_fake[] = { ":refs/heads/fake" };
+ /* Force has no effect for delete. */
+ const char *specs_del_fake_force[] = { "+:refs/heads/fake" };
+ push_status exp_stats_fake[] = { { "refs/heads/fake", 1 } };
+
+ const char *specs_delete[] = { ":refs/heads/tgt1" };
+ push_status exp_stats_delete[] = { { "refs/heads/tgt1", 1 } };
+ expected_ref exp_refs_delete[] = { { "refs/heads/tgt2", &_oid_b1 } };
+ /* Force has no effect for delete. */
+ const char *specs_delete_force[] = { "+:refs/heads/tgt1" };
+
+ do_push(specs1, ARRAY_SIZE(specs1),
+ exp_stats1, ARRAY_SIZE(exp_stats1),
+ exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1);
+
+ /* When deleting a non-existent branch, the git client sends zero for both
+ * the old and new commit id. This should succeed on the server with the
+ * same status report as if the branch were actually deleted. The server
+ * returns a warning on the side-band iff the side-band is supported.
+ * Since libgit2 doesn't support the side-band yet, there are no warnings.
+ */
+ do_push(specs_del_fake, ARRAY_SIZE(specs_del_fake),
+ exp_stats_fake, 1,
+ exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0);
+ do_push(specs_del_fake_force, ARRAY_SIZE(specs_del_fake_force),
+ exp_stats_fake, 1,
+ exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0);
+
+ /* Delete one of the pushed branches. */
+ do_push(specs_delete, ARRAY_SIZE(specs_delete),
+ exp_stats_delete, ARRAY_SIZE(exp_stats_delete),
+ exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0, 0, 0);
+
+ /* Re-push branches and retry delete with force. */
+ do_push(specs1, ARRAY_SIZE(specs1),
+ exp_stats1, ARRAY_SIZE(exp_stats1),
+ exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0);
+ do_push(specs_delete_force, ARRAY_SIZE(specs_delete_force),
+ exp_stats_delete, ARRAY_SIZE(exp_stats_delete),
+ exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0, 0, 0);
+}
+
+void test_online_push__bad_refspecs(void)
+{
+ /* All classes of refspecs that should be rejected by
+ * git_push_add_refspec() should go in this test.
+ */
+ git_push *push;
+
+ if (_remote) {
+/* cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH)); */
+ cl_git_pass(git_push_new(&push, _remote));
+
+ /* Unexpanded branch names not supported */
+ cl_git_fail(git_push_add_refspec(push, "b6:b6"));
+
+ git_push_free(push);
+ }
+}
+
+void test_online_push__expressions(void)
+{
+ /* TODO: Expressions in refspecs doesn't actually work yet */
+ const char *specs_left_expr[] = { "refs/heads/b2~1:refs/heads/b2" };
+
+ /* expect not NULL to indicate failure (core git replies "funny refname",
+ * other servers may be less pithy. */
+ const char *specs_right_expr[] = { "refs/heads/b2:refs/heads/b2~1" };
+ push_status exp_stats_right_expr[] = { { "refs/heads/b2~1", 0 } };
+
+ /* TODO: Find a more precise way of checking errors than a exit code of -1. */
+ do_push(specs_left_expr, ARRAY_SIZE(specs_left_expr),
+ NULL, 0,
+ NULL, 0, -1, 0, 0);
+
+ do_push(specs_right_expr, ARRAY_SIZE(specs_right_expr),
+ exp_stats_right_expr, ARRAY_SIZE(exp_stats_right_expr),
+ NULL, 0, 0, 1, 1);
+}
+
+void test_online_push__notes(void)
+{
+ git_oid note_oid, *target_oid, expected_oid;
+ git_signature *signature;
+ const char *specs[] = { "refs/notes/commits:refs/notes/commits" };
+ push_status exp_stats[] = { { "refs/notes/commits", 1 } };
+ expected_ref exp_refs[] = { { "refs/notes/commits", &expected_oid } };
+ git_oid_fromstr(&expected_oid, "8461a99b27b7043e58ff6e1f5d2cf07d282534fb");
+
+ target_oid = &_oid_b6;
+
+ /* Create note to push */
+ cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */
+ cl_git_pass(git_note_create(&note_oid, _repo, signature, signature, NULL, target_oid, "hello world\n", 0));
+
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
+
+ git_signature_free(signature);
+}
diff --git a/tests/online/push_util.c b/tests/online/push_util.c
new file mode 100644
index 000000000..038c144db
--- /dev/null
+++ b/tests/online/push_util.c
@@ -0,0 +1,132 @@
+
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "vector.h"
+#include "push_util.h"
+
+const git_oid OID_ZERO = {{ 0 }};
+
+void updated_tip_free(updated_tip *t)
+{
+ git__free(t->name);
+ git__free(t->old_oid);
+ git__free(t->new_oid);
+ git__free(t);
+}
+
+void record_callbacks_data_clear(record_callbacks_data *data)
+{
+ size_t i;
+ updated_tip *tip;
+
+ git_vector_foreach(&data->updated_tips, i, tip)
+ updated_tip_free(tip);
+
+ git_vector_free(&data->updated_tips);
+}
+
+int record_update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *data)
+{
+ updated_tip *t;
+ record_callbacks_data *record_data = (record_callbacks_data *)data;
+
+ cl_assert(t = git__malloc(sizeof(*t)));
+
+ cl_assert(t->name = git__strdup(refname));
+ cl_assert(t->old_oid = git__malloc(sizeof(*t->old_oid)));
+ git_oid_cpy(t->old_oid, a);
+
+ cl_assert(t->new_oid = git__malloc(sizeof(*t->new_oid)));
+ git_oid_cpy(t->new_oid, b);
+
+ git_vector_insert(&record_data->updated_tips, t);
+
+ return 0;
+}
+
+int create_deletion_refspecs(git_vector *out, const git_remote_head **heads, size_t heads_len)
+{
+ git_buf del_spec = GIT_BUF_INIT;
+ size_t i;
+
+ for (i = 0; i < heads_len; i++) {
+ const git_remote_head *head = heads[i];
+ /* Ignore malformed ref names (which also saves us from tag^{} */
+ if (!git_reference_is_valid_name(head->name))
+ return 0;
+
+ /* Create a refspec that deletes a branch in the remote */
+ if (strcmp(head->name, "refs/heads/master")) {
+ cl_git_pass(git_buf_putc(&del_spec, ':'));
+ cl_git_pass(git_buf_puts(&del_spec, head->name));
+ cl_git_pass(git_vector_insert(out, git_buf_detach(&del_spec)));
+ }
+ }
+
+ return 0;
+}
+
+int record_ref_cb(git_remote_head *head, void *payload)
+{
+ git_vector *refs = (git_vector *) payload;
+ return git_vector_insert(refs, head);
+}
+
+void verify_remote_refs(const git_remote_head *actual_refs[], size_t actual_refs_len, const expected_ref expected_refs[], size_t expected_refs_len)
+{
+ size_t i, j = 0;
+ git_buf msg = GIT_BUF_INIT;
+ const git_remote_head *actual;
+ char *oid_str;
+ bool master_present = false;
+
+ /* We don't care whether "master" is present on the other end or not */
+ for (i = 0; i < actual_refs_len; i++) {
+ actual = actual_refs[i];
+ if (!strcmp(actual->name, "refs/heads/master")) {
+ master_present = true;
+ break;
+ }
+ }
+
+ if (expected_refs_len + (master_present ? 1 : 0) != actual_refs_len)
+ goto failed;
+
+ for (i = 0; i < actual_refs_len; i++) {
+ actual = actual_refs[i];
+ if (master_present && !strcmp(actual->name, "refs/heads/master"))
+ continue;
+
+ if (strcmp(expected_refs[j].name, actual->name) ||
+ git_oid_cmp(expected_refs[j].oid, &actual->oid))
+ goto failed;
+
+ j++;
+ }
+
+ return;
+
+failed:
+ git_buf_puts(&msg, "Expected and actual refs differ:\nEXPECTED:\n");
+
+ for(i = 0; i < expected_refs_len; i++) {
+ cl_assert(oid_str = git_oid_allocfmt(expected_refs[i].oid));
+ cl_git_pass(git_buf_printf(&msg, "%s = %s\n", expected_refs[i].name, oid_str));
+ git__free(oid_str);
+ }
+
+ git_buf_puts(&msg, "\nACTUAL:\n");
+ for (i = 0; i < actual_refs_len; i++) {
+ actual = actual_refs[i];
+ if (master_present && !strcmp(actual->name, "refs/heads/master"))
+ continue;
+
+ cl_assert(oid_str = git_oid_allocfmt(&actual->oid));
+ cl_git_pass(git_buf_printf(&msg, "%s = %s\n", actual->name, oid_str));
+ git__free(oid_str);
+ }
+
+ cl_fail(git_buf_cstr(&msg));
+
+ git_buf_free(&msg);
+}
diff --git a/tests/online/push_util.h b/tests/online/push_util.h
new file mode 100644
index 000000000..a7207c49e
--- /dev/null
+++ b/tests/online/push_util.h
@@ -0,0 +1,71 @@
+#ifndef INCLUDE_cl_push_util_h__
+#define INCLUDE_cl_push_util_h__
+
+#include "git2/oid.h"
+
+/* Constant for zero oid */
+extern const git_oid OID_ZERO;
+
+/**
+ * Macro for initializing git_remote_callbacks to use test helpers that
+ * record data in a record_callbacks_data instance.
+ * @param data pointer to a record_callbacks_data instance
+ */
+#define RECORD_CALLBACKS_INIT(data) \
+ { GIT_REMOTE_CALLBACKS_VERSION, NULL, NULL, cred_acquire_cb, NULL, record_update_tips_cb, data }
+
+typedef struct {
+ char *name;
+ git_oid *old_oid;
+ git_oid *new_oid;
+} updated_tip;
+
+typedef struct {
+ git_vector updated_tips;
+} record_callbacks_data;
+
+typedef struct {
+ const char *name;
+ const git_oid *oid;
+} expected_ref;
+
+void updated_tip_free(updated_tip *t);
+
+void record_callbacks_data_clear(record_callbacks_data *data);
+
+/**
+ * Callback for git_remote_update_tips that records updates
+ *
+ * @param data (git_vector *) of updated_tip instances
+ */
+int record_update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *data);
+
+/**
+ * Create a set of refspecs that deletes each of the inputs
+ *
+ * @param out the vector in which to store the refspecs
+ * @param heads the remote heads
+ * @param heads_len the size of the array
+ */
+int create_deletion_refspecs(git_vector *out, const git_remote_head **heads, size_t heads_len);
+
+/**
+ * Callback for git_remote_list that adds refspecs to vector
+ *
+ * @param head a ref on the remote
+ * @param payload (git_vector *) of git_remote_head instances
+ */
+int record_ref_cb(git_remote_head *head, void *payload);
+
+/**
+ * Verifies that refs on remote stored by record_ref_cb match the expected
+ * names, oids, and order.
+ *
+ * @param actual_refs actual refs in the remote
+ * @param actual_refs_len length of actual_refs
+ * @param expected_refs expected remote refs
+ * @param expected_refs_len length of expected_refs
+ */
+void verify_remote_refs(const git_remote_head *actual_refs[], size_t actual_refs_len, const expected_ref expected_refs[], size_t expected_refs_len);
+
+#endif /* INCLUDE_cl_push_util_h__ */
diff --git a/tests/pack/indexer.c b/tests/pack/indexer.c
new file mode 100644
index 000000000..084f8e666
--- /dev/null
+++ b/tests/pack/indexer.c
@@ -0,0 +1,127 @@
+#include "clar_libgit2.h"
+#include <git2.h>
+#include "fileops.h"
+#include "hash.h"
+#include "iterator.h"
+#include "vector.h"
+#include "posix.h"
+
+
+/*
+ * This is a packfile with three objects. The second is a delta which
+ * depends on the third, which is also a delta.
+ */
+static const unsigned char out_of_order_pack[] = {
+ 0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03,
+ 0x32, 0x78, 0x9c, 0x63, 0x67, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x76,
+ 0xe6, 0x8f, 0xe8, 0x12, 0x9b, 0x54, 0x6b, 0x10, 0x1a, 0xee, 0x95, 0x10,
+ 0xc5, 0x32, 0x8e, 0x7f, 0x21, 0xca, 0x1d, 0x18, 0x78, 0x9c, 0x63, 0x62,
+ 0x66, 0x4e, 0xcb, 0xcf, 0x07, 0x00, 0x02, 0xac, 0x01, 0x4d, 0x75, 0x01,
+ 0xd7, 0x71, 0x36, 0x66, 0xf4, 0xde, 0x82, 0x27, 0x76, 0xc7, 0x62, 0x2c,
+ 0x10, 0xf1, 0xb0, 0x7d, 0xe2, 0x80, 0xdc, 0x78, 0x9c, 0x63, 0x62, 0x62,
+ 0x62, 0xb7, 0x03, 0x00, 0x00, 0x69, 0x00, 0x4c, 0xde, 0x7d, 0xaa, 0xe4,
+ 0x19, 0x87, 0x58, 0x80, 0x61, 0x09, 0x9a, 0x33, 0xca, 0x7a, 0x31, 0x92,
+ 0x6f, 0xae, 0x66, 0x75
+};
+static const unsigned int out_of_order_pack_len = 112;
+
+/*
+ * Packfile with two objects. The second is a delta against an object
+ * which is not in the packfile
+ */
+static const unsigned char thin_pack[] = {
+ 0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02,
+ 0x32, 0x78, 0x9c, 0x63, 0x67, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x76,
+ 0xe6, 0x8f, 0xe8, 0x12, 0x9b, 0x54, 0x6b, 0x10, 0x1a, 0xee, 0x95, 0x10,
+ 0xc5, 0x32, 0x8e, 0x7f, 0x21, 0xca, 0x1d, 0x18, 0x78, 0x9c, 0x63, 0x62,
+ 0x66, 0x4e, 0xcb, 0xcf, 0x07, 0x00, 0x02, 0xac, 0x01, 0x4d, 0x42, 0x52,
+ 0x3a, 0x6f, 0x39, 0xd1, 0xfe, 0x66, 0x68, 0x6b, 0xa5, 0xe5, 0xe2, 0x97,
+ 0xac, 0x94, 0x6c, 0x76, 0x0b, 0x04
+};
+static const unsigned int thin_pack_len = 78;
+
+static const unsigned char base_obj[] = { 07, 076 };
+static const unsigned int base_obj_len = 2;
+
+void test_pack_indexer__out_of_order(void)
+{
+ git_indexer *idx = 0;
+ git_transfer_progress stats = { 0 };
+
+ cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL, NULL));
+ cl_git_pass(git_indexer_append(
+ idx, out_of_order_pack, out_of_order_pack_len, &stats));
+ cl_git_pass(git_indexer_commit(idx, &stats));
+
+ cl_assert_equal_i(stats.total_objects, 3);
+ cl_assert_equal_i(stats.received_objects, 3);
+ cl_assert_equal_i(stats.indexed_objects, 3);
+
+ git_indexer_free(idx);
+}
+
+void test_pack_indexer__fix_thin(void)
+{
+ git_indexer *idx = NULL;
+ git_transfer_progress stats = { 0 };
+ git_repository *repo;
+ git_odb *odb;
+ git_oid id, should_id;
+
+ cl_git_pass(git_repository_init(&repo, "thin.git", true));
+ cl_git_pass(git_repository_odb(&odb, repo));
+
+ /* Store the missing base into your ODB so the indexer can fix the pack */
+ cl_git_pass(git_odb_write(&id, odb, base_obj, base_obj_len, GIT_OBJ_BLOB));
+ git_oid_fromstr(&should_id, "e68fe8129b546b101aee9510c5328e7f21ca1d18");
+ cl_assert(!git_oid_cmp(&id, &should_id));
+
+ cl_git_pass(git_indexer_new(&idx, ".", 0, odb, NULL, NULL));
+ cl_git_pass(git_indexer_append(idx, thin_pack, thin_pack_len, &stats));
+ cl_git_pass(git_indexer_commit(idx, &stats));
+
+ cl_assert_equal_i(stats.total_objects, 2);
+ cl_assert_equal_i(stats.received_objects, 2);
+ cl_assert_equal_i(stats.indexed_objects, 2);
+ cl_assert_equal_i(stats.local_objects, 1);
+
+ git_oid_fromstr(&should_id, "11f0f69b334728fdd8bc86b80499f22f29d85b15");
+ cl_assert(!git_oid_cmp(git_indexer_hash(idx), &should_id));
+
+ git_indexer_free(idx);
+ git_odb_free(odb);
+ git_repository_free(repo);
+
+ /*
+ * The pack's name/hash only tells us what objects there are,
+ * so we need to go through the packfile again in order to
+ * figure out whether we calculated the trailer correctly.
+ */
+ {
+ unsigned char buffer[128];
+ int fd;
+ ssize_t read;
+ struct stat st;
+ const char *name = "pack-11f0f69b334728fdd8bc86b80499f22f29d85b15.pack";
+
+ fd = p_open(name, O_RDONLY);
+ cl_assert(fd != -1);
+
+ cl_git_pass(p_stat(name, &st));
+
+ cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL, NULL));
+ read = p_read(fd, buffer, sizeof(buffer));
+ cl_assert(read != -1);
+ p_close(fd);
+
+ cl_git_pass(git_indexer_append(idx, buffer, read, &stats));
+ cl_git_pass(git_indexer_commit(idx, &stats));
+
+ cl_assert_equal_i(stats.total_objects, 3);
+ cl_assert_equal_i(stats.received_objects, 3);
+ cl_assert_equal_i(stats.indexed_objects, 3);
+ cl_assert_equal_i(stats.local_objects, 0);
+
+ git_indexer_free(idx);
+ }
+}
diff --git a/tests/pack/packbuilder.c b/tests/pack/packbuilder.c
new file mode 100644
index 000000000..1059424ed
--- /dev/null
+++ b/tests/pack/packbuilder.c
@@ -0,0 +1,225 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+#include "pack.h"
+#include "hash.h"
+#include "iterator.h"
+#include "vector.h"
+#include "posix.h"
+
+static git_repository *_repo;
+static git_revwalk *_revwalker;
+static git_packbuilder *_packbuilder;
+static git_indexer *_indexer;
+static git_vector _commits;
+static int _commits_is_initialized;
+static git_transfer_progress _stats;
+
+void test_pack_packbuilder__initialize(void)
+{
+ _repo = cl_git_sandbox_init("testrepo.git");
+ cl_git_pass(p_chdir("testrepo.git"));
+ cl_git_pass(git_revwalk_new(&_revwalker, _repo));
+ cl_git_pass(git_packbuilder_new(&_packbuilder, _repo));
+ cl_git_pass(git_vector_init(&_commits, 0, NULL));
+ _commits_is_initialized = 1;
+ memset(&_stats, 0, sizeof(_stats));
+}
+
+void test_pack_packbuilder__cleanup(void)
+{
+ git_oid *o;
+ unsigned int i;
+
+ if (_commits_is_initialized) {
+ _commits_is_initialized = 0;
+ git_vector_foreach(&_commits, i, o) {
+ git__free(o);
+ }
+ git_vector_free(&_commits);
+ }
+
+ git_packbuilder_free(_packbuilder);
+ _packbuilder = NULL;
+
+ git_revwalk_free(_revwalker);
+ _revwalker = NULL;
+
+ git_indexer_free(_indexer);
+ _indexer = NULL;
+
+ p_chdir("..");
+ cl_git_sandbox_cleanup();
+ _repo = NULL;
+}
+
+static void seed_packbuilder(void)
+{
+ git_oid oid, *o;
+ unsigned int i;
+
+ git_revwalk_sorting(_revwalker, GIT_SORT_TIME);
+ cl_git_pass(git_revwalk_push_ref(_revwalker, "HEAD"));
+
+ while (git_revwalk_next(&oid, _revwalker) == 0) {
+ o = git__malloc(GIT_OID_RAWSZ);
+ cl_assert(o != NULL);
+ git_oid_cpy(o, &oid);
+ cl_git_pass(git_vector_insert(&_commits, o));
+ }
+
+ git_vector_foreach(&_commits, i, o) {
+ cl_git_pass(git_packbuilder_insert(_packbuilder, o, NULL));
+ }
+
+ git_vector_foreach(&_commits, i, o) {
+ git_object *obj;
+ cl_git_pass(git_object_lookup(&obj, _repo, o, GIT_OBJ_COMMIT));
+ cl_git_pass(git_packbuilder_insert_tree(_packbuilder,
+ git_commit_tree_id((git_commit *)obj)));
+ git_object_free(obj);
+ }
+}
+
+static int feed_indexer(void *ptr, size_t len, void *payload)
+{
+ git_transfer_progress *stats = (git_transfer_progress *)payload;
+
+ return git_indexer_append(_indexer, ptr, len, stats);
+}
+
+void test_pack_packbuilder__create_pack(void)
+{
+ git_transfer_progress stats;
+ git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT;
+ git_hash_ctx ctx;
+ git_oid hash;
+ char hex[41]; hex[40] = '\0';
+
+ seed_packbuilder();
+
+ cl_git_pass(git_indexer_new(&_indexer, ".", 0, NULL, NULL, NULL));
+ cl_git_pass(git_packbuilder_foreach(_packbuilder, feed_indexer, &stats));
+ cl_git_pass(git_indexer_commit(_indexer, &stats));
+
+ git_oid_fmt(hex, git_indexer_hash(_indexer));
+ git_buf_printf(&path, "pack-%s.pack", hex);
+
+ /*
+ * By default, packfiles are created with only one thread.
+ * Therefore we can predict the object ordering and make sure
+ * we create exactly the same pack as git.git does when *not*
+ * reusing existing deltas (as libgit2).
+ *
+ * $ cd tests/resources/testrepo.git
+ * $ git rev-list --objects HEAD | \
+ * git pack-objects -q --no-reuse-delta --threads=1 pack
+ * $ sha1sum git-80e61eb315239ef3c53033e37fee43b744d57122.pack
+ * 5d410bdf97cf896f9007681b92868471d636954b
+ *
+ */
+
+ cl_git_pass(git_futils_readbuffer(&buf, git_buf_cstr(&path)));
+
+ cl_git_pass(git_hash_ctx_init(&ctx));
+ cl_git_pass(git_hash_update(&ctx, buf.ptr, buf.size));
+ cl_git_pass(git_hash_final(&hash, &ctx));
+ git_hash_ctx_cleanup(&ctx);
+
+ git_buf_free(&path);
+ git_buf_free(&buf);
+
+ git_oid_fmt(hex, &hash);
+
+ cl_assert_equal_s(hex, "5d410bdf97cf896f9007681b92868471d636954b");
+}
+
+void test_pack_packbuilder__get_hash(void)
+{
+ char hex[41]; hex[40] = '\0';
+
+ seed_packbuilder();
+
+ git_packbuilder_write(_packbuilder, ".", 0, NULL, NULL);
+ git_oid_fmt(hex, git_packbuilder_hash(_packbuilder));
+
+ cl_assert_equal_s(hex, "80e61eb315239ef3c53033e37fee43b744d57122");
+}
+
+static void test_write_pack_permission(mode_t given, mode_t expected)
+{
+ struct stat statbuf;
+ mode_t mask, os_mask;
+
+ seed_packbuilder();
+
+ git_packbuilder_write(_packbuilder, ".", given, NULL, NULL);
+
+ /* Windows does not return group/user bits from stat,
+ * files are never executable.
+ */
+#ifdef GIT_WIN32
+ os_mask = 0600;
+#else
+ os_mask = 0777;
+#endif
+
+ mask = p_umask(0);
+ p_umask(mask);
+
+ cl_git_pass(p_stat("pack-80e61eb315239ef3c53033e37fee43b744d57122.idx", &statbuf));
+ cl_assert_equal_i(statbuf.st_mode & os_mask, (expected & ~mask) & os_mask);
+
+ cl_git_pass(p_stat("pack-80e61eb315239ef3c53033e37fee43b744d57122.pack", &statbuf));
+ cl_assert_equal_i(statbuf.st_mode & os_mask, (expected & ~mask) & os_mask);
+}
+
+void test_pack_packbuilder__permissions_standard(void)
+{
+ test_write_pack_permission(0, GIT_PACK_FILE_MODE);
+}
+
+void test_pack_packbuilder__permissions_readonly(void)
+{
+ test_write_pack_permission(0444, 0444);
+}
+
+void test_pack_packbuilder__permissions_readwrite(void)
+{
+ test_write_pack_permission(0666, 0666);
+}
+
+static int foreach_cb(void *buf, size_t len, void *payload)
+{
+ git_indexer *idx = (git_indexer *) payload;
+ cl_git_pass(git_indexer_append(idx, buf, len, &_stats));
+ return 0;
+}
+
+void test_pack_packbuilder__foreach(void)
+{
+ git_indexer *idx;
+
+ seed_packbuilder();
+ cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL, NULL));
+ cl_git_pass(git_packbuilder_foreach(_packbuilder, foreach_cb, idx));
+ cl_git_pass(git_indexer_commit(idx, &_stats));
+ git_indexer_free(idx);
+}
+
+static int foreach_cancel_cb(void *buf, size_t len, void *payload)
+{
+ git_indexer *idx = (git_indexer *)payload;
+ cl_git_pass(git_indexer_append(idx, buf, len, &_stats));
+ return (_stats.total_objects > 2) ? -1111 : 0;
+}
+
+void test_pack_packbuilder__foreach_with_cancel(void)
+{
+ git_indexer *idx;
+
+ seed_packbuilder();
+ cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL, NULL));
+ cl_git_fail_with(
+ git_packbuilder_foreach(_packbuilder, foreach_cancel_cb, idx), -1111);
+ git_indexer_free(idx);
+}
diff --git a/tests/refs/branches/create.c b/tests/refs/branches/create.c
new file mode 100644
index 000000000..3a4f33b6e
--- /dev/null
+++ b/tests/refs/branches/create.c
@@ -0,0 +1,198 @@
+#include "clar_libgit2.h"
+#include "refs.h"
+#include "path.h"
+
+static git_repository *repo;
+static git_commit *target;
+static git_reference *branch;
+
+void test_refs_branches_create__initialize(void)
+{
+ repo = cl_git_sandbox_init("testrepo.git");
+ branch = NULL;
+ target = NULL;
+}
+
+void test_refs_branches_create__cleanup(void)
+{
+ git_reference_free(branch);
+ branch = NULL;
+
+ git_commit_free(target);
+ target = NULL;
+
+ cl_git_sandbox_cleanup();
+ repo = NULL;
+}
+
+static void retrieve_target_from_oid(git_commit **out, git_repository *repo, const char *sha)
+{
+ git_object *obj;
+
+ cl_git_pass(git_revparse_single(&obj, repo, sha));
+ cl_git_pass(git_commit_lookup(out, repo, git_object_id(obj)));
+ git_object_free(obj);
+}
+
+static void retrieve_known_commit(git_commit **commit, git_repository *repo)
+{
+ retrieve_target_from_oid(commit, repo, "e90810b8df3");
+}
+
+#define NEW_BRANCH_NAME "new-branch-on-the-block"
+
+void test_refs_branches_create__can_create_a_local_branch(void)
+{
+ retrieve_known_commit(&target, repo);
+
+ cl_git_pass(git_branch_create(&branch, repo, NEW_BRANCH_NAME, target, 0, NULL, NULL));
+ cl_git_pass(git_oid_cmp(git_reference_target(branch), git_commit_id(target)));
+}
+
+void test_refs_branches_create__can_not_create_a_branch_if_its_name_collide_with_an_existing_one(void)
+{
+ retrieve_known_commit(&target, repo);
+
+ cl_assert_equal_i(GIT_EEXISTS, git_branch_create(&branch, repo, "br2", target, 0, NULL, NULL));
+}
+
+void test_refs_branches_create__can_force_create_over_an_existing_branch(void)
+{
+ retrieve_known_commit(&target, repo);
+
+ cl_git_pass(git_branch_create(&branch, repo, "br2", target, 1, NULL, NULL));
+ cl_git_pass(git_oid_cmp(git_reference_target(branch), git_commit_id(target)));
+ cl_assert_equal_s("refs/heads/br2", git_reference_name(branch));
+}
+
+void test_refs_branches_create__cannot_force_create_over_current_branch(void)
+{
+ const git_oid *oid;
+ git_reference *branch2;
+ retrieve_known_commit(&target, repo);
+
+ cl_git_pass(git_branch_lookup(&branch2, repo, "master", GIT_BRANCH_LOCAL));
+ cl_assert_equal_s("refs/heads/master", git_reference_name(branch2));
+ cl_assert_equal_i(true, git_branch_is_head(branch2));
+ oid = git_reference_target(branch2);
+
+ cl_git_fail_with(-1, git_branch_create(&branch, repo, "master", target, 1, NULL, NULL));
+ branch = NULL;
+ cl_git_pass(git_branch_lookup(&branch, repo, "master", GIT_BRANCH_LOCAL));
+ cl_assert_equal_s("refs/heads/master", git_reference_name(branch));
+ cl_git_pass(git_oid_cmp(git_reference_target(branch), oid));
+ git_reference_free(branch2);
+}
+
+void test_refs_branches_create__creating_a_branch_with_an_invalid_name_returns_EINVALIDSPEC(void)
+{
+ retrieve_known_commit(&target, repo);
+
+ cl_assert_equal_i(GIT_EINVALIDSPEC,
+ git_branch_create(&branch, repo, "inv@{id", target, 0, NULL, NULL));
+}
+
+void test_refs_branches_create__creation_creates_new_reflog(void)
+{
+ git_reflog *log;
+ const git_reflog_entry *entry;
+ git_signature *sig;
+
+ cl_git_pass(git_signature_now(&sig, "me", "foo@example.com"));
+
+ retrieve_known_commit(&target, repo);
+ cl_git_pass(git_branch_create(&branch, repo, NEW_BRANCH_NAME, target, false, sig, "create!"));
+ cl_git_pass(git_reflog_read(&log, repo, "refs/heads/" NEW_BRANCH_NAME));
+
+ cl_assert_equal_i(1, git_reflog_entrycount(log));
+ entry = git_reflog_entry_byindex(log, 0);
+ cl_assert_equal_s("create!", git_reflog_entry_message(entry));
+ cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email);
+
+ git_reflog_free(log);
+ git_signature_free(sig);
+}
+
+void test_refs_branches_create__default_reflog_message(void)
+{
+ git_reflog *log;
+ const git_reflog_entry *entry;
+ git_signature *sig;
+ git_config *cfg;
+
+ cl_git_pass(git_repository_config(&cfg, repo));
+ cl_git_pass(git_config_set_string(cfg, "user.name", "Foo Bar"));
+ cl_git_pass(git_config_set_string(cfg, "user.email", "foo@example.com"));
+ git_config_free(cfg);
+
+ cl_git_pass(git_signature_default(&sig, repo));
+
+ retrieve_known_commit(&target, repo);
+ cl_git_pass(git_branch_create(&branch, repo, NEW_BRANCH_NAME, target, false, NULL, NULL));
+ cl_git_pass(git_reflog_read(&log, repo, "refs/heads/" NEW_BRANCH_NAME));
+
+ entry = git_reflog_entry_byindex(log, 0);
+ cl_assert_equal_s("Branch: created", git_reflog_entry_message(entry));
+ cl_assert_equal_s(sig->email, git_reflog_entry_committer(entry)->email);
+
+ git_reflog_free(log);
+ git_signature_free(sig);
+}
+
+static void assert_branch_matches_name(
+ const char *expected, const char *lookup_as)
+{
+ git_reference *ref;
+ git_buf b = GIT_BUF_INIT;
+
+ cl_git_pass(git_branch_lookup(&ref, repo, lookup_as, GIT_BRANCH_LOCAL));
+
+ cl_git_pass(git_buf_sets(&b, "refs/heads/"));
+ cl_git_pass(git_buf_puts(&b, expected));
+ cl_assert_equal_s(b.ptr, git_reference_name(ref));
+
+ cl_git_pass(
+ git_oid_cmp(git_reference_target(ref), git_commit_id(target)));
+
+ git_reference_free(ref);
+ git_buf_free(&b);
+}
+
+void test_refs_branches_create__can_create_branch_with_unicode(void)
+{
+ const char *nfc = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D";
+ const char *nfd = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D";
+ const char *emoji = "\xF0\x9F\x8D\xB7";
+ const char *names[] = { nfc, nfd, emoji };
+ const char *alt[] = { nfd, nfc, NULL };
+ const char *expected[] = { nfc, nfd, emoji };
+ unsigned int i;
+ bool fs_decompose_unicode =
+ git_path_does_fs_decompose_unicode(git_repository_path(repo));
+
+ retrieve_known_commit(&target, repo);
+
+ if (cl_repo_get_bool(repo, "core.precomposeunicode"))
+ expected[1] = nfc;
+ /* test decomp. because not all Mac filesystems decompose unicode */
+ else if (fs_decompose_unicode)
+ expected[0] = nfd;
+
+ for (i = 0; i < ARRAY_SIZE(names); ++i) {
+ const char *name;
+ cl_git_pass(git_branch_create(
+ &branch, repo, names[i], target, 0, NULL, NULL));
+ cl_git_pass(git_oid_cmp(
+ git_reference_target(branch), git_commit_id(target)));
+
+ cl_git_pass(git_branch_name(&name, branch));
+ cl_assert_equal_s(expected[i], name);
+ assert_branch_matches_name(expected[i], names[i]);
+ if (fs_decompose_unicode && alt[i])
+ assert_branch_matches_name(expected[i], alt[i]);
+
+ cl_git_pass(git_branch_delete(branch));
+ git_reference_free(branch);
+ branch = NULL;
+ }
+}
diff --git a/tests/refs/branches/delete.c b/tests/refs/branches/delete.c
new file mode 100644
index 000000000..e3199e230
--- /dev/null
+++ b/tests/refs/branches/delete.c
@@ -0,0 +1,140 @@
+#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;
+
+void test_refs_branches_delete__initialize(void)
+{
+ git_oid id;
+
+ repo = cl_git_sandbox_init("testrepo.git");
+
+ cl_git_pass(git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"));
+ cl_git_pass(git_reference_create(&fake_remote, repo, "refs/remotes/nulltoken/master", &id, 0, NULL, NULL));
+}
+
+void test_refs_branches_delete__cleanup(void)
+{
+ git_reference_free(fake_remote);
+ fake_remote = NULL;
+
+ cl_git_sandbox_cleanup();
+ repo = NULL;
+}
+
+void test_refs_branches_delete__can_not_delete_a_branch_pointed_at_by_HEAD(void)
+{
+ git_reference *head;
+ git_reference *branch;
+
+ /* Ensure HEAD targets the local master branch */
+ cl_git_pass(git_reference_lookup(&head, repo, GIT_HEAD_FILE));
+ cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head));
+ git_reference_free(head);
+
+ cl_git_pass(git_branch_lookup(&branch, repo, "master", GIT_BRANCH_LOCAL));
+ cl_git_fail(git_branch_delete(branch));
+ git_reference_free(branch);
+}
+
+void test_refs_branches_delete__can_delete_a_branch_even_if_HEAD_is_missing(void)
+{
+ git_reference *head;
+ git_reference *branch;
+
+ cl_git_pass(git_reference_lookup(&head, repo, GIT_HEAD_FILE));
+ git_reference_delete(head);
+ git_reference_free(head);
+
+ cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL));
+ cl_git_pass(git_branch_delete(branch));
+ git_reference_free(branch);
+}
+
+void test_refs_branches_delete__can_delete_a_branch_when_HEAD_is_unborn(void)
+{
+ git_reference *branch;
+
+ make_head_unborn(repo, NON_EXISTING_HEAD);
+
+ cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL));
+ cl_git_pass(git_branch_delete(branch));
+ git_reference_free(branch);
+}
+
+void test_refs_branches_delete__can_delete_a_branch_pointed_at_by_detached_HEAD(void)
+{
+ git_reference *head, *branch;
+
+ cl_git_pass(git_reference_lookup(&head, repo, GIT_HEAD_FILE));
+ cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head));
+ cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head));
+ git_reference_free(head);
+
+ /* Detach HEAD and make it target the commit that "master" points to */
+ git_repository_detach_head(repo, NULL, NULL);
+
+ cl_git_pass(git_branch_lookup(&branch, repo, "master", GIT_BRANCH_LOCAL));
+ cl_git_pass(git_branch_delete(branch));
+ git_reference_free(branch);
+}
+
+void test_refs_branches_delete__can_delete_a_local_branch(void)
+{
+ git_reference *branch;
+ cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL));
+ cl_git_pass(git_branch_delete(branch));
+ git_reference_free(branch);
+}
+
+void test_refs_branches_delete__can_delete_a_remote_branch(void)
+{
+ git_reference *branch;
+ cl_git_pass(git_branch_lookup(&branch, repo, "nulltoken/master", GIT_BRANCH_REMOTE));
+ cl_git_pass(git_branch_delete(branch));
+ git_reference_free(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));
+ git_reference_free(branch);
+
+ assert_config_entry_existence(repo, "branch.track-local.remote", false);
+ assert_config_entry_existence(repo, "branch.track-local.merge", false);
+}
+
+void test_refs_branches_delete__removes_reflog(void)
+{
+ git_reference *branch;
+ git_reflog *log;
+ git_oid oidzero = {{0}};
+ git_signature *sig;
+
+ /* Ensure the reflog has at least one entry */
+ cl_git_pass(git_signature_now(&sig, "Me", "user@example.com"));
+ cl_git_pass(git_reflog_read(&log, repo, "refs/heads/track-local"));
+ cl_git_pass(git_reflog_append(log, &oidzero, sig, "message"));
+ cl_assert(git_reflog_entrycount(log) > 0);
+ git_signature_free(sig);
+ git_reflog_free(log);
+
+ cl_git_pass(git_branch_lookup(&branch, repo, "track-local", GIT_BRANCH_LOCAL));
+ cl_git_pass(git_branch_delete(branch));
+ git_reference_free(branch);
+
+ /* Reading a nonexistant reflog creates it, but it should be empty */
+ cl_git_pass(git_reflog_read(&log, repo, "refs/heads/track-local"));
+ cl_assert_equal_i(0, git_reflog_entrycount(log));
+ git_reflog_free(log);
+}
+
diff --git a/tests/refs/branches/ishead.c b/tests/refs/branches/ishead.c
new file mode 100644
index 000000000..d16a79652
--- /dev/null
+++ b/tests/refs/branches/ishead.c
@@ -0,0 +1,98 @@
+#include "clar_libgit2.h"
+#include "refs.h"
+#include "repo/repo_helpers.h"
+
+static git_repository *repo;
+static git_reference *branch;
+
+void test_refs_branches_ishead__initialize(void)
+{
+ repo = cl_git_sandbox_init("testrepo.git");
+ branch = NULL;
+}
+
+void test_refs_branches_ishead__cleanup(void)
+{
+ git_reference_free(branch);
+ branch = NULL;
+
+ cl_git_sandbox_cleanup();
+ repo = NULL;
+}
+
+void test_refs_branches_ishead__can_tell_if_a_branch_is_pointed_at_by_HEAD(void)
+{
+ cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master"));
+
+ cl_assert_equal_i(true, git_branch_is_head(branch));
+}
+
+void test_refs_branches_ishead__can_properly_handle_unborn_HEAD(void)
+{
+ make_head_unborn(repo, NON_EXISTING_HEAD);
+
+ cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master"));
+
+ cl_assert_equal_i(false, git_branch_is_head(branch));
+}
+
+void test_refs_branches_ishead__can_properly_handle_missing_HEAD(void)
+{
+ delete_head(repo);
+
+ cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master"));
+
+ cl_assert_equal_i(false, git_branch_is_head(branch));
+}
+
+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"));
+
+ cl_assert_equal_i(false, git_branch_is_head(branch));
+}
+
+void test_refs_branches_ishead__wont_be_fooled_by_a_non_branch(void)
+{
+ cl_git_pass(git_reference_lookup(&branch, repo, "refs/tags/e90810b"));
+
+ cl_assert_equal_i(false, git_branch_is_head(branch));
+}
+
+/*
+ * $ git init .
+ * Initialized empty Git repository in d:/temp/tempee/.git/
+ *
+ * $ touch a && git add a
+ * $ git commit -m" boom"
+ * [master (root-commit) b47b758] boom
+ * 0 files changed
+ * create mode 100644 a
+ *
+ * $ echo "ref: refs/heads/master" > .git/refs/heads/linked
+ * $ echo "ref: refs/heads/linked" > .git/refs/heads/super
+ * $ echo "ref: refs/heads/super" > .git/HEAD
+ *
+ * $ git branch
+ * linked -> master
+ * * master
+ * super -> master
+ */
+void test_refs_branches_ishead__only_direct_references_are_considered(void)
+{
+ git_reference *linked, *super, *head;
+
+ cl_git_pass(git_reference_symbolic_create(&linked, repo, "refs/heads/linked", "refs/heads/master", 0, NULL, NULL));
+ cl_git_pass(git_reference_symbolic_create(&super, repo, "refs/heads/super", "refs/heads/linked", 0, NULL, NULL));
+ cl_git_pass(git_reference_symbolic_create(&head, repo, GIT_HEAD_FILE, "refs/heads/super", 1, NULL, NULL));
+
+ cl_assert_equal_i(false, git_branch_is_head(linked));
+ cl_assert_equal_i(false, git_branch_is_head(super));
+
+ cl_git_pass(git_repository_head(&branch, repo));
+ cl_assert_equal_s("refs/heads/master", git_reference_name(branch));
+
+ git_reference_free(linked);
+ git_reference_free(super);
+ git_reference_free(head);
+}
diff --git a/tests/refs/branches/iterator.c b/tests/refs/branches/iterator.c
new file mode 100644
index 000000000..76b35a7d6
--- /dev/null
+++ b/tests/refs/branches/iterator.c
@@ -0,0 +1,151 @@
+#include "clar_libgit2.h"
+#include "refs.h"
+
+static git_repository *repo;
+static git_reference *fake_remote;
+
+void test_refs_branches_iterator__initialize(void)
+{
+ git_oid id;
+
+ cl_fixture_sandbox("testrepo.git");
+ cl_git_pass(git_repository_open(&repo, "testrepo.git"));
+
+ cl_git_pass(git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"));
+ cl_git_pass(git_reference_create(&fake_remote, repo, "refs/remotes/nulltoken/master", &id, 0, NULL, NULL));
+}
+
+void test_refs_branches_iterator__cleanup(void)
+{
+ git_reference_free(fake_remote);
+ fake_remote = NULL;
+
+ git_repository_free(repo);
+ repo = NULL;
+
+ cl_fixture_cleanup("testrepo.git");
+
+ cl_git_sandbox_cleanup();
+}
+
+static void assert_retrieval(unsigned int flags, unsigned int expected_count)
+{
+ git_branch_iterator *iter;
+ git_reference *ref;
+ int count = 0, error;
+ git_branch_t type;
+
+ cl_git_pass(git_branch_iterator_new(&iter, repo, flags));
+ while ((error = git_branch_next(&ref, &type, iter)) == 0) {
+ count++;
+ git_reference_free(ref);
+ }
+
+ git_branch_iterator_free(iter);
+ cl_assert_equal_i(error, GIT_ITEROVER);
+ cl_assert_equal_i(expected_count, count);
+}
+
+void test_refs_branches_iterator__retrieve_all_branches(void)
+{
+ assert_retrieval(GIT_BRANCH_ALL, 14);
+}
+
+void test_refs_branches_iterator__retrieve_remote_branches(void)
+{
+ assert_retrieval(GIT_BRANCH_REMOTE, 2);
+}
+
+void test_refs_branches_iterator__retrieve_local_branches(void)
+{
+ assert_retrieval(GIT_BRANCH_LOCAL, 12);
+}
+
+struct expectations {
+ const char *branch_name;
+ int encounters;
+};
+
+static void assert_branch_has_been_found(struct expectations *findings, const char* expected_branch_name)
+{
+ int pos = 0;
+
+ for (pos = 0; findings[pos].branch_name; ++pos) {
+ if (strcmp(expected_branch_name, findings[pos].branch_name) == 0) {
+ cl_assert_equal_i(1, findings[pos].encounters);
+ return;
+ }
+ }
+
+ cl_fail("expected branch not found in list.");
+}
+
+static void contains_branches(struct expectations exp[], git_branch_iterator *iter)
+{
+ git_reference *ref;
+ git_branch_t type;
+ int error, pos = 0;
+
+ while ((error = git_branch_next(&ref, &type, iter)) == 0) {
+ for (pos = 0; exp[pos].branch_name; ++pos) {
+ if (strcmp(git_reference_shorthand(ref), exp[pos].branch_name) == 0)
+ exp[pos].encounters++;
+ }
+
+ git_reference_free(ref);
+ }
+
+ cl_assert_equal_i(error, GIT_ITEROVER);
+}
+
+/*
+ * $ git branch -r
+ * nulltoken/HEAD -> nulltoken/master
+ * nulltoken/master
+ */
+void test_refs_branches_iterator__retrieve_remote_symbolic_HEAD_when_present(void)
+{
+ git_branch_iterator *iter;
+ struct expectations exp[] = {
+ { "nulltoken/HEAD", 0 },
+ { "nulltoken/master", 0 },
+ { NULL, 0 }
+ };
+
+ git_reference_free(fake_remote);
+ cl_git_pass(git_reference_symbolic_create(&fake_remote, repo, "refs/remotes/nulltoken/HEAD", "refs/remotes/nulltoken/master", 0, NULL, NULL));
+
+ assert_retrieval(GIT_BRANCH_REMOTE, 3);
+
+ cl_git_pass(git_branch_iterator_new(&iter, repo, GIT_BRANCH_REMOTE));
+ contains_branches(exp, iter);
+ git_branch_iterator_free(iter);
+
+ assert_branch_has_been_found(exp, "nulltoken/HEAD");
+ assert_branch_has_been_found(exp, "nulltoken/master");
+}
+
+void test_refs_branches_iterator__mix_of_packed_and_loose(void)
+{
+ git_branch_iterator *iter;
+ struct expectations exp[] = {
+ { "master", 0 },
+ { "origin/HEAD", 0 },
+ { "origin/master", 0 },
+ { "origin/packed", 0 },
+ { NULL, 0 }
+ };
+ git_repository *r2;
+
+ r2 = cl_git_sandbox_init("testrepo2");
+
+ cl_git_pass(git_branch_iterator_new(&iter, r2, GIT_BRANCH_ALL));
+ contains_branches(exp, iter);
+
+ git_branch_iterator_free(iter);
+
+ assert_branch_has_been_found(exp, "master");
+ assert_branch_has_been_found(exp, "origin/HEAD");
+ assert_branch_has_been_found(exp, "origin/master");
+ assert_branch_has_been_found(exp, "origin/packed");
+}
diff --git a/tests/refs/branches/lookup.c b/tests/refs/branches/lookup.c
new file mode 100644
index 000000000..95d49a4b3
--- /dev/null
+++ b/tests/refs/branches/lookup.c
@@ -0,0 +1,45 @@
+#include "clar_libgit2.h"
+#include "refs.h"
+
+static git_repository *repo;
+static git_reference *branch;
+
+void test_refs_branches_lookup__initialize(void)
+{
+ cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
+
+ branch = NULL;
+}
+
+void test_refs_branches_lookup__cleanup(void)
+{
+ git_reference_free(branch);
+ branch = NULL;
+
+ git_repository_free(repo);
+ repo = NULL;
+}
+
+void test_refs_branches_lookup__can_retrieve_a_local_branch(void)
+{
+ cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL));
+}
+
+void test_refs_branches_lookup__can_retrieve_a_remote_tracking_branch(void)
+{
+ cl_git_pass(git_branch_lookup(&branch, repo, "test/master", GIT_BRANCH_REMOTE));
+}
+
+void test_refs_branches_lookup__trying_to_retrieve_an_unknown_branch_returns_ENOTFOUND(void)
+{
+ cl_assert_equal_i(GIT_ENOTFOUND, git_branch_lookup(&branch, repo, "where/are/you", GIT_BRANCH_LOCAL));
+ cl_assert_equal_i(GIT_ENOTFOUND, git_branch_lookup(&branch, repo, "over/here", GIT_BRANCH_REMOTE));
+}
+
+void test_refs_branches_lookup__trying_to_retrieve_a_branch_with_an_invalid_name_returns_EINVALIDSPEC(void)
+{
+ cl_assert_equal_i(GIT_EINVALIDSPEC,
+ git_branch_lookup(&branch, repo, "are/you/inv@{id", GIT_BRANCH_LOCAL));
+ cl_assert_equal_i(GIT_EINVALIDSPEC,
+ git_branch_lookup(&branch, repo, "yes/i am", GIT_BRANCH_REMOTE));
+}
diff --git a/tests/refs/branches/move.c b/tests/refs/branches/move.c
new file mode 100644
index 000000000..f136b00d6
--- /dev/null
+++ b/tests/refs/branches/move.c
@@ -0,0 +1,260 @@
+#include "clar_libgit2.h"
+#include "refs.h"
+#include "config/config_helpers.h"
+
+static git_repository *repo;
+
+void test_refs_branches_move__initialize(void)
+{
+ repo = cl_git_sandbox_init("testrepo.git");
+}
+
+void test_refs_branches_move__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+#define NEW_BRANCH_NAME "new-branch-on-the-block"
+
+void test_refs_branches_move__can_move_a_local_branch(void)
+{
+ git_reference *original_ref, *new_ref;
+
+ cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2"));
+
+ cl_git_pass(git_branch_move(&new_ref, original_ref, NEW_BRANCH_NAME, 0, NULL, NULL));
+ cl_assert_equal_s(GIT_REFS_HEADS_DIR NEW_BRANCH_NAME, git_reference_name(new_ref));
+
+ git_reference_free(original_ref);
+ git_reference_free(new_ref);
+}
+
+void test_refs_branches_move__can_move_a_local_branch_to_a_different_namespace(void)
+{
+ git_reference *original_ref, *new_ref, *newer_ref;
+
+ cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2"));
+
+ /* Downward */
+ cl_git_pass(git_branch_move(&new_ref, original_ref, "somewhere/" NEW_BRANCH_NAME, 0, NULL, NULL));
+ git_reference_free(original_ref);
+
+ /* Upward */
+ cl_git_pass(git_branch_move(&newer_ref, new_ref, "br2", 0, NULL, NULL));
+ git_reference_free(new_ref);
+
+ git_reference_free(newer_ref);
+}
+
+void test_refs_branches_move__can_move_a_local_branch_to_a_partially_colliding_namespace(void)
+{
+ git_reference *original_ref, *new_ref, *newer_ref;
+
+ cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2"));
+
+ /* Downward */
+ cl_git_pass(git_branch_move(&new_ref, original_ref, "br2/" NEW_BRANCH_NAME, 0, NULL, NULL));
+ git_reference_free(original_ref);
+
+ /* Upward */
+ cl_git_pass(git_branch_move(&newer_ref, new_ref, "br2", 0, NULL, NULL));
+ git_reference_free(new_ref);
+
+ git_reference_free(newer_ref);
+}
+
+void test_refs_branches_move__can_not_move_a_branch_if_its_destination_name_collide_with_an_existing_one(void)
+{
+ git_reference *original_ref, *new_ref;
+ git_config *config;
+ const git_config_entry *ce;
+ char *original_remote, *original_merge;
+
+ cl_git_pass(git_repository_config(&config, repo));
+
+ cl_git_pass(git_config_get_entry(&ce, config, "branch.master.remote"));
+ original_remote = strdup(ce->value);
+ cl_git_pass(git_config_get_entry(&ce, config, "branch.master.merge"));
+ original_merge = strdup(ce->value);
+
+
+ cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2"));
+
+ cl_assert_equal_i(GIT_EEXISTS,
+ git_branch_move(&new_ref, original_ref, "master", 0, NULL, NULL));
+
+ cl_assert(giterr_last()->message != NULL);
+ cl_git_pass(git_config_get_entry(&ce, config, "branch.master.remote"));
+ cl_assert_equal_s(original_remote, ce->value);
+ cl_git_pass(git_config_get_entry(&ce, config, "branch.master.merge"));
+ cl_assert_equal_s(original_merge, ce->value);
+
+
+ cl_assert_equal_i(GIT_EEXISTS,
+ git_branch_move(&new_ref, original_ref, "cannot-fetch", 0, NULL, NULL));
+
+ cl_assert(giterr_last()->message != NULL);
+ cl_git_pass(git_config_get_entry(&ce, config, "branch.master.remote"));
+ cl_assert_equal_s(original_remote, ce->value);
+ cl_git_pass(git_config_get_entry(&ce, config, "branch.master.merge"));
+ cl_assert_equal_s(original_merge, ce->value);
+
+ git_reference_free(original_ref);
+ cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/track-local"));
+
+ cl_assert_equal_i(GIT_EEXISTS,
+ git_branch_move(&new_ref, original_ref, "master", 0, NULL, NULL));
+
+ cl_assert(giterr_last()->message != NULL);
+ cl_git_pass(git_config_get_entry(&ce, config, "branch.master.remote"));
+ cl_assert_equal_s(original_remote, ce->value);
+ cl_git_pass(git_config_get_entry(&ce, config, "branch.master.merge"));
+ cl_assert_equal_s(original_merge, ce->value);
+
+ free(original_remote); free(original_merge);
+ git_reference_free(original_ref);
+ git_config_free(config);
+}
+
+void test_refs_branches_move__moving_a_branch_with_an_invalid_name_returns_EINVALIDSPEC(void)
+{
+ git_reference *original_ref, *new_ref;
+
+ cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2"));
+
+ cl_assert_equal_i(GIT_EINVALIDSPEC, git_branch_move(&new_ref, original_ref, "Inv@{id", 0, NULL, NULL));
+
+ git_reference_free(original_ref);
+}
+
+void test_refs_branches_move__can_not_move_a_non_branch(void)
+{
+ git_reference *tag, *new_ref;
+
+ cl_git_pass(git_reference_lookup(&tag, repo, "refs/tags/e90810b"));
+ cl_git_fail(git_branch_move(&new_ref, tag, NEW_BRANCH_NAME, 0, NULL, NULL));
+
+ git_reference_free(tag);
+}
+
+void test_refs_branches_move__can_force_move_over_an_existing_branch(void)
+{
+ git_reference *original_ref, *new_ref;
+
+ cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2"));
+
+ cl_git_pass(git_branch_move(&new_ref, original_ref, "master", 1, NULL, NULL));
+
+ git_reference_free(original_ref);
+ git_reference_free(new_ref);
+}
+
+void test_refs_branches_move__moving_a_branch_moves_related_configuration_data(void)
+{
+ git_reference *branch;
+ git_reference *new_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(&new_branch, branch, "moved", 0, NULL, NULL));
+ git_reference_free(branch);
+
+ 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(new_branch);
+}
+
+void test_refs_branches_move__moving_the_branch_pointed_at_by_HEAD_updates_HEAD(void)
+{
+ git_reference *branch;
+ git_reference *new_branch;
+
+ cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master"));
+ cl_git_pass(git_branch_move(&new_branch, branch, "master2", 0, NULL, NULL));
+ git_reference_free(branch);
+ git_reference_free(new_branch);
+
+ cl_git_pass(git_repository_head(&branch, repo));
+ cl_assert_equal_s("refs/heads/master2", git_reference_name(branch));
+ git_reference_free(branch);
+}
+
+void test_refs_branches_move__updates_the_reflog(void)
+{
+ git_reference *branch;
+ git_reference *new_branch;
+ git_reflog *log;
+ const git_reflog_entry *entry;
+ git_signature *sig;
+
+ cl_git_pass(git_signature_now(&sig, "me", "foo@example.com"));
+
+ cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master"));
+ cl_git_pass(git_branch_move(&new_branch, branch, "master2", 0, sig, "message"));
+
+ cl_git_pass(git_reflog_read(&log, repo, git_reference_name(new_branch)));
+ entry = git_reflog_entry_byindex(log, 0);
+ cl_assert_equal_s("message", git_reflog_entry_message(entry));
+ cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email);
+
+ git_reference_free(branch);
+ git_reference_free(new_branch);
+ git_reflog_free(log);
+ git_signature_free(sig);
+}
+
+void test_refs_branches_move__default_reflog_message(void)
+{
+ git_reference *branch;
+ git_reference *new_branch;
+ git_reflog *log;
+ const git_reflog_entry *entry;
+ git_signature *sig;
+ git_config *cfg;
+
+ cl_git_pass(git_repository_config(&cfg, repo));
+ cl_git_pass(git_config_set_string(cfg, "user.name", "Foo Bar"));
+ cl_git_pass(git_config_set_string(cfg, "user.email", "foo@example.com"));
+ git_config_free(cfg);
+
+ cl_git_pass(git_signature_default(&sig, repo));
+
+ cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master"));
+ cl_git_pass(git_branch_move(&new_branch, branch, "master2", 0, NULL, NULL));
+
+ cl_git_pass(git_reflog_read(&log, repo, git_reference_name(new_branch)));
+ entry = git_reflog_entry_byindex(log, 0);
+ cl_assert_equal_s("Branch: renamed refs/heads/master to refs/heads/master2",
+ git_reflog_entry_message(entry));
+ cl_assert_equal_s(sig->email, git_reflog_entry_committer(entry)->email);
+
+ git_reference_free(branch);
+ git_reference_free(new_branch);
+ git_reflog_free(log);
+ git_signature_free(sig);
+}
+
+void test_refs_branches_move__can_move_with_unicode(void)
+{
+ git_reference *original_ref, *new_ref;
+ const char *new_branch_name = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D";
+
+ cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2"));
+ cl_git_pass(git_branch_move(&new_ref, original_ref, new_branch_name, 0, NULL, NULL));
+
+ if (cl_repo_get_bool(repo, "core.precomposeunicode"))
+ cl_assert_equal_s(GIT_REFS_HEADS_DIR "\xC3\x85\x73\x74\x72\xC3\xB6\x6D", git_reference_name(new_ref));
+ else
+ cl_assert_equal_s(GIT_REFS_HEADS_DIR "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D", git_reference_name(new_ref));
+
+ git_reference_free(original_ref);
+ git_reference_free(new_ref);
+}
diff --git a/tests/refs/branches/name.c b/tests/refs/branches/name.c
new file mode 100644
index 000000000..176f836a4
--- /dev/null
+++ b/tests/refs/branches/name.c
@@ -0,0 +1,45 @@
+#include "clar_libgit2.h"
+#include "branch.h"
+
+static git_repository *repo;
+static git_reference *ref;
+
+void test_refs_branches_name__initialize(void)
+{
+ cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
+}
+
+void test_refs_branches_name__cleanup(void)
+{
+ git_reference_free(ref);
+ ref = NULL;
+
+ git_repository_free(repo);
+ repo = NULL;
+}
+
+void test_refs_branches_name__can_get_local_branch_name(void)
+{
+ const char *name;
+
+ cl_git_pass(git_branch_lookup(&ref,repo,"master",GIT_BRANCH_LOCAL));
+ cl_git_pass(git_branch_name(&name,ref));
+ cl_assert_equal_s("master",name);
+}
+
+void test_refs_branches_name__can_get_remote_branch_name(void)
+{
+ const char *name;
+
+ cl_git_pass(git_branch_lookup(&ref,repo,"test/master",GIT_BRANCH_REMOTE));
+ cl_git_pass(git_branch_name(&name,ref));
+ cl_assert_equal_s("test/master",name);
+}
+
+void test_refs_branches_name__error_when_ref_is_no_branch(void)
+{
+ const char *name;
+
+ cl_git_pass(git_reference_lookup(&ref,repo,"refs/notes/fanout"));
+ cl_git_fail(git_branch_name(&name,ref));
+}
diff --git a/tests/refs/branches/remote.c b/tests/refs/branches/remote.c
new file mode 100644
index 000000000..bac088454
--- /dev/null
+++ b/tests/refs/branches/remote.c
@@ -0,0 +1,73 @@
+#include "clar_libgit2.h"
+#include "branch.h"
+#include "remote.h"
+
+static git_repository *g_repo;
+static const char *remote_tracking_branch_name = "refs/remotes/test/master";
+static const char *expected_remote_name = "test";
+static int expected_remote_name_length;
+
+void test_refs_branches_remote__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo");
+
+ expected_remote_name_length = (int)strlen(expected_remote_name) + 1;
+}
+
+void test_refs_branches_remote__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_refs_branches_remote__can_get_remote_for_branch(void)
+{
+ git_buf remotename = {0};
+
+ cl_git_pass(git_branch_remote_name(&remotename, g_repo, remote_tracking_branch_name));
+
+ cl_assert_equal_s("test", remotename.ptr);
+ git_buf_free(&remotename);
+}
+
+void test_refs_branches_remote__no_matching_remote_returns_error(void)
+{
+ const char *unknown = "refs/remotes/nonexistent/master";
+ git_buf buf;
+
+ giterr_clear();
+ memset(&buf, 0, sizeof(git_buf));
+ cl_git_fail_with(git_branch_remote_name(&buf, g_repo, unknown), GIT_ENOTFOUND);
+ cl_assert(giterr_last() != NULL);
+}
+
+void test_refs_branches_remote__local_remote_returns_error(void)
+{
+ const char *local = "refs/heads/master";
+ git_buf buf;
+
+ giterr_clear();
+ memset(&buf, 0, sizeof(git_buf));
+ cl_git_fail_with(git_branch_remote_name(&buf, g_repo, local), GIT_ERROR);
+ cl_assert(giterr_last() != NULL);
+}
+
+void test_refs_branches_remote__ambiguous_remote_returns_error(void)
+{
+ git_remote *remote;
+ git_buf buf;
+
+ /* Create the remote */
+ cl_git_pass(git_remote_create(&remote, g_repo, "addtest", "http://github.com/libgit2/libgit2"));
+
+ /* Update the remote fetch spec */
+ git_remote_clear_refspecs(remote);
+ cl_git_pass(git_remote_add_fetch(remote, "refs/heads/*:refs/remotes/test/*"));
+ cl_git_pass(git_remote_save(remote));
+
+ git_remote_free(remote);
+
+ giterr_clear();
+ memset(&buf, 0, sizeof(git_buf));
+ cl_git_fail_with(git_branch_remote_name(&buf, g_repo, remote_tracking_branch_name), GIT_EAMBIGUOUS);
+ cl_assert(giterr_last() != NULL);
+}
diff --git a/tests/refs/branches/upstream.c b/tests/refs/branches/upstream.c
new file mode 100644
index 000000000..ce3569813
--- /dev/null
+++ b/tests/refs/branches/upstream.c
@@ -0,0 +1,143 @@
+#include "clar_libgit2.h"
+#include "refs.h"
+
+static git_repository *repo;
+static git_reference *branch, *upstream;
+
+void test_refs_branches_upstream__initialize(void)
+{
+ cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
+
+ branch = NULL;
+ upstream = NULL;
+}
+
+void test_refs_branches_upstream__cleanup(void)
+{
+ git_reference_free(upstream);
+ git_reference_free(branch);
+ branch = NULL;
+
+ git_repository_free(repo);
+ repo = NULL;
+}
+
+void test_refs_branches_upstream__can_retrieve_the_remote_tracking_reference_of_a_local_branch(void)
+{
+ cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master"));
+
+ cl_git_pass(git_branch_upstream(&upstream, branch));
+
+ cl_assert_equal_s("refs/remotes/test/master", git_reference_name(upstream));
+}
+
+void test_refs_branches_upstream__can_retrieve_the_local_upstream_reference_of_a_local_branch(void)
+{
+ cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/track-local"));
+
+ cl_git_pass(git_branch_upstream(&upstream, branch));
+
+ cl_assert_equal_s("refs/heads/master", git_reference_name(upstream));
+}
+
+void test_refs_branches_upstream__cannot_retrieve_a_remote_upstream_reference_from_a_non_branch(void)
+{
+ cl_git_pass(git_reference_lookup(&branch, repo, "refs/tags/e90810b"));
+
+ cl_git_fail(git_branch_upstream(&upstream, branch));
+}
+
+void test_refs_branches_upstream__trying_to_retrieve_a_remote_tracking_reference_from_a_plain_local_branch_returns_GIT_ENOTFOUND(void)
+{
+ cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/subtrees"));
+
+ cl_assert_equal_i(GIT_ENOTFOUND, git_branch_upstream(&upstream, branch));
+}
+
+void test_refs_branches_upstream__trying_to_retrieve_a_remote_tracking_reference_from_a_branch_with_no_fetchspec_returns_GIT_ENOTFOUND(void)
+{
+ cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/cannot-fetch"));
+
+ cl_assert_equal_i(GIT_ENOTFOUND, git_branch_upstream(&upstream, branch));
+}
+
+static void assert_merge_and_or_remote_key_missing(git_repository *repository, const git_commit *target, const char *entry_name)
+{
+ git_reference *branch;
+
+ cl_assert_equal_i(GIT_OBJ_COMMIT, git_object_type((git_object*)target));
+ cl_git_pass(git_branch_create(&branch, repository, entry_name, (git_commit*)target, 0, NULL, NULL));
+
+ cl_assert_equal_i(GIT_ENOTFOUND, git_branch_upstream(&upstream, branch));
+
+ git_reference_free(branch);
+}
+
+void test_refs_branches_upstream__retrieve_a_remote_tracking_reference_from_a_branch_with_no_remote_returns_GIT_ENOTFOUND(void)
+{
+ git_reference *head;
+ git_repository *repository;
+ git_commit *target;
+
+ repository = cl_git_sandbox_init("testrepo.git");
+
+ cl_git_pass(git_repository_head(&head, repository));
+ cl_git_pass(git_reference_peel(((git_object **)&target), head, GIT_OBJ_COMMIT));
+ git_reference_free(head);
+
+ assert_merge_and_or_remote_key_missing(repository, target, "remoteless");
+ assert_merge_and_or_remote_key_missing(repository, target, "mergeless");
+ assert_merge_and_or_remote_key_missing(repository, target, "mergeandremoteless");
+
+ git_commit_free(target);
+
+ cl_git_sandbox_cleanup();
+}
+
+void test_refs_branches_upstream__set_unset_upstream(void)
+{
+ git_reference *branch;
+ git_repository *repository;
+ const char *value;
+ git_config *config;
+
+ repository = cl_git_sandbox_init("testrepo.git");
+
+ /* remote */
+ cl_git_pass(git_reference_lookup(&branch, repository, "refs/heads/test"));
+ cl_git_pass(git_branch_set_upstream(branch, "test/master"));
+
+ cl_git_pass(git_repository_config(&config, repository));
+ cl_git_pass(git_config_get_string(&value, config, "branch.test.remote"));
+ cl_assert_equal_s(value, "test");
+ cl_git_pass(git_config_get_string(&value, config, "branch.test.merge"));
+ cl_assert_equal_s(value, "refs/heads/master");
+
+ git_reference_free(branch);
+
+ /* local */
+ cl_git_pass(git_reference_lookup(&branch, repository, "refs/heads/test"));
+ cl_git_pass(git_branch_set_upstream(branch, "master"));
+
+ cl_git_pass(git_config_get_string(&value, config, "branch.test.remote"));
+ cl_assert_equal_s(value, ".");
+ cl_git_pass(git_config_get_string(&value, config, "branch.test.merge"));
+ cl_assert_equal_s(value, "refs/heads/master");
+
+ /* unset */
+ cl_git_pass(git_branch_set_upstream(branch, NULL));
+ cl_git_fail_with(git_config_get_string(&value, config, "branch.test.merge"), GIT_ENOTFOUND);
+ cl_git_fail_with(git_config_get_string(&value, config, "branch.test.remote"), GIT_ENOTFOUND);
+
+ git_reference_free(branch);
+
+ cl_git_pass(git_reference_lookup(&branch, repository, "refs/heads/master"));
+ cl_git_pass(git_branch_set_upstream(branch, NULL));
+ cl_git_fail_with(git_config_get_string(&value, config, "branch.master.merge"), GIT_ENOTFOUND);
+ cl_git_fail_with(git_config_get_string(&value, config, "branch.master.remote"), GIT_ENOTFOUND);
+
+ git_reference_free(branch);
+
+ git_config_free(config);
+ cl_git_sandbox_cleanup();
+}
diff --git a/tests/refs/branches/upstreamname.c b/tests/refs/branches/upstreamname.c
new file mode 100644
index 000000000..d30002e08
--- /dev/null
+++ b/tests/refs/branches/upstreamname.c
@@ -0,0 +1,36 @@
+#include "clar_libgit2.h"
+#include "branch.h"
+
+static git_repository *repo;
+static git_buf upstream_name;
+
+void test_refs_branches_upstreamname__initialize(void)
+{
+ cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
+
+ git_buf_init(&upstream_name, 0);
+}
+
+void test_refs_branches_upstreamname__cleanup(void)
+{
+ git_buf_free(&upstream_name);
+
+ git_repository_free(repo);
+ repo = NULL;
+}
+
+void test_refs_branches_upstreamname__can_retrieve_the_remote_tracking_reference_name_of_a_local_branch(void)
+{
+ cl_git_pass(git_branch_upstream_name(
+ &upstream_name, repo, "refs/heads/master"));
+
+ cl_assert_equal_s("refs/remotes/test/master", git_buf_cstr(&upstream_name));
+}
+
+void test_refs_branches_upstreamname__can_retrieve_the_local_upstream_reference_name_of_a_local_branch(void)
+{
+ cl_git_pass(git_branch_upstream_name(
+ &upstream_name, repo, "refs/heads/track-local"));
+
+ cl_assert_equal_s("refs/heads/master", git_buf_cstr(&upstream_name));
+}
diff --git a/tests/refs/crashes.c b/tests/refs/crashes.c
new file mode 100644
index 000000000..03082d71e
--- /dev/null
+++ b/tests/refs/crashes.c
@@ -0,0 +1,20 @@
+#include "clar_libgit2.h"
+
+void test_refs_crashes__double_free(void)
+{
+ git_repository *repo;
+ git_reference *ref, *ref2;
+ const char *REFNAME = "refs/heads/xxx";
+
+ cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
+ cl_git_pass(git_reference_symbolic_create(&ref, repo, REFNAME, "refs/heads/master", 0, NULL, NULL));
+ cl_git_pass(git_reference_lookup(&ref2, repo, REFNAME));
+ cl_git_pass(git_reference_delete(ref));
+ git_reference_free(ref);
+ git_reference_free(ref2);
+
+ /* reference is gone from disk, so reloading it will fail */
+ cl_git_fail(git_reference_lookup(&ref2, repo, REFNAME));
+
+ git_repository_free(repo);
+}
diff --git a/tests/refs/create.c b/tests/refs/create.c
new file mode 100644
index 000000000..50b8e84f8
--- /dev/null
+++ b/tests/refs/create.c
@@ -0,0 +1,168 @@
+#include "clar_libgit2.h"
+
+#include "repository.h"
+#include "git2/reflog.h"
+#include "reflog.h"
+#include "ref_helpers.h"
+
+static const char *current_master_tip = "099fabac3a9ea935598528c27f866e34089c2eff";
+static const char *current_head_target = "refs/heads/master";
+
+static git_repository *g_repo;
+
+void test_refs_create__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo");
+}
+
+void test_refs_create__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_refs_create__symbolic(void)
+{
+ // create a new symbolic reference
+ git_reference *new_reference, *looked_up_ref, *resolved_ref;
+ git_repository *repo2;
+ git_oid id;
+
+ const char *new_head_tracker = "ANOTHER_HEAD_TRACKER";
+
+ git_oid_fromstr(&id, current_master_tip);
+
+ /* Create and write the new symbolic reference */
+ cl_git_pass(git_reference_symbolic_create(&new_reference, g_repo, new_head_tracker, current_head_target, 0, NULL, NULL));
+
+ /* Ensure the reference can be looked-up... */
+ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, new_head_tracker));
+ cl_assert(git_reference_type(looked_up_ref) & GIT_REF_SYMBOLIC);
+ cl_assert(reference_is_packed(looked_up_ref) == 0);
+ cl_assert_equal_s(looked_up_ref->name, new_head_tracker);
+
+ /* ...peeled.. */
+ cl_git_pass(git_reference_resolve(&resolved_ref, looked_up_ref));
+ cl_assert(git_reference_type(resolved_ref) == GIT_REF_OID);
+
+ /* ...and that it points to the current master tip */
+ cl_assert(git_oid_cmp(&id, git_reference_target(resolved_ref)) == 0);
+ git_reference_free(looked_up_ref);
+ git_reference_free(resolved_ref);
+
+ /* Similar test with a fresh new repository */
+ cl_git_pass(git_repository_open(&repo2, "testrepo"));
+
+ cl_git_pass(git_reference_lookup(&looked_up_ref, repo2, new_head_tracker));
+ cl_git_pass(git_reference_resolve(&resolved_ref, looked_up_ref));
+ cl_assert(git_oid_cmp(&id, git_reference_target(resolved_ref)) == 0);
+
+ git_repository_free(repo2);
+
+ git_reference_free(new_reference);
+ git_reference_free(looked_up_ref);
+ git_reference_free(resolved_ref);
+}
+
+void test_refs_create__deep_symbolic(void)
+{
+ // create a deep symbolic reference
+ git_reference *new_reference, *looked_up_ref, *resolved_ref;
+ git_oid id;
+
+ const char *new_head_tracker = "deep/rooted/tracker";
+
+ git_oid_fromstr(&id, current_master_tip);
+
+ cl_git_pass(git_reference_symbolic_create(&new_reference, g_repo, new_head_tracker, current_head_target, 0, NULL, NULL));
+ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, new_head_tracker));
+ cl_git_pass(git_reference_resolve(&resolved_ref, looked_up_ref));
+ cl_assert(git_oid_cmp(&id, git_reference_target(resolved_ref)) == 0);
+
+ git_reference_free(new_reference);
+ git_reference_free(looked_up_ref);
+ git_reference_free(resolved_ref);
+}
+
+void test_refs_create__oid(void)
+{
+ // create a new OID reference
+ git_reference *new_reference, *looked_up_ref;
+ git_repository *repo2;
+ git_oid id;
+
+ const char *new_head = "refs/heads/new-head";
+
+ git_oid_fromstr(&id, current_master_tip);
+
+ /* Create and write the new object id reference */
+ cl_git_pass(git_reference_create(&new_reference, g_repo, new_head, &id, 0, NULL, NULL));
+
+ /* Ensure the reference can be looked-up... */
+ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, new_head));
+ cl_assert(git_reference_type(looked_up_ref) & GIT_REF_OID);
+ cl_assert(reference_is_packed(looked_up_ref) == 0);
+ cl_assert_equal_s(looked_up_ref->name, new_head);
+
+ /* ...and that it points to the current master tip */
+ cl_assert(git_oid_cmp(&id, git_reference_target(looked_up_ref)) == 0);
+ git_reference_free(looked_up_ref);
+
+ /* Similar test with a fresh new repository */
+ cl_git_pass(git_repository_open(&repo2, "testrepo"));
+
+ cl_git_pass(git_reference_lookup(&looked_up_ref, repo2, new_head));
+ cl_assert(git_oid_cmp(&id, git_reference_target(looked_up_ref)) == 0);
+
+ git_repository_free(repo2);
+
+ git_reference_free(new_reference);
+ git_reference_free(looked_up_ref);
+}
+
+void test_refs_create__oid_unknown(void)
+{
+ // Can not create a new OID reference which targets at an unknown id
+ git_reference *new_reference, *looked_up_ref;
+ git_oid id;
+
+ const char *new_head = "refs/heads/new-head";
+
+ git_oid_fromstr(&id, "deadbeef3f795b2b4353bcce3a527ad0a4f7f644");
+
+ /* Create and write the new object id reference */
+ cl_git_fail(git_reference_create(&new_reference, g_repo, new_head, &id, 0, NULL, NULL));
+
+ /* Ensure the reference can't be looked-up... */
+ cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, new_head));
+}
+
+void test_refs_create__propagate_eexists(void)
+{
+ int error;
+ git_oid oid;
+ git_reference *ref;
+
+ /* Make sure it works for oid and for symbolic both */
+ git_oid_fromstr(&oid, current_master_tip);
+ error = git_reference_create(&ref, g_repo, current_head_target, &oid, false, NULL, NULL);
+ cl_assert(error == GIT_EEXISTS);
+
+ error = git_reference_symbolic_create(&ref, g_repo, "HEAD", current_head_target, false, NULL, NULL);
+ cl_assert(error == GIT_EEXISTS);
+}
+
+void test_refs_create__creating_a_reference_with_an_invalid_name_returns_EINVALIDSPEC(void)
+{
+ git_reference *new_reference;
+ git_oid id;
+
+ const char *name = "refs/heads/inv@{id";
+
+ git_oid_fromstr(&id, current_master_tip);
+
+ cl_assert_equal_i(GIT_EINVALIDSPEC, git_reference_create(
+ &new_reference, g_repo, name, &id, 0, NULL, NULL));
+
+ cl_assert_equal_i(GIT_EINVALIDSPEC, git_reference_symbolic_create(
+ &new_reference, g_repo, name, current_head_target, 0, NULL, NULL));
+}
diff --git a/tests/refs/createwithlog.c b/tests/refs/createwithlog.c
new file mode 100644
index 000000000..026ff6d6a
--- /dev/null
+++ b/tests/refs/createwithlog.c
@@ -0,0 +1,51 @@
+#include "clar_libgit2.h"
+
+#include "repository.h"
+#include "git2/reflog.h"
+#include "reflog.h"
+#include "ref_helpers.h"
+
+static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750";
+
+static git_repository *g_repo;
+
+void test_refs_createwithlog__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo.git");
+}
+
+void test_refs_createwithlog__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_refs_createwithlog__creating_a_direct_reference_adds_a_reflog_entry(void)
+{
+ git_reference *reference;
+ git_oid id;
+ git_signature *signature;
+ git_reflog *reflog;
+ const git_reflog_entry *entry;
+
+ const char *name = "refs/heads/new-head";
+ const char *message = "You've been logged, mate!";
+
+ git_oid_fromstr(&id, current_master_tip);
+
+ cl_git_pass(git_signature_now(&signature, "foo", "foo@bar"));
+
+ cl_git_pass(
+ git_reference_create(&reference, g_repo, name, &id, 0, signature, message));
+
+ cl_git_pass(git_reflog_read(&reflog, g_repo, name));
+ cl_assert_equal_sz(1, git_reflog_entrycount(reflog));
+
+ entry = git_reflog_entry_byindex(reflog, 0);
+ cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0);
+ cl_assert(git_oid_cmp(&id, &entry->oid_cur) == 0);
+ cl_assert_equal_s(message, entry->msg);
+
+ git_reflog_free(reflog);
+ git_reference_free(reference);
+ git_signature_free(signature);
+}
diff --git a/tests/refs/delete.c b/tests/refs/delete.c
new file mode 100644
index 000000000..9d1c3fd79
--- /dev/null
+++ b/tests/refs/delete.c
@@ -0,0 +1,107 @@
+#include "clar_libgit2.h"
+
+#include "fileops.h"
+#include "git2/reflog.h"
+#include "git2/refdb.h"
+#include "reflog.h"
+#include "ref_helpers.h"
+
+static const char *packed_test_head_name = "refs/heads/packed-test";
+static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750";
+
+static git_repository *g_repo;
+
+
+
+void test_refs_delete__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo");
+}
+
+void test_refs_delete__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+
+
+void test_refs_delete__packed_loose(void)
+{
+ // deleting a ref which is both packed and loose should remove both tracks in the filesystem
+ git_reference *looked_up_ref, *another_looked_up_ref;
+ git_buf temp_path = GIT_BUF_INIT;
+
+ /* Ensure the loose reference exists on the file system */
+ cl_git_pass(git_buf_joinpath(&temp_path, git_repository_path(g_repo), packed_test_head_name));
+ cl_assert(git_path_exists(temp_path.ptr));
+
+ /* Lookup the reference */
+ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_test_head_name));
+
+ /* Ensure it's the loose version that has been found */
+ cl_assert(reference_is_packed(looked_up_ref) == 0);
+
+ /* Now that the reference is deleted... */
+ cl_git_pass(git_reference_delete(looked_up_ref));
+ git_reference_free(looked_up_ref);
+
+ /* Looking up the reference once again should not retrieve it */
+ cl_git_fail(git_reference_lookup(&another_looked_up_ref, g_repo, packed_test_head_name));
+
+ /* Ensure the loose reference doesn't exist any longer on the file system */
+ cl_assert(!git_path_exists(temp_path.ptr));
+
+ git_reference_free(another_looked_up_ref);
+ git_buf_free(&temp_path);
+}
+
+void test_refs_delete__packed_only(void)
+{
+ // can delete a just packed reference
+ git_reference *ref;
+ git_refdb *refdb;
+ git_oid id;
+ const char *new_ref = "refs/heads/new_ref";
+
+ git_oid_fromstr(&id, current_master_tip);
+
+ /* Create and write the new object id reference */
+ cl_git_pass(git_reference_create(&ref, g_repo, new_ref, &id, 0, NULL, NULL));
+ git_reference_free(ref);
+
+ /* Lookup the reference */
+ cl_git_pass(git_reference_lookup(&ref, g_repo, new_ref));
+
+ /* Ensure it's a loose reference */
+ cl_assert(reference_is_packed(ref) == 0);
+
+ /* Pack all existing references */
+ cl_git_pass(git_repository_refdb(&refdb, g_repo));
+ cl_git_pass(git_refdb_compress(refdb));
+
+ /* Reload the reference from disk */
+ git_reference_free(ref);
+ cl_git_pass(git_reference_lookup(&ref, g_repo, new_ref));
+
+ /* Ensure it's a packed reference */
+ cl_assert(reference_is_packed(ref) == 1);
+
+ /* This should pass */
+ cl_git_pass(git_reference_delete(ref));
+ git_reference_free(ref);
+ git_refdb_free(refdb);
+}
+
+void test_refs_delete__remove(void)
+{
+ git_reference *ref;
+
+ /* Check that passing no old values lets us delete */
+
+ cl_git_pass(git_reference_lookup(&ref, g_repo, packed_test_head_name));
+ git_reference_free(ref);
+
+ cl_git_pass(git_reference_remove(g_repo, packed_test_head_name));
+
+ cl_git_fail(git_reference_lookup(&ref, g_repo, packed_test_head_name));
+}
diff --git a/tests/refs/foreachglob.c b/tests/refs/foreachglob.c
new file mode 100644
index 000000000..b992b07b8
--- /dev/null
+++ b/tests/refs/foreachglob.c
@@ -0,0 +1,95 @@
+#include "clar_libgit2.h"
+#include "refs.h"
+
+static git_repository *repo;
+static git_reference *fake_remote;
+
+void test_refs_foreachglob__initialize(void)
+{
+ git_oid id;
+
+ cl_fixture_sandbox("testrepo.git");
+ cl_git_pass(git_repository_open(&repo, "testrepo.git"));
+
+ cl_git_pass(git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"));
+ cl_git_pass(git_reference_create(&fake_remote, repo, "refs/remotes/nulltoken/master", &id, 0, NULL, NULL));
+}
+
+void test_refs_foreachglob__cleanup(void)
+{
+ git_reference_free(fake_remote);
+ fake_remote = NULL;
+
+ git_repository_free(repo);
+ repo = NULL;
+
+ cl_fixture_cleanup("testrepo.git");
+}
+
+static int count_cb(const char *reference_name, void *payload)
+{
+ int *count = (int *)payload;
+
+ GIT_UNUSED(reference_name);
+
+ (*count)++;
+
+ return 0;
+}
+
+static void assert_retrieval(const char *glob, int expected_count)
+{
+ int count = 0;
+
+ cl_git_pass(git_reference_foreach_glob(repo, glob, count_cb, &count));
+
+ cl_assert_equal_i(expected_count, count);
+}
+
+void test_refs_foreachglob__retrieve_all_refs(void)
+{
+ /* 12 heads (including one packed head) + 1 note + 2 remotes + 7 tags */
+ assert_retrieval("*", 22);
+}
+
+void test_refs_foreachglob__retrieve_remote_branches(void)
+{
+ assert_retrieval("refs/remotes/*", 2);
+}
+
+void test_refs_foreachglob__retrieve_local_branches(void)
+{
+ assert_retrieval("refs/heads/*", 12);
+}
+
+void test_refs_foreachglob__retrieve_partially_named_references(void)
+{
+ /*
+ * refs/heads/packed-test, refs/heads/test
+ * refs/remotes/test/master, refs/tags/test
+ */
+
+ assert_retrieval("*test*", 4);
+}
+
+
+static int interrupt_cb(const char *reference_name, void *payload)
+{
+ int *count = (int *)payload;
+
+ GIT_UNUSED(reference_name);
+
+ (*count)++;
+
+ return (*count == 11) ? -1000 : 0;
+}
+
+void test_refs_foreachglob__can_cancel(void)
+{
+ int count = 0;
+
+ cl_assert_equal_i(-1000, git_reference_foreach_glob(
+ repo, "*", interrupt_cb, &count) );
+
+ cl_assert_equal_i(11, count);
+}
diff --git a/tests/refs/isvalidname.c b/tests/refs/isvalidname.c
new file mode 100644
index 000000000..65c70ba4d
--- /dev/null
+++ b/tests/refs/isvalidname.c
@@ -0,0 +1,31 @@
+#include "clar_libgit2.h"
+
+void test_refs_isvalidname__can_detect_invalid_formats(void)
+{
+ cl_assert_equal_i(false, git_reference_is_valid_name("refs/tags/0.17.0^{}"));
+ cl_assert_equal_i(false, git_reference_is_valid_name("TWO/LEVELS"));
+ cl_assert_equal_i(false, git_reference_is_valid_name("ONE.LEVEL"));
+ cl_assert_equal_i(false, git_reference_is_valid_name("HEAD/"));
+ cl_assert_equal_i(false, git_reference_is_valid_name("NO_TRAILING_UNDERSCORE_"));
+ cl_assert_equal_i(false, git_reference_is_valid_name("_NO_LEADING_UNDERSCORE"));
+ cl_assert_equal_i(false, git_reference_is_valid_name("HEAD/aa"));
+ cl_assert_equal_i(false, git_reference_is_valid_name("lower_case"));
+ cl_assert_equal_i(false, git_reference_is_valid_name("/stupid/name/master"));
+ cl_assert_equal_i(false, git_reference_is_valid_name("/"));
+ cl_assert_equal_i(false, git_reference_is_valid_name("//"));
+ cl_assert_equal_i(false, git_reference_is_valid_name(""));
+ cl_assert_equal_i(false, git_reference_is_valid_name("refs/heads/sub.lock/webmatrix"));
+}
+
+void test_refs_isvalidname__wont_hopefully_choke_on_valid_formats(void)
+{
+ cl_assert_equal_i(true, git_reference_is_valid_name("refs/tags/0.17.0"));
+ cl_assert_equal_i(true, git_reference_is_valid_name("refs/LEVELS"));
+ cl_assert_equal_i(true, git_reference_is_valid_name("HEAD"));
+ cl_assert_equal_i(true, git_reference_is_valid_name("ONE_LEVEL"));
+ cl_assert_equal_i(true, git_reference_is_valid_name("refs/stash"));
+ cl_assert_equal_i(true, git_reference_is_valid_name("refs/remotes/origin/bim_with_3d@11296"));
+ cl_assert_equal_i(true, git_reference_is_valid_name("refs/master{yesterday"));
+ cl_assert_equal_i(true, git_reference_is_valid_name("refs/master}yesterday"));
+ cl_assert_equal_i(true, git_reference_is_valid_name("refs/master{yesterday}"));
+}
diff --git a/tests/refs/iterator.c b/tests/refs/iterator.c
new file mode 100644
index 000000000..c77451309
--- /dev/null
+++ b/tests/refs/iterator.c
@@ -0,0 +1,221 @@
+#include "clar_libgit2.h"
+#include "refs.h"
+#include "vector.h"
+
+static git_repository *repo;
+
+void test_refs_iterator__initialize(void)
+{
+ cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
+}
+
+void test_refs_iterator__cleanup(void)
+{
+ git_repository_free(repo);
+}
+
+static const char *refnames[] = {
+ "refs/heads/br2",
+ "refs/heads/cannot-fetch",
+ "refs/heads/chomped",
+ "refs/heads/haacked",
+ "refs/heads/master",
+ "refs/heads/not-good",
+ "refs/heads/packed",
+ "refs/heads/packed-test",
+ "refs/heads/subtrees",
+ "refs/heads/test",
+ "refs/heads/track-local",
+ "refs/heads/trailing",
+ "refs/notes/fanout",
+ "refs/remotes/test/master",
+ "refs/tags/annotated_tag_to_blob",
+ "refs/tags/e90810b",
+ "refs/tags/hard_tag",
+ "refs/tags/point_to_blob",
+ "refs/tags/taggerless",
+ "refs/tags/test",
+ "refs/tags/wrapped_tag",
+};
+
+static int refcmp_cb(const void *a, const void *b)
+{
+ const git_reference *refa = (const git_reference *)a;
+ const git_reference *refb = (const git_reference *)b;
+
+ return strcmp(refa->name, refb->name);
+}
+
+static void assert_all_refnames_match(git_vector *output)
+{
+ size_t i;
+ git_reference *ref;
+
+ cl_assert_equal_sz(output->length, ARRAY_SIZE(refnames));
+
+ git_vector_sort(output);
+
+ git_vector_foreach(output, i, ref) {
+ cl_assert_equal_s(ref->name, refnames[i]);
+ git_reference_free(ref);
+ }
+
+ git_vector_free(output);
+}
+
+void test_refs_iterator__list(void)
+{
+ git_reference_iterator *iter;
+ git_vector output;
+ git_reference *ref;
+
+ cl_git_pass(git_vector_init(&output, 32, &refcmp_cb));
+ cl_git_pass(git_reference_iterator_new(&iter, repo));
+
+ while (1) {
+ int error = git_reference_next(&ref, iter);
+ if (error == GIT_ITEROVER)
+ break;
+ cl_git_pass(error);
+ cl_git_pass(git_vector_insert(&output, ref));
+ }
+
+ git_reference_iterator_free(iter);
+
+ assert_all_refnames_match(&output);
+}
+
+void test_refs_iterator__empty(void)
+{
+ git_reference_iterator *iter;
+ git_odb *odb;
+ git_reference *ref;
+ git_repository *empty;
+
+ cl_git_pass(git_odb_new(&odb));
+ cl_git_pass(git_repository_wrap_odb(&empty, odb));
+
+ cl_git_pass(git_reference_iterator_new(&iter, empty));
+ cl_assert_equal_i(GIT_ITEROVER, git_reference_next(&ref, iter));
+
+ git_reference_iterator_free(iter);
+ git_odb_free(odb);
+ git_repository_free(empty);
+}
+
+static int refs_foreach_cb(git_reference *reference, void *payload)
+{
+ git_vector *output = payload;
+ cl_git_pass(git_vector_insert(output, reference));
+ return 0;
+}
+
+void test_refs_iterator__foreach(void)
+{
+ git_vector output;
+ cl_git_pass(git_vector_init(&output, 32, &refcmp_cb));
+ cl_git_pass(git_reference_foreach(repo, refs_foreach_cb, &output));
+ assert_all_refnames_match(&output);
+}
+
+static int refs_foreach_cancel_cb(git_reference *reference, void *payload)
+{
+ int *cancel_after = payload;
+
+ git_reference_free(reference);
+
+ if (!*cancel_after)
+ return -333;
+ (*cancel_after)--;
+ return 0;
+}
+
+void test_refs_iterator__foreach_can_cancel(void)
+{
+ int cancel_after = 3;
+ cl_git_fail_with(
+ git_reference_foreach(repo, refs_foreach_cancel_cb, &cancel_after),
+ -333);
+ cl_assert_equal_i(0, cancel_after);
+}
+
+static int refs_foreach_name_cb(const char *name, void *payload)
+{
+ git_vector *output = payload;
+ cl_git_pass(git_vector_insert(output, git__strdup(name)));
+ return 0;
+}
+
+void test_refs_iterator__foreach_name(void)
+{
+ git_vector output;
+ size_t i;
+ char *name;
+
+ cl_git_pass(git_vector_init(&output, 32, &git__strcmp_cb));
+ cl_git_pass(
+ git_reference_foreach_name(repo, refs_foreach_name_cb, &output));
+
+ cl_assert_equal_sz(output.length, ARRAY_SIZE(refnames));
+ git_vector_sort(&output);
+
+ git_vector_foreach(&output, i, name) {
+ cl_assert_equal_s(name, refnames[i]);
+ git__free(name);
+ }
+
+ git_vector_free(&output);
+}
+
+static int refs_foreach_name_cancel_cb(const char *name, void *payload)
+{
+ int *cancel_after = payload;
+ if (!*cancel_after)
+ return -333;
+ GIT_UNUSED(name);
+ (*cancel_after)--;
+ return 0;
+}
+
+void test_refs_iterator__foreach_name_can_cancel(void)
+{
+ int cancel_after = 5;
+ cl_git_fail_with(
+ git_reference_foreach_name(
+ repo, refs_foreach_name_cancel_cb, &cancel_after),
+ -333);
+ cl_assert_equal_i(0, cancel_after);
+}
+
+void test_refs_iterator__concurrent_delete(void)
+{
+ git_reference_iterator *iter;
+ size_t full_count = 0, concurrent_count = 0;
+ const char *name;
+ int error;
+
+ git_repository_free(repo);
+ repo = cl_git_sandbox_init("testrepo");
+
+ cl_git_pass(git_reference_iterator_new(&iter, repo));
+ while ((error = git_reference_next_name(&name, iter)) == 0) {
+ full_count++;
+ }
+
+ git_reference_iterator_free(iter);
+ cl_assert_equal_i(GIT_ITEROVER, error);
+
+ cl_git_pass(git_reference_iterator_new(&iter, repo));
+ while ((error = git_reference_next_name(&name, iter)) == 0) {
+ cl_git_pass(git_reference_remove(repo, name));
+ concurrent_count++;
+ }
+
+ git_reference_iterator_free(iter);
+ cl_assert_equal_i(GIT_ITEROVER, error);
+
+ cl_assert_equal_i(full_count, concurrent_count);
+
+ cl_git_sandbox_cleanup();
+ repo = NULL;
+}
diff --git a/tests/refs/list.c b/tests/refs/list.c
new file mode 100644
index 000000000..de5c0fd3d
--- /dev/null
+++ b/tests/refs/list.c
@@ -0,0 +1,57 @@
+#include "clar_libgit2.h"
+
+#include "repository.h"
+#include "git2/reflog.h"
+#include "reflog.h"
+
+static git_repository *g_repo;
+
+
+
+void test_refs_list__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo");
+}
+
+void test_refs_list__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+
+
+void test_refs_list__all(void)
+{
+ // try to list all the references in our test repo
+ git_strarray ref_list;
+
+ cl_git_pass(git_reference_list(&ref_list, g_repo));
+
+ /*{
+ unsigned short i;
+ for (i = 0; i < ref_list.count; ++i)
+ printf("# %s\n", ref_list.strings[i]);
+ }*/
+
+ /* We have exactly 12 refs in total if we include the packed ones:
+ * there is a reference that exists both in the packfile and as
+ * loose, but we only list it once */
+ cl_assert_equal_i((int)ref_list.count, 14);
+
+ git_strarray_free(&ref_list);
+}
+
+void test_refs_list__do_not_retrieve_references_which_name_end_with_a_lock_extension(void)
+{
+ git_strarray ref_list;
+
+ /* Create a fake locked reference */
+ cl_git_mkfile(
+ "./testrepo/.git/refs/heads/hanwen.lock",
+ "144344043ba4d4a405da03de3844aa829ae8be0e\n");
+
+ cl_git_pass(git_reference_list(&ref_list, g_repo));
+ cl_assert_equal_i((int)ref_list.count, 14);
+
+ git_strarray_free(&ref_list);
+}
diff --git a/tests/refs/listall.c b/tests/refs/listall.c
new file mode 100644
index 000000000..c696fbb2e
--- /dev/null
+++ b/tests/refs/listall.c
@@ -0,0 +1,47 @@
+#include "clar_libgit2.h"
+#include "posix.h"
+
+static git_repository *repo;
+static git_strarray ref_list;
+
+static void ensure_no_refname_starts_with_a_forward_slash(const char *path)
+{
+ size_t i;
+
+ cl_git_pass(git_repository_open(&repo, path));
+ cl_git_pass(git_reference_list(&ref_list, repo));
+
+ cl_assert(ref_list.count > 0);
+
+ for (i = 0; i < ref_list.count; i++)
+ cl_assert(git__prefixcmp(ref_list.strings[i], "/") != 0);
+
+ git_strarray_free(&ref_list);
+ git_repository_free(repo);
+}
+
+void test_refs_listall__from_repository_opened_through_workdir_path(void)
+{
+ cl_fixture_sandbox("status");
+ cl_git_pass(p_rename("status/.gitted", "status/.git"));
+
+ ensure_no_refname_starts_with_a_forward_slash("status");
+
+ cl_fixture_cleanup("status");
+}
+
+void test_refs_listall__from_repository_opened_through_gitdir_path(void)
+{
+ ensure_no_refname_starts_with_a_forward_slash(cl_fixture("testrepo.git"));
+}
+
+void test_refs_listall__from_repository_with_no_trailing_newline(void)
+{
+ cl_git_pass(git_repository_open(&repo, cl_fixture("bad_tag.git")));
+ cl_git_pass(git_reference_list(&ref_list, repo));
+
+ cl_assert(ref_list.count > 0);
+
+ git_strarray_free(&ref_list);
+ git_repository_free(repo);
+}
diff --git a/tests/refs/lookup.c b/tests/refs/lookup.c
new file mode 100644
index 000000000..2e31cf0f6
--- /dev/null
+++ b/tests/refs/lookup.c
@@ -0,0 +1,60 @@
+#include "clar_libgit2.h"
+#include "refs.h"
+
+static git_repository *g_repo;
+
+void test_refs_lookup__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo.git");
+}
+
+void test_refs_lookup__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_refs_lookup__with_resolve(void)
+{
+ git_reference *a, *b, *temp;
+
+ cl_git_pass(git_reference_lookup(&temp, g_repo, "HEAD"));
+ cl_git_pass(git_reference_resolve(&a, temp));
+ git_reference_free(temp);
+
+ cl_git_pass(git_reference_lookup_resolved(&b, g_repo, "HEAD", 5));
+ cl_assert(git_reference_cmp(a, b) == 0);
+ git_reference_free(b);
+
+ cl_git_pass(git_reference_lookup_resolved(&b, g_repo, "HEAD_TRACKER", 5));
+ cl_assert(git_reference_cmp(a, b) == 0);
+ git_reference_free(b);
+
+ git_reference_free(a);
+}
+
+void test_refs_lookup__invalid_name(void)
+{
+ git_oid oid;
+ cl_git_fail(git_reference_name_to_id(&oid, g_repo, "/refs/tags/point_to_blob"));
+}
+
+void test_refs_lookup__oid(void)
+{
+ git_oid tag, expected;
+
+ cl_git_pass(git_reference_name_to_id(&tag, g_repo, "refs/tags/point_to_blob"));
+ cl_git_pass(git_oid_fromstr(&expected, "1385f264afb75a56a5bec74243be9b367ba4ca08"));
+ cl_assert(git_oid_cmp(&tag, &expected) == 0);
+}
+
+void test_refs_lookup__namespace(void)
+{
+ int error;
+ git_reference *ref;
+
+ error = git_reference_lookup(&ref, g_repo, "refs/heads");
+ cl_assert_equal_i(error, GIT_ENOTFOUND);
+
+ error = git_reference_lookup(&ref, g_repo, "refs/heads/");
+ cl_assert_equal_i(error, GIT_EINVALIDSPEC);
+}
diff --git a/tests/refs/normalize.c b/tests/refs/normalize.c
new file mode 100644
index 000000000..7f313ef38
--- /dev/null
+++ b/tests/refs/normalize.c
@@ -0,0 +1,403 @@
+#include "clar_libgit2.h"
+
+#include "repository.h"
+#include "git2/reflog.h"
+#include "reflog.h"
+
+// Helpers
+static void ensure_refname_normalized(
+ unsigned int flags,
+ const char *input_refname,
+ const char *expected_refname)
+{
+ char buffer_out[GIT_REFNAME_MAX];
+
+ cl_git_pass(git_reference_normalize_name(buffer_out, sizeof(buffer_out), input_refname, flags));
+
+ cl_assert_equal_s(expected_refname, buffer_out);
+}
+
+static void ensure_refname_invalid(unsigned int flags, const char *input_refname)
+{
+ char buffer_out[GIT_REFNAME_MAX];
+
+ cl_assert_equal_i(
+ GIT_EINVALIDSPEC,
+ git_reference_normalize_name(buffer_out, sizeof(buffer_out), input_refname, flags));
+}
+
+void test_refs_normalize__can_normalize_a_direct_reference_name(void)
+{
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_NORMAL, "refs/dummy/a", "refs/dummy/a");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_NORMAL, "refs/stash", "refs/stash");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_NORMAL, "refs/tags/a", "refs/tags/a");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_NORMAL, "refs/heads/a/b", "refs/heads/a/b");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_NORMAL, "refs/heads/a./b", "refs/heads/a./b");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_NORMAL, "refs/heads/v@ation", "refs/heads/v@ation");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_NORMAL, "refs///heads///a", "refs/heads/a");
+}
+
+void test_refs_normalize__cannot_normalize_any_direct_reference_name(void)
+{
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_NORMAL, "a");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_NORMAL, "/a");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_NORMAL, "//a");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_NORMAL, "");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_NORMAL, "/refs/heads/a/");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_NORMAL, "refs/heads/a/");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_NORMAL, "refs/heads/a.");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_NORMAL, "refs/heads/a.lock");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_NORMAL, "refs/heads/foo?bar");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_NORMAL, "refs/heads\foo");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_NORMAL, "refs/heads/v@ation", "refs/heads/v@ation");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_NORMAL, "refs///heads///a", "refs/heads/a");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_NORMAL, "refs/heads/.a/b");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_NORMAL, "refs/heads/foo/../bar");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_NORMAL, "refs/heads/foo..bar");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_NORMAL, "refs/heads/./foo");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_NORMAL, "refs/heads/v@{ation");
+}
+
+void test_refs_normalize__symbolic(void)
+{
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "heads\foo");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "/");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "///");
+
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "ALL_CAPS_AND_UNDERSCORES", "ALL_CAPS_AND_UNDERSCORES");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/MixedCasing", "refs/MixedCasing");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs///heads///a", "refs/heads/a");
+
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "HEAD", "HEAD");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "MERGE_HEAD", "MERGE_HEAD");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "FETCH_HEAD", "FETCH_HEAD");
+}
+
+/* Ported from JGit, BSD licence.
+ * See https://github.com/spearce/JGit/commit/e4bf8f6957bbb29362575d641d1e77a02d906739
+ *
+ * Copyright (C) 2009, Google Inc.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+void test_refs_normalize__jgit_suite(void)
+{
+ // tests borrowed from JGit
+
+/* EmptyString */
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "/");
+
+/* MustHaveTwoComponents */
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_NORMAL, "master");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_NORMAL, "heads/master", "heads/master");
+
+/* ValidHead */
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master", "refs/heads/master");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/pu", "refs/heads/pu");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/z", "refs/heads/z");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/FoO", "refs/heads/FoO");
+
+/* ValidTag */
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/tags/v1.0", "refs/tags/v1.0");
+
+/* NoLockSuffix */
+ ensure_refname_invalid(GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master.lock");
+
+/* NoDirectorySuffix */
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master/");
+
+/* NoSpace */
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/i haz space");
+
+/* NoAsciiControlCharacters */
+ {
+ char c;
+ char buffer[GIT_REFNAME_MAX];
+ for (c = '\1'; c < ' '; c++) {
+ strncpy(buffer, "refs/heads/mast", 15);
+ strncpy(buffer + 15, (const char *)&c, 1);
+ strncpy(buffer + 16, "er", 2);
+ buffer[18 - 1] = '\0';
+ ensure_refname_invalid(GIT_REF_FORMAT_ALLOW_ONELEVEL, buffer);
+ }
+ }
+
+/* NoBareDot */
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/.");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/..");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/./master");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/../master");
+
+/* NoLeadingOrTrailingDot */
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, ".");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/.bar");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/..bar");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/bar.");
+
+/* ContainsDot */
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/m.a.s.t.e.r", "refs/heads/m.a.s.t.e.r");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master..pu");
+
+/* NoMagicRefCharacters */
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master^");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/^master");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "^refs/heads/master");
+
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master~");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/~master");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "~refs/heads/master");
+
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master:");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/:master");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, ":refs/heads/master");
+
+/* ShellGlob */
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master?");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/?master");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "?refs/heads/master");
+
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master[");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/[master");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "[refs/heads/master");
+
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master*");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/*master");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "*refs/heads/master");
+
+/* ValidSpecialCharacters */
+ ensure_refname_normalized
+ (GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/!", "refs/heads/!");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/\"", "refs/heads/\"");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/#", "refs/heads/#");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/$", "refs/heads/$");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/%", "refs/heads/%");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/&", "refs/heads/&");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/'", "refs/heads/'");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/(", "refs/heads/(");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/)", "refs/heads/)");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/+", "refs/heads/+");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/,", "refs/heads/,");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/-", "refs/heads/-");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/;", "refs/heads/;");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/<", "refs/heads/<");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/=", "refs/heads/=");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/>", "refs/heads/>");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/@", "refs/heads/@");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/]", "refs/heads/]");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/_", "refs/heads/_");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/`", "refs/heads/`");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/{", "refs/heads/{");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/|", "refs/heads/|");
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/}", "refs/heads/}");
+
+ // This is valid on UNIX, but not on Windows
+ // hence we make in invalid due to non-portability
+ //
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/\\");
+
+/* UnicodeNames */
+ /*
+ * Currently this fails.
+ * ensure_refname_normalized(GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/\u00e5ngstr\u00f6m", "refs/heads/\u00e5ngstr\u00f6m");
+ */
+
+/* RefLogQueryIsValidRef */
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master@{1}");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master@{1.hour.ago}");
+}
+
+void test_refs_normalize__buffer_has_to_be_big_enough_to_hold_the_normalized_version(void)
+{
+ char buffer_out[21];
+
+ cl_git_pass(git_reference_normalize_name(
+ buffer_out, 21, "refs//heads///long///name", GIT_REF_FORMAT_NORMAL));
+ cl_git_fail(git_reference_normalize_name(
+ buffer_out, 20, "refs//heads///long///name", GIT_REF_FORMAT_NORMAL));
+}
+
+#define ONE_LEVEL_AND_REFSPEC \
+ GIT_REF_FORMAT_ALLOW_ONELEVEL \
+ | GIT_REF_FORMAT_REFSPEC_PATTERN
+
+void test_refs_normalize__refspec_pattern(void)
+{
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_REFSPEC_PATTERN, "heads/*foo/bar");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_REFSPEC_PATTERN, "heads/foo*/bar");
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_REFSPEC_PATTERN, "heads/f*o/bar");
+
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_REFSPEC_PATTERN, "foo");
+ ensure_refname_normalized(
+ ONE_LEVEL_AND_REFSPEC, "FOO", "FOO");
+
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_REFSPEC_PATTERN, "foo/bar", "foo/bar");
+ ensure_refname_normalized(
+ ONE_LEVEL_AND_REFSPEC, "foo/bar", "foo/bar");
+
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_REFSPEC_PATTERN, "*/foo", "*/foo");
+ ensure_refname_normalized(
+ ONE_LEVEL_AND_REFSPEC, "*/foo", "*/foo");
+
+ ensure_refname_normalized(
+ GIT_REF_FORMAT_REFSPEC_PATTERN, "foo/*/bar", "foo/*/bar");
+ ensure_refname_normalized(
+ ONE_LEVEL_AND_REFSPEC, "foo/*/bar", "foo/*/bar");
+
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_REFSPEC_PATTERN, "*");
+ ensure_refname_normalized(
+ ONE_LEVEL_AND_REFSPEC, "*", "*");
+
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_REFSPEC_PATTERN, "foo/*/*");
+ ensure_refname_invalid(
+ ONE_LEVEL_AND_REFSPEC, "foo/*/*");
+
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_REFSPEC_PATTERN, "*/foo/*");
+ ensure_refname_invalid(
+ ONE_LEVEL_AND_REFSPEC, "*/foo/*");
+
+ ensure_refname_invalid(
+ GIT_REF_FORMAT_REFSPEC_PATTERN, "*/*/foo");
+ ensure_refname_invalid(
+ ONE_LEVEL_AND_REFSPEC, "*/*/foo");
+}
diff --git a/tests/refs/overwrite.c b/tests/refs/overwrite.c
new file mode 100644
index 000000000..78ce4ace7
--- /dev/null
+++ b/tests/refs/overwrite.c
@@ -0,0 +1,136 @@
+#include "clar_libgit2.h"
+
+#include "repository.h"
+#include "git2/reflog.h"
+#include "reflog.h"
+
+static const char *ref_name = "refs/heads/other";
+static const char *ref_master_name = "refs/heads/master";
+static const char *ref_branch_name = "refs/heads/branch";
+static const char *ref_test_name = "refs/heads/test";
+
+static git_repository *g_repo;
+
+void test_refs_overwrite__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo");
+}
+
+void test_refs_overwrite__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_refs_overwrite__symbolic(void)
+{
+ // Overwrite an existing symbolic reference
+ git_reference *ref, *branch_ref;
+
+ /* The target needds to exist and we need to check the name has changed */
+ cl_git_pass(git_reference_symbolic_create(&branch_ref, g_repo, ref_branch_name, ref_master_name, 0, NULL, NULL));
+ cl_git_pass(git_reference_symbolic_create(&ref, g_repo, ref_name, ref_branch_name, 0, NULL, NULL));
+ git_reference_free(ref);
+
+ /* Ensure it points to the right place*/
+ cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name));
+ cl_assert(git_reference_type(ref) & GIT_REF_SYMBOLIC);
+ cl_assert_equal_s(git_reference_symbolic_target(ref), ref_branch_name);
+ git_reference_free(ref);
+
+ /* Ensure we can't create it unless we force it to */
+ cl_git_fail(git_reference_symbolic_create(&ref, g_repo, ref_name, ref_master_name, 0, NULL, NULL));
+ cl_git_pass(git_reference_symbolic_create(&ref, g_repo, ref_name, ref_master_name, 1, NULL, NULL));
+ git_reference_free(ref);
+
+ /* Ensure it points to the right place */
+ cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name));
+ cl_assert(git_reference_type(ref) & GIT_REF_SYMBOLIC);
+ cl_assert_equal_s(git_reference_symbolic_target(ref), ref_master_name);
+
+ git_reference_free(ref);
+ git_reference_free(branch_ref);
+}
+
+void test_refs_overwrite__object_id(void)
+{
+ // Overwrite an existing object id reference
+ git_reference *ref;
+ git_oid id;
+
+ cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name));
+ cl_assert(git_reference_type(ref) & GIT_REF_OID);
+ git_oid_cpy(&id, git_reference_target(ref));
+ git_reference_free(ref);
+
+ /* Create it */
+ cl_git_pass(git_reference_create(&ref, g_repo, ref_name, &id, 0, NULL, NULL));
+ git_reference_free(ref);
+
+ cl_git_pass(git_reference_lookup(&ref, g_repo, ref_test_name));
+ cl_assert(git_reference_type(ref) & GIT_REF_OID);
+ git_oid_cpy(&id, git_reference_target(ref));
+ git_reference_free(ref);
+
+ /* Ensure we can't overwrite unless we force it */
+ cl_git_fail(git_reference_create(&ref, g_repo, ref_name, &id, 0, NULL, NULL));
+ cl_git_pass(git_reference_create(&ref, g_repo, ref_name, &id, 1, NULL, NULL));
+ git_reference_free(ref);
+
+ /* Ensure it has been overwritten */
+ cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name));
+ cl_assert(!git_oid_cmp(&id, git_reference_target(ref)));
+
+ git_reference_free(ref);
+}
+
+void test_refs_overwrite__object_id_with_symbolic(void)
+{
+ // Overwrite an existing object id reference with a symbolic one
+ git_reference *ref;
+ git_oid id;
+
+ cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name));
+ cl_assert(git_reference_type(ref) & GIT_REF_OID);
+ git_oid_cpy(&id, git_reference_target(ref));
+ git_reference_free(ref);
+
+ cl_git_pass(git_reference_create(&ref, g_repo, ref_name, &id, 0, NULL, NULL));
+ git_reference_free(ref);
+ cl_git_fail(git_reference_symbolic_create(&ref, g_repo, ref_name, ref_master_name, 0, NULL, NULL));
+ cl_git_pass(git_reference_symbolic_create(&ref, g_repo, ref_name, ref_master_name, 1, NULL, NULL));
+ git_reference_free(ref);
+
+ /* Ensure it points to the right place */
+ cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name));
+ cl_assert(git_reference_type(ref) & GIT_REF_SYMBOLIC);
+ cl_assert_equal_s(git_reference_symbolic_target(ref), ref_master_name);
+
+ git_reference_free(ref);
+}
+
+void test_refs_overwrite__symbolic_with_object_id(void)
+{
+ // Overwrite an existing symbolic reference with an object id one
+ git_reference *ref;
+ git_oid id;
+
+ cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name));
+ cl_assert(git_reference_type(ref) & GIT_REF_OID);
+ git_oid_cpy(&id, git_reference_target(ref));
+ git_reference_free(ref);
+
+ /* Create the symbolic ref */
+ cl_git_pass(git_reference_symbolic_create(&ref, g_repo, ref_name, ref_master_name, 0, NULL, NULL));
+ git_reference_free(ref);
+ /* It shouldn't overwrite unless we tell it to */
+ cl_git_fail(git_reference_create(&ref, g_repo, ref_name, &id, 0, NULL, NULL));
+ cl_git_pass(git_reference_create(&ref, g_repo, ref_name, &id, 1, NULL, NULL));
+ git_reference_free(ref);
+
+ /* Ensure it points to the right place */
+ cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name));
+ cl_assert(git_reference_type(ref) & GIT_REF_OID);
+ cl_assert(!git_oid_cmp(git_reference_target(ref), &id));
+
+ git_reference_free(ref);
+}
diff --git a/tests/refs/pack.c b/tests/refs/pack.c
new file mode 100644
index 000000000..7f5c611a7
--- /dev/null
+++ b/tests/refs/pack.c
@@ -0,0 +1,105 @@
+#include "clar_libgit2.h"
+
+#include "fileops.h"
+#include "git2/reflog.h"
+#include "git2/refdb.h"
+#include "reflog.h"
+#include "refs.h"
+#include "ref_helpers.h"
+
+static const char *loose_tag_ref_name = "refs/tags/e90810b";
+
+static git_repository *g_repo;
+
+void test_refs_pack__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo");
+}
+
+void test_refs_pack__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+static void packall(void)
+{
+ git_refdb *refdb;
+
+ cl_git_pass(git_repository_refdb(&refdb, g_repo));
+ cl_git_pass(git_refdb_compress(refdb));
+ git_refdb_free(refdb);
+}
+
+void test_refs_pack__empty(void)
+{
+ /* create a packfile for an empty folder */
+ git_buf temp_path = GIT_BUF_INIT;
+
+ cl_git_pass(git_buf_join_n(&temp_path, '/', 3, git_repository_path(g_repo), GIT_REFS_HEADS_DIR, "empty_dir"));
+ cl_git_pass(git_futils_mkdir_r(temp_path.ptr, NULL, GIT_REFS_DIR_MODE));
+ git_buf_free(&temp_path);
+
+ packall();
+}
+
+void test_refs_pack__loose(void)
+{
+ /* create a packfile from all the loose refs in a repo */
+ git_reference *reference;
+ git_buf temp_path = GIT_BUF_INIT;
+
+ /* Ensure a known loose ref can be looked up */
+ cl_git_pass(git_reference_lookup(&reference, g_repo, loose_tag_ref_name));
+ cl_assert(reference_is_packed(reference) == 0);
+ cl_assert_equal_s(reference->name, loose_tag_ref_name);
+ git_reference_free(reference);
+
+ /*
+ * We are now trying to pack also a loose reference
+ * called `points_to_blob`, to make sure we can properly
+ * pack weak tags
+ */
+ packall();
+
+ /* Ensure the packed-refs file exists */
+ cl_git_pass(git_buf_joinpath(&temp_path, git_repository_path(g_repo), GIT_PACKEDREFS_FILE));
+ cl_assert(git_path_exists(temp_path.ptr));
+
+ /* Ensure the known ref can still be looked up but is now packed */
+ cl_git_pass(git_reference_lookup(&reference, g_repo, loose_tag_ref_name));
+ cl_assert(reference_is_packed(reference));
+ cl_assert_equal_s(reference->name, loose_tag_ref_name);
+
+ /* Ensure the known ref has been removed from the loose folder structure */
+ cl_git_pass(git_buf_joinpath(&temp_path, git_repository_path(g_repo), loose_tag_ref_name));
+ cl_assert(!git_path_exists(temp_path.ptr));
+
+ git_reference_free(reference);
+ git_buf_free(&temp_path);
+}
+
+void test_refs_pack__symbolic(void)
+{
+ /* create a packfile from loose refs skipping symbolic refs */
+ int i;
+ git_oid head;
+ git_reference *ref;
+ char name[128];
+
+ cl_git_pass(git_reference_name_to_id(&head, g_repo, "HEAD"));
+
+ /* make a bunch of references */
+
+ for (i = 0; i < 100; ++i) {
+ snprintf(name, sizeof(name), "refs/heads/symbolic-%03d", i);
+ cl_git_pass(git_reference_symbolic_create(
+ &ref, g_repo, name, "refs/heads/master", 0, NULL, NULL));
+ git_reference_free(ref);
+
+ snprintf(name, sizeof(name), "refs/heads/direct-%03d", i);
+ cl_git_pass(git_reference_create(&ref, g_repo, name, &head, 0, NULL, NULL));
+ git_reference_free(ref);
+ }
+
+ packall();
+}
diff --git a/tests/refs/peel.c b/tests/refs/peel.c
new file mode 100644
index 000000000..f2fb6e259
--- /dev/null
+++ b/tests/refs/peel.c
@@ -0,0 +1,119 @@
+#include "clar_libgit2.h"
+
+static git_repository *g_repo;
+static git_repository *g_peel_repo;
+
+void test_refs_peel__initialize(void)
+{
+ cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git")));
+ cl_git_pass(git_repository_open(&g_peel_repo, cl_fixture("peeled.git")));
+}
+
+void test_refs_peel__cleanup(void)
+{
+ git_repository_free(g_repo);
+ g_repo = NULL;
+ git_repository_free(g_peel_repo);
+ g_peel_repo = NULL;
+}
+
+static void assert_peel_generic(
+ git_repository *repo,
+ const char *ref_name,
+ git_otype requested_type,
+ const char* expected_sha,
+ git_otype expected_type)
+{
+ git_oid expected_oid;
+ git_reference *ref;
+ git_object *peeled;
+
+ cl_git_pass(git_reference_lookup(&ref, repo, ref_name));
+
+ cl_git_pass(git_reference_peel(&peeled, ref, requested_type));
+
+ cl_git_pass(git_oid_fromstr(&expected_oid, expected_sha));
+ cl_assert_equal_i(0, git_oid_cmp(&expected_oid, git_object_id(peeled)));
+
+ cl_assert_equal_i(expected_type, git_object_type(peeled));
+
+ git_object_free(peeled);
+ git_reference_free(ref);
+}
+
+static void assert_peel(
+ const char *ref_name,
+ git_otype requested_type,
+ const char* expected_sha,
+ git_otype expected_type)
+{
+ assert_peel_generic(g_repo, ref_name, requested_type,
+ expected_sha, expected_type);
+}
+
+static void assert_peel_error(int error, const char *ref_name, git_otype requested_type)
+{
+ git_reference *ref;
+ git_object *peeled;
+
+ cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name));
+
+ cl_assert_equal_i(error, git_reference_peel(&peeled, ref, requested_type));
+
+ git_reference_free(ref);
+}
+
+void test_refs_peel__can_peel_a_tag(void)
+{
+ assert_peel("refs/tags/test", GIT_OBJ_TAG,
+ "b25fa35b38051e4ae45d4222e795f9df2e43f1d1", GIT_OBJ_TAG);
+ assert_peel("refs/tags/test", GIT_OBJ_COMMIT,
+ "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_COMMIT);
+ assert_peel("refs/tags/test", GIT_OBJ_TREE,
+ "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_TREE);
+ assert_peel("refs/tags/point_to_blob", GIT_OBJ_BLOB,
+ "1385f264afb75a56a5bec74243be9b367ba4ca08", GIT_OBJ_BLOB);
+}
+
+void test_refs_peel__can_peel_a_branch(void)
+{
+ assert_peel("refs/heads/master", GIT_OBJ_COMMIT,
+ "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", GIT_OBJ_COMMIT);
+ assert_peel("refs/heads/master", GIT_OBJ_TREE,
+ "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162", GIT_OBJ_TREE);
+}
+
+void test_refs_peel__can_peel_a_symbolic_reference(void)
+{
+ assert_peel("HEAD", GIT_OBJ_COMMIT,
+ "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", GIT_OBJ_COMMIT);
+ assert_peel("HEAD", GIT_OBJ_TREE,
+ "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162", GIT_OBJ_TREE);
+}
+
+void test_refs_peel__cannot_peel_into_a_non_existing_target(void)
+{
+ assert_peel_error(GIT_ENOTFOUND, "refs/tags/point_to_blob", GIT_OBJ_TAG);
+}
+
+void test_refs_peel__can_peel_into_any_non_tag_object(void)
+{
+ assert_peel("refs/heads/master", GIT_OBJ_ANY,
+ "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", GIT_OBJ_COMMIT);
+ assert_peel("refs/tags/point_to_blob", GIT_OBJ_ANY,
+ "1385f264afb75a56a5bec74243be9b367ba4ca08", GIT_OBJ_BLOB);
+ assert_peel("refs/tags/test", GIT_OBJ_ANY,
+ "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_COMMIT);
+}
+
+void test_refs_peel__can_peel_fully_peeled_packed_refs(void)
+{
+ assert_peel_generic(g_peel_repo,
+ "refs/tags/tag-inside-tags", GIT_OBJ_ANY,
+ "0df1a5865c8abfc09f1f2182e6a31be550e99f07",
+ GIT_OBJ_COMMIT);
+ assert_peel_generic(g_peel_repo,
+ "refs/foo/tag-outside-tags", GIT_OBJ_ANY,
+ "0df1a5865c8abfc09f1f2182e6a31be550e99f07",
+ GIT_OBJ_COMMIT);
+}
diff --git a/tests/refs/races.c b/tests/refs/races.c
new file mode 100644
index 000000000..643290a8e
--- /dev/null
+++ b/tests/refs/races.c
@@ -0,0 +1,152 @@
+#include "clar_libgit2.h"
+
+#include "repository.h"
+#include "git2/reflog.h"
+#include "reflog.h"
+#include "ref_helpers.h"
+
+static const char *commit_id = "099fabac3a9ea935598528c27f866e34089c2eff";
+static const char *refname = "refs/heads/master";
+static const char *other_refname = "refs/heads/foo";
+static const char *other_commit_id = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750";
+
+static git_repository *g_repo;
+
+void test_refs_races__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo");
+}
+
+void test_refs_races__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_refs_races__create_matching(void)
+{
+ git_reference *ref, *ref2, *ref3;
+ git_oid id, other_id;
+
+ git_oid_fromstr(&id, commit_id);
+ git_oid_fromstr(&other_id, other_commit_id);
+
+ cl_git_fail_with(GIT_EMODIFIED, git_reference_create_matching(&ref, g_repo, refname, &other_id, 1, &other_id, NULL, NULL));
+
+ cl_git_pass(git_reference_lookup(&ref, g_repo, refname));
+ cl_git_pass(git_reference_create_matching(&ref2, g_repo, refname, &other_id, 1, &id, NULL, NULL));
+ cl_git_fail_with(GIT_EMODIFIED, git_reference_set_target(&ref3, ref, &other_id, NULL, NULL));
+
+ git_reference_free(ref);
+ git_reference_free(ref2);
+ git_reference_free(ref3);
+}
+
+void test_refs_races__symbolic_create_matching(void)
+{
+ git_reference *ref, *ref2, *ref3;
+ git_oid id, other_id;
+
+ git_oid_fromstr(&id, commit_id);
+ git_oid_fromstr(&other_id, other_commit_id);
+
+ cl_git_fail_with(GIT_EMODIFIED, git_reference_symbolic_create_matching(&ref, g_repo, "HEAD", other_refname, 1, other_refname, NULL, NULL));
+
+ cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD"));
+ cl_git_pass(git_reference_symbolic_create_matching(&ref2, g_repo, "HEAD", other_refname, 1, NULL, NULL, refname));
+ cl_git_fail_with(GIT_EMODIFIED, git_reference_symbolic_set_target(&ref3, ref, other_refname, NULL, NULL));
+
+ git_reference_free(ref);
+ git_reference_free(ref2);
+ git_reference_free(ref3);
+}
+
+void test_refs_races__delete(void)
+{
+ git_reference *ref, *ref2;
+ git_oid id, other_id;
+
+ git_oid_fromstr(&id, commit_id);
+ git_oid_fromstr(&other_id, other_commit_id);
+
+ /* We can delete a value that matches */
+ cl_git_pass(git_reference_lookup(&ref, g_repo, refname));
+ cl_git_pass(git_reference_delete(ref));
+ git_reference_free(ref);
+
+ /* We cannot delete a symbolic value that doesn't match */
+ cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD"));
+ cl_git_pass(git_reference_symbolic_create_matching(&ref2, g_repo, "HEAD", other_refname, 1, NULL, NULL, refname));
+ cl_git_fail_with(GIT_EMODIFIED, git_reference_delete(ref));
+
+ git_reference_free(ref);
+ git_reference_free(ref2);
+
+ cl_git_pass(git_reference_create(&ref, g_repo, refname, &id, 1, NULL, NULL));
+ git_reference_free(ref);
+
+ /* We cannot delete an oid value that doesn't match */
+ cl_git_pass(git_reference_lookup(&ref, g_repo, refname));
+ cl_git_pass(git_reference_create_matching(&ref2, g_repo, refname, &other_id, 1, &id, NULL, NULL));
+ cl_git_fail_with(GIT_EMODIFIED, git_reference_delete(ref));
+
+ git_reference_free(ref);
+ git_reference_free(ref2);
+}
+
+void test_refs_races__switch_oid_to_symbolic(void)
+{
+ git_reference *ref, *ref2, *ref3;
+ git_oid id, other_id;
+
+ git_oid_fromstr(&id, commit_id);
+ git_oid_fromstr(&other_id, other_commit_id);
+
+ /* Removing a direct ref when it's currently symbolic should fail */
+ cl_git_pass(git_reference_lookup(&ref, g_repo, refname));
+ cl_git_pass(git_reference_symbolic_create(&ref2, g_repo, refname, other_refname, 1, NULL, NULL));
+ cl_git_fail_with(GIT_EMODIFIED, git_reference_delete(ref));
+
+ git_reference_free(ref);
+ git_reference_free(ref2);
+
+ cl_git_pass(git_reference_create(&ref, g_repo, refname, &id, 1, NULL, NULL));
+ git_reference_free(ref);
+
+ /* Updating a direct ref when it's currently symbolic should fail */
+ cl_git_pass(git_reference_lookup(&ref, g_repo, refname));
+ cl_git_pass(git_reference_symbolic_create(&ref2, g_repo, refname, other_refname, 1, NULL, NULL));
+ cl_git_fail_with(GIT_EMODIFIED, git_reference_set_target(&ref3, ref, &other_id, NULL, NULL));
+
+ git_reference_free(ref);
+ git_reference_free(ref2);
+ git_reference_free(ref3);
+}
+
+void test_refs_races__switch_symbolic_to_oid(void)
+{
+ git_reference *ref, *ref2, *ref3;
+ git_oid id, other_id;
+
+ git_oid_fromstr(&id, commit_id);
+ git_oid_fromstr(&other_id, other_commit_id);
+
+ /* Removing a symbolic ref when it's currently direct should fail */
+ cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD"));
+ cl_git_pass(git_reference_create(&ref2, g_repo, "HEAD", &id, 1, NULL, NULL));
+ cl_git_fail_with(GIT_EMODIFIED, git_reference_delete(ref));
+
+ git_reference_free(ref);
+ git_reference_free(ref2);
+
+ cl_git_pass(git_reference_symbolic_create(&ref, g_repo, "HEAD", refname, 1, NULL, NULL));
+ git_reference_free(ref);
+
+ /* Updating a symbolic ref when it's currently direct should fail */
+ cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD"));
+ cl_git_pass(git_reference_create(&ref2, g_repo, "HEAD", &id, 1, NULL, NULL));
+ cl_git_fail_with(GIT_EMODIFIED, git_reference_symbolic_set_target(&ref3, ref, other_refname, NULL, NULL));
+
+ git_reference_free(ref);
+ git_reference_free(ref2);
+ git_reference_free(ref3);
+}
diff --git a/tests/refs/read.c b/tests/refs/read.c
new file mode 100644
index 000000000..52c307eb0
--- /dev/null
+++ b/tests/refs/read.c
@@ -0,0 +1,299 @@
+#include "clar_libgit2.h"
+
+#include "repository.h"
+#include "git2/reflog.h"
+#include "reflog.h"
+#include "ref_helpers.h"
+
+static const char *loose_tag_ref_name = "refs/tags/e90810b";
+static const char *non_existing_tag_ref_name = "refs/tags/i-do-not-exist";
+static const char *head_tracker_sym_ref_name = "HEAD_TRACKER";
+static const char *current_head_target = "refs/heads/master";
+static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750";
+static const char *packed_head_name = "refs/heads/packed";
+static const char *packed_test_head_name = "refs/heads/packed-test";
+
+static git_repository *g_repo;
+
+void test_refs_read__initialize(void)
+{
+ cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git")));
+}
+
+void test_refs_read__cleanup(void)
+{
+ git_repository_free(g_repo);
+ g_repo = NULL;
+}
+
+void test_refs_read__loose_tag(void)
+{
+ // lookup a loose tag reference
+ git_reference *reference;
+ git_object *object;
+ git_buf ref_name_from_tag_name = GIT_BUF_INIT;
+
+ cl_git_pass(git_reference_lookup(&reference, g_repo, loose_tag_ref_name));
+ cl_assert(git_reference_type(reference) & GIT_REF_OID);
+ cl_assert(reference_is_packed(reference) == 0);
+ cl_assert_equal_s(reference->name, loose_tag_ref_name);
+
+ cl_git_pass(git_object_lookup(&object, g_repo, git_reference_target(reference), GIT_OBJ_ANY));
+ cl_assert(object != NULL);
+ cl_assert(git_object_type(object) == GIT_OBJ_TAG);
+
+ /* Ensure the name of the tag matches the name of the reference */
+ cl_git_pass(git_buf_joinpath(&ref_name_from_tag_name, GIT_REFS_TAGS_DIR, git_tag_name((git_tag *)object)));
+ cl_assert_equal_s(ref_name_from_tag_name.ptr, loose_tag_ref_name);
+ git_buf_free(&ref_name_from_tag_name);
+
+ git_object_free(object);
+
+ git_reference_free(reference);
+}
+
+void test_refs_read__nonexisting_tag(void)
+{
+ // lookup a loose tag reference that doesn't exist
+ git_reference *reference;
+
+ cl_git_fail(git_reference_lookup(&reference, g_repo, non_existing_tag_ref_name));
+
+ git_reference_free(reference);
+}
+
+
+void test_refs_read__symbolic(void)
+{
+ // lookup a symbolic reference
+ git_reference *reference, *resolved_ref;
+ git_object *object;
+ git_oid id;
+
+ cl_git_pass(git_reference_lookup(&reference, g_repo, GIT_HEAD_FILE));
+ cl_assert(git_reference_type(reference) & GIT_REF_SYMBOLIC);
+ cl_assert(reference_is_packed(reference) == 0);
+ cl_assert_equal_s(reference->name, GIT_HEAD_FILE);
+
+ cl_git_pass(git_reference_resolve(&resolved_ref, reference));
+ cl_assert(git_reference_type(resolved_ref) == GIT_REF_OID);
+
+ cl_git_pass(git_object_lookup(&object, g_repo, git_reference_target(resolved_ref), GIT_OBJ_ANY));
+ cl_assert(object != NULL);
+ cl_assert(git_object_type(object) == GIT_OBJ_COMMIT);
+
+ git_oid_fromstr(&id, current_master_tip);
+ cl_assert(git_oid_cmp(&id, git_object_id(object)) == 0);
+
+ git_object_free(object);
+
+ git_reference_free(reference);
+ git_reference_free(resolved_ref);
+}
+
+void test_refs_read__nested_symbolic(void)
+{
+ // lookup a nested symbolic reference
+ git_reference *reference, *resolved_ref;
+ git_object *object;
+ git_oid id;
+
+ cl_git_pass(git_reference_lookup(&reference, g_repo, head_tracker_sym_ref_name));
+ cl_assert(git_reference_type(reference) & GIT_REF_SYMBOLIC);
+ cl_assert(reference_is_packed(reference) == 0);
+ cl_assert_equal_s(reference->name, head_tracker_sym_ref_name);
+
+ cl_git_pass(git_reference_resolve(&resolved_ref, reference));
+ cl_assert(git_reference_type(resolved_ref) == GIT_REF_OID);
+
+ cl_git_pass(git_object_lookup(&object, g_repo, git_reference_target(resolved_ref), GIT_OBJ_ANY));
+ cl_assert(object != NULL);
+ cl_assert(git_object_type(object) == GIT_OBJ_COMMIT);
+
+ git_oid_fromstr(&id, current_master_tip);
+ cl_assert(git_oid_cmp(&id, git_object_id(object)) == 0);
+
+ git_object_free(object);
+
+ git_reference_free(reference);
+ git_reference_free(resolved_ref);
+}
+
+void test_refs_read__head_then_master(void)
+{
+ // lookup the HEAD and resolve the master branch
+ git_reference *reference, *resolved_ref, *comp_base_ref;
+
+ cl_git_pass(git_reference_lookup(&reference, g_repo, head_tracker_sym_ref_name));
+ cl_git_pass(git_reference_resolve(&comp_base_ref, reference));
+ git_reference_free(reference);
+
+ cl_git_pass(git_reference_lookup(&reference, g_repo, GIT_HEAD_FILE));
+ cl_git_pass(git_reference_resolve(&resolved_ref, reference));
+ cl_git_pass(git_oid_cmp(git_reference_target(comp_base_ref), git_reference_target(resolved_ref)));
+ git_reference_free(reference);
+ git_reference_free(resolved_ref);
+
+ cl_git_pass(git_reference_lookup(&reference, g_repo, current_head_target));
+ cl_git_pass(git_reference_resolve(&resolved_ref, reference));
+ cl_git_pass(git_oid_cmp(git_reference_target(comp_base_ref), git_reference_target(resolved_ref)));
+ git_reference_free(reference);
+ git_reference_free(resolved_ref);
+
+ git_reference_free(comp_base_ref);
+}
+
+void test_refs_read__master_then_head(void)
+{
+ // lookup the master branch and then the HEAD
+ git_reference *reference, *master_ref, *resolved_ref;
+
+ cl_git_pass(git_reference_lookup(&master_ref, g_repo, current_head_target));
+ cl_git_pass(git_reference_lookup(&reference, g_repo, GIT_HEAD_FILE));
+
+ cl_git_pass(git_reference_resolve(&resolved_ref, reference));
+ cl_git_pass(git_oid_cmp(git_reference_target(master_ref), git_reference_target(resolved_ref)));
+
+ git_reference_free(reference);
+ git_reference_free(resolved_ref);
+ git_reference_free(master_ref);
+}
+
+
+void test_refs_read__packed(void)
+{
+ // lookup a packed reference
+ git_reference *reference;
+ git_object *object;
+
+ cl_git_pass(git_reference_lookup(&reference, g_repo, packed_head_name));
+ cl_assert(git_reference_type(reference) & GIT_REF_OID);
+ cl_assert(reference_is_packed(reference));
+ cl_assert_equal_s(reference->name, packed_head_name);
+
+ cl_git_pass(git_object_lookup(&object, g_repo, git_reference_target(reference), GIT_OBJ_ANY));
+ cl_assert(object != NULL);
+ cl_assert(git_object_type(object) == GIT_OBJ_COMMIT);
+
+ git_object_free(object);
+
+ git_reference_free(reference);
+}
+
+void test_refs_read__loose_first(void)
+{
+ // assure that a loose reference is looked up before a packed reference
+ git_reference *reference;
+
+ cl_git_pass(git_reference_lookup(&reference, g_repo, packed_head_name));
+ git_reference_free(reference);
+ cl_git_pass(git_reference_lookup(&reference, g_repo, packed_test_head_name));
+ cl_assert(git_reference_type(reference) & GIT_REF_OID);
+ cl_assert(reference_is_packed(reference) == 0);
+ cl_assert_equal_s(reference->name, packed_test_head_name);
+
+ git_reference_free(reference);
+}
+
+void test_refs_read__chomped(void)
+{
+ git_reference *test, *chomped;
+
+ cl_git_pass(git_reference_lookup(&test, g_repo, "refs/heads/test"));
+ cl_git_pass(git_reference_lookup(&chomped, g_repo, "refs/heads/chomped"));
+ cl_git_pass(git_oid_cmp(git_reference_target(test), git_reference_target(chomped)));
+
+ git_reference_free(test);
+ git_reference_free(chomped);
+}
+
+void test_refs_read__trailing(void)
+{
+ git_reference *test, *trailing;
+
+ cl_git_pass(git_reference_lookup(&test, g_repo, "refs/heads/test"));
+ cl_git_pass(git_reference_lookup(&trailing, g_repo, "refs/heads/trailing"));
+ cl_git_pass(git_oid_cmp(git_reference_target(test), git_reference_target(trailing)));
+ git_reference_free(trailing);
+ cl_git_pass(git_reference_lookup(&trailing, g_repo, "FETCH_HEAD"));
+
+ git_reference_free(test);
+ git_reference_free(trailing);
+}
+
+void test_refs_read__unfound_return_ENOTFOUND(void)
+{
+ git_reference *reference;
+ git_oid id;
+
+ cl_assert_equal_i(GIT_ENOTFOUND,
+ git_reference_lookup(&reference, g_repo, "TEST_MASTER"));
+ cl_assert_equal_i(GIT_ENOTFOUND,
+ git_reference_lookup(&reference, g_repo, "refs/test/master"));
+ cl_assert_equal_i(GIT_ENOTFOUND,
+ git_reference_lookup(&reference, g_repo, "refs/tags/test/master"));
+ cl_assert_equal_i(GIT_ENOTFOUND,
+ git_reference_lookup(&reference, g_repo, "refs/tags/test/farther/master"));
+
+ cl_assert_equal_i(GIT_ENOTFOUND,
+ git_reference_name_to_id(&id, g_repo, "refs/tags/test/farther/master"));
+}
+
+static void assert_is_branch(const char *name, bool expected_branchness)
+{
+ git_reference *reference;
+ cl_git_pass(git_reference_lookup(&reference, g_repo, name));
+ cl_assert_equal_i(expected_branchness, git_reference_is_branch(reference));
+ git_reference_free(reference);
+}
+
+void test_refs_read__can_determine_if_a_reference_is_a_local_branch(void)
+{
+ assert_is_branch("refs/heads/master", true);
+ assert_is_branch("refs/heads/packed", true);
+ assert_is_branch("refs/remotes/test/master", false);
+ assert_is_branch("refs/tags/e90810b", false);
+}
+
+static void assert_is_tag(const char *name, bool expected_tagness)
+{
+ git_reference *reference;
+ cl_git_pass(git_reference_lookup(&reference, g_repo, name));
+ cl_assert_equal_i(expected_tagness, git_reference_is_tag(reference));
+ git_reference_free(reference);
+}
+
+void test_refs_read__can_determine_if_a_reference_is_a_tag(void)
+{
+ assert_is_tag("refs/tags/e90810b", true);
+ assert_is_tag("refs/tags/test", true);
+ assert_is_tag("refs/heads/packed", false);
+ assert_is_tag("refs/remotes/test/master", false);
+}
+
+static void assert_is_note(const char *name, bool expected_noteness)
+{
+ git_reference *reference;
+ cl_git_pass(git_reference_lookup(&reference, g_repo, name));
+ cl_assert_equal_i(expected_noteness, git_reference_is_note(reference));
+ git_reference_free(reference);
+}
+
+void test_refs_read__can_determine_if_a_reference_is_a_note(void)
+{
+ assert_is_note("refs/notes/fanout", true);
+ assert_is_note("refs/heads/packed", false);
+ assert_is_note("refs/remotes/test/master", false);
+}
+
+void test_refs_read__invalid_name_returns_EINVALIDSPEC(void)
+{
+ git_reference *reference;
+ git_oid id;
+
+ cl_assert_equal_i(GIT_EINVALIDSPEC,
+ git_reference_lookup(&reference, g_repo, "refs/heads/Inv@{id"));
+
+ cl_assert_equal_i(GIT_EINVALIDSPEC,
+ git_reference_name_to_id(&id, g_repo, "refs/heads/Inv@{id"));
+}
diff --git a/tests/refs/ref_helpers.c b/tests/refs/ref_helpers.c
new file mode 100644
index 000000000..7676e65a7
--- /dev/null
+++ b/tests/refs/ref_helpers.c
@@ -0,0 +1,25 @@
+#include "git2/repository.h"
+#include "git2/refs.h"
+#include "common.h"
+#include "util.h"
+#include "buffer.h"
+#include "path.h"
+
+int reference_is_packed(git_reference *ref)
+{
+ git_buf ref_path = GIT_BUF_INIT;
+ int packed;
+
+ assert(ref);
+
+ if (git_buf_joinpath(&ref_path,
+ git_repository_path(git_reference_owner(ref)),
+ git_reference_name(ref)) < 0)
+ return -1;
+
+ packed = !git_path_isfile(ref_path.ptr);
+
+ git_buf_free(&ref_path);
+
+ return packed;
+}
diff --git a/tests/refs/ref_helpers.h b/tests/refs/ref_helpers.h
new file mode 100644
index 000000000..0ef55bfce
--- /dev/null
+++ b/tests/refs/ref_helpers.h
@@ -0,0 +1 @@
+int reference_is_packed(git_reference *ref);
diff --git a/tests/refs/reflog/drop.c b/tests/refs/reflog/drop.c
new file mode 100644
index 000000000..916bd9933
--- /dev/null
+++ b/tests/refs/reflog/drop.c
@@ -0,0 +1,115 @@
+#include "clar_libgit2.h"
+
+#include "reflog.h"
+
+static git_repository *g_repo;
+static git_reflog *g_reflog;
+static size_t entrycount;
+
+void test_refs_reflog_drop__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo.git");
+
+ git_reflog_read(&g_reflog, g_repo, "HEAD");
+ entrycount = git_reflog_entrycount(g_reflog);
+}
+
+void test_refs_reflog_drop__cleanup(void)
+{
+ git_reflog_free(g_reflog);
+ g_reflog = NULL;
+
+ cl_git_sandbox_cleanup();
+}
+
+void test_refs_reflog_drop__dropping_a_non_exisiting_entry_from_the_log_returns_ENOTFOUND(void)
+{
+ cl_assert_equal_i(GIT_ENOTFOUND, git_reflog_drop(g_reflog, entrycount, 0));
+
+ cl_assert_equal_sz(entrycount, git_reflog_entrycount(g_reflog));
+}
+
+void test_refs_reflog_drop__can_drop_an_entry(void)
+{
+ cl_assert(entrycount > 4);
+
+ cl_git_pass(git_reflog_drop(g_reflog, 2, 0));
+ cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog));
+}
+
+void test_refs_reflog_drop__can_drop_an_entry_and_rewrite_the_log_history(void)
+{
+ 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_current = git_reflog_entry_byindex(g_reflog, 1);
+
+ git_oid_cpy(&before_current_old_oid, &before_current->oid_old);
+ git_oid_cpy(&before_current_cur_oid, &before_current->oid_cur);
+
+ cl_git_pass(git_reflog_drop(g_reflog, 1, 1));
+
+ cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog));
+
+ after_current = git_reflog_entry_byindex(g_reflog, 0);
+
+ 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_oldest_entry(void)
+{
+ const git_reflog_entry *entry;
+
+ cl_assert(entrycount > 2);
+
+ cl_git_pass(git_reflog_drop(g_reflog, entrycount - 1, 0));
+ cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog));
+
+ entry = git_reflog_entry_byindex(g_reflog, entrycount - 2);
+ cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) != 0);
+}
+
+void test_refs_reflog_drop__can_drop_the_oldest_entry_and_rewrite_the_log_history(void)
+{
+ const git_reflog_entry *entry;
+
+ cl_assert(entrycount > 2);
+
+ cl_git_pass(git_reflog_drop(g_reflog, entrycount - 1, 1));
+ cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog));
+
+ entry = git_reflog_entry_byindex(g_reflog, entrycount - 2);
+ cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0);
+}
+
+void test_refs_reflog_drop__can_drop_all_the_entries(void)
+{
+ cl_assert(--entrycount > 0);
+
+ do {
+ cl_git_pass(git_reflog_drop(g_reflog, 0, 1));
+ } while (--entrycount > 0);
+
+ cl_git_pass(git_reflog_drop(g_reflog, 0, 1));
+
+ cl_assert_equal_i(0, (int)git_reflog_entrycount(g_reflog));
+}
+
+void test_refs_reflog_drop__can_persist_deletion_on_disk(void)
+{
+ cl_assert(entrycount > 2);
+
+ cl_git_pass(git_reflog_drop(g_reflog, 0, 1));
+ cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog));
+ cl_git_pass(git_reflog_write(g_reflog));
+
+ git_reflog_free(g_reflog);
+
+ git_reflog_read(&g_reflog, g_repo, "HEAD");
+
+ cl_assert_equal_sz(entrycount - 1, git_reflog_entrycount(g_reflog));
+}
diff --git a/tests/refs/reflog/reflog.c b/tests/refs/reflog/reflog.c
new file mode 100644
index 000000000..792b0f05d
--- /dev/null
+++ b/tests/refs/reflog/reflog.c
@@ -0,0 +1,336 @@
+#include "clar_libgit2.h"
+
+#include "fileops.h"
+#include "git2/reflog.h"
+#include "reflog.h"
+
+
+static const char *new_ref = "refs/heads/test-reflog";
+static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750";
+#define commit_msg "commit: bla bla"
+
+static git_repository *g_repo;
+
+
+// helpers
+static void assert_signature(const git_signature *expected, const git_signature *actual)
+{
+ cl_assert(actual);
+ cl_assert_equal_s(expected->name, actual->name);
+ cl_assert_equal_s(expected->email, actual->email);
+ cl_assert(expected->when.offset == actual->when.offset);
+ cl_assert(expected->when.time == actual->when.time);
+}
+
+
+// Fixture setup and teardown
+void test_refs_reflog_reflog__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo.git");
+}
+
+void test_refs_reflog_reflog__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+static void assert_appends(const git_signature *committer, const git_oid *oid)
+{
+ git_repository *repo2;
+ git_reference *lookedup_ref;
+ git_reflog *reflog;
+ const git_reflog_entry *entry;
+
+ /* Reopen a new instance of the repository */
+ cl_git_pass(git_repository_open(&repo2, "testrepo.git"));
+
+ /* Lookup the previously created branch */
+ cl_git_pass(git_reference_lookup(&lookedup_ref, repo2, new_ref));
+
+ /* Read and parse the reflog for this branch */
+ cl_git_pass(git_reflog_read(&reflog, repo2, new_ref));
+ cl_assert_equal_i(3, (int)git_reflog_entrycount(reflog));
+
+ /* The first one was the creation of the branch */
+ entry = git_reflog_entry_byindex(reflog, 2);
+ cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0);
+
+ entry = git_reflog_entry_byindex(reflog, 1);
+ 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);
+ cl_assert(entry->msg == NULL);
+
+ entry = git_reflog_entry_byindex(reflog, 0);
+ assert_signature(committer, entry->committer);
+ cl_assert(git_oid_cmp(oid, &entry->oid_cur) == 0);
+ cl_assert_equal_s(commit_msg, entry->msg);
+
+ git_reflog_free(reflog);
+ git_repository_free(repo2);
+
+ git_reference_free(lookedup_ref);
+}
+
+void test_refs_reflog_reflog__append_then_read(void)
+{
+ /* write a reflog for a given reference and ensure it can be read back */
+ git_reference *ref;
+ git_oid oid;
+ git_signature *committer;
+ git_reflog *reflog;
+
+ /* Create a new branch pointing at the HEAD */
+ git_oid_fromstr(&oid, current_master_tip);
+ cl_git_pass(git_reference_create(&ref, g_repo, new_ref, &oid, 0, NULL, NULL));
+ git_reference_free(ref);
+
+ cl_git_pass(git_signature_now(&committer, "foo", "foo@bar"));
+
+ cl_git_pass(git_reflog_read(&reflog, g_repo, new_ref));
+
+ cl_git_fail(git_reflog_append(reflog, &oid, committer, "no inner\nnewline"));
+ cl_git_pass(git_reflog_append(reflog, &oid, committer, NULL));
+ cl_git_pass(git_reflog_append(reflog, &oid, committer, commit_msg "\n"));
+ cl_git_pass(git_reflog_write(reflog));
+ git_reflog_free(reflog);
+
+ assert_appends(committer, &oid);
+
+ git_signature_free(committer);
+}
+
+void test_refs_reflog_reflog__renaming_the_reference_moves_the_reflog(void)
+{
+ git_reference *master, *new_master;
+ git_buf master_log_path = GIT_BUF_INIT, moved_log_path = GIT_BUF_INIT;
+
+ git_buf_joinpath(&master_log_path, git_repository_path(g_repo), GIT_REFLOG_DIR);
+ git_buf_puts(&moved_log_path, git_buf_cstr(&master_log_path));
+ git_buf_joinpath(&master_log_path, git_buf_cstr(&master_log_path), "refs/heads/master");
+ git_buf_joinpath(&moved_log_path, git_buf_cstr(&moved_log_path), "refs/moved");
+
+ cl_assert_equal_i(true, git_path_isfile(git_buf_cstr(&master_log_path)));
+ cl_assert_equal_i(false, git_path_isfile(git_buf_cstr(&moved_log_path)));
+
+ cl_git_pass(git_reference_lookup(&master, g_repo, "refs/heads/master"));
+ cl_git_pass(git_reference_rename(&new_master, master, "refs/moved", 0, NULL, NULL));
+ git_reference_free(master);
+
+ cl_assert_equal_i(false, git_path_isfile(git_buf_cstr(&master_log_path)));
+ cl_assert_equal_i(true, git_path_isfile(git_buf_cstr(&moved_log_path)));
+
+ git_reference_free(new_master);
+ git_buf_free(&moved_log_path);
+ git_buf_free(&master_log_path);
+}
+
+static void assert_has_reflog(bool expected_result, const char *name)
+{
+ cl_assert_equal_i(expected_result, git_reference_has_log(g_repo, name));
+}
+
+void test_refs_reflog_reflog__reference_has_reflog(void)
+{
+ assert_has_reflog(true, "HEAD");
+ assert_has_reflog(true, "refs/heads/master");
+ assert_has_reflog(false, "refs/heads/subtrees");
+}
+
+void test_refs_reflog_reflog__reading_the_reflog_from_a_reference_with_no_log_returns_an_empty_one(void)
+{
+ git_reflog *reflog;
+ const char *refname = "refs/heads/subtrees";
+ git_buf subtrees_log_path = GIT_BUF_INIT;
+
+ git_buf_join_n(&subtrees_log_path, '/', 3, git_repository_path(g_repo), GIT_REFLOG_DIR, refname);
+ cl_assert_equal_i(false, git_path_isfile(git_buf_cstr(&subtrees_log_path)));
+
+ cl_git_pass(git_reflog_read(&reflog, g_repo, refname));
+
+ cl_assert_equal_i(0, (int)git_reflog_entrycount(reflog));
+
+ git_reflog_free(reflog);
+ git_buf_free(&subtrees_log_path);
+}
+
+void test_refs_reflog_reflog__cannot_write_a_moved_reflog(void)
+{
+ git_reference *master, *new_master;
+ git_buf master_log_path = GIT_BUF_INIT, moved_log_path = GIT_BUF_INIT;
+ git_reflog *reflog;
+
+ cl_git_pass(git_reference_lookup(&master, g_repo, "refs/heads/master"));
+ cl_git_pass(git_reflog_read(&reflog, g_repo, "refs/heads/master"));
+
+ cl_git_pass(git_reflog_write(reflog));
+
+ cl_git_pass(git_reference_rename(&new_master, master, "refs/moved", 0, NULL, NULL));
+ git_reference_free(master);
+
+ cl_git_fail(git_reflog_write(reflog));
+
+ git_reflog_free(reflog);
+ git_reference_free(new_master);
+ git_buf_free(&moved_log_path);
+ git_buf_free(&master_log_path);
+}
+
+void test_refs_reflog_reflog__renaming_with_an_invalid_name_returns_EINVALIDSPEC(void)
+{
+ cl_assert_equal_i(GIT_EINVALIDSPEC,
+ git_reflog_rename(g_repo, "refs/heads/master", "refs/heads/Inv@{id"));
+}
+
+void test_refs_reflog_reflog__write_only_std_locations(void)
+{
+ git_reference *ref;
+ git_oid id;
+
+ git_oid_fromstr(&id, current_master_tip);
+
+ cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/foo", &id, 1, NULL, NULL));
+ git_reference_free(ref);
+ cl_git_pass(git_reference_create(&ref, g_repo, "refs/tags/foo", &id, 1, NULL, NULL));
+ git_reference_free(ref);
+ cl_git_pass(git_reference_create(&ref, g_repo, "refs/notes/foo", &id, 1, NULL, NULL));
+ git_reference_free(ref);
+
+ assert_has_reflog(true, "refs/heads/foo");
+ assert_has_reflog(false, "refs/tags/foo");
+ assert_has_reflog(true, "refs/notes/foo");
+
+}
+
+void test_refs_reflog_reflog__write_when_explicitly_active(void)
+{
+ git_reference *ref;
+ git_oid id;
+
+ git_oid_fromstr(&id, current_master_tip);
+ git_reference_ensure_log(g_repo, "refs/tags/foo");
+
+ cl_git_pass(git_reference_create(&ref, g_repo, "refs/tags/foo", &id, 1, NULL, NULL));
+ git_reference_free(ref);
+ assert_has_reflog(true, "refs/tags/foo");
+}
+
+void test_refs_reflog_reflog__append_to_HEAD_when_changing_current_branch(void)
+{
+ size_t nlogs, nlogs_after;
+ git_reference *ref;
+ git_reflog *log;
+ git_oid id;
+
+ cl_git_pass(git_reflog_read(&log, g_repo, "HEAD"));
+ nlogs = git_reflog_entrycount(log);
+ git_reflog_free(log);
+
+ /* Move it back */
+ git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/master", &id, 1, NULL, NULL));
+ git_reference_free(ref);
+
+ cl_git_pass(git_reflog_read(&log, g_repo, "HEAD"));
+ nlogs_after = git_reflog_entrycount(log);
+ git_reflog_free(log);
+
+ cl_assert_equal_i(nlogs_after, nlogs + 1);
+}
+
+void test_refs_reflog_reflog__do_not_append_when_no_update(void)
+{
+ size_t nlogs, nlogs_after;
+ git_reference *ref, *ref2;
+ git_reflog *log;
+
+ cl_git_pass(git_reflog_read(&log, g_repo, "HEAD"));
+ nlogs = git_reflog_entrycount(log);
+ git_reflog_free(log);
+
+ cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/master"));
+ cl_git_pass(git_reference_create(&ref2, g_repo, "refs/heads/master",
+ git_reference_target(ref), 1, NULL, NULL));
+
+ git_reference_free(ref);
+ git_reference_free(ref2);
+
+ cl_git_pass(git_reflog_read(&log, g_repo, "HEAD"));
+ nlogs_after = git_reflog_entrycount(log);
+ git_reflog_free(log);
+
+ cl_assert_equal_i(nlogs_after, nlogs);
+}
+
+static void assert_no_reflog_update(void)
+{
+ size_t nlogs, nlogs_after;
+ size_t nlogs_master, nlogs_master_after;
+ git_reference *ref;
+ git_reflog *log;
+ git_oid id;
+
+ cl_git_pass(git_reflog_read(&log, g_repo, "HEAD"));
+ nlogs = git_reflog_entrycount(log);
+ git_reflog_free(log);
+
+ cl_git_pass(git_reflog_read(&log, g_repo, "refs/heads/master"));
+ nlogs_master = git_reflog_entrycount(log);
+ git_reflog_free(log);
+
+ /* Move it back */
+ git_oid_fromstr(&id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ cl_git_pass(git_reference_create(&ref, g_repo, "refs/heads/master", &id, 1, NULL, NULL));
+ git_reference_free(ref);
+
+ cl_git_pass(git_reflog_read(&log, g_repo, "HEAD"));
+ nlogs_after = git_reflog_entrycount(log);
+ git_reflog_free(log);
+
+ cl_assert_equal_i(nlogs_after, nlogs);
+
+ cl_git_pass(git_reflog_read(&log, g_repo, "refs/heads/master"));
+ nlogs_master_after = git_reflog_entrycount(log);
+ git_reflog_free(log);
+
+ cl_assert_equal_i(nlogs_after, nlogs);
+ cl_assert_equal_i(nlogs_master_after, nlogs_master);
+
+}
+
+void test_refs_reflog_reflog__logallrefupdates_bare_set_false(void)
+{
+ git_config *config;
+
+ cl_git_pass(git_repository_config(&config, g_repo));
+ cl_git_pass(git_config_set_bool(config, "core.logallrefupdates", false));
+ git_config_free(config);
+
+ assert_no_reflog_update();
+}
+
+void test_refs_reflog_reflog__logallrefupdates_bare_unset(void)
+{
+ git_config *config;
+
+ cl_git_pass(git_repository_config(&config, g_repo));
+ cl_git_pass(git_config_delete_entry(config, "core.logallrefupdates"));
+ git_config_free(config);
+
+ assert_no_reflog_update();
+}
+
+void test_refs_reflog_reflog__logallrefupdates_nonbare_set_false(void)
+{
+ git_config *config;
+
+ cl_git_sandbox_cleanup();
+ g_repo = cl_git_sandbox_init("testrepo");
+
+
+ cl_git_pass(git_repository_config(&config, g_repo));
+ cl_git_pass(git_config_set_bool(config, "core.logallrefupdates", false));
+ git_config_free(config);
+
+ assert_no_reflog_update();
+}
diff --git a/tests/refs/rename.c b/tests/refs/rename.c
new file mode 100644
index 000000000..88f0afd9c
--- /dev/null
+++ b/tests/refs/rename.c
@@ -0,0 +1,390 @@
+#include "clar_libgit2.h"
+
+#include "fileops.h"
+#include "git2/reflog.h"
+#include "reflog.h"
+#include "refs.h"
+#include "ref_helpers.h"
+
+static const char *loose_tag_ref_name = "refs/tags/e90810b";
+static const char *packed_head_name = "refs/heads/packed";
+static const char *packed_test_head_name = "refs/heads/packed-test";
+static const char *ref_one_name = "refs/heads/one/branch";
+static const char *ref_one_name_new = "refs/heads/two/branch";
+static const char *ref_two_name = "refs/heads/two";
+static const char *ref_master_name = "refs/heads/master";
+static const char *ref_two_name_new = "refs/heads/two/two";
+
+static git_repository *g_repo;
+
+
+
+void test_refs_rename__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo");
+}
+
+void test_refs_rename__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+
+
+void test_refs_rename__loose(void)
+{
+ // rename a loose reference
+ git_reference *looked_up_ref, *new_ref, *another_looked_up_ref;
+ git_buf temp_path = GIT_BUF_INIT;
+ const char *new_name = "refs/tags/Nemo/knows/refs.kung-fu";
+
+ /* Ensure the ref doesn't exist on the file system */
+ cl_git_pass(git_buf_joinpath(&temp_path, git_repository_path(g_repo), new_name));
+ cl_assert(!git_path_exists(temp_path.ptr));
+
+ /* Retrieval of the reference to rename */
+ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, loose_tag_ref_name));
+
+ /* ... which is indeed loose */
+ cl_assert(reference_is_packed(looked_up_ref) == 0);
+
+ /* Now that the reference is renamed... */
+ cl_git_pass(git_reference_rename(&new_ref, looked_up_ref, new_name, 0, NULL, NULL));
+ cl_assert_equal_s(new_ref->name, new_name);
+ git_reference_free(looked_up_ref);
+
+ /* ...It can't be looked-up with the old name... */
+ cl_git_fail(git_reference_lookup(&another_looked_up_ref, g_repo, loose_tag_ref_name));
+
+ /* ...but the new name works ok... */
+ cl_git_pass(git_reference_lookup(&another_looked_up_ref, g_repo, new_name));
+ cl_assert_equal_s(new_ref->name, new_name);
+
+ /* .. the new ref is loose... */
+ cl_assert(reference_is_packed(another_looked_up_ref) == 0);
+ cl_assert(reference_is_packed(new_ref) == 0);
+
+ /* ...and the ref can be found in the file system */
+ cl_git_pass(git_buf_joinpath(&temp_path, git_repository_path(g_repo), new_name));
+ cl_assert(git_path_exists(temp_path.ptr));
+
+ git_reference_free(new_ref);
+ git_reference_free(another_looked_up_ref);
+ git_buf_free(&temp_path);
+}
+
+void test_refs_rename__packed(void)
+{
+ // rename a packed reference (should make it loose)
+ git_reference *looked_up_ref, *new_ref, *another_looked_up_ref;
+ git_buf temp_path = GIT_BUF_INIT;
+ const char *brand_new_name = "refs/heads/brand_new_name";
+
+ /* Ensure the ref doesn't exist on the file system */
+ cl_git_pass(git_buf_joinpath(&temp_path, git_repository_path(g_repo), packed_head_name));
+ cl_assert(!git_path_exists(temp_path.ptr));
+
+ /* The reference can however be looked-up... */
+ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name));
+
+ /* .. and it's packed */
+ cl_assert(reference_is_packed(looked_up_ref) != 0);
+
+ /* Now that the reference is renamed... */
+ cl_git_pass(git_reference_rename(&new_ref, looked_up_ref, brand_new_name, 0, NULL, NULL));
+ cl_assert_equal_s(new_ref->name, brand_new_name);
+ git_reference_free(looked_up_ref);
+
+ /* ...It can't be looked-up with the old name... */
+ cl_git_fail(git_reference_lookup(&another_looked_up_ref, g_repo, packed_head_name));
+
+ /* ...but the new name works ok... */
+ cl_git_pass(git_reference_lookup(&another_looked_up_ref, g_repo, brand_new_name));
+ cl_assert_equal_s(another_looked_up_ref->name, brand_new_name);
+
+ /* .. the ref is no longer packed... */
+ cl_assert(reference_is_packed(another_looked_up_ref) == 0);
+ cl_assert(reference_is_packed(new_ref) == 0);
+
+ /* ...and the ref now happily lives in the file system */
+ cl_git_pass(git_buf_joinpath(&temp_path, git_repository_path(g_repo), brand_new_name));
+ cl_assert(git_path_exists(temp_path.ptr));
+
+ git_reference_free(new_ref);
+ git_reference_free(another_looked_up_ref);
+ git_buf_free(&temp_path);
+}
+
+void test_refs_rename__packed_doesnt_pack_others(void)
+{
+ // renaming a packed reference does not pack another reference which happens to be in both loose and pack state
+ git_reference *looked_up_ref, *another_looked_up_ref, *renamed_ref;
+ git_buf temp_path = GIT_BUF_INIT;
+ const char *brand_new_name = "refs/heads/brand_new_name";
+
+ /* Ensure the other reference exists on the file system */
+ cl_git_pass(git_buf_joinpath(&temp_path, git_repository_path(g_repo), packed_test_head_name));
+ cl_assert(git_path_exists(temp_path.ptr));
+
+ /* Lookup the other reference */
+ cl_git_pass(git_reference_lookup(&another_looked_up_ref, g_repo, packed_test_head_name));
+
+ /* Ensure it's loose */
+ cl_assert(reference_is_packed(another_looked_up_ref) == 0);
+ git_reference_free(another_looked_up_ref);
+
+ /* Lookup the reference to rename */
+ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name));
+
+ /* Ensure it's packed */
+ cl_assert(reference_is_packed(looked_up_ref) != 0);
+
+ /* Now that the reference is renamed... */
+ cl_git_pass(git_reference_rename(&renamed_ref, looked_up_ref, brand_new_name, 0, NULL, NULL));
+ git_reference_free(looked_up_ref);
+
+ /* Lookup the other reference */
+ cl_git_pass(git_reference_lookup(&another_looked_up_ref, g_repo, packed_test_head_name));
+
+ /* Ensure it's loose */
+ cl_assert(reference_is_packed(another_looked_up_ref) == 0);
+
+ /* Ensure the other ref still exists on the file system */
+ cl_assert(git_path_exists(temp_path.ptr));
+
+ git_reference_free(renamed_ref);
+ git_reference_free(another_looked_up_ref);
+ git_buf_free(&temp_path);
+}
+
+void test_refs_rename__name_collision(void)
+{
+ // can not rename a reference with the name of an existing reference
+ git_reference *looked_up_ref, *renamed_ref;
+
+ /* An existing reference... */
+ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name));
+
+ /* Can not be renamed to the name of another existing reference. */
+ cl_git_fail(git_reference_rename(&renamed_ref, looked_up_ref, packed_test_head_name, 0, NULL, NULL));
+ git_reference_free(looked_up_ref);
+
+ /* Failure to rename it hasn't corrupted its state */
+ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name));
+ cl_assert_equal_s(looked_up_ref->name, packed_head_name);
+
+ git_reference_free(looked_up_ref);
+}
+
+void test_refs_rename__invalid_name(void)
+{
+ // can not rename a reference with an invalid name
+ git_reference *looked_up_ref, *renamed_ref;
+
+ /* An existing oid reference... */
+ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_test_head_name));
+
+ /* Can not be renamed with an invalid name. */
+ cl_assert_equal_i(
+ GIT_EINVALIDSPEC,
+ git_reference_rename(&renamed_ref, looked_up_ref, "Hello! I'm a very invalid name.", 0, NULL, NULL));
+
+ /* Can not be renamed outside of the refs hierarchy
+ * unless it's ALL_CAPS_AND_UNDERSCORES.
+ */
+ cl_assert_equal_i(GIT_EINVALIDSPEC, git_reference_rename(&renamed_ref, looked_up_ref, "i-will-sudo-you", 0, NULL, NULL));
+
+ /* Failure to rename it hasn't corrupted its state */
+ git_reference_free(looked_up_ref);
+ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_test_head_name));
+ cl_assert_equal_s(looked_up_ref->name, packed_test_head_name);
+
+ git_reference_free(looked_up_ref);
+}
+
+void test_refs_rename__force_loose_packed(void)
+{
+ // can force-rename a packed reference with the name of an existing loose and packed reference
+ git_reference *looked_up_ref, *renamed_ref;
+ git_oid oid;
+
+ /* An existing reference... */
+ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name));
+ git_oid_cpy(&oid, git_reference_target(looked_up_ref));
+
+ /* Can be force-renamed to the name of another existing reference. */
+ cl_git_pass(git_reference_rename(&renamed_ref, looked_up_ref, packed_test_head_name, 1, NULL, NULL));
+ git_reference_free(looked_up_ref);
+ git_reference_free(renamed_ref);
+
+ /* Check we actually renamed it */
+ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_test_head_name));
+ cl_assert_equal_s(looked_up_ref->name, packed_test_head_name);
+ cl_assert(!git_oid_cmp(&oid, git_reference_target(looked_up_ref)));
+ git_reference_free(looked_up_ref);
+
+ /* And that the previous one doesn't exist any longer */
+ cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name));
+}
+
+void test_refs_rename__force_loose(void)
+{
+ // can force-rename a loose reference with the name of an existing loose reference
+ git_reference *looked_up_ref, *renamed_ref;
+ git_oid oid;
+
+ /* An existing reference... */
+ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, "refs/heads/br2"));
+ git_oid_cpy(&oid, git_reference_target(looked_up_ref));
+
+ /* Can be force-renamed to the name of another existing reference. */
+ cl_git_pass(git_reference_rename(&renamed_ref, looked_up_ref, "refs/heads/test", 1, NULL, NULL));
+ git_reference_free(looked_up_ref);
+ git_reference_free(renamed_ref);
+
+ /* Check we actually renamed it */
+ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, "refs/heads/test"));
+ cl_assert_equal_s(looked_up_ref->name, "refs/heads/test");
+ cl_assert(!git_oid_cmp(&oid, git_reference_target(looked_up_ref)));
+ git_reference_free(looked_up_ref);
+
+ /* And that the previous one doesn't exist any longer */
+ cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, "refs/heads/br2"));
+
+ git_reference_free(looked_up_ref);
+}
+
+
+void test_refs_rename__overwrite(void)
+{
+ // can not overwrite name of existing reference
+ git_reference *ref, *ref_one, *ref_one_new, *ref_two;
+ git_refdb *refdb;
+ git_oid id;
+
+ cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name));
+ cl_assert(git_reference_type(ref) & GIT_REF_OID);
+
+ git_oid_cpy(&id, git_reference_target(ref));
+
+ /* Create loose references */
+ cl_git_pass(git_reference_create(&ref_one, g_repo, ref_one_name, &id, 0, NULL, NULL));
+ cl_git_pass(git_reference_create(&ref_two, g_repo, ref_two_name, &id, 0, NULL, NULL));
+
+ /* Pack everything */
+ cl_git_pass(git_repository_refdb(&refdb, g_repo));
+ cl_git_pass(git_refdb_compress(refdb));
+
+ /* Attempt to create illegal reference */
+ cl_git_fail(git_reference_create(&ref_one_new, g_repo, ref_one_name_new, &id, 0, NULL, NULL));
+
+ /* Illegal reference couldn't be created so this is supposed to fail */
+ cl_git_fail(git_reference_lookup(&ref_one_new, g_repo, ref_one_name_new));
+
+ git_reference_free(ref);
+ git_reference_free(ref_one);
+ git_reference_free(ref_one_new);
+ git_reference_free(ref_two);
+ git_refdb_free(refdb);
+}
+
+
+void test_refs_rename__prefix(void)
+{
+ // can be renamed to a new name prefixed with the old name
+ git_reference *ref, *ref_two, *looked_up_ref, *renamed_ref;
+ git_oid id;
+
+ cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name));
+ cl_assert(git_reference_type(ref) & GIT_REF_OID);
+
+ git_oid_cpy(&id, git_reference_target(ref));
+
+ /* Create loose references */
+ cl_git_pass(git_reference_create(&ref_two, g_repo, ref_two_name, &id, 0, NULL, NULL));
+
+ /* An existing reference... */
+ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name));
+
+ /* Can be rename to a new name starting with the old name. */
+ cl_git_pass(git_reference_rename(&renamed_ref, looked_up_ref, ref_two_name_new, 0, NULL, NULL));
+ git_reference_free(looked_up_ref);
+ git_reference_free(renamed_ref);
+
+ /* Check we actually renamed it */
+ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name_new));
+ cl_assert_equal_s(looked_up_ref->name, ref_two_name_new);
+ git_reference_free(looked_up_ref);
+ cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name));
+
+ git_reference_free(ref);
+ git_reference_free(ref_two);
+ git_reference_free(looked_up_ref);
+}
+
+void test_refs_rename__move_up(void)
+{
+ // can move a reference to a upper reference hierarchy
+ git_reference *ref, *ref_two, *looked_up_ref, *renamed_ref;
+ git_oid id;
+
+ cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name));
+ cl_assert(git_reference_type(ref) & GIT_REF_OID);
+
+ git_oid_cpy(&id, git_reference_target(ref));
+
+ /* Create loose references */
+ cl_git_pass(git_reference_create(&ref_two, g_repo, ref_two_name_new, &id, 0, NULL, NULL));
+ git_reference_free(ref_two);
+
+ /* An existing reference... */
+ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name_new));
+
+ /* Can be renamed upward the reference tree. */
+ cl_git_pass(git_reference_rename(&renamed_ref, looked_up_ref, ref_two_name, 0, NULL, NULL));
+ git_reference_free(looked_up_ref);
+ git_reference_free(renamed_ref);
+
+ /* Check we actually renamed it */
+ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name));
+ cl_assert_equal_s(looked_up_ref->name, ref_two_name);
+ git_reference_free(looked_up_ref);
+
+ cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name_new));
+ git_reference_free(ref);
+ git_reference_free(looked_up_ref);
+}
+
+void test_refs_rename__propagate_eexists(void)
+{
+ git_reference *ref, *new_ref;
+
+ cl_git_pass(git_reference_lookup(&ref, g_repo, packed_head_name));
+
+ cl_assert_equal_i(GIT_EEXISTS, git_reference_rename(&new_ref, ref, packed_test_head_name, 0, NULL, NULL));
+
+ git_reference_free(ref);
+}
+
+void test_refs_rename__writes_to_reflog(void)
+{
+ git_reference *ref, *new_ref;
+ git_reflog *log;
+ const git_reflog_entry *entry;
+ git_signature *sig;
+
+ cl_git_pass(git_signature_now(&sig, "me", "foo@example.com"));
+
+ cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name));
+ cl_git_pass(git_reference_rename(&new_ref, ref, ref_one_name_new, false,
+ sig, "message"));
+ cl_git_pass(git_reflog_read(&log, g_repo, git_reference_name(new_ref)));
+ entry = git_reflog_entry_byindex(log, 0);
+ cl_assert_equal_s("message", git_reflog_entry_message(entry));
+ cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email);
+
+ git_reflog_free(log);
+ git_reference_free(ref);
+ git_reference_free(new_ref);
+ git_signature_free(sig);
+}
diff --git a/tests/refs/revparse.c b/tests/refs/revparse.c
new file mode 100644
index 000000000..a540f389d
--- /dev/null
+++ b/tests/refs/revparse.c
@@ -0,0 +1,814 @@
+#include "clar_libgit2.h"
+
+#include "git2/revparse.h"
+#include "buffer.h"
+#include "refs.h"
+#include "path.h"
+
+static git_repository *g_repo;
+static git_object *g_obj;
+
+/* Helpers */
+static void test_object_and_ref_inrepo(
+ const char *spec,
+ const char *expected_oid,
+ const char *expected_refname,
+ git_repository *repo,
+ bool assert_reference_retrieval)
+{
+ char objstr[64] = {0};
+ git_object *obj = NULL;
+ git_reference *ref = NULL;
+ int error;
+
+ error = git_revparse_ext(&obj, &ref, repo, spec);
+
+ if (expected_oid != NULL) {
+ cl_assert_equal_i(0, error);
+ git_oid_fmt(objstr, git_object_id(obj));
+ cl_assert_equal_s(objstr, expected_oid);
+ } else
+ cl_assert_equal_i(GIT_ENOTFOUND, error);
+
+ if (assert_reference_retrieval) {
+ if (expected_refname == NULL)
+ cl_assert(NULL == ref);
+ else
+ cl_assert_equal_s(expected_refname, git_reference_name(ref));
+ }
+
+ git_object_free(obj);
+ git_reference_free(ref);
+}
+
+static void test_object_inrepo(const char *spec, const char *expected_oid, git_repository *repo)
+{
+ test_object_and_ref_inrepo(spec, expected_oid, NULL, repo, false);
+}
+
+static void test_id_inrepo(
+ const char *spec,
+ const char *expected_left,
+ const char *expected_right,
+ git_revparse_mode_t expected_flags,
+ git_repository *repo)
+{
+ git_revspec revspec;
+ int error = git_revparse(&revspec, repo, spec);
+
+ if (expected_left) {
+ char str[64] = {0};
+ cl_assert_equal_i(0, error);
+ git_oid_fmt(str, git_object_id(revspec.from));
+ cl_assert_equal_s(str, expected_left);
+ git_object_free(revspec.from);
+ } else {
+ cl_assert_equal_i(GIT_ENOTFOUND, error);
+ }
+
+ if (expected_right) {
+ char str[64] = {0};
+ git_oid_fmt(str, git_object_id(revspec.to));
+ cl_assert_equal_s(str, expected_right);
+ git_object_free(revspec.to);
+ }
+
+ if (expected_flags)
+ cl_assert_equal_i(expected_flags, revspec.flags);
+}
+
+static void test_object(const char *spec, const char *expected_oid)
+{
+ test_object_inrepo(spec, expected_oid, g_repo);
+}
+
+static void test_object_and_ref(const char *spec, const char *expected_oid, const char *expected_refname)
+{
+ test_object_and_ref_inrepo(spec, expected_oid, expected_refname, g_repo, true);
+}
+
+static void test_rangelike(const char *rangelike,
+ const char *expected_left,
+ const char *expected_right,
+ git_revparse_mode_t expected_revparseflags)
+{
+ char objstr[64] = {0};
+ git_revspec revspec;
+ int error;
+
+ error = git_revparse(&revspec, g_repo, rangelike);
+
+ if (expected_left != NULL) {
+ cl_assert_equal_i(0, error);
+ cl_assert_equal_i(revspec.flags, expected_revparseflags);
+ git_oid_fmt(objstr, git_object_id(revspec.from));
+ cl_assert_equal_s(objstr, expected_left);
+ git_oid_fmt(objstr, git_object_id(revspec.to));
+ cl_assert_equal_s(objstr, expected_right);
+ } else
+ cl_assert(error != 0);
+
+ git_object_free(revspec.from);
+ git_object_free(revspec.to);
+}
+
+
+static void test_id(
+ const char *spec,
+ const char *expected_left,
+ const char *expected_right,
+ git_revparse_mode_t expected_flags)
+{
+ test_id_inrepo(spec, expected_left, expected_right, expected_flags, g_repo);
+}
+
+void test_refs_revparse__initialize(void)
+{
+ cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git")));
+}
+
+void test_refs_revparse__cleanup(void)
+{
+ git_repository_free(g_repo);
+}
+
+void test_refs_revparse__nonexistant_object(void)
+{
+ test_object("this-does-not-exist", NULL);
+ test_object("this-does-not-exist^1", NULL);
+ test_object("this-does-not-exist~2", NULL);
+}
+
+static void assert_invalid_single_spec(const char *invalid_spec)
+{
+ cl_assert_equal_i(
+ GIT_EINVALIDSPEC, git_revparse_single(&g_obj, g_repo, invalid_spec));
+}
+
+void test_refs_revparse__invalid_reference_name(void)
+{
+ assert_invalid_single_spec("this doesn't make sense");
+ assert_invalid_single_spec("Inv@{id");
+ assert_invalid_single_spec("");
+}
+
+void test_refs_revparse__shas(void)
+{
+ test_object("c47800c7266a2be04c571c04d5a6614691ea99bd", "c47800c7266a2be04c571c04d5a6614691ea99bd");
+ test_object("c47800c", "c47800c7266a2be04c571c04d5a6614691ea99bd");
+}
+
+void test_refs_revparse__head(void)
+{
+ test_object("HEAD", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ test_object("HEAD^0", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ test_object("HEAD~0", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ test_object("master", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+}
+
+void test_refs_revparse__full_refs(void)
+{
+ test_object("refs/heads/master", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ test_object("refs/heads/test", "e90810b8df3e80c413d903f631643c716887138d");
+ test_object("refs/tags/test", "b25fa35b38051e4ae45d4222e795f9df2e43f1d1");
+}
+
+void test_refs_revparse__partial_refs(void)
+{
+ test_object("point_to_blob", "1385f264afb75a56a5bec74243be9b367ba4ca08");
+ test_object("packed-test", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045");
+ test_object("br2", "a4a7dce85cf63874e984719f4fdd239f5145052f");
+}
+
+void test_refs_revparse__describe_output(void)
+{
+ test_object("blah-7-gc47800c", "c47800c7266a2be04c571c04d5a6614691ea99bd");
+ test_object("not-good", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+}
+
+void test_refs_revparse__nth_parent(void)
+{
+ assert_invalid_single_spec("be3563a^-1");
+ assert_invalid_single_spec("^");
+ assert_invalid_single_spec("be3563a^{tree}^");
+ assert_invalid_single_spec("point_to_blob^{blob}^");
+ assert_invalid_single_spec("this doesn't make sense^1");
+
+ test_object("be3563a^1", "9fd738e8f7967c078dceed8190330fc8648ee56a");
+ test_object("be3563a^", "9fd738e8f7967c078dceed8190330fc8648ee56a");
+ test_object("be3563a^2", "c47800c7266a2be04c571c04d5a6614691ea99bd");
+ test_object("be3563a^1^1", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045");
+ test_object("be3563a^^", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045");
+ test_object("be3563a^2^1", "5b5b025afb0b4c913b4c338a42934a3863bf3644");
+ test_object("be3563a^0", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ test_object("be3563a^{commit}^", "9fd738e8f7967c078dceed8190330fc8648ee56a");
+
+ test_object("be3563a^42", NULL);
+}
+
+void test_refs_revparse__not_tag(void)
+{
+ test_object("point_to_blob^{}", "1385f264afb75a56a5bec74243be9b367ba4ca08");
+ test_object("wrapped_tag^{}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ test_object("master^{}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ test_object("master^{tree}^{}", "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162");
+ test_object("e90810b^{}", "e90810b8df3e80c413d903f631643c716887138d");
+ test_object("tags/e90810b^{}", "e90810b8df3e80c413d903f631643c716887138d");
+ test_object("e908^{}", "e90810b8df3e80c413d903f631643c716887138d");
+}
+
+void test_refs_revparse__to_type(void)
+{
+ assert_invalid_single_spec("wrapped_tag^{trip}");
+ test_object("point_to_blob^{commit}", NULL);
+ cl_assert_equal_i(
+ GIT_EAMBIGUOUS, git_revparse_single(&g_obj, g_repo, "wrapped_tag^{blob}"));
+
+ test_object("wrapped_tag^{commit}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ test_object("wrapped_tag^{tree}", "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162");
+ test_object("point_to_blob^{blob}", "1385f264afb75a56a5bec74243be9b367ba4ca08");
+ test_object("master^{commit}^{commit}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+}
+
+void test_refs_revparse__linear_history(void)
+{
+ assert_invalid_single_spec("~");
+ test_object("foo~bar", NULL);
+
+ assert_invalid_single_spec("master~bar");
+ assert_invalid_single_spec("master~-1");
+ assert_invalid_single_spec("master~0bar");
+ assert_invalid_single_spec("this doesn't make sense~2");
+ assert_invalid_single_spec("be3563a^{tree}~");
+ assert_invalid_single_spec("point_to_blob^{blob}~");
+
+ test_object("master~0", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ test_object("master~1", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ test_object("master~2", "9fd738e8f7967c078dceed8190330fc8648ee56a");
+ test_object("master~1~1", "9fd738e8f7967c078dceed8190330fc8648ee56a");
+ test_object("master~~", "9fd738e8f7967c078dceed8190330fc8648ee56a");
+}
+
+void test_refs_revparse__chaining(void)
+{
+ assert_invalid_single_spec("master@{0}@{0}");
+ assert_invalid_single_spec("@{u}@{-1}");
+ assert_invalid_single_spec("@{-1}@{-1}");
+ assert_invalid_single_spec("@{-3}@{0}");
+
+ test_object("master@{0}~1^1", "9fd738e8f7967c078dceed8190330fc8648ee56a");
+ test_object("@{u}@{0}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ test_object("@{-1}@{0}", "a4a7dce85cf63874e984719f4fdd239f5145052f");
+ test_object("@{-4}@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ test_object("master~1^1", "9fd738e8f7967c078dceed8190330fc8648ee56a");
+ test_object("master~1^2", "c47800c7266a2be04c571c04d5a6614691ea99bd");
+ test_object("master^1^2~1", "5b5b025afb0b4c913b4c338a42934a3863bf3644");
+ test_object("master^^2^", "5b5b025afb0b4c913b4c338a42934a3863bf3644");
+ test_object("master^1^1^1^1^1", "8496071c1b46c854b31185ea97743be6a8774479");
+ test_object("master^^1^2^1", NULL);
+}
+
+void test_refs_revparse__upstream(void)
+{
+ assert_invalid_single_spec("e90810b@{u}");
+ assert_invalid_single_spec("refs/tags/e90810b@{u}");
+ test_object("refs/heads/e90810b@{u}", NULL);
+
+ test_object("master@{upstream}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ test_object("@{u}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ test_object("master@{u}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ test_object("heads/master@{u}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ test_object("refs/heads/master@{u}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+}
+
+void test_refs_revparse__ordinal(void)
+{
+ assert_invalid_single_spec("master@{-2}");
+
+ /* TODO: make the test below actually fail
+ * cl_git_fail(git_revparse_single(&g_obj, g_repo, "master@{1a}"));
+ */
+
+ test_object("nope@{0}", NULL);
+ test_object("master@{31415}", NULL);
+ test_object("@{1000}", NULL);
+ test_object("@{2}", NULL);
+
+ test_object("@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ test_object("@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+
+ test_object("master@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ test_object("master@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ test_object("heads/master@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ test_object("refs/heads/master@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+}
+
+void test_refs_revparse__previous_head(void)
+{
+ assert_invalid_single_spec("@{-xyz}");
+ assert_invalid_single_spec("@{-0}");
+ assert_invalid_single_spec("@{-1b}");
+
+ test_object("@{-42}", NULL);
+
+ test_object("@{-2}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ test_object("@{-1}", "a4a7dce85cf63874e984719f4fdd239f5145052f");
+}
+
+static void create_fake_stash_reference_and_reflog(git_repository *repo)
+{
+ git_reference *master, *new_master;
+ git_buf log_path = GIT_BUF_INIT;
+
+ git_buf_joinpath(&log_path, git_repository_path(repo), "logs/refs/fakestash");
+
+ cl_assert_equal_i(false, git_path_isfile(git_buf_cstr(&log_path)));
+
+ cl_git_pass(git_reference_lookup(&master, repo, "refs/heads/master"));
+ cl_git_pass(git_reference_rename(&new_master, master, "refs/fakestash", 0, NULL, NULL));
+ git_reference_free(master);
+
+ cl_assert_equal_i(true, git_path_isfile(git_buf_cstr(&log_path)));
+
+ git_buf_free(&log_path);
+ git_reference_free(new_master);
+}
+
+void test_refs_revparse__reflog_of_a_ref_under_refs(void)
+{
+ git_repository *repo = cl_git_sandbox_init("testrepo.git");
+
+ test_object_inrepo("refs/fakestash", NULL, repo);
+
+ create_fake_stash_reference_and_reflog(repo);
+
+ /*
+ * $ git reflog -1 refs/fakestash
+ * a65fedf refs/fakestash@{0}: commit: checking in
+ *
+ * $ git reflog -1 refs/fakestash@{0}
+ * a65fedf refs/fakestash@{0}: commit: checking in
+ *
+ * $ git reflog -1 fakestash
+ * a65fedf fakestash@{0}: commit: checking in
+ *
+ * $ git reflog -1 fakestash@{0}
+ * a65fedf fakestash@{0}: commit: checking in
+ */
+ test_object_inrepo("refs/fakestash", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", repo);
+ test_object_inrepo("refs/fakestash@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", repo);
+ test_object_inrepo("fakestash", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", repo);
+ test_object_inrepo("fakestash@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", repo);
+
+ cl_git_sandbox_cleanup();
+}
+
+void test_refs_revparse__revwalk(void)
+{
+ test_object("master^{/not found in any commit}", NULL);
+ test_object("master^{/merge}", NULL);
+ assert_invalid_single_spec("master^{/((}");
+
+ test_object("master^{/anoth}", "5b5b025afb0b4c913b4c338a42934a3863bf3644");
+ test_object("master^{/Merge}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ test_object("br2^{/Merge}", "a4a7dce85cf63874e984719f4fdd239f5145052f");
+ test_object("master^{/fo.rth}", "9fd738e8f7967c078dceed8190330fc8648ee56a");
+}
+
+void test_refs_revparse__date(void)
+{
+ /*
+ * $ git reflog HEAD --date=iso
+ * a65fedf HEAD@{2012-04-30 08:23:41 -0900}: checkout: moving from br2 to master
+ * a4a7dce HEAD@{2012-04-30 08:23:37 -0900}: commit: checking in
+ * c47800c HEAD@{2012-04-30 08:23:28 -0900}: checkout: moving from master to br2
+ * a65fedf HEAD@{2012-04-30 08:23:23 -0900}: commit:
+ * be3563a HEAD@{2012-04-30 10:22:43 -0700}: clone: from /Users/ben/src/libgit2/tes
+ *
+ * $ git reflog HEAD --date=raw
+ * a65fedf HEAD@{1335806621 -0900}: checkout: moving from br2 to master
+ * a4a7dce HEAD@{1335806617 -0900}: commit: checking in
+ * c47800c HEAD@{1335806608 -0900}: checkout: moving from master to br2
+ * a65fedf HEAD@{1335806603 -0900}: commit:
+ * be3563a HEAD@{1335806563 -0700}: clone: from /Users/ben/src/libgit2/tests/resour
+ */
+ test_object("HEAD@{10 years ago}", NULL);
+
+ test_object("HEAD@{1 second}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ test_object("HEAD@{1 second ago}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ test_object("HEAD@{2 days ago}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+
+ /*
+ * $ git reflog master --date=iso
+ * a65fedf master@{2012-04-30 09:23:23 -0800}: commit: checking in
+ * be3563a master@{2012-04-30 09:22:43 -0800}: clone: from /Users/ben/src...
+ *
+ * $ git reflog master --date=raw
+ * a65fedf master@{1335806603 -0800}: commit: checking in
+ * be3563a master@{1335806563 -0800}: clone: from /Users/ben/src/libgit2/tests/reso
+ */
+
+
+ /*
+ * $ git reflog -1 "master@{2012-04-30 17:22:42 +0000}"
+ * warning: Log for 'master' only goes back to Mon, 30 Apr 2012 09:22:43 -0800.
+ */
+ test_object("master@{2012-04-30 17:22:42 +0000}", NULL);
+ test_object("master@{2012-04-30 09:22:42 -0800}", NULL);
+
+ /*
+ * $ git reflog -1 "master@{2012-04-30 17:22:43 +0000}"
+ * be3563a master@{Mon Apr 30 09:22:43 2012 -0800}: clone: from /Users/ben/src/libg
+ */
+ test_object("master@{2012-04-30 17:22:43 +0000}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ test_object("master@{2012-04-30 09:22:43 -0800}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+
+ /*
+ * $ git reflog -1 "master@{2012-4-30 09:23:27 -0800}"
+ * a65fedf master@{Mon Apr 30 09:23:23 2012 -0800}: commit: checking in
+ */
+ test_object("master@{2012-4-30 09:23:27 -0800}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+
+ /*
+ * $ git reflog -1 master@{2012-05-03}
+ * a65fedf master@{Mon Apr 30 09:23:23 2012 -0800}: commit: checking in
+ */
+ test_object("master@{2012-05-03}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+
+ /*
+ * $ git reflog -1 "master@{1335806603}"
+ * a65fedf
+ *
+ * $ git reflog -1 "master@{1335806602}"
+ * be3563a
+ */
+ test_object("master@{1335806603}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ test_object("master@{1335806602}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+}
+
+void test_refs_revparse__colon(void)
+{
+ assert_invalid_single_spec(":/");
+ assert_invalid_single_spec("point_to_blob:readme.txt");
+ cl_git_fail(git_revparse_single(&g_obj, g_repo, ":2:README")); /* Not implemented */
+
+ test_object(":/not found in any commit", NULL);
+ test_object("subtrees:ab/42.txt", NULL);
+ test_object("subtrees:ab/4.txt/nope", NULL);
+ test_object("subtrees:nope", NULL);
+ test_object("test/master^1:branch_file.txt", NULL);
+
+ /* From tags */
+ test_object("test:readme.txt", "0266163a49e280c4f5ed1e08facd36a2bd716bcf");
+ test_object("tags/test:readme.txt", "0266163a49e280c4f5ed1e08facd36a2bd716bcf");
+ test_object("e90810b:readme.txt", "0266163a49e280c4f5ed1e08facd36a2bd716bcf");
+ test_object("tags/e90810b:readme.txt", "0266163a49e280c4f5ed1e08facd36a2bd716bcf");
+
+ /* From commits */
+ test_object("a65f:branch_file.txt", "3697d64be941a53d4ae8f6a271e4e3fa56b022cc");
+
+ /* From trees */
+ test_object("a65f^{tree}:branch_file.txt", "3697d64be941a53d4ae8f6a271e4e3fa56b022cc");
+ test_object("944c:branch_file.txt", "3697d64be941a53d4ae8f6a271e4e3fa56b022cc");
+
+ /* Retrieving trees */
+ test_object("master:", "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162");
+ test_object("subtrees:", "ae90f12eea699729ed24555e40b9fd669da12a12");
+ test_object("subtrees:ab", "f1425cef211cc08caa31e7b545ffb232acb098c3");
+ test_object("subtrees:ab/", "f1425cef211cc08caa31e7b545ffb232acb098c3");
+
+ /* Retrieving blobs */
+ test_object("subtrees:ab/4.txt", "d6c93164c249c8000205dd4ec5cbca1b516d487f");
+ test_object("subtrees:ab/de/fgh/1.txt", "1f67fc4386b2d171e0d21be1c447e12660561f9b");
+ test_object("master:README", "a8233120f6ad708f843d861ce2b7228ec4e3dec6");
+ test_object("master:new.txt", "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd");
+ test_object(":/Merge", "a4a7dce85cf63874e984719f4fdd239f5145052f");
+ test_object(":/one", "c47800c7266a2be04c571c04d5a6614691ea99bd");
+ test_object(":/packed commit t", "41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9");
+ test_object("test/master^2:branch_file.txt", "45b983be36b73c0788dc9cbcb76cbb80fc7bb057");
+ test_object("test/master@{1}:branch_file.txt", "3697d64be941a53d4ae8f6a271e4e3fa56b022cc");
+}
+
+void test_refs_revparse__disambiguation(void)
+{
+ /*
+ * $ git show e90810b
+ * tag e90810b
+ * Tagger: Vicent Marti <tanoku@gmail.com>
+ * Date: Thu Aug 12 03:59:17 2010 +0200
+ *
+ * This is a very simple tag.
+ *
+ * commit e90810b8df3e80c413d903f631643c716887138d
+ * Author: Vicent Marti <tanoku@gmail.com>
+ * Date: Thu Aug 5 18:42:20 2010 +0200
+ *
+ * Test commit 2
+ *
+ * diff --git a/readme.txt b/readme.txt
+ * index 6336846..0266163 100644
+ * --- a/readme.txt
+ * +++ b/readme.txt
+ * @@ -1 +1,2 @@
+ * Testing a readme.txt
+ * +Now we add a single line here
+ *
+ * $ git show-ref e90810b
+ * 7b4384978d2493e851f9cca7858815fac9b10980 refs/tags/e90810b
+ *
+ */
+ test_object("e90810b", "7b4384978d2493e851f9cca7858815fac9b10980");
+
+ /*
+ * $ git show e90810
+ * commit e90810b8df3e80c413d903f631643c716887138d
+ * Author: Vicent Marti <tanoku@gmail.com>
+ * Date: Thu Aug 5 18:42:20 2010 +0200
+ *
+ * Test commit 2
+ *
+ * diff --git a/readme.txt b/readme.txt
+ * index 6336846..0266163 100644
+ * --- a/readme.txt
+ * +++ b/readme.txt
+ * @@ -1 +1,2 @@
+ * Testing a readme.txt
+ * +Now we add a single line here
+ */
+ test_object("e90810", "e90810b8df3e80c413d903f631643c716887138d");
+}
+
+void test_refs_revparse__a_too_short_objectid_returns_EAMBIGUOUS(void)
+{
+ cl_assert_equal_i(
+ GIT_EAMBIGUOUS, git_revparse_single(&g_obj, g_repo, "e90"));
+}
+
+/*
+ * $ echo "aabqhq" | git hash-object -t blob --stdin
+ * dea509d0b3cb8ee0650f6ca210bc83f4678851ba
+ *
+ * $ echo "aaazvc" | git hash-object -t blob --stdin
+ * dea509d097ce692e167dfc6a48a7a280cc5e877e
+ */
+void test_refs_revparse__a_not_precise_enough_objectid_returns_EAMBIGUOUS(void)
+{
+ git_repository *repo;
+ git_index *index;
+ git_object *obj;
+
+ repo = cl_git_sandbox_init("testrepo");
+
+ cl_git_mkfile("testrepo/one.txt", "aabqhq\n");
+ cl_git_mkfile("testrepo/two.txt", "aaazvc\n");
+
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_git_pass(git_index_add_bypath(index, "one.txt"));
+ cl_git_pass(git_index_add_bypath(index, "two.txt"));
+
+ cl_git_fail_with(git_revparse_single(&obj, repo, "dea509d0"), GIT_EAMBIGUOUS);
+
+ cl_git_pass(git_revparse_single(&obj, repo, "dea509d09"));
+
+ git_object_free(obj);
+ git_index_free(index);
+ cl_git_sandbox_cleanup();
+}
+
+void test_refs_revparse__issue_994(void)
+{
+ git_repository *repo;
+ git_reference *head, *with_at;
+ git_object *target;
+
+ repo = cl_git_sandbox_init("testrepo.git");
+
+ cl_assert_equal_i(GIT_ENOTFOUND,
+ git_revparse_single(&target, repo, "origin/bim_with_3d@11296"));
+
+ cl_assert_equal_i(GIT_ENOTFOUND,
+ git_revparse_single(&target, repo, "refs/remotes/origin/bim_with_3d@11296"));
+
+
+ cl_git_pass(git_repository_head(&head, repo));
+ cl_git_pass(git_reference_create(
+ &with_at,
+ repo,
+ "refs/remotes/origin/bim_with_3d@11296",
+ git_reference_target(head),
+ 0,
+ NULL, NULL));
+
+ cl_git_pass(git_revparse_single(&target, repo, "origin/bim_with_3d@11296"));
+ git_object_free(target);
+
+ cl_git_pass(git_revparse_single(&target, repo, "refs/remotes/origin/bim_with_3d@11296"));
+ git_object_free(target);
+
+ git_reference_free(with_at);
+ git_reference_free(head);
+ cl_git_sandbox_cleanup();
+}
+
+/**
+ * $ git rev-parse blah-7-gc47800c
+ * c47800c7266a2be04c571c04d5a6614691ea99bd
+ *
+ * $ git rev-parse HEAD~3
+ * 4a202b346bb0fb0db7eff3cffeb3c70babbd2045
+ *
+ * $ git branch blah-7-gc47800c HEAD~3
+ *
+ * $ git rev-parse blah-7-gc47800c
+ * 4a202b346bb0fb0db7eff3cffeb3c70babbd2045
+ */
+void test_refs_revparse__try_to_retrieve_branch_before_described_tag(void)
+{
+ git_repository *repo;
+ git_reference *branch;
+ git_object *target;
+ char sha[GIT_OID_HEXSZ + 1];
+
+ repo = cl_git_sandbox_init("testrepo.git");
+
+ test_object_inrepo("blah-7-gc47800c", "c47800c7266a2be04c571c04d5a6614691ea99bd", repo);
+
+ cl_git_pass(git_revparse_single(&target, repo, "HEAD~3"));
+ cl_git_pass(git_branch_create(&branch, repo, "blah-7-gc47800c", (git_commit *)target, 0, NULL, NULL));
+
+ git_oid_tostr(sha, GIT_OID_HEXSZ + 1, git_object_id(target));
+
+ test_object_inrepo("blah-7-gc47800c", sha, repo);
+
+ git_reference_free(branch);
+ git_object_free(target);
+ cl_git_sandbox_cleanup();
+}
+
+/**
+ * $ git rev-parse a65fedf39aefe402d3bb6e24df4d4f5fe4547750
+ * a65fedf39aefe402d3bb6e24df4d4f5fe4547750
+ *
+ * $ git rev-parse HEAD~3
+ * 4a202b346bb0fb0db7eff3cffeb3c70babbd2045
+ *
+ * $ git branch a65fedf39aefe402d3bb6e24df4d4f5fe4547750 HEAD~3
+ *
+ * $ git rev-parse a65fedf39aefe402d3bb6e24df4d4f5fe4547750
+ * a65fedf39aefe402d3bb6e24df4d4f5fe4547750
+ *
+ * $ git rev-parse heads/a65fedf39aefe402d3bb6e24df4d4f5fe4547750
+ * 4a202b346bb0fb0db7eff3cffeb3c70babbd2045
+ */
+void test_refs_revparse__try_to_retrieve_sha_before_branch(void)
+{
+ git_repository *repo;
+ git_reference *branch;
+ git_object *target;
+ char sha[GIT_OID_HEXSZ + 1];
+
+ repo = cl_git_sandbox_init("testrepo.git");
+
+ test_object_inrepo("a65fedf39aefe402d3bb6e24df4d4f5fe4547750", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", repo);
+
+ cl_git_pass(git_revparse_single(&target, repo, "HEAD~3"));
+ cl_git_pass(git_branch_create(&branch, repo, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", (git_commit *)target, 0, NULL, NULL));
+
+ git_oid_tostr(sha, GIT_OID_HEXSZ + 1, git_object_id(target));
+
+ test_object_inrepo("a65fedf39aefe402d3bb6e24df4d4f5fe4547750", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", repo);
+ test_object_inrepo("heads/a65fedf39aefe402d3bb6e24df4d4f5fe4547750", sha, repo);
+
+ git_reference_free(branch);
+ git_object_free(target);
+ cl_git_sandbox_cleanup();
+}
+
+/**
+ * $ git rev-parse c47800
+ * c47800c7266a2be04c571c04d5a6614691ea99bd
+ *
+ * $ git rev-parse HEAD~3
+ * 4a202b346bb0fb0db7eff3cffeb3c70babbd2045
+ *
+ * $ git branch c47800 HEAD~3
+ *
+ * $ git rev-parse c47800
+ * 4a202b346bb0fb0db7eff3cffeb3c70babbd2045
+ */
+void test_refs_revparse__try_to_retrieve_branch_before_abbrev_sha(void)
+{
+ git_repository *repo;
+ git_reference *branch;
+ git_object *target;
+ char sha[GIT_OID_HEXSZ + 1];
+
+ repo = cl_git_sandbox_init("testrepo.git");
+
+ test_object_inrepo("c47800", "c47800c7266a2be04c571c04d5a6614691ea99bd", repo);
+
+ cl_git_pass(git_revparse_single(&target, repo, "HEAD~3"));
+ cl_git_pass(git_branch_create(&branch, repo, "c47800", (git_commit *)target, 0, NULL, NULL));
+
+ git_oid_tostr(sha, GIT_OID_HEXSZ + 1, git_object_id(target));
+
+ test_object_inrepo("c47800", sha, repo);
+
+ git_reference_free(branch);
+ git_object_free(target);
+ cl_git_sandbox_cleanup();
+}
+
+
+void test_refs_revparse__range(void)
+{
+ assert_invalid_single_spec("be3563a^1..be3563a");
+
+ test_rangelike("be3563a^1..be3563a",
+ "9fd738e8f7967c078dceed8190330fc8648ee56a",
+ "be3563ae3f795b2b4353bcce3a527ad0a4f7f644",
+ GIT_REVPARSE_RANGE);
+
+ test_rangelike("be3563a^1...be3563a",
+ "9fd738e8f7967c078dceed8190330fc8648ee56a",
+ "be3563ae3f795b2b4353bcce3a527ad0a4f7f644",
+ GIT_REVPARSE_RANGE | GIT_REVPARSE_MERGE_BASE);
+
+ test_rangelike("be3563a^1.be3563a", NULL, NULL, 0);
+}
+
+void test_refs_revparse__parses_range_operator(void)
+{
+ test_id("HEAD", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", NULL, GIT_REVPARSE_SINGLE);
+ test_id("HEAD~3..HEAD",
+ "4a202b346bb0fb0db7eff3cffeb3c70babbd2045",
+ "a65fedf39aefe402d3bb6e24df4d4f5fe4547750",
+ GIT_REVPARSE_RANGE);
+
+ test_id("HEAD~3...HEAD",
+ "4a202b346bb0fb0db7eff3cffeb3c70babbd2045",
+ "a65fedf39aefe402d3bb6e24df4d4f5fe4547750",
+ GIT_REVPARSE_RANGE | GIT_REVPARSE_MERGE_BASE);
+}
+
+void test_refs_revparse__ext_retrieves_both_the_reference_and_its_target(void)
+{
+ test_object_and_ref(
+ "master@{upstream}",
+ "be3563ae3f795b2b4353bcce3a527ad0a4f7f644",
+ "refs/remotes/test/master");
+
+ test_object_and_ref(
+ "@{-1}",
+ "a4a7dce85cf63874e984719f4fdd239f5145052f",
+ "refs/heads/br2");
+}
+
+void test_refs_revparse__ext_can_expand_short_reference_names(void)
+{
+ test_object_and_ref(
+ "master",
+ "a65fedf39aefe402d3bb6e24df4d4f5fe4547750",
+ "refs/heads/master");
+
+ test_object_and_ref(
+ "HEAD",
+ "a65fedf39aefe402d3bb6e24df4d4f5fe4547750",
+ "refs/heads/master");
+
+ test_object_and_ref(
+ "tags/test",
+ "b25fa35b38051e4ae45d4222e795f9df2e43f1d1",
+ "refs/tags/test");
+}
+
+void test_refs_revparse__ext_returns_NULL_reference_when_expression_points_at_a_revision(void)
+{
+ test_object_and_ref(
+ "HEAD~3",
+ "4a202b346bb0fb0db7eff3cffeb3c70babbd2045",
+ NULL);
+
+ test_object_and_ref(
+ "HEAD~0",
+ "a65fedf39aefe402d3bb6e24df4d4f5fe4547750",
+ NULL);
+
+ test_object_and_ref(
+ "HEAD^0",
+ "a65fedf39aefe402d3bb6e24df4d4f5fe4547750",
+ NULL);
+
+ test_object_and_ref(
+ "@{-1}@{0}",
+ "a4a7dce85cf63874e984719f4fdd239f5145052f",
+ NULL);
+}
+
+void test_refs_revparse__ext_returns_NULL_reference_when_expression_points_at_a_tree_content(void)
+{
+ test_object_and_ref(
+ "tags/test:readme.txt",
+ "0266163a49e280c4f5ed1e08facd36a2bd716bcf",
+ NULL);
+}
diff --git a/tests/refs/settargetwithlog.c b/tests/refs/settargetwithlog.c
new file mode 100644
index 000000000..524ce771c
--- /dev/null
+++ b/tests/refs/settargetwithlog.c
@@ -0,0 +1,55 @@
+#include "clar_libgit2.h"
+
+#include "repository.h"
+#include "git2/reflog.h"
+#include "reflog.h"
+#include "ref_helpers.h"
+
+static const char *br2_tip = "a4a7dce85cf63874e984719f4fdd239f5145052f";
+static const char *master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750";
+static const char *br2_name = "refs/heads/br2";
+
+static git_repository *g_repo;
+
+void test_refs_settargetwithlog__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo.git");
+}
+
+void test_refs_settargetwithlog__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_refs_settargetwithlog__updating_a_direct_reference_adds_a_reflog_entry(void)
+{
+ git_reference *reference, *reference_out;
+ git_oid current_id, target_id;
+ git_signature *signature;
+ git_reflog *reflog;
+ const git_reflog_entry *entry;
+
+ const char *message = "You've been logged, mate!";
+
+ git_oid_fromstr(&current_id, br2_tip);
+ git_oid_fromstr(&target_id, master_tip);
+
+ cl_git_pass(git_reference_lookup(&reference, g_repo, br2_name));
+
+ cl_git_pass(git_signature_now(&signature, "foo", "foo@bar"));
+
+ cl_git_pass(git_reference_set_target(
+ &reference_out, reference, &target_id, signature, message));
+
+ cl_git_pass(git_reflog_read(&reflog, g_repo, br2_name));
+
+ entry = git_reflog_entry_byindex(reflog, 0);
+ cl_assert(git_oid_cmp(&current_id, &entry->oid_old) == 0);
+ cl_assert(git_oid_cmp(&target_id, &entry->oid_cur) == 0);
+ cl_assert_equal_s(message, entry->msg);
+
+ git_reflog_free(reflog);
+ git_reference_free(reference_out);
+ git_reference_free(reference);
+ git_signature_free(signature);
+}
diff --git a/tests/refs/setter.c b/tests/refs/setter.c
new file mode 100644
index 000000000..9a945db00
--- /dev/null
+++ b/tests/refs/setter.c
@@ -0,0 +1,99 @@
+#include "clar_libgit2.h"
+
+#include "repository.h"
+#include "git2/reflog.h"
+#include "reflog.h"
+#include "git2/refs.h"
+
+static const char *ref_name = "refs/heads/other";
+static const char *ref_master_name = "refs/heads/master";
+static const char *ref_test_name = "refs/heads/test";
+
+static git_repository *g_repo;
+
+void test_refs_setter__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo");
+}
+
+void test_refs_setter__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_refs_setter__update_direct(void)
+{
+ git_reference *ref, *test_ref, *new_ref;
+ git_oid id;
+
+ cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name));
+ cl_assert(git_reference_type(ref) == GIT_REF_OID);
+ git_oid_cpy(&id, git_reference_target(ref));
+ git_reference_free(ref);
+
+ cl_git_pass(git_reference_lookup(&test_ref, g_repo, ref_test_name));
+ cl_assert(git_reference_type(test_ref) == GIT_REF_OID);
+
+ cl_git_pass(git_reference_set_target(&new_ref, test_ref, &id, NULL, NULL));
+
+ git_reference_free(test_ref);
+ git_reference_free(new_ref);
+
+ cl_git_pass(git_reference_lookup(&test_ref, g_repo, ref_test_name));
+ cl_assert(git_reference_type(test_ref) == GIT_REF_OID);
+ cl_assert(git_oid_cmp(&id, git_reference_target(test_ref)) == 0);
+ git_reference_free(test_ref);
+}
+
+void test_refs_setter__update_symbolic(void)
+{
+ git_reference *head, *new_head;
+
+ cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD"));
+ cl_assert(git_reference_type(head) == GIT_REF_SYMBOLIC);
+ cl_assert(strcmp(git_reference_symbolic_target(head), ref_master_name) == 0);
+
+ cl_git_pass(git_reference_symbolic_set_target(&new_head, head, ref_test_name, NULL, NULL));
+ git_reference_free(new_head);
+ git_reference_free(head);
+
+ cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD"));
+ cl_assert(git_reference_type(head) == GIT_REF_SYMBOLIC);
+ cl_assert(strcmp(git_reference_symbolic_target(head), ref_test_name) == 0);
+ git_reference_free(head);
+}
+
+void test_refs_setter__cant_update_direct_with_symbolic(void)
+{
+ // Overwrite an existing object id reference with a symbolic one
+ git_reference *ref, *new;
+ git_oid id;
+
+ cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name));
+ cl_assert(git_reference_type(ref) == GIT_REF_OID);
+ git_oid_cpy(&id, git_reference_target(ref));
+
+ cl_git_fail(git_reference_symbolic_set_target(&new, ref, ref_name, NULL, NULL));
+
+ git_reference_free(ref);
+}
+
+void test_refs_setter__cant_update_symbolic_with_direct(void)
+{
+ // Overwrite an existing symbolic reference with an object id one
+ git_reference *ref, *new;
+ git_oid id;
+
+ cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name));
+ cl_assert(git_reference_type(ref) == GIT_REF_OID);
+ git_oid_cpy(&id, git_reference_target(ref));
+ git_reference_free(ref);
+
+ /* Create the symbolic ref */
+ cl_git_pass(git_reference_symbolic_create(&ref, g_repo, ref_name, ref_master_name, 0, NULL, NULL));
+
+ /* Can't set an OID on a direct ref */
+ cl_git_fail(git_reference_set_target(&new, ref, &id, NULL, NULL));
+
+ git_reference_free(ref);
+}
diff --git a/tests/refs/shorthand.c b/tests/refs/shorthand.c
new file mode 100644
index 000000000..f995d26ca
--- /dev/null
+++ b/tests/refs/shorthand.c
@@ -0,0 +1,27 @@
+#include "clar_libgit2.h"
+
+#include "repository.h"
+
+void assert_shorthand(git_repository *repo, const char *refname, const char *shorthand)
+{
+ git_reference *ref;
+
+ cl_git_pass(git_reference_lookup(&ref, repo, refname));
+ cl_assert_equal_s(git_reference_shorthand(ref), shorthand);
+ git_reference_free(ref);
+}
+
+void test_refs_shorthand__0(void)
+{
+ git_repository *repo;
+
+ cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
+
+
+ assert_shorthand(repo, "refs/heads/master", "master");
+ assert_shorthand(repo, "refs/tags/test", "test");
+ assert_shorthand(repo, "refs/remotes/test/master", "test/master");
+ assert_shorthand(repo, "refs/notes/fanout", "notes/fanout");
+
+ git_repository_free(repo);
+}
diff --git a/tests/refs/unicode.c b/tests/refs/unicode.c
new file mode 100644
index 000000000..471b0b8d3
--- /dev/null
+++ b/tests/refs/unicode.c
@@ -0,0 +1,56 @@
+#include "clar_libgit2.h"
+
+static git_repository *repo;
+
+void test_refs_unicode__initialize(void)
+{
+ repo = cl_git_sandbox_init("testrepo.git");
+}
+
+void test_refs_unicode__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+ repo = NULL;
+}
+
+void test_refs_unicode__create_and_lookup(void)
+{
+ git_reference *ref0, *ref1, *ref2;
+ git_repository *repo2;
+
+ const char *REFNAME = "refs/heads/" "\303\205" "ngstr" "\303\266" "m";
+ const char *master = "refs/heads/master";
+
+ /* Create the reference */
+ cl_git_pass(git_reference_lookup(&ref0, repo, master));
+ cl_git_pass(git_reference_create(
+ &ref1, repo, REFNAME, git_reference_target(ref0), 0, NULL, NULL));
+ cl_assert_equal_s(REFNAME, git_reference_name(ref1));
+ git_reference_free(ref0);
+
+ /* Lookup the reference in a different instance of the repository */
+ cl_git_pass(git_repository_open(&repo2, "testrepo.git"));
+
+ cl_git_pass(git_reference_lookup(&ref2, repo2, REFNAME));
+ cl_assert_equal_i(
+ 0, git_oid_cmp(git_reference_target(ref1), git_reference_target(ref2)));
+ cl_assert_equal_s(REFNAME, git_reference_name(ref2));
+ git_reference_free(ref2);
+
+#if GIT_USE_ICONV
+ /* Lookup reference by decomposed unicode name */
+
+#define REFNAME_DECOMPOSED "refs/heads/" "A" "\314\212" "ngstro" "\314\210" "m"
+
+ cl_git_pass(git_reference_lookup(&ref2, repo2, REFNAME_DECOMPOSED));
+ cl_assert_equal_i(
+ 0, git_oid_cmp(git_reference_target(ref1), git_reference_target(ref2)));
+ cl_assert_equal_s(REFNAME, git_reference_name(ref2));
+ git_reference_free(ref2);
+#endif
+
+ /* Cleanup */
+
+ git_reference_free(ref1);
+ git_repository_free(repo2);
+}
diff --git a/tests/refs/update.c b/tests/refs/update.c
new file mode 100644
index 000000000..873fc4ebe
--- /dev/null
+++ b/tests/refs/update.c
@@ -0,0 +1,26 @@
+#include "clar_libgit2.h"
+
+#include "refs.h"
+
+static git_repository *g_repo;
+
+void test_refs_update__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo.git");
+}
+
+void test_refs_update__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_refs_update__updating_the_target_of_a_symref_with_an_invalid_name_returns_EINVALIDSPEC(void)
+{
+ git_reference *head;
+
+ cl_git_pass(git_reference_lookup(&head, g_repo, GIT_HEAD_FILE));
+ cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head));
+ git_reference_free(head);
+
+ cl_assert_equal_i(GIT_EINVALIDSPEC, git_reference_symbolic_create(&head, g_repo, GIT_HEAD_FILE, "refs/heads/inv@{id", 1, NULL, NULL));
+}
diff --git a/tests/repo/config.c b/tests/repo/config.c
new file mode 100644
index 000000000..93dedd576
--- /dev/null
+++ b/tests/repo/config.c
@@ -0,0 +1,211 @@
+#include "clar_libgit2.h"
+#include "sysdir.h"
+#include "fileops.h"
+#include <ctype.h>
+
+static git_buf path = GIT_BUF_INIT;
+
+void test_repo_config__initialize(void)
+{
+ cl_fixture_sandbox("empty_standard_repo");
+ cl_git_pass(cl_rename(
+ "empty_standard_repo/.gitted", "empty_standard_repo/.git"));
+
+ git_buf_clear(&path);
+
+ cl_must_pass(p_mkdir("alternate", 0777));
+ cl_git_pass(git_path_prettify(&path, "alternate", NULL));
+}
+
+void test_repo_config__cleanup(void)
+{
+ cl_sandbox_set_search_path_defaults();
+
+ git_buf_free(&path);
+
+ cl_git_pass(
+ git_futils_rmdir_r("alternate", NULL, GIT_RMDIR_REMOVE_FILES));
+ cl_assert(!git_path_isdir("alternate"));
+
+ cl_fixture_cleanup("empty_standard_repo");
+
+}
+
+void test_repo_config__can_open_global_when_there_is_no_file(void)
+{
+ git_repository *repo;
+ git_config *config, *global;
+
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr));
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr));
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr));
+
+ cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
+ cl_git_pass(git_repository_config(&config, repo));
+ cl_git_pass(git_config_open_level(
+ &global, config, GIT_CONFIG_LEVEL_GLOBAL));
+
+ cl_git_pass(git_config_set_string(global, "test.set", "42"));
+
+ git_config_free(global);
+ git_config_free(config);
+ git_repository_free(repo);
+}
+
+void test_repo_config__can_open_missing_global_with_separators(void)
+{
+ git_repository *repo;
+ git_config *config, *global;
+
+ cl_git_pass(git_buf_printf(
+ &path, "%c%s", GIT_PATH_LIST_SEPARATOR, "dummy"));
+
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr));
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr));
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr));
+
+ git_buf_free(&path);
+
+ cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
+ cl_git_pass(git_repository_config(&config, repo));
+ cl_git_pass(git_config_open_level(
+ &global, config, GIT_CONFIG_LEVEL_GLOBAL));
+
+ cl_git_pass(git_config_set_string(global, "test.set", "42"));
+
+ git_config_free(global);
+ git_config_free(config);
+ git_repository_free(repo);
+}
+
+#include "repository.h"
+
+void test_repo_config__read_with_no_configs_at_all(void)
+{
+ git_repository *repo;
+ int val;
+
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr));
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr));
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr));
+
+ /* with none */
+
+ cl_must_pass(p_unlink("empty_standard_repo/.git/config"));
+ cl_assert(!git_path_isfile("empty_standard_repo/.git/config"));
+
+ cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
+ git_repository__cvar_cache_clear(repo);
+ val = -1;
+ cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV));
+ cl_assert_equal_i(GIT_ABBREV_DEFAULT, val);
+ git_repository_free(repo);
+
+ /* with no local config, just system */
+
+ cl_sandbox_set_search_path_defaults();
+
+ cl_must_pass(p_mkdir("alternate/1", 0777));
+ cl_git_pass(git_buf_joinpath(&path, path.ptr, "1"));
+ cl_git_rewritefile("alternate/1/gitconfig", "[core]\n\tabbrev = 10\n");
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr));
+
+ cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
+ git_repository__cvar_cache_clear(repo);
+ val = -1;
+ cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV));
+ cl_assert_equal_i(10, val);
+ git_repository_free(repo);
+
+ /* with just xdg + system */
+
+ cl_must_pass(p_mkdir("alternate/2", 0777));
+ path.ptr[path.size - 1] = '2';
+ cl_git_rewritefile("alternate/2/config", "[core]\n\tabbrev = 20\n");
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr));
+
+ cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
+ git_repository__cvar_cache_clear(repo);
+ val = -1;
+ cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV));
+ cl_assert_equal_i(20, val);
+ git_repository_free(repo);
+
+ /* with global + xdg + system */
+
+ cl_must_pass(p_mkdir("alternate/3", 0777));
+ path.ptr[path.size - 1] = '3';
+ cl_git_rewritefile("alternate/3/.gitconfig", "[core]\n\tabbrev = 30\n");
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr));
+
+ cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
+ git_repository__cvar_cache_clear(repo);
+ val = -1;
+ cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV));
+ cl_assert_equal_i(30, val);
+ git_repository_free(repo);
+
+ /* with all configs */
+
+ cl_git_rewritefile("empty_standard_repo/.git/config", "[core]\n\tabbrev = 40\n");
+
+ cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
+ git_repository__cvar_cache_clear(repo);
+ val = -1;
+ cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV));
+ cl_assert_equal_i(40, val);
+ git_repository_free(repo);
+
+ /* with all configs but delete the files ? */
+
+ cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
+ git_repository__cvar_cache_clear(repo);
+ val = -1;
+ cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV));
+ cl_assert_equal_i(40, val);
+
+ cl_must_pass(p_unlink("empty_standard_repo/.git/config"));
+ cl_assert(!git_path_isfile("empty_standard_repo/.git/config"));
+
+ cl_must_pass(p_unlink("alternate/1/gitconfig"));
+ cl_assert(!git_path_isfile("alternate/1/gitconfig"));
+
+ cl_must_pass(p_unlink("alternate/2/config"));
+ cl_assert(!git_path_isfile("alternate/2/config"));
+
+ cl_must_pass(p_unlink("alternate/3/.gitconfig"));
+ cl_assert(!git_path_isfile("alternate/3/.gitconfig"));
+
+ git_repository__cvar_cache_clear(repo);
+ val = -1;
+ cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV));
+ cl_assert_equal_i(40, val);
+ git_repository_free(repo);
+
+ /* reopen */
+
+ cl_assert(!git_path_isfile("empty_standard_repo/.git/config"));
+ cl_assert(!git_path_isfile("alternate/3/.gitconfig"));
+
+ cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
+ git_repository__cvar_cache_clear(repo);
+ val = -1;
+ cl_git_pass(git_repository__cvar(&val, repo, GIT_CVAR_ABBREV));
+ cl_assert_equal_i(7, val);
+ git_repository_free(repo);
+
+ cl_assert(!git_path_exists("empty_standard_repo/.git/config"));
+ cl_assert(!git_path_exists("alternate/3/.gitconfig"));
+}
diff --git a/tests/repo/discover.c b/tests/repo/discover.c
new file mode 100644
index 000000000..7904b6496
--- /dev/null
+++ b/tests/repo/discover.c
@@ -0,0 +1,143 @@
+#include "clar_libgit2.h"
+
+#include "odb.h"
+#include "fileops.h"
+#include "repository.h"
+
+#define TEMP_REPO_FOLDER "temprepo/"
+#define DISCOVER_FOLDER TEMP_REPO_FOLDER "discover.git"
+
+#define SUB_REPOSITORY_FOLDER_NAME "sub_repo"
+#define SUB_REPOSITORY_FOLDER DISCOVER_FOLDER "/" SUB_REPOSITORY_FOLDER_NAME
+#define SUB_REPOSITORY_FOLDER_SUB SUB_REPOSITORY_FOLDER "/sub"
+#define SUB_REPOSITORY_FOLDER_SUB_SUB SUB_REPOSITORY_FOLDER_SUB "/subsub"
+#define SUB_REPOSITORY_FOLDER_SUB_SUB_SUB SUB_REPOSITORY_FOLDER_SUB_SUB "/subsubsub"
+
+#define REPOSITORY_ALTERNATE_FOLDER DISCOVER_FOLDER "/alternate_sub_repo"
+#define REPOSITORY_ALTERNATE_FOLDER_SUB REPOSITORY_ALTERNATE_FOLDER "/sub"
+#define REPOSITORY_ALTERNATE_FOLDER_SUB_SUB REPOSITORY_ALTERNATE_FOLDER_SUB "/subsub"
+#define REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB REPOSITORY_ALTERNATE_FOLDER_SUB_SUB "/subsubsub"
+
+#define ALTERNATE_MALFORMED_FOLDER1 DISCOVER_FOLDER "/alternate_malformed_repo1"
+#define ALTERNATE_MALFORMED_FOLDER2 DISCOVER_FOLDER "/alternate_malformed_repo2"
+#define ALTERNATE_MALFORMED_FOLDER3 DISCOVER_FOLDER "/alternate_malformed_repo3"
+#define ALTERNATE_NOT_FOUND_FOLDER DISCOVER_FOLDER "/alternate_not_found_repo"
+
+static void ensure_repository_discover(const char *start_path,
+ const char *ceiling_dirs,
+ git_buf *expected_path)
+{
+ git_buf found_path = GIT_BUF_INIT;
+ cl_git_pass(git_repository_discover(&found_path, start_path, 0, ceiling_dirs));
+ //across_fs is always 0 as we can't automate the filesystem change tests
+ cl_assert_equal_s(found_path.ptr, expected_path->ptr);
+ git_buf_free(&found_path);
+}
+
+static void write_file(const char *path, const char *content)
+{
+ git_file file;
+ int error;
+
+ if (git_path_exists(path)) {
+ cl_git_pass(p_unlink(path));
+ }
+
+ file = git_futils_creat_withpath(path, 0777, 0666);
+ cl_assert(file >= 0);
+
+ error = p_write(file, content, strlen(content) * sizeof(char));
+ p_close(file);
+ cl_git_pass(error);
+}
+
+//no check is performed on ceiling_dirs length, so be sure it's long enough
+static void append_ceiling_dir(git_buf *ceiling_dirs, const char *path)
+{
+ git_buf pretty_path = GIT_BUF_INIT;
+ char ceiling_separator[2] = { GIT_PATH_LIST_SEPARATOR, '\0' };
+
+ cl_git_pass(git_path_prettify_dir(&pretty_path, path, NULL));
+
+ if (ceiling_dirs->size > 0)
+ git_buf_puts(ceiling_dirs, ceiling_separator);
+
+ git_buf_puts(ceiling_dirs, pretty_path.ptr);
+
+ git_buf_free(&pretty_path);
+ cl_assert(git_buf_oom(ceiling_dirs) == 0);
+}
+
+void test_repo_discover__0(void)
+{
+ // test discover
+ git_repository *repo;
+ git_buf ceiling_dirs_buf = GIT_BUF_INIT, repository_path = GIT_BUF_INIT,
+ sub_repository_path = GIT_BUF_INIT, found_path = GIT_BUF_INIT;
+ const char *ceiling_dirs;
+ const mode_t mode = 0777;
+
+ git_futils_mkdir_r(DISCOVER_FOLDER, NULL, mode);
+ append_ceiling_dir(&ceiling_dirs_buf, TEMP_REPO_FOLDER);
+ ceiling_dirs = git_buf_cstr(&ceiling_dirs_buf);
+
+ cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&repository_path, DISCOVER_FOLDER, 0, ceiling_dirs));
+
+ cl_git_pass(git_repository_init(&repo, DISCOVER_FOLDER, 1));
+ cl_git_pass(git_repository_discover(&repository_path, DISCOVER_FOLDER, 0, ceiling_dirs));
+ git_repository_free(repo);
+
+ cl_git_pass(git_repository_init(&repo, SUB_REPOSITORY_FOLDER, 0));
+ cl_git_pass(git_futils_mkdir_r(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, NULL, mode));
+ cl_git_pass(git_repository_discover(&sub_repository_path, SUB_REPOSITORY_FOLDER, 0, ceiling_dirs));
+
+ cl_git_pass(git_futils_mkdir_r(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, NULL, mode));
+ ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB, ceiling_dirs, &sub_repository_path);
+ ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB_SUB, ceiling_dirs, &sub_repository_path);
+ ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, ceiling_dirs, &sub_repository_path);
+
+ cl_git_pass(git_futils_mkdir_r(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, NULL, mode));
+ write_file(REPOSITORY_ALTERNATE_FOLDER "/" DOT_GIT, "gitdir: ../" SUB_REPOSITORY_FOLDER_NAME "/" DOT_GIT);
+ write_file(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB "/" DOT_GIT, "gitdir: ../../../" SUB_REPOSITORY_FOLDER_NAME "/" DOT_GIT);
+ write_file(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB "/" DOT_GIT, "gitdir: ../../../../");
+ ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER, ceiling_dirs, &sub_repository_path);
+ ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB, ceiling_dirs, &sub_repository_path);
+ 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_mkdir_r(ALTERNATE_MALFORMED_FOLDER1, NULL, mode));
+ write_file(ALTERNATE_MALFORMED_FOLDER1 "/" DOT_GIT, "Anything but not gitdir:");
+ cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER2, NULL, mode));
+ write_file(ALTERNATE_MALFORMED_FOLDER2 "/" DOT_GIT, "gitdir:");
+ cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER3, NULL, mode));
+ write_file(ALTERNATE_MALFORMED_FOLDER3 "/" DOT_GIT, "gitdir: \n\n\n");
+ cl_git_pass(git_futils_mkdir_r(ALTERNATE_NOT_FOUND_FOLDER, NULL, mode));
+ write_file(ALTERNATE_NOT_FOUND_FOLDER "/" DOT_GIT, "gitdir: a_repository_that_surely_does_not_exist");
+ cl_git_fail(git_repository_discover(&found_path, ALTERNATE_MALFORMED_FOLDER1, 0, ceiling_dirs));
+ cl_git_fail(git_repository_discover(&found_path, ALTERNATE_MALFORMED_FOLDER2, 0, ceiling_dirs));
+ cl_git_fail(git_repository_discover(&found_path, ALTERNATE_MALFORMED_FOLDER3, 0, ceiling_dirs));
+ cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, ALTERNATE_NOT_FOUND_FOLDER, 0, ceiling_dirs));
+
+ append_ceiling_dir(&ceiling_dirs_buf, SUB_REPOSITORY_FOLDER);
+ ceiling_dirs = git_buf_cstr(&ceiling_dirs_buf);
+
+ //this must pass as ceiling_directories cannot predent the current
+ //working directory to be checked
+ cl_git_pass(git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER, 0, ceiling_dirs));
+ cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB, 0, ceiling_dirs));
+ cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB_SUB, 0, ceiling_dirs));
+ cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, 0, ceiling_dirs));
+
+ //.gitfile redirection should not be affected by ceiling directories
+ ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER, ceiling_dirs, &sub_repository_path);
+ ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB, ceiling_dirs, &sub_repository_path);
+ 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_RMDIR_REMOVE_FILES));
+ git_repository_free(repo);
+ git_buf_free(&ceiling_dirs_buf);
+ git_buf_free(&repository_path);
+ git_buf_free(&sub_repository_path);
+}
+
diff --git a/tests/repo/getters.c b/tests/repo/getters.c
new file mode 100644
index 000000000..b8ede126c
--- /dev/null
+++ b/tests/repo/getters.c
@@ -0,0 +1,40 @@
+#include "clar_libgit2.h"
+
+void test_repo_getters__is_empty_correctly_deals_with_pristine_looking_repos(void)
+{
+ git_repository *repo;
+
+ 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__is_empty_can_detect_used_repositories(void)
+{
+ git_repository *repo;
+
+ cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
+
+ 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)
+{
+ git_odb *odb;
+ git_repository *repo;
+
+ 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.val == 2);
+
+ git_repository_free(repo);
+ cl_assert(((git_refcount *)odb)->refcount.val == 1);
+
+ git_odb_free(odb);
+}
diff --git a/tests/repo/hashfile.c b/tests/repo/hashfile.c
new file mode 100644
index 000000000..4cc9f18b4
--- /dev/null
+++ b/tests/repo/hashfile.c
@@ -0,0 +1,85 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+
+static git_repository *_repo;
+
+void test_repo_hashfile__initialize(void)
+{
+ _repo = cl_git_sandbox_init("status");
+}
+
+void test_repo_hashfile__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+ _repo = NULL;
+}
+
+void test_repo_hashfile__simple(void)
+{
+ git_oid a, b;
+ git_buf full = GIT_BUF_INIT;
+
+ /* hash with repo relative path */
+ cl_git_pass(git_odb_hashfile(&a, "status/current_file", GIT_OBJ_BLOB));
+ cl_git_pass(git_repository_hashfile(&b, _repo, "current_file", GIT_OBJ_BLOB, NULL));
+ cl_assert(git_oid_equal(&a, &b));
+
+ cl_git_pass(git_buf_joinpath(&full, git_repository_workdir(_repo), "current_file"));
+
+ /* hash with full path */
+ cl_git_pass(git_odb_hashfile(&a, full.ptr, GIT_OBJ_BLOB));
+ cl_git_pass(git_repository_hashfile(&b, _repo, full.ptr, GIT_OBJ_BLOB, NULL));
+ cl_assert(git_oid_equal(&a, &b));
+
+ /* hash with invalid type */
+ cl_git_fail(git_odb_hashfile(&a, full.ptr, GIT_OBJ_ANY));
+ cl_git_fail(git_repository_hashfile(&b, _repo, full.ptr, GIT_OBJ_OFS_DELTA, NULL));
+
+ git_buf_free(&full);
+}
+
+void test_repo_hashfile__filtered(void)
+{
+ git_oid a, b;
+
+ cl_repo_set_bool(_repo, "core.autocrlf", true);
+
+ cl_git_append2file("status/.gitattributes", "*.txt text\n*.bin binary\n\n");
+
+ /* create some sample content with CRLF in it */
+ cl_git_mkfile("status/testfile.txt", "content\r\n");
+ cl_git_mkfile("status/testfile.bin", "other\r\nstuff\r\n");
+
+ /* not equal hashes because of filtering */
+ cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJ_BLOB));
+ cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJ_BLOB, NULL));
+ cl_assert(git_oid_cmp(&a, &b));
+
+ /* equal hashes because filter is binary */
+ cl_git_pass(git_odb_hashfile(&a, "status/testfile.bin", GIT_OBJ_BLOB));
+ cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.bin", GIT_OBJ_BLOB, NULL));
+ cl_assert(git_oid_equal(&a, &b));
+
+ /* equal hashes when 'as_file' points to binary filtering */
+ cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJ_BLOB));
+ cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJ_BLOB, "foo.bin"));
+ cl_assert(git_oid_equal(&a, &b));
+
+ /* not equal hashes when 'as_file' points to text filtering */
+ cl_git_pass(git_odb_hashfile(&a, "status/testfile.bin", GIT_OBJ_BLOB));
+ cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.bin", GIT_OBJ_BLOB, "foo.txt"));
+ cl_assert(git_oid_cmp(&a, &b));
+
+ /* equal hashes when 'as_file' is empty and turns off filtering */
+ cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJ_BLOB));
+ cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJ_BLOB, ""));
+ cl_assert(git_oid_equal(&a, &b));
+
+ cl_git_pass(git_odb_hashfile(&a, "status/testfile.bin", GIT_OBJ_BLOB));
+ cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.bin", GIT_OBJ_BLOB, ""));
+ cl_assert(git_oid_equal(&a, &b));
+
+ /* some hash type failures */
+ cl_git_fail(git_odb_hashfile(&a, "status/testfile.txt", 0));
+ cl_git_fail(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJ_ANY, NULL));
+}
diff --git a/tests/repo/head.c b/tests/repo/head.c
new file mode 100644
index 000000000..79892a3ea
--- /dev/null
+++ b/tests/repo/head.c
@@ -0,0 +1,469 @@
+#include "clar_libgit2.h"
+#include "refs.h"
+#include "repo_helpers.h"
+#include "posix.h"
+
+static git_repository *repo;
+
+void test_repo_head__initialize(void)
+{
+ repo = cl_git_sandbox_init("testrepo.git");
+}
+
+void test_repo_head__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+static void check_last_reflog_entry(const char *email, const char *message)
+{
+ git_reflog *log;
+ const git_reflog_entry *entry;
+
+ cl_git_pass(git_reflog_read(&log, repo, GIT_HEAD_FILE));
+ cl_assert(git_reflog_entrycount(log) > 0);
+ entry = git_reflog_entry_byindex(log, 0);
+ if (email)
+ cl_assert_equal_s(email, git_reflog_entry_committer(entry)->email);
+ if (message)
+ cl_assert_equal_s(message, git_reflog_entry_message(entry));
+ git_reflog_free(log);
+}
+
+void test_repo_head__head_detached(void)
+{
+ git_reference *ref;
+ git_signature *sig;
+
+ cl_git_pass(git_signature_now(&sig, "Foo Bar", "foo@example.com"));
+
+ cl_assert_equal_i(false, git_repository_head_detached(repo));
+
+ cl_git_pass(git_repository_detach_head(repo, sig, "CABLE DETACHED"));
+ check_last_reflog_entry(sig->email, "CABLE DETACHED");
+ cl_assert_equal_i(true, git_repository_head_detached(repo));
+
+ /* take the repo back to it's original state */
+ cl_git_pass(git_reference_symbolic_create(&ref, repo, "HEAD", "refs/heads/master",
+ true, sig, "REATTACH"));
+ git_reference_free(ref);
+
+ check_last_reflog_entry(sig->email, "REATTACH");
+ cl_assert_equal_i(false, git_repository_head_detached(repo));
+ git_signature_free(sig);
+}
+
+void test_repo_head__unborn_head(void)
+{
+ git_reference *ref;
+
+ cl_git_pass(git_repository_head_detached(repo));
+
+ make_head_unborn(repo, NON_EXISTING_HEAD);
+
+ cl_assert(git_repository_head_unborn(repo) == 1);
+
+
+ /* take the repo back to it's original state */
+ cl_git_pass(git_reference_symbolic_create(&ref, repo, "HEAD", "refs/heads/master", 1, NULL, NULL));
+ cl_assert(git_repository_head_unborn(repo) == 0);
+
+ git_reference_free(ref);
+}
+
+void test_repo_head__set_head_Attaches_HEAD_to_un_unborn_branch_when_the_branch_doesnt_exist(void)
+{
+ git_reference *head;
+
+ cl_git_pass(git_repository_set_head(repo, "refs/heads/doesnt/exist/yet", NULL, NULL));
+
+ cl_assert_equal_i(false, git_repository_head_detached(repo));
+
+ cl_assert_equal_i(GIT_EUNBORNBRANCH, git_repository_head(&head, repo));
+}
+
+void test_repo_head__set_head_Returns_ENOTFOUND_when_the_reference_doesnt_exist(void)
+{
+ cl_assert_equal_i(GIT_ENOTFOUND, git_repository_set_head(repo, "refs/tags/doesnt/exist/yet", NULL, NULL));
+}
+
+void test_repo_head__set_head_Fails_when_the_reference_points_to_a_non_commitish(void)
+{
+ cl_git_fail(git_repository_set_head(repo, "refs/tags/point_to_blob", NULL, NULL));
+}
+
+void test_repo_head__set_head_Attaches_HEAD_when_the_reference_points_to_a_branch(void)
+{
+ git_reference *head;
+
+ cl_git_pass(git_repository_set_head(repo, "refs/heads/br2", NULL, NULL));
+
+ cl_assert_equal_i(false, git_repository_head_detached(repo));
+
+ cl_git_pass(git_repository_head(&head, repo));
+ cl_assert_equal_s("refs/heads/br2", git_reference_name(head));
+
+ git_reference_free(head);
+}
+
+static void assert_head_is_correctly_detached(void)
+{
+ git_reference *head;
+ git_object *commit;
+
+ cl_assert_equal_i(true, git_repository_head_detached(repo));
+
+ cl_git_pass(git_repository_head(&head, repo));
+
+ cl_git_pass(git_object_lookup(&commit, repo, git_reference_target(head), GIT_OBJ_COMMIT));
+
+ git_object_free(commit);
+ git_reference_free(head);
+}
+
+void test_repo_head__set_head_Detaches_HEAD_when_the_reference_doesnt_point_to_a_branch(void)
+{
+ cl_git_pass(git_repository_set_head(repo, "refs/tags/test", NULL, NULL));
+
+ cl_assert_equal_i(true, git_repository_head_detached(repo));
+
+ assert_head_is_correctly_detached();
+}
+
+void test_repo_head__set_head_detached_Return_ENOTFOUND_when_the_object_doesnt_exist(void)
+{
+ git_oid oid;
+
+ cl_git_pass(git_oid_fromstr(&oid, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
+
+ cl_assert_equal_i(GIT_ENOTFOUND, git_repository_set_head_detached(repo, &oid, NULL, NULL));
+}
+
+void test_repo_head__set_head_detached_Fails_when_the_object_isnt_a_commitish(void)
+{
+ git_object *blob;
+
+ cl_git_pass(git_revparse_single(&blob, repo, "point_to_blob"));
+
+ cl_git_fail(git_repository_set_head_detached(repo, git_object_id(blob), NULL, NULL));
+
+ git_object_free(blob);
+}
+
+void test_repo_head__set_head_detached_Detaches_HEAD_and_make_it_point_to_the_peeled_commit(void)
+{
+ git_object *tag;
+
+ cl_git_pass(git_revparse_single(&tag, repo, "tags/test"));
+ cl_assert_equal_i(GIT_OBJ_TAG, git_object_type(tag));
+
+ cl_git_pass(git_repository_set_head_detached(repo, git_object_id(tag), NULL, NULL));
+
+ assert_head_is_correctly_detached();
+
+ git_object_free(tag);
+}
+
+void test_repo_head__detach_head_Detaches_HEAD_and_make_it_point_to_the_peeled_commit(void)
+{
+ cl_assert_equal_i(false, git_repository_head_detached(repo));
+
+ cl_git_pass(git_repository_detach_head(repo, NULL, NULL));
+
+ assert_head_is_correctly_detached();
+}
+
+void test_repo_head__detach_head_Fails_if_HEAD_and_point_to_a_non_commitish(void)
+{
+ git_reference *head;
+
+ cl_git_pass(git_reference_symbolic_create(&head, repo, GIT_HEAD_FILE, "refs/tags/point_to_blob", 1, NULL, NULL));
+
+ cl_git_fail(git_repository_detach_head(repo, NULL, NULL));
+
+ git_reference_free(head);
+}
+
+void test_repo_head__detaching_an_unborn_branch_returns_GIT_EUNBORNBRANCH(void)
+{
+ make_head_unborn(repo, NON_EXISTING_HEAD);
+
+ cl_assert_equal_i(GIT_EUNBORNBRANCH, git_repository_detach_head(repo, NULL, NULL));
+}
+
+void test_repo_head__retrieving_an_unborn_branch_returns_GIT_EUNBORNBRANCH(void)
+{
+ git_reference *head;
+
+ make_head_unborn(repo, NON_EXISTING_HEAD);
+
+ cl_assert_equal_i(GIT_EUNBORNBRANCH, 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_unborn_head_is_detached(void)
+{
+ make_head_unborn(repo, NON_EXISTING_HEAD);
+
+ cl_assert_equal_i(false, git_repository_head_detached(repo));
+}
+
+static void test_reflog(git_repository *repo, size_t idx,
+ const char *old_spec, const char *new_spec,
+ const char *email, const char *message)
+{
+ git_reflog *log;
+ const git_reflog_entry *entry;
+
+ cl_git_pass(git_reflog_read(&log, repo, "HEAD"));
+ entry = git_reflog_entry_byindex(log, idx);
+
+ if (old_spec) {
+ git_object *obj;
+ cl_git_pass(git_revparse_single(&obj, repo, old_spec));
+ cl_assert_equal_i(0, git_oid_cmp(git_object_id(obj), git_reflog_entry_id_old(entry)));
+ git_object_free(obj);
+ }
+ if (new_spec) {
+ git_object *obj;
+ cl_git_pass(git_revparse_single(&obj, repo, new_spec));
+ cl_assert_equal_i(0, git_oid_cmp(git_object_id(obj), git_reflog_entry_id_new(entry)));
+ git_object_free(obj);
+ }
+
+ if (email) {
+ cl_assert_equal_s(email, git_reflog_entry_committer(entry)->email);
+ }
+ if (message) {
+ cl_assert_equal_s(message, git_reflog_entry_message(entry));
+ }
+
+ git_reflog_free(log);
+}
+
+void test_repo_head__setting_head_updates_reflog(void)
+{
+ git_object *tag;
+ git_signature *sig;
+
+ cl_git_pass(git_signature_now(&sig, "me", "foo@example.com"));
+
+ cl_git_pass(git_repository_set_head(repo, "refs/heads/haacked", sig, "message1"));
+ cl_git_pass(git_repository_set_head(repo, "refs/heads/unborn", sig, "message2"));
+ cl_git_pass(git_revparse_single(&tag, repo, "tags/test"));
+ cl_git_pass(git_repository_set_head_detached(repo, git_object_id(tag), sig, "message3"));
+ cl_git_pass(git_repository_set_head(repo, "refs/heads/haacked", sig, "message4"));
+
+ test_reflog(repo, 2, NULL, "refs/heads/haacked", "foo@example.com", "message1");
+ test_reflog(repo, 1, NULL, "tags/test^{commit}", "foo@example.com", "message3");
+ test_reflog(repo, 0, "tags/test^{commit}", "refs/heads/haacked", "foo@example.com", "message4");
+
+ git_object_free(tag);
+ git_signature_free(sig);
+}
+
+static void assert_head_reflog(git_repository *repo, size_t idx,
+ const char *old_id, const char *new_id, const char *message)
+{
+ git_reflog *log;
+ const git_reflog_entry *entry;
+ char id_str[GIT_OID_HEXSZ + 1] = {0};
+
+ cl_git_pass(git_reflog_read(&log, repo, GIT_HEAD_FILE));
+ entry = git_reflog_entry_byindex(log, idx);
+
+ git_oid_fmt(id_str, git_reflog_entry_id_old(entry));
+ cl_assert_equal_s(old_id, id_str);
+
+ git_oid_fmt(id_str, git_reflog_entry_id_new(entry));
+ cl_assert_equal_s(new_id, id_str);
+
+ cl_assert_equal_s(message, git_reflog_entry_message(entry));
+
+ git_reflog_free(log);
+}
+
+void test_repo_head__detaching_writes_reflog(void)
+{
+ git_signature *sig;
+ git_oid id;
+ const char *msg;
+
+ cl_git_pass(git_signature_now(&sig, "me", "foo@example.com"));
+
+ msg = "message1";
+ git_oid_fromstr(&id, "e90810b8df3e80c413d903f631643c716887138d");
+ cl_git_pass(git_repository_set_head_detached(repo, &id, sig, msg));
+ assert_head_reflog(repo, 0, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750",
+ "e90810b8df3e80c413d903f631643c716887138d", msg);
+
+ msg = "message2";
+ cl_git_pass(git_repository_set_head(repo, "refs/heads/haacked", sig, msg));
+ assert_head_reflog(repo, 0, "e90810b8df3e80c413d903f631643c716887138d",
+ "258f0e2a959a364e40ed6603d5d44fbb24765b10", msg);
+
+ git_signature_free(sig);
+}
+
+void test_repo_head__orphan_branch_does_not_count(void)
+{
+ git_signature *sig;
+ git_oid id;
+ const char *msg;
+
+ cl_git_pass(git_signature_now(&sig, "me", "foo@example.com"));
+
+ /* Have something known */
+ msg = "message1";
+ git_oid_fromstr(&id, "e90810b8df3e80c413d903f631643c716887138d");
+ cl_git_pass(git_repository_set_head_detached(repo, &id, sig, msg));
+ assert_head_reflog(repo, 0, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750",
+ "e90810b8df3e80c413d903f631643c716887138d", msg);
+
+ /* Switching to an orphan branch does not write tot he reflog */
+ cl_git_pass(git_repository_set_head(repo, "refs/heads/orphan", sig, "ignored message"));
+ assert_head_reflog(repo, 0, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750",
+ "e90810b8df3e80c413d903f631643c716887138d", msg);
+
+ /* And coming back, we set the source to zero */
+ msg = "message2";
+ cl_git_pass(git_repository_set_head(repo, "refs/heads/haacked", sig, msg));
+ assert_head_reflog(repo, 0, "0000000000000000000000000000000000000000",
+ "258f0e2a959a364e40ed6603d5d44fbb24765b10", msg);
+
+ git_signature_free(sig);
+}
+
+void test_repo_head__set_to_current_target(void)
+{
+ git_signature *sig;
+ const char *msg;
+ git_reflog *log;
+ size_t nentries, nentries_after;
+
+ cl_git_pass(git_reflog_read(&log, repo, GIT_HEAD_FILE));
+ nentries = git_reflog_entrycount(log);
+ git_reflog_free(log);
+
+ cl_git_pass(git_signature_now(&sig, "me", "foo@example.com"));
+
+ msg = "message 1";
+ cl_git_pass(git_repository_set_head(repo, "refs/heads/haacked", sig, msg));
+ cl_git_pass(git_repository_set_head(repo, "refs/heads/haacked", sig, msg));
+
+ cl_git_pass(git_reflog_read(&log, repo, GIT_HEAD_FILE));
+ nentries_after = git_reflog_entrycount(log);
+ git_reflog_free(log);
+
+ cl_assert_equal_i(nentries + 1, nentries_after);
+
+ git_signature_free(sig);
+
+}
+
+void test_repo_head__branch_birth(void)
+{
+ git_signature *sig;
+ git_oid id;
+ git_tree *tree;
+ git_reference *ref;
+ const char *msg;
+ git_reflog *log;
+ size_t nentries, nentries_after;
+
+ cl_git_pass(git_reflog_read(&log, repo, GIT_HEAD_FILE));
+ nentries = git_reflog_entrycount(log);
+ git_reflog_free(log);
+
+ cl_git_pass(git_signature_now(&sig, "me", "foo@example.com"));
+
+ cl_git_pass(git_repository_head(&ref, repo));
+ cl_git_pass(git_reference_peel((git_object **) &tree, ref, GIT_OBJ_TREE));
+ git_reference_free(ref);
+
+ msg = "message 1";
+ cl_git_pass(git_repository_set_head(repo, "refs/heads/orphan", sig, msg));
+
+ cl_git_pass(git_reflog_read(&log, repo, GIT_HEAD_FILE));
+ nentries_after = git_reflog_entrycount(log);
+ git_reflog_free(log);
+
+ cl_assert_equal_i(nentries, nentries_after);
+
+ msg = "message 2";
+ cl_git_pass(git_commit_create(&id, repo, "HEAD", sig, sig, NULL, msg, tree, 0, NULL));
+
+ git_tree_free(tree);
+
+ cl_git_pass(git_reflog_read(&log, repo, "refs/heads/orphan"));
+ cl_assert_equal_i(1, git_reflog_entrycount(log));
+ git_reflog_free(log);
+
+ cl_git_pass(git_reflog_read(&log, repo, GIT_HEAD_FILE));
+ nentries_after = git_reflog_entrycount(log);
+ git_reflog_free(log);
+
+ cl_assert_equal_i(nentries + 1, nentries_after);
+
+ git_signature_free(sig);
+
+}
+
+static size_t entrycount(git_repository *repo, const char *name)
+{
+ git_reflog *log;
+ size_t ret;
+
+ cl_git_pass(git_reflog_read(&log, repo, name));
+ ret = git_reflog_entrycount(log);
+ git_reflog_free(log);
+
+ return ret;
+}
+
+void test_repo_head__symref_chain(void)
+{
+ git_signature *sig;
+ git_oid id;
+ git_tree *tree;
+ git_reference *ref;
+ const char *msg;
+ size_t nentries, nentries_master;
+
+ nentries = entrycount(repo, GIT_HEAD_FILE);
+
+ cl_git_pass(git_signature_now(&sig, "me", "foo@example.com"));
+
+ cl_git_pass(git_repository_head(&ref, repo));
+ cl_git_pass(git_reference_peel((git_object **) &tree, ref, GIT_OBJ_TREE));
+ git_reference_free(ref);
+
+ nentries_master = entrycount(repo, "refs/heads/master");
+
+ msg = "message 1";
+ cl_git_pass(git_reference_symbolic_create(&ref, repo, "refs/heads/master", "refs/heads/foo", 1, sig, msg));
+ git_reference_free(ref);
+
+ cl_assert_equal_i(0, entrycount(repo, "refs/heads/foo"));
+ cl_assert_equal_i(nentries, entrycount(repo, GIT_HEAD_FILE));
+ cl_assert_equal_i(nentries_master, entrycount(repo, "refs/heads/master"));
+
+ msg = "message 2";
+ cl_git_pass(git_commit_create(&id, repo, "HEAD", sig, sig, NULL, msg, tree, 0, NULL));
+ git_tree_free(tree);
+
+ cl_assert_equal_i(1, entrycount(repo, "refs/heads/foo"));
+ cl_assert_equal_i(nentries +1, entrycount(repo, GIT_HEAD_FILE));
+ cl_assert_equal_i(nentries_master, entrycount(repo, "refs/heads/master"));
+
+ git_signature_free(sig);
+
+}
diff --git a/tests/repo/headtree.c b/tests/repo/headtree.c
new file mode 100644
index 000000000..79d88c0a7
--- /dev/null
+++ b/tests/repo/headtree.c
@@ -0,0 +1,53 @@
+#include "clar_libgit2.h"
+#include "repository.h"
+#include "repo_helpers.h"
+#include "posix.h"
+
+static git_repository *repo;
+static git_tree *tree;
+
+void test_repo_headtree__initialize(void)
+{
+ repo = cl_git_sandbox_init("testrepo.git");
+ tree = NULL;
+}
+
+void test_repo_headtree__cleanup(void)
+{
+ git_tree_free(tree);
+ cl_git_sandbox_cleanup();
+}
+
+void test_repo_headtree__can_retrieve_the_root_tree_from_a_detached_head(void)
+{
+ cl_git_pass(git_repository_detach_head(repo, NULL, NULL));
+
+ cl_git_pass(git_repository_head_tree(&tree, repo));
+
+ cl_assert(git_oid_streq(git_tree_id(tree), "az"));
+}
+
+void test_repo_headtree__can_retrieve_the_root_tree_from_a_non_detached_head(void)
+{
+ cl_assert_equal_i(false, git_repository_head_detached(repo));
+
+ cl_git_pass(git_repository_head_tree(&tree, repo));
+
+ cl_assert(git_oid_streq(git_tree_id(tree), "az"));
+}
+
+void test_repo_headtree__when_head_is_unborn_returns_EUNBORNBRANCH(void)
+{
+ make_head_unborn(repo, NON_EXISTING_HEAD);
+
+ cl_assert_equal_i(true, git_repository_head_unborn(repo));
+
+ cl_assert_equal_i(GIT_EUNBORNBRANCH, git_repository_head_tree(&tree, repo));
+}
+
+void test_repo_headtree__when_head_is_missing_returns_ENOTFOUND(void)
+{
+ delete_head(repo);
+
+ cl_assert_equal_i(GIT_ENOTFOUND, git_repository_head_tree(&tree, repo));
+}
diff --git a/tests/repo/init.c b/tests/repo/init.c
new file mode 100644
index 000000000..aea383c64
--- /dev/null
+++ b/tests/repo/init.c
@@ -0,0 +1,606 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+#include "repository.h"
+#include "config.h"
+#include "path.h"
+
+enum repo_mode {
+ STANDARD_REPOSITORY = 0,
+ BARE_REPOSITORY = 1
+};
+
+static git_repository *_repo = NULL;
+static mode_t g_umask = 0;
+
+void test_repo_init__initialize(void)
+{
+ _repo = NULL;
+
+ /* load umask if not already loaded */
+ if (!g_umask) {
+ g_umask = p_umask(022);
+ (void)p_umask(g_umask);
+ }
+}
+
+static void cleanup_repository(void *path)
+{
+ git_repository_free(_repo);
+ _repo = NULL;
+
+ cl_fixture_cleanup((const char *)path);
+}
+
+static void ensure_repository_init(
+ const char *working_directory,
+ int is_bare,
+ const char *expected_path_repository,
+ const char *expected_working_directory)
+{
+ const char *workdir;
+
+ cl_assert(!git_path_isdir(working_directory));
+
+ cl_git_pass(git_repository_init(&_repo, working_directory, is_bare));
+
+ workdir = git_repository_workdir(_repo);
+ if (workdir != NULL || expected_working_directory != NULL) {
+ cl_assert(
+ git__suffixcmp(workdir, expected_working_directory) == 0
+ );
+ }
+
+ cl_assert(
+ git__suffixcmp(git_repository_path(_repo), expected_path_repository) == 0
+ );
+
+ cl_assert(git_repository_is_bare(_repo) == is_bare);
+
+#ifdef GIT_WIN32
+ if (!is_bare) {
+ DWORD fattrs = GetFileAttributes(git_repository_path(_repo));
+ cl_assert((fattrs & FILE_ATTRIBUTE_HIDDEN) != 0);
+ }
+#endif
+
+ cl_assert(git_repository_is_empty(_repo));
+}
+
+void test_repo_init__standard_repo(void)
+{
+ cl_set_cleanup(&cleanup_repository, "testrepo");
+ ensure_repository_init("testrepo/", 0, "testrepo/.git/", "testrepo/");
+}
+
+void test_repo_init__standard_repo_noslash(void)
+{
+ cl_set_cleanup(&cleanup_repository, "testrepo");
+ ensure_repository_init("testrepo", 0, "testrepo/.git/", "testrepo/");
+}
+
+void test_repo_init__bare_repo(void)
+{
+ cl_set_cleanup(&cleanup_repository, "testrepo.git");
+ ensure_repository_init("testrepo.git/", 1, "testrepo.git/", NULL);
+}
+
+void test_repo_init__bare_repo_noslash(void)
+{
+ cl_set_cleanup(&cleanup_repository, "testrepo.git");
+ ensure_repository_init("testrepo.git", 1, "testrepo.git/", NULL);
+}
+
+void test_repo_init__bare_repo_escaping_current_workdir(void)
+{
+ git_buf path_repository = GIT_BUF_INIT;
+ git_buf path_current_workdir = GIT_BUF_INIT;
+
+ cl_git_pass(git_path_prettify_dir(&path_current_workdir, ".", NULL));
+
+ cl_git_pass(git_buf_joinpath(&path_repository, git_buf_cstr(&path_current_workdir), "a/b/c"));
+ cl_git_pass(git_futils_mkdir_r(git_buf_cstr(&path_repository), NULL, GIT_DIR_MODE));
+
+ /* Change the current working directory */
+ cl_git_pass(chdir(git_buf_cstr(&path_repository)));
+
+ /* Initialize a bare repo with a relative path escaping out of the current working directory */
+ cl_git_pass(git_repository_init(&_repo, "../d/e.git", 1));
+ cl_git_pass(git__suffixcmp(git_repository_path(_repo), "/a/b/d/e.git/"));
+
+ git_repository_free(_repo);
+
+ /* Open a bare repo with a relative path escaping out of the current working directory */
+ cl_git_pass(git_repository_open(&_repo, "../d/e.git"));
+
+ cl_git_pass(chdir(git_buf_cstr(&path_current_workdir)));
+
+ git_buf_free(&path_current_workdir);
+ git_buf_free(&path_repository);
+
+ cleanup_repository("a");
+}
+
+void test_repo_init__reinit_bare_repo(void)
+{
+ cl_set_cleanup(&cleanup_repository, "reinit.git");
+
+ /* Initialize the repository */
+ cl_git_pass(git_repository_init(&_repo, "reinit.git", 1));
+ git_repository_free(_repo);
+
+ /* Reinitialize the repository */
+ cl_git_pass(git_repository_init(&_repo, "reinit.git", 1));
+}
+
+void test_repo_init__reinit_too_recent_bare_repo(void)
+{
+ git_config *config;
+
+ /* Initialize the repository */
+ cl_git_pass(git_repository_init(&_repo, "reinit.git", 1));
+ git_repository_config(&config, _repo);
+
+ /*
+ * Hack the config of the repository to make it look like it has
+ * been created by a recenter version of git/libgit2
+ */
+ cl_git_pass(git_config_set_int32(config, "core.repositoryformatversion", 42));
+
+ git_config_free(config);
+ git_repository_free(_repo);
+
+ /* Try to reinitialize the repository */
+ cl_git_fail(git_repository_init(&_repo, "reinit.git", 1));
+
+ cl_fixture_cleanup("reinit.git");
+}
+
+void test_repo_init__additional_templates(void)
+{
+ git_buf path = GIT_BUF_INIT;
+
+ cl_set_cleanup(&cleanup_repository, "tester");
+
+ ensure_repository_init("tester", 0, "tester/.git/", "tester/");
+
+ cl_git_pass(
+ git_buf_joinpath(&path, git_repository_path(_repo), "description"));
+ cl_assert(git_path_isfile(git_buf_cstr(&path)));
+
+ cl_git_pass(
+ git_buf_joinpath(&path, git_repository_path(_repo), "info/exclude"));
+ cl_assert(git_path_isfile(git_buf_cstr(&path)));
+
+ cl_git_pass(
+ git_buf_joinpath(&path, git_repository_path(_repo), "hooks"));
+ cl_assert(git_path_isdir(git_buf_cstr(&path)));
+ /* won't confirm specific contents of hooks dir since it may vary */
+
+ git_buf_free(&path);
+}
+
+static void assert_config_entry_on_init_bytype(
+ const char *config_key, int expected_value, bool is_bare)
+{
+ git_config *config;
+ int error, current_value;
+ const char *repo_path = is_bare ?
+ "config_entry/test.bare.git" : "config_entry/test.non.bare.git";
+
+ cl_set_cleanup(&cleanup_repository, "config_entry");
+
+ cl_git_pass(git_repository_init(&_repo, repo_path, is_bare));
+
+ cl_git_pass(git_repository_config(&config, _repo));
+ error = git_config_get_bool(&current_value, config, config_key);
+ git_config_free(config);
+
+ if (expected_value >= 0) {
+ cl_assert_equal_i(0, error);
+ cl_assert_equal_i(expected_value, current_value);
+ } else {
+ cl_assert_equal_i(expected_value, error);
+ }
+}
+
+static void assert_config_entry_on_init(
+ const char *config_key, int expected_value)
+{
+ assert_config_entry_on_init_bytype(config_key, expected_value, true);
+ git_repository_free(_repo);
+
+ assert_config_entry_on_init_bytype(config_key, expected_value, false);
+}
+
+void test_repo_init__detect_filemode(void)
+{
+ assert_config_entry_on_init("core.filemode", cl_is_chmod_supported());
+}
+
+void test_repo_init__detect_ignorecase(void)
+{
+ struct stat st;
+ bool found_without_match;
+
+ cl_git_write2file("testCAPS", "whatever\n", 0, O_CREAT | O_WRONLY, 0666);
+ found_without_match = (p_stat("Testcaps", &st) == 0);
+ cl_must_pass(p_unlink("testCAPS"));
+
+ assert_config_entry_on_init(
+ "core.ignorecase", found_without_match ? true : GIT_ENOTFOUND);
+}
+
+void test_repo_init__detect_precompose_unicode_required(void)
+{
+#ifdef GIT_USE_ICONV
+ char *composed = "ḱṷṓn", *decomposed = "kÌuÌ­oÌ„Ìn";
+ struct stat st;
+ bool found_with_nfd;
+
+ cl_git_write2file(composed, "whatever\n", 0, O_CREAT | O_WRONLY, 0666);
+ found_with_nfd = (p_stat(decomposed, &st) == 0);
+ cl_must_pass(p_unlink(composed));
+
+ assert_config_entry_on_init("core.precomposeunicode", found_with_nfd);
+#else
+ assert_config_entry_on_init("core.precomposeunicode", GIT_ENOTFOUND);
+#endif
+}
+
+void test_repo_init__reinit_doesnot_overwrite_ignorecase(void)
+{
+ git_config *config;
+ int current_value;
+
+ /* Init a new repo */
+ cl_set_cleanup(&cleanup_repository, "not.overwrite.git");
+ cl_git_pass(git_repository_init(&_repo, "not.overwrite.git", 1));
+
+ /* Change the "core.ignorecase" config value to something unlikely */
+ git_repository_config(&config, _repo);
+ git_config_set_int32(config, "core.ignorecase", 42);
+ git_config_free(config);
+ git_repository_free(_repo);
+ _repo = NULL;
+
+ /* Reinit the repository */
+ cl_git_pass(git_repository_init(&_repo, "not.overwrite.git", 1));
+ git_repository_config(&config, _repo);
+
+ /* Ensure the "core.ignorecase" config value hasn't been updated */
+ cl_git_pass(git_config_get_int32(&current_value, config, "core.ignorecase"));
+ cl_assert_equal_i(42, current_value);
+
+ git_config_free(config);
+}
+
+void test_repo_init__reinit_overwrites_filemode(void)
+{
+ int expected = cl_is_chmod_supported(), current_value;
+
+ /* Init a new repo */
+ cl_set_cleanup(&cleanup_repository, "overwrite.git");
+ cl_git_pass(git_repository_init(&_repo, "overwrite.git", 1));
+
+ /* Change the "core.filemode" config value to something unlikely */
+ cl_repo_set_bool(_repo, "core.filemode", !expected);
+
+ git_repository_free(_repo);
+ _repo = NULL;
+
+ /* Reinit the repository */
+ cl_git_pass(git_repository_init(&_repo, "overwrite.git", 1));
+
+ /* Ensure the "core.filemode" config value has been reset */
+ current_value = cl_repo_get_bool(_repo, "core.filemode");
+ cl_assert_equal_i(expected, current_value);
+}
+
+void test_repo_init__sets_logAllRefUpdates_according_to_type_of_repository(void)
+{
+ assert_config_entry_on_init_bytype("core.logallrefupdates", GIT_ENOTFOUND, true);
+ git_repository_free(_repo);
+ assert_config_entry_on_init_bytype("core.logallrefupdates", true, false);
+}
+
+void test_repo_init__extended_0(void)
+{
+ git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
+
+ /* without MKDIR this should fail */
+ cl_git_fail(git_repository_init_ext(&_repo, "extended", &opts));
+
+ /* make the directory first, then it should succeed */
+ cl_git_pass(git_futils_mkdir("extended", NULL, 0775, 0));
+ cl_git_pass(git_repository_init_ext(&_repo, "extended", &opts));
+
+ cl_assert(!git__suffixcmp(git_repository_workdir(_repo), "/extended/"));
+ cl_assert(!git__suffixcmp(git_repository_path(_repo), "/extended/.git/"));
+ cl_assert(!git_repository_is_bare(_repo));
+ cl_assert(git_repository_is_empty(_repo));
+
+ cleanup_repository("extended");
+}
+
+void test_repo_init__extended_1(void)
+{
+ git_reference *ref;
+ git_remote *remote;
+ struct stat st;
+ git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
+
+ opts.flags = GIT_REPOSITORY_INIT_MKPATH |
+ GIT_REPOSITORY_INIT_NO_DOTGIT_DIR;
+ opts.mode = GIT_REPOSITORY_INIT_SHARED_GROUP;
+ opts.workdir_path = "../c_wd";
+ opts.description = "Awesomest test repository evah";
+ opts.initial_head = "development";
+ opts.origin_url = "https://github.com/libgit2/libgit2.git";
+
+ cl_git_pass(git_repository_init_ext(&_repo, "root/b/c.git", &opts));
+
+ cl_assert(!git__suffixcmp(git_repository_workdir(_repo), "/c_wd/"));
+ cl_assert(!git__suffixcmp(git_repository_path(_repo), "/c.git/"));
+ cl_assert(git_path_isfile("root/b/c_wd/.git"));
+ cl_assert(!git_repository_is_bare(_repo));
+ /* repo will not be counted as empty because we set head to "development" */
+ cl_assert(!git_repository_is_empty(_repo));
+
+ cl_git_pass(git_path_lstat(git_repository_path(_repo), &st));
+ cl_assert(S_ISDIR(st.st_mode));
+ if (cl_is_chmod_supported())
+ cl_assert((S_ISGID & st.st_mode) == S_ISGID);
+ else
+ cl_assert((S_ISGID & st.st_mode) == 0);
+
+ cl_git_pass(git_reference_lookup(&ref, _repo, "HEAD"));
+ cl_assert(git_reference_type(ref) == GIT_REF_SYMBOLIC);
+ cl_assert_equal_s("refs/heads/development", git_reference_symbolic_target(ref));
+ git_reference_free(ref);
+
+ cl_git_pass(git_remote_load(&remote, _repo, "origin"));
+ cl_assert_equal_s("origin", git_remote_name(remote));
+ cl_assert_equal_s(opts.origin_url, git_remote_url(remote));
+ git_remote_free(remote);
+
+ git_repository_free(_repo);
+ cl_fixture_cleanup("root");
+}
+
+#define CLEAR_FOR_CORE_FILEMODE(M) ((M) &= ~0177)
+
+static void assert_hooks_match(
+ const char *template_dir,
+ const char *repo_dir,
+ const char *hook_path,
+ bool core_filemode)
+{
+ git_buf expected = GIT_BUF_INIT;
+ git_buf actual = GIT_BUF_INIT;
+ struct stat expected_st, st;
+
+ cl_git_pass(git_buf_joinpath(&expected, template_dir, hook_path));
+ cl_git_pass(git_path_lstat(expected.ptr, &expected_st));
+
+ cl_git_pass(git_buf_joinpath(&actual, repo_dir, hook_path));
+ cl_git_pass(git_path_lstat(actual.ptr, &st));
+
+ cl_assert(expected_st.st_size == st.st_size);
+
+ if (GIT_MODE_TYPE(expected_st.st_mode) != GIT_FILEMODE_LINK) {
+ mode_t expected_mode =
+ GIT_MODE_TYPE(expected_st.st_mode) |
+ (GIT_PERMS_FOR_WRITE(expected_st.st_mode) & ~g_umask);
+
+ if (!core_filemode) {
+ CLEAR_FOR_CORE_FILEMODE(expected_mode);
+ CLEAR_FOR_CORE_FILEMODE(st.st_mode);
+ }
+
+ cl_assert_equal_i_fmt(expected_mode, st.st_mode, "%07o");
+ }
+
+ git_buf_free(&expected);
+ git_buf_free(&actual);
+}
+
+static void assert_mode_seems_okay(
+ const char *base, const char *path,
+ git_filemode_t expect_mode, bool expect_setgid, bool core_filemode)
+{
+ git_buf full = GIT_BUF_INIT;
+ struct stat st;
+
+ cl_git_pass(git_buf_joinpath(&full, base, path));
+ cl_git_pass(git_path_lstat(full.ptr, &st));
+ git_buf_free(&full);
+
+ if (!core_filemode) {
+ CLEAR_FOR_CORE_FILEMODE(expect_mode);
+ CLEAR_FOR_CORE_FILEMODE(st.st_mode);
+ expect_setgid = false;
+ }
+
+ if (S_ISGID != 0)
+ cl_assert_equal_b(expect_setgid, (st.st_mode & S_ISGID) != 0);
+
+ cl_assert_equal_b(
+ GIT_PERMS_IS_EXEC(expect_mode), GIT_PERMS_IS_EXEC(st.st_mode));
+
+ cl_assert_equal_i_fmt(
+ GIT_MODE_TYPE(expect_mode), GIT_MODE_TYPE(st.st_mode), "%07o");
+}
+
+void test_repo_init__extended_with_template(void)
+{
+ git_buf expected = GIT_BUF_INIT;
+ git_buf actual = GIT_BUF_INIT;
+ git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
+ int filemode;
+
+ cl_set_cleanup(&cleanup_repository, "templated.git");
+
+ opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_BARE |
+ GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE;
+ opts.template_path = cl_fixture("template");
+
+ cl_git_pass(git_repository_init_ext(&_repo, "templated.git", &opts));
+
+ cl_assert(git_repository_is_bare(_repo));
+
+ cl_assert(!git__suffixcmp(git_repository_path(_repo), "/templated.git/"));
+
+ cl_git_pass(git_futils_readbuffer(
+ &expected, cl_fixture("template/description")));
+ cl_git_pass(git_futils_readbuffer(
+ &actual, "templated.git/description"));
+
+ cl_assert_equal_s(expected.ptr, actual.ptr);
+
+ git_buf_free(&expected);
+ git_buf_free(&actual);
+
+ filemode = cl_repo_get_bool(_repo, "core.filemode");
+
+ assert_hooks_match(
+ cl_fixture("template"), git_repository_path(_repo),
+ "hooks/update.sample", filemode);
+
+ assert_hooks_match(
+ cl_fixture("template"), git_repository_path(_repo),
+ "hooks/link.sample", filemode);
+}
+
+void test_repo_init__extended_with_template_and_shared_mode(void)
+{
+ git_buf expected = GIT_BUF_INIT;
+ git_buf actual = GIT_BUF_INIT;
+ git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
+ int filemode = true;
+ const char *repo_path = NULL;
+
+ cl_set_cleanup(&cleanup_repository, "init_shared_from_tpl");
+
+ opts.flags = GIT_REPOSITORY_INIT_MKPATH |
+ GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE;
+ opts.template_path = cl_fixture("template");
+ opts.mode = GIT_REPOSITORY_INIT_SHARED_GROUP;
+
+ cl_git_pass(git_repository_init_ext(&_repo, "init_shared_from_tpl", &opts));
+
+ cl_assert(!git_repository_is_bare(_repo));
+ cl_assert(!git__suffixcmp(git_repository_path(_repo), "/init_shared_from_tpl/.git/"));
+
+ filemode = cl_repo_get_bool(_repo, "core.filemode");
+
+ cl_git_pass(git_futils_readbuffer(
+ &expected, cl_fixture("template/description")));
+ cl_git_pass(git_futils_readbuffer(
+ &actual, "init_shared_from_tpl/.git/description"));
+
+ cl_assert_equal_s(expected.ptr, actual.ptr);
+
+ git_buf_free(&expected);
+ git_buf_free(&actual);
+
+ repo_path = git_repository_path(_repo);
+ assert_mode_seems_okay(repo_path, "hooks",
+ GIT_FILEMODE_TREE | GIT_REPOSITORY_INIT_SHARED_GROUP, true, filemode);
+ assert_mode_seems_okay(repo_path, "info",
+ GIT_FILEMODE_TREE | GIT_REPOSITORY_INIT_SHARED_GROUP, true, filemode);
+ assert_mode_seems_okay(repo_path, "description",
+ GIT_FILEMODE_BLOB, false, filemode);
+
+ /* for a non-symlinked hook, it should have shared permissions now */
+ assert_hooks_match(
+ cl_fixture("template"), git_repository_path(_repo),
+ "hooks/update.sample", filemode);
+
+ /* for a symlinked hook, the permissions still should match the
+ * source link, not the GIT_REPOSITORY_INIT_SHARED_GROUP value
+ */
+ assert_hooks_match(
+ cl_fixture("template"), git_repository_path(_repo),
+ "hooks/link.sample", filemode);
+}
+
+void test_repo_init__can_reinit_an_initialized_repository(void)
+{
+ git_repository *reinit;
+
+ cl_set_cleanup(&cleanup_repository, "extended");
+
+ cl_git_pass(git_futils_mkdir("extended", NULL, 0775, 0));
+ cl_git_pass(git_repository_init(&_repo, "extended", false));
+
+ cl_git_pass(git_repository_init(&reinit, "extended", false));
+
+ cl_assert_equal_s(git_repository_path(_repo), git_repository_path(reinit));
+
+ git_repository_free(reinit);
+}
+
+void test_repo_init__init_with_initial_commit(void)
+{
+ git_index *index;
+
+ cl_set_cleanup(&cleanup_repository, "committed");
+
+ /* Initialize the repository */
+ cl_git_pass(git_repository_init(&_repo, "committed", 0));
+
+ /* Init will be automatically created when requested for a new repo */
+ cl_git_pass(git_repository_index(&index, _repo));
+
+ /* Create a file so we can commit it
+ *
+ * If you are writing code outside the test suite, you can create this
+ * file any way that you like, such as:
+ * FILE *fp = fopen("committed/file.txt", "w");
+ * fputs("some stuff\n", fp);
+ * fclose(fp);
+ * We like to use the help functions because they do error detection
+ * in a way that's easily compatible with our test suite.
+ */
+ cl_git_mkfile("committed/file.txt", "some stuff\n");
+
+ /* Add file to the index */
+ cl_git_pass(git_index_add_bypath(index, "file.txt"));
+ cl_git_pass(git_index_write(index));
+
+ /* Intentionally not using cl_repo_commit_from_index here so this code
+ * can be used as an example of how an initial commit is typically
+ * made to a repository...
+ */
+
+ /* Make sure we're ready to use git_signature_default :-) */
+ {
+ git_config *cfg, *local;
+ cl_git_pass(git_repository_config(&cfg, _repo));
+ cl_git_pass(git_config_open_level(&local, cfg, GIT_CONFIG_LEVEL_LOCAL));
+ cl_git_pass(git_config_set_string(local, "user.name", "Test User"));
+ cl_git_pass(git_config_set_string(local, "user.email", "t@example.com"));
+ git_config_free(local);
+ git_config_free(cfg);
+ }
+
+ /* Create a commit with the new contents of the index */
+ {
+ git_signature *sig;
+ git_oid tree_id, commit_id;
+ git_tree *tree;
+
+ cl_git_pass(git_signature_default(&sig, _repo));
+ cl_git_pass(git_index_write_tree(&tree_id, index));
+ cl_git_pass(git_tree_lookup(&tree, _repo, &tree_id));
+
+ cl_git_pass(git_commit_create_v(
+ &commit_id, _repo, "HEAD", sig, sig,
+ NULL, "First", tree, 0));
+
+ git_tree_free(tree);
+ git_signature_free(sig);
+ }
+
+ git_index_free(index);
+}
diff --git a/tests/repo/iterator.c b/tests/repo/iterator.c
new file mode 100644
index 000000000..fb70a9ea0
--- /dev/null
+++ b/tests/repo/iterator.c
@@ -0,0 +1,962 @@
+#include "clar_libgit2.h"
+#include "iterator.h"
+#include "repository.h"
+#include "fileops.h"
+#include <stdarg.h>
+
+static git_repository *g_repo;
+
+void test_repo_iterator__initialize(void)
+{
+}
+
+void test_repo_iterator__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+ g_repo = NULL;
+}
+
+static void expect_iterator_items(
+ git_iterator *i,
+ int expected_flat,
+ const char **expected_flat_paths,
+ int expected_total,
+ const char **expected_total_paths)
+{
+ const git_index_entry *entry;
+ int count, error;
+ int no_trees = !(git_iterator_flags(i) & GIT_ITERATOR_INCLUDE_TREES);
+ bool v = false;
+
+ if (expected_flat < 0) { v = true; expected_flat = -expected_flat; }
+ if (expected_total < 0) { v = true; expected_total = -expected_total; }
+
+ if (v) fprintf(stderr, "== %s ==\n", no_trees ? "notrees" : "trees");
+
+ count = 0;
+
+ while (!git_iterator_advance(&entry, i)) {
+ if (v) fprintf(stderr, " %s %07o\n", entry->path, (int)entry->mode);
+
+ if (no_trees)
+ cl_assert(entry->mode != GIT_FILEMODE_TREE);
+
+ if (expected_flat_paths) {
+ const char *expect_path = expected_flat_paths[count];
+ size_t expect_len = strlen(expect_path);
+
+ cl_assert_equal_s(expect_path, entry->path);
+
+ if (expect_path[expect_len - 1] == '/')
+ cl_assert_equal_i(GIT_FILEMODE_TREE, entry->mode);
+ else
+ cl_assert(entry->mode != GIT_FILEMODE_TREE);
+ }
+
+ if (++count > expected_flat)
+ break;
+ }
+
+ cl_assert_equal_i(expected_flat, count);
+
+ cl_git_pass(git_iterator_reset(i, NULL, NULL));
+
+ count = 0;
+ cl_git_pass(git_iterator_current(&entry, i));
+
+ if (v) fprintf(stderr, "-- %s --\n", no_trees ? "notrees" : "trees");
+
+ while (entry != NULL) {
+ if (v) fprintf(stderr, " %s %07o\n", entry->path, (int)entry->mode);
+
+ if (no_trees)
+ cl_assert(entry->mode != GIT_FILEMODE_TREE);
+
+ if (expected_total_paths) {
+ const char *expect_path = expected_total_paths[count];
+ size_t expect_len = strlen(expect_path);
+
+ cl_assert_equal_s(expect_path, entry->path);
+
+ if (expect_path[expect_len - 1] == '/')
+ cl_assert_equal_i(GIT_FILEMODE_TREE, entry->mode);
+ else
+ cl_assert(entry->mode != GIT_FILEMODE_TREE);
+ }
+
+ if (entry->mode == GIT_FILEMODE_TREE) {
+ error = git_iterator_advance_into(&entry, i);
+
+ /* could return NOTFOUND if directory is empty */
+ cl_assert(!error || error == GIT_ENOTFOUND);
+
+ if (error == GIT_ENOTFOUND) {
+ error = git_iterator_advance(&entry, i);
+ cl_assert(!error || error == GIT_ITEROVER);
+ }
+ } else {
+ error = git_iterator_advance(&entry, i);
+ cl_assert(!error || error == GIT_ITEROVER);
+ }
+
+ if (++count > expected_total)
+ break;
+ }
+
+ cl_assert_equal_i(expected_total, count);
+}
+
+/* Index contents (including pseudotrees):
+ *
+ * 0: a 5: F 10: k/ 16: L/
+ * 1: B 6: g 11: k/1 17: L/1
+ * 2: c 7: H 12: k/a 18: L/a
+ * 3: D 8: i 13: k/B 19: L/B
+ * 4: e 9: J 14: k/c 20: L/c
+ * 15: k/D 21: L/D
+ *
+ * 0: B 5: L/ 11: a 16: k/
+ * 1: D 6: L/1 12: c 17: k/1
+ * 2: F 7: L/B 13: e 18: k/B
+ * 3: H 8: L/D 14: g 19: k/D
+ * 4: J 9: L/a 15: i 20: k/a
+ * 10: L/c 21: k/c
+ */
+
+void test_repo_iterator__index(void)
+{
+ git_iterator *i;
+ git_index *index;
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ /* autoexpand with no tree entries for index */
+ cl_git_pass(git_iterator_for_index(&i, index, 0, NULL, NULL));
+ expect_iterator_items(i, 20, NULL, 20, NULL);
+ git_iterator_free(i);
+
+ /* auto expand with tree entries */
+ cl_git_pass(git_iterator_for_index(
+ &i, index, GIT_ITERATOR_INCLUDE_TREES, NULL, NULL));
+ expect_iterator_items(i, 22, NULL, 22, NULL);
+ git_iterator_free(i);
+
+ /* no auto expand (implies trees included) */
+ cl_git_pass(git_iterator_for_index(
+ &i, index, GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL));
+ expect_iterator_items(i, 12, NULL, 22, NULL);
+ git_iterator_free(i);
+
+ git_index_free(index);
+}
+
+void test_repo_iterator__index_icase(void)
+{
+ git_iterator *i;
+ git_index *index;
+ int caps;
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ caps = git_index_caps(index);
+
+ /* force case sensitivity */
+ cl_git_pass(git_index_set_caps(index, caps & ~GIT_INDEXCAP_IGNORE_CASE));
+
+ /* autoexpand with no tree entries over range */
+ cl_git_pass(git_iterator_for_index(&i, index, 0, "c", "k/D"));
+ expect_iterator_items(i, 7, NULL, 7, NULL);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_index(&i, index, 0, "k", "k/Z"));
+ expect_iterator_items(i, 3, NULL, 3, NULL);
+ git_iterator_free(i);
+
+ /* auto expand with tree entries */
+ cl_git_pass(git_iterator_for_index(
+ &i, index, GIT_ITERATOR_INCLUDE_TREES, "c", "k/D"));
+ expect_iterator_items(i, 8, NULL, 8, NULL);
+ git_iterator_free(i);
+ cl_git_pass(git_iterator_for_index(
+ &i, index, GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z"));
+ expect_iterator_items(i, 4, NULL, 4, NULL);
+ git_iterator_free(i);
+
+ /* no auto expand (implies trees included) */
+ cl_git_pass(git_iterator_for_index(
+ &i, index, GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D"));
+ expect_iterator_items(i, 5, NULL, 8, NULL);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_index(
+ &i, index, GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z"));
+ expect_iterator_items(i, 1, NULL, 4, NULL);
+ git_iterator_free(i);
+
+ /* force case insensitivity */
+ cl_git_pass(git_index_set_caps(index, caps | GIT_INDEXCAP_IGNORE_CASE));
+
+ /* autoexpand with no tree entries over range */
+ cl_git_pass(git_iterator_for_index(&i, index, 0, "c", "k/D"));
+ expect_iterator_items(i, 13, NULL, 13, NULL);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_index(&i, index, 0, "k", "k/Z"));
+ expect_iterator_items(i, 5, NULL, 5, NULL);
+ git_iterator_free(i);
+
+ /* auto expand with tree entries */
+ cl_git_pass(git_iterator_for_index(
+ &i, index, GIT_ITERATOR_INCLUDE_TREES, "c", "k/D"));
+ expect_iterator_items(i, 14, NULL, 14, NULL);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_index(
+ &i, index, GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z"));
+ expect_iterator_items(i, 6, NULL, 6, NULL);
+ git_iterator_free(i);
+
+ /* no auto expand (implies trees included) */
+ cl_git_pass(git_iterator_for_index(
+ &i, index, GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D"));
+ expect_iterator_items(i, 9, NULL, 14, NULL);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_index(
+ &i, index, GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z"));
+ expect_iterator_items(i, 1, NULL, 6, NULL);
+ git_iterator_free(i);
+
+ cl_git_pass(git_index_set_caps(index, caps));
+ git_index_free(index);
+}
+
+void test_repo_iterator__tree(void)
+{
+ git_iterator *i;
+ git_tree *head;
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ cl_git_pass(git_repository_head_tree(&head, g_repo));
+
+ /* auto expand with no tree entries */
+ cl_git_pass(git_iterator_for_tree(&i, head, 0, NULL, NULL));
+ expect_iterator_items(i, 20, NULL, 20, NULL);
+ git_iterator_free(i);
+
+ /* auto expand with tree entries */
+ cl_git_pass(git_iterator_for_tree(
+ &i, head, GIT_ITERATOR_INCLUDE_TREES, NULL, NULL));
+ expect_iterator_items(i, 22, NULL, 22, NULL);
+ git_iterator_free(i);
+
+ /* no auto expand (implies trees included) */
+ cl_git_pass(git_iterator_for_tree(
+ &i, head, GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL));
+ expect_iterator_items(i, 12, NULL, 22, NULL);
+ git_iterator_free(i);
+
+ git_tree_free(head);
+}
+
+void test_repo_iterator__tree_icase(void)
+{
+ git_iterator *i;
+ git_tree *head;
+ git_iterator_flag_t flag;
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ cl_git_pass(git_repository_head_tree(&head, g_repo));
+
+ flag = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ /* auto expand with no tree entries */
+ cl_git_pass(git_iterator_for_tree(&i, head, flag, "c", "k/D"));
+ expect_iterator_items(i, 7, NULL, 7, NULL);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_tree(&i, head, flag, "k", "k/Z"));
+ expect_iterator_items(i, 3, NULL, 3, NULL);
+ git_iterator_free(i);
+
+ /* auto expand with tree entries */
+ cl_git_pass(git_iterator_for_tree(
+ &i, head, flag | GIT_ITERATOR_INCLUDE_TREES, "c", "k/D"));
+ expect_iterator_items(i, 8, NULL, 8, NULL);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_tree(
+ &i, head, flag | GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z"));
+ expect_iterator_items(i, 4, NULL, 4, NULL);
+ git_iterator_free(i);
+
+ /* no auto expand (implies trees included) */
+ cl_git_pass(git_iterator_for_tree(
+ &i, head, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D"));
+ expect_iterator_items(i, 5, NULL, 8, NULL);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_tree(
+ &i, head, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z"));
+ expect_iterator_items(i, 1, NULL, 4, NULL);
+ git_iterator_free(i);
+
+ flag = GIT_ITERATOR_IGNORE_CASE;
+
+ /* auto expand with no tree entries */
+ cl_git_pass(git_iterator_for_tree(&i, head, flag, "c", "k/D"));
+ expect_iterator_items(i, 13, NULL, 13, NULL);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_tree(&i, head, flag, "k", "k/Z"));
+ expect_iterator_items(i, 5, NULL, 5, NULL);
+ git_iterator_free(i);
+
+ /* auto expand with tree entries */
+ cl_git_pass(git_iterator_for_tree(
+ &i, head, flag | GIT_ITERATOR_INCLUDE_TREES, "c", "k/D"));
+ expect_iterator_items(i, 14, NULL, 14, NULL);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_tree(
+ &i, head, flag | GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z"));
+ expect_iterator_items(i, 6, NULL, 6, NULL);
+ git_iterator_free(i);
+
+ /* no auto expand (implies trees included) */
+ cl_git_pass(git_iterator_for_tree(
+ &i, head, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D"));
+ expect_iterator_items(i, 9, NULL, 14, NULL);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_tree(
+ &i, head, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z"));
+ expect_iterator_items(i, 1, NULL, 6, NULL);
+ git_iterator_free(i);
+
+ git_tree_free(head);
+}
+
+void test_repo_iterator__tree_more(void)
+{
+ git_iterator *i;
+ git_tree *head;
+ static const char *expect_basic[] = {
+ "current_file",
+ "file_deleted",
+ "modified_file",
+ "staged_changes",
+ "staged_changes_file_deleted",
+ "staged_changes_modified_file",
+ "staged_delete_file_deleted",
+ "staged_delete_modified_file",
+ "subdir.txt",
+ "subdir/current_file",
+ "subdir/deleted_file",
+ "subdir/modified_file",
+ NULL,
+ };
+ static const char *expect_trees[] = {
+ "current_file",
+ "file_deleted",
+ "modified_file",
+ "staged_changes",
+ "staged_changes_file_deleted",
+ "staged_changes_modified_file",
+ "staged_delete_file_deleted",
+ "staged_delete_modified_file",
+ "subdir.txt",
+ "subdir/",
+ "subdir/current_file",
+ "subdir/deleted_file",
+ "subdir/modified_file",
+ NULL,
+ };
+ static const char *expect_noauto[] = {
+ "current_file",
+ "file_deleted",
+ "modified_file",
+ "staged_changes",
+ "staged_changes_file_deleted",
+ "staged_changes_modified_file",
+ "staged_delete_file_deleted",
+ "staged_delete_modified_file",
+ "subdir.txt",
+ "subdir/",
+ NULL
+ };
+
+ g_repo = cl_git_sandbox_init("status");
+
+ cl_git_pass(git_repository_head_tree(&head, g_repo));
+
+ /* auto expand with no tree entries */
+ cl_git_pass(git_iterator_for_tree(&i, head, 0, NULL, NULL));
+ expect_iterator_items(i, 12, expect_basic, 12, expect_basic);
+ git_iterator_free(i);
+
+ /* auto expand with tree entries */
+ cl_git_pass(git_iterator_for_tree(
+ &i, head, GIT_ITERATOR_INCLUDE_TREES, NULL, NULL));
+ expect_iterator_items(i, 13, expect_trees, 13, expect_trees);
+ git_iterator_free(i);
+
+ /* no auto expand (implies trees included) */
+ cl_git_pass(git_iterator_for_tree(
+ &i, head, GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL));
+ expect_iterator_items(i, 10, expect_noauto, 13, expect_trees);
+ git_iterator_free(i);
+
+ git_tree_free(head);
+}
+
+/* "b=name,t=name", blob_id, tree_id */
+static void build_test_tree(
+ git_oid *out, git_repository *repo, const char *fmt, ...)
+{
+ git_oid *id;
+ git_treebuilder *builder;
+ const char *scan = fmt, *next;
+ char type, delimiter;
+ git_filemode_t mode = GIT_FILEMODE_BLOB;
+ git_buf name = GIT_BUF_INIT;
+ va_list arglist;
+
+ cl_git_pass(git_treebuilder_create(&builder, NULL)); /* start builder */
+
+ va_start(arglist, fmt);
+ while (*scan) {
+ switch (type = *scan++) {
+ case 't': case 'T': mode = GIT_FILEMODE_TREE; break;
+ case 'b': case 'B': mode = GIT_FILEMODE_BLOB; break;
+ default:
+ cl_assert(type == 't' || type == 'T' || type == 'b' || type == 'B');
+ }
+
+ delimiter = *scan++; /* read and skip delimiter */
+ for (next = scan; *next && *next != delimiter; ++next)
+ /* seek end */;
+ cl_git_pass(git_buf_set(&name, scan, (size_t)(next - scan)));
+ for (scan = next; *scan && (*scan == delimiter || *scan == ','); ++scan)
+ /* skip delimiter and optional comma */;
+
+ id = va_arg(arglist, git_oid *);
+
+ cl_git_pass(git_treebuilder_insert(NULL, builder, name.ptr, id, mode));
+ }
+ va_end(arglist);
+
+ cl_git_pass(git_treebuilder_write(out, repo, builder));
+
+ git_treebuilder_free(builder);
+ git_buf_free(&name);
+}
+
+void test_repo_iterator__tree_case_conflicts_0(void)
+{
+ const char *blob_sha = "d44e18fb93b7107b5cd1b95d601591d77869a1b6";
+ git_tree *tree;
+ git_oid blob_id, biga_id, littlea_id, tree_id;
+ git_iterator *i;
+ const char *expect_cs[] = {
+ "A/1.file", "A/3.file", "a/2.file", "a/4.file" };
+ const char *expect_ci[] = {
+ "A/1.file", "a/2.file", "A/3.file", "a/4.file" };
+ const char *expect_cs_trees[] = {
+ "A/", "A/1.file", "A/3.file", "a/", "a/2.file", "a/4.file" };
+ const char *expect_ci_trees[] = {
+ "A/", "A/1.file", "a/2.file", "A/3.file", "a/4.file" };
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ cl_git_pass(git_oid_fromstr(&blob_id, blob_sha)); /* lookup blob */
+
+ /* create tree with: A/1.file, A/3.file, a/2.file, a/4.file */
+ build_test_tree(
+ &biga_id, g_repo, "b|1.file|,b|3.file|", &blob_id, &blob_id);
+ build_test_tree(
+ &littlea_id, g_repo, "b|2.file|,b|4.file|", &blob_id, &blob_id);
+ build_test_tree(
+ &tree_id, g_repo, "t|A|,t|a|", &biga_id, &littlea_id);
+
+ cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
+
+ cl_git_pass(git_iterator_for_tree(
+ &i, tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL));
+ expect_iterator_items(i, 4, expect_cs, 4, expect_cs);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_tree(
+ &i, tree, GIT_ITERATOR_IGNORE_CASE, NULL, NULL));
+ expect_iterator_items(i, 4, expect_ci, 4, expect_ci);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_tree(
+ &i, tree, GIT_ITERATOR_DONT_IGNORE_CASE |
+ GIT_ITERATOR_INCLUDE_TREES, NULL, NULL));
+ expect_iterator_items(i, 6, expect_cs_trees, 6, expect_cs_trees);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_tree(
+ &i, tree, GIT_ITERATOR_IGNORE_CASE |
+ GIT_ITERATOR_INCLUDE_TREES, NULL, NULL));
+ expect_iterator_items(i, 5, expect_ci_trees, 5, expect_ci_trees);
+ git_iterator_free(i);
+
+ git_tree_free(tree);
+}
+
+void test_repo_iterator__tree_case_conflicts_1(void)
+{
+ const char *blob_sha = "d44e18fb93b7107b5cd1b95d601591d77869a1b6";
+ git_tree *tree;
+ git_oid blob_id, Ab_id, biga_id, littlea_id, tree_id;
+ git_iterator *i;
+ const char *expect_cs[] = {
+ "A/a", "A/b/1", "A/c", "a/C", "a/a", "a/b" };
+ const char *expect_ci[] = {
+ "A/a", "a/b", "A/b/1", "A/c" };
+ const char *expect_cs_trees[] = {
+ "A/", "A/a", "A/b/", "A/b/1", "A/c", "a/", "a/C", "a/a", "a/b" };
+ const char *expect_ci_trees[] = {
+ "A/", "A/a", "a/b", "A/b/", "A/b/1", "A/c" };
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ cl_git_pass(git_oid_fromstr(&blob_id, blob_sha)); /* lookup blob */
+
+ /* create: A/a A/b/1 A/c a/a a/b a/C */
+ build_test_tree(&Ab_id, g_repo, "b|1|", &blob_id);
+ build_test_tree(
+ &biga_id, g_repo, "b|a|,t|b|,b|c|", &blob_id, &Ab_id, &blob_id);
+ build_test_tree(
+ &littlea_id, g_repo, "b|a|,b|b|,b|C|", &blob_id, &blob_id, &blob_id);
+ build_test_tree(
+ &tree_id, g_repo, "t|A|,t|a|", &biga_id, &littlea_id);
+
+ cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
+
+ cl_git_pass(git_iterator_for_tree(
+ &i, tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL));
+ expect_iterator_items(i, 6, expect_cs, 6, expect_cs);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_tree(
+ &i, tree, GIT_ITERATOR_IGNORE_CASE, NULL, NULL));
+ expect_iterator_items(i, 4, expect_ci, 4, expect_ci);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_tree(
+ &i, tree, GIT_ITERATOR_DONT_IGNORE_CASE |
+ GIT_ITERATOR_INCLUDE_TREES, NULL, NULL));
+ expect_iterator_items(i, 9, expect_cs_trees, 9, expect_cs_trees);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_tree(
+ &i, tree, GIT_ITERATOR_IGNORE_CASE |
+ GIT_ITERATOR_INCLUDE_TREES, NULL, NULL));
+ expect_iterator_items(i, 6, expect_ci_trees, 6, expect_ci_trees);
+ git_iterator_free(i);
+
+ git_tree_free(tree);
+}
+
+void test_repo_iterator__tree_case_conflicts_2(void)
+{
+ const char *blob_sha = "d44e18fb93b7107b5cd1b95d601591d77869a1b6";
+ git_tree *tree;
+ git_oid blob_id, d1, d2, c1, c2, b1, b2, a1, a2, tree_id;
+ git_iterator *i;
+ const char *expect_cs[] = {
+ "A/B/C/D/16", "A/B/C/D/foo", "A/B/C/d/15", "A/B/C/d/FOO",
+ "A/B/c/D/14", "A/B/c/D/foo", "A/B/c/d/13", "A/B/c/d/FOO",
+ "A/b/C/D/12", "A/b/C/D/foo", "A/b/C/d/11", "A/b/C/d/FOO",
+ "A/b/c/D/10", "A/b/c/D/foo", "A/b/c/d/09", "A/b/c/d/FOO",
+ "a/B/C/D/08", "a/B/C/D/foo", "a/B/C/d/07", "a/B/C/d/FOO",
+ "a/B/c/D/06", "a/B/c/D/foo", "a/B/c/d/05", "a/B/c/d/FOO",
+ "a/b/C/D/04", "a/b/C/D/foo", "a/b/C/d/03", "a/b/C/d/FOO",
+ "a/b/c/D/02", "a/b/c/D/foo", "a/b/c/d/01", "a/b/c/d/FOO", };
+ const char *expect_ci[] = {
+ "a/b/c/d/01", "a/b/c/D/02", "a/b/C/d/03", "a/b/C/D/04",
+ "a/B/c/d/05", "a/B/c/D/06", "a/B/C/d/07", "a/B/C/D/08",
+ "A/b/c/d/09", "A/b/c/D/10", "A/b/C/d/11", "A/b/C/D/12",
+ "A/B/c/d/13", "A/B/c/D/14", "A/B/C/d/15", "A/B/C/D/16",
+ "A/B/C/D/foo", };
+ const char *expect_ci_trees[] = {
+ "A/", "A/B/", "A/B/C/", "A/B/C/D/",
+ "a/b/c/d/01", "a/b/c/D/02", "a/b/C/d/03", "a/b/C/D/04",
+ "a/B/c/d/05", "a/B/c/D/06", "a/B/C/d/07", "a/B/C/D/08",
+ "A/b/c/d/09", "A/b/c/D/10", "A/b/C/d/11", "A/b/C/D/12",
+ "A/B/c/d/13", "A/B/c/D/14", "A/B/C/d/15", "A/B/C/D/16",
+ "A/B/C/D/foo", };
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ cl_git_pass(git_oid_fromstr(&blob_id, blob_sha)); /* lookup blob */
+
+ build_test_tree(&d1, g_repo, "b|16|,b|foo|", &blob_id, &blob_id);
+ build_test_tree(&d2, g_repo, "b|15|,b|FOO|", &blob_id, &blob_id);
+ build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2);
+ build_test_tree(&d1, g_repo, "b|14|,b|foo|", &blob_id, &blob_id);
+ build_test_tree(&d2, g_repo, "b|13|,b|FOO|", &blob_id, &blob_id);
+ build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2);
+ build_test_tree(&b1, g_repo, "t|C|,t|c|", &c1, &c2);
+
+ build_test_tree(&d1, g_repo, "b|12|,b|foo|", &blob_id, &blob_id);
+ build_test_tree(&d2, g_repo, "b|11|,b|FOO|", &blob_id, &blob_id);
+ build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2);
+ build_test_tree(&d1, g_repo, "b|10|,b|foo|", &blob_id, &blob_id);
+ build_test_tree(&d2, g_repo, "b|09|,b|FOO|", &blob_id, &blob_id);
+ build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2);
+ build_test_tree(&b2, g_repo, "t|C|,t|c|", &c1, &c2);
+
+ build_test_tree(&a1, g_repo, "t|B|,t|b|", &b1, &b2);
+
+ build_test_tree(&d1, g_repo, "b|08|,b|foo|", &blob_id, &blob_id);
+ build_test_tree(&d2, g_repo, "b|07|,b|FOO|", &blob_id, &blob_id);
+ build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2);
+ build_test_tree(&d1, g_repo, "b|06|,b|foo|", &blob_id, &blob_id);
+ build_test_tree(&d2, g_repo, "b|05|,b|FOO|", &blob_id, &blob_id);
+ build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2);
+ build_test_tree(&b1, g_repo, "t|C|,t|c|", &c1, &c2);
+
+ build_test_tree(&d1, g_repo, "b|04|,b|foo|", &blob_id, &blob_id);
+ build_test_tree(&d2, g_repo, "b|03|,b|FOO|", &blob_id, &blob_id);
+ build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2);
+ build_test_tree(&d1, g_repo, "b|02|,b|foo|", &blob_id, &blob_id);
+ build_test_tree(&d2, g_repo, "b|01|,b|FOO|", &blob_id, &blob_id);
+ build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2);
+ build_test_tree(&b2, g_repo, "t|C|,t|c|", &c1, &c2);
+
+ build_test_tree(&a2, g_repo, "t|B|,t|b|", &b1, &b2);
+
+ build_test_tree(&tree_id, g_repo, "t/A/,t/a/", &a1, &a2);
+
+ cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
+
+ cl_git_pass(git_iterator_for_tree(
+ &i, tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL));
+ expect_iterator_items(i, 32, expect_cs, 32, expect_cs);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_tree(
+ &i, tree, GIT_ITERATOR_IGNORE_CASE, NULL, NULL));
+ expect_iterator_items(i, 17, expect_ci, 17, expect_ci);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_tree(
+ &i, tree, GIT_ITERATOR_IGNORE_CASE |
+ GIT_ITERATOR_INCLUDE_TREES, NULL, NULL));
+ expect_iterator_items(i, 21, expect_ci_trees, 21, expect_ci_trees);
+ git_iterator_free(i);
+
+ git_tree_free(tree);
+}
+
+void test_repo_iterator__workdir(void)
+{
+ git_iterator *i;
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ /* auto expand with no tree entries */
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, 0, NULL, NULL));
+ expect_iterator_items(i, 20, NULL, 20, NULL);
+ git_iterator_free(i);
+
+ /* auto expand with tree entries */
+ cl_git_pass(git_iterator_for_workdir(
+ &i, g_repo, GIT_ITERATOR_INCLUDE_TREES, NULL, NULL));
+ expect_iterator_items(i, 22, NULL, 22, NULL);
+ git_iterator_free(i);
+
+ /* no auto expand (implies trees included) */
+ cl_git_pass(git_iterator_for_workdir(
+ &i, g_repo, GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL));
+ expect_iterator_items(i, 12, NULL, 22, NULL);
+ git_iterator_free(i);
+}
+
+void test_repo_iterator__workdir_icase(void)
+{
+ git_iterator *i;
+ git_iterator_flag_t flag;
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ flag = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ /* auto expand with no tree entries */
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, flag, "c", "k/D"));
+ expect_iterator_items(i, 7, NULL, 7, NULL);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, flag, "k", "k/Z"));
+ expect_iterator_items(i, 3, NULL, 3, NULL);
+ git_iterator_free(i);
+
+ /* auto expand with tree entries */
+ cl_git_pass(git_iterator_for_workdir(
+ &i, g_repo, flag | GIT_ITERATOR_INCLUDE_TREES, "c", "k/D"));
+ expect_iterator_items(i, 8, NULL, 8, NULL);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_workdir(
+ &i, g_repo, flag | GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z"));
+ expect_iterator_items(i, 4, NULL, 4, NULL);
+ git_iterator_free(i);
+
+ /* no auto expand (implies trees included) */
+ cl_git_pass(git_iterator_for_workdir(
+ &i, g_repo, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D"));
+ expect_iterator_items(i, 5, NULL, 8, NULL);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_workdir(
+ &i, g_repo, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z"));
+ expect_iterator_items(i, 1, NULL, 4, NULL);
+ git_iterator_free(i);
+
+ flag = GIT_ITERATOR_IGNORE_CASE;
+
+ /* auto expand with no tree entries */
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, flag, "c", "k/D"));
+ expect_iterator_items(i, 13, NULL, 13, NULL);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, flag, "k", "k/Z"));
+ expect_iterator_items(i, 5, NULL, 5, NULL);
+ git_iterator_free(i);
+
+ /* auto expand with tree entries */
+ cl_git_pass(git_iterator_for_workdir(
+ &i, g_repo, flag | GIT_ITERATOR_INCLUDE_TREES, "c", "k/D"));
+ expect_iterator_items(i, 14, NULL, 14, NULL);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_workdir(
+ &i, g_repo, flag | GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z"));
+ expect_iterator_items(i, 6, NULL, 6, NULL);
+ git_iterator_free(i);
+
+ /* no auto expand (implies trees included) */
+ cl_git_pass(git_iterator_for_workdir(
+ &i, g_repo, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D"));
+ expect_iterator_items(i, 9, NULL, 14, NULL);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_workdir(
+ &i, g_repo, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z"));
+ expect_iterator_items(i, 1, NULL, 6, NULL);
+ git_iterator_free(i);
+}
+
+static void build_workdir_tree(const char *root, int dirs, int subs)
+{
+ int i, j;
+ char buf[64], sub[64];
+
+ for (i = 0; i < dirs; ++i) {
+ if (i % 2 == 0) {
+ p_snprintf(buf, sizeof(buf), "%s/dir%02d", root, i);
+ cl_git_pass(git_futils_mkdir(buf, NULL, 0775, GIT_MKDIR_PATH));
+
+ p_snprintf(buf, sizeof(buf), "%s/dir%02d/file", root, i);
+ cl_git_mkfile(buf, buf);
+ buf[strlen(buf) - 5] = '\0';
+ } else {
+ p_snprintf(buf, sizeof(buf), "%s/DIR%02d", root, i);
+ cl_git_pass(git_futils_mkdir(buf, NULL, 0775, GIT_MKDIR_PATH));
+ }
+
+ for (j = 0; j < subs; ++j) {
+ switch (j % 4) {
+ case 0: p_snprintf(sub, sizeof(sub), "%s/sub%02d", buf, j); break;
+ case 1: p_snprintf(sub, sizeof(sub), "%s/sUB%02d", buf, j); break;
+ case 2: p_snprintf(sub, sizeof(sub), "%s/Sub%02d", buf, j); break;
+ case 3: p_snprintf(sub, sizeof(sub), "%s/SUB%02d", buf, j); break;
+ }
+ cl_git_pass(git_futils_mkdir(sub, NULL, 0775, GIT_MKDIR_PATH));
+
+ if (j % 2 == 0) {
+ size_t sublen = strlen(sub);
+ memcpy(&sub[sublen], "/file", sizeof("/file"));
+ cl_git_mkfile(sub, sub);
+ sub[sublen] = '\0';
+ }
+ }
+ }
+}
+
+void test_repo_iterator__workdir_depth(void)
+{
+ git_iterator *iter;
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ build_workdir_tree("icase", 10, 10);
+ build_workdir_tree("icase/DIR01/sUB01", 50, 0);
+ build_workdir_tree("icase/dir02/sUB01", 50, 0);
+
+ /* auto expand with no tree entries */
+ cl_git_pass(git_iterator_for_workdir(&iter, g_repo, 0, NULL, NULL));
+ expect_iterator_items(iter, 125, NULL, 125, NULL);
+ git_iterator_free(iter);
+
+ /* auto expand with tree entries (empty dirs silently skipped) */
+ cl_git_pass(git_iterator_for_workdir(
+ &iter, g_repo, GIT_ITERATOR_INCLUDE_TREES, NULL, NULL));
+ expect_iterator_items(iter, 337, NULL, 337, NULL);
+ git_iterator_free(iter);
+}
+
+void test_repo_iterator__fs(void)
+{
+ git_iterator *i;
+ static const char *expect_base[] = {
+ "DIR01/Sub02/file",
+ "DIR01/sub00/file",
+ "current_file",
+ "dir00/Sub02/file",
+ "dir00/file",
+ "dir00/sub00/file",
+ "modified_file",
+ "new_file",
+ NULL,
+ };
+ static const char *expect_trees[] = {
+ "DIR01/",
+ "DIR01/SUB03/",
+ "DIR01/Sub02/",
+ "DIR01/Sub02/file",
+ "DIR01/sUB01/",
+ "DIR01/sub00/",
+ "DIR01/sub00/file",
+ "current_file",
+ "dir00/",
+ "dir00/SUB03/",
+ "dir00/Sub02/",
+ "dir00/Sub02/file",
+ "dir00/file",
+ "dir00/sUB01/",
+ "dir00/sub00/",
+ "dir00/sub00/file",
+ "modified_file",
+ "new_file",
+ NULL,
+ };
+ static const char *expect_noauto[] = {
+ "DIR01/",
+ "current_file",
+ "dir00/",
+ "modified_file",
+ "new_file",
+ NULL,
+ };
+
+ g_repo = cl_git_sandbox_init("status");
+
+ build_workdir_tree("status/subdir", 2, 4);
+
+ cl_git_pass(git_iterator_for_filesystem(
+ &i, "status/subdir", 0, NULL, NULL));
+ expect_iterator_items(i, 8, expect_base, 8, expect_base);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_filesystem(
+ &i, "status/subdir", GIT_ITERATOR_INCLUDE_TREES, NULL, NULL));
+ expect_iterator_items(i, 18, expect_trees, 18, expect_trees);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_filesystem(
+ &i, "status/subdir", GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL));
+ expect_iterator_items(i, 5, expect_noauto, 18, expect_trees);
+ git_iterator_free(i);
+
+ git__tsort((void **)expect_base, 8, (git__tsort_cmp)git__strcasecmp);
+ git__tsort((void **)expect_trees, 18, (git__tsort_cmp)git__strcasecmp);
+ git__tsort((void **)expect_noauto, 5, (git__tsort_cmp)git__strcasecmp);
+
+ cl_git_pass(git_iterator_for_filesystem(
+ &i, "status/subdir", GIT_ITERATOR_IGNORE_CASE, NULL, NULL));
+ expect_iterator_items(i, 8, expect_base, 8, expect_base);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_filesystem(
+ &i, "status/subdir", GIT_ITERATOR_IGNORE_CASE |
+ GIT_ITERATOR_INCLUDE_TREES, NULL, NULL));
+ expect_iterator_items(i, 18, expect_trees, 18, expect_trees);
+ git_iterator_free(i);
+
+ cl_git_pass(git_iterator_for_filesystem(
+ &i, "status/subdir", GIT_ITERATOR_IGNORE_CASE |
+ GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL));
+ expect_iterator_items(i, 5, expect_noauto, 18, expect_trees);
+ git_iterator_free(i);
+}
+
+void test_repo_iterator__fs2(void)
+{
+ git_iterator *i;
+ static const char *expect_base[] = {
+ "heads/br2",
+ "heads/dir",
+ "heads/long-file-name",
+ "heads/master",
+ "heads/packed-test",
+ "heads/subtrees",
+ "heads/test",
+ "tags/e90810b",
+ "tags/foo/bar",
+ "tags/foo/foo/bar",
+ "tags/point_to_blob",
+ "tags/test",
+ NULL,
+ };
+
+ g_repo = cl_git_sandbox_init("testrepo");
+
+ cl_git_pass(git_iterator_for_filesystem(
+ &i, "testrepo/.git/refs", 0, NULL, NULL));
+ expect_iterator_items(i, 12, expect_base, 12, expect_base);
+ git_iterator_free(i);
+}
+
+void test_repo_iterator__fs_preserves_error(void)
+{
+ git_iterator *i;
+ const git_index_entry *e;
+
+ if (!cl_is_chmod_supported())
+ return;
+
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+
+ cl_must_pass(p_mkdir("empty_standard_repo/r", 0777));
+ cl_git_mkfile("empty_standard_repo/r/a", "hello");
+ cl_must_pass(p_mkdir("empty_standard_repo/r/b", 0777));
+ cl_git_mkfile("empty_standard_repo/r/b/problem", "not me");
+ cl_must_pass(p_chmod("empty_standard_repo/r/b", 0000));
+ cl_must_pass(p_mkdir("empty_standard_repo/r/c", 0777));
+ cl_git_mkfile("empty_standard_repo/r/d", "final");
+
+ cl_git_pass(git_iterator_for_filesystem(
+ &i, "empty_standard_repo/r", 0, NULL, NULL));
+
+ cl_git_pass(git_iterator_advance(&e, i)); /* a */
+ cl_git_fail(git_iterator_advance(&e, i)); /* b */
+ cl_assert(giterr_last());
+ cl_assert(giterr_last()->message != NULL);
+ /* skip 'c/' empty directory */
+ cl_git_pass(git_iterator_advance(&e, i)); /* d */
+ cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&e, i));
+
+ cl_must_pass(p_chmod("empty_standard_repo/r/b", 0777));
+
+ git_iterator_free(i);
+}
diff --git a/tests/repo/message.c b/tests/repo/message.c
new file mode 100644
index 000000000..87574590b
--- /dev/null
+++ b/tests/repo/message.c
@@ -0,0 +1,39 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "refs.h"
+#include "posix.h"
+
+static git_repository *_repo;
+
+void test_repo_message__initialize(void)
+{
+ _repo = cl_git_sandbox_init("testrepo.git");
+}
+
+void test_repo_message__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_repo_message__none(void)
+{
+ git_buf actual = GIT_BUF_INIT;
+ cl_assert_equal_i(GIT_ENOTFOUND, git_repository_message(&actual, _repo));
+}
+
+void test_repo_message__message(void)
+{
+ git_buf path = GIT_BUF_INIT, actual = GIT_BUF_INIT;
+ const char expected[] = "Test\n\nThis is a test of the emergency broadcast system\n";
+
+ cl_git_pass(git_buf_joinpath(&path, git_repository_path(_repo), "MERGE_MSG"));
+ cl_git_mkfile(git_buf_cstr(&path), expected);
+
+ cl_git_pass(git_repository_message(&actual, _repo));
+ cl_assert_equal_s(expected, git_buf_cstr(&actual));
+ git_buf_free(&actual);
+
+ cl_git_pass(p_unlink(git_buf_cstr(&path)));
+ cl_assert_equal_i(GIT_ENOTFOUND, git_repository_message(&actual, _repo));
+ git_buf_free(&path);
+}
diff --git a/tests/repo/open.c b/tests/repo/open.c
new file mode 100644
index 000000000..637c785d5
--- /dev/null
+++ b/tests/repo/open.c
@@ -0,0 +1,379 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+#include "sysdir.h"
+#include <ctype.h>
+
+void test_repo_open__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+
+ if (git_path_isdir("alternate"))
+ git_futils_rmdir_r("alternate", NULL, GIT_RMDIR_REMOVE_FILES);
+}
+
+void test_repo_open__bare_empty_repo(void)
+{
+ git_repository *repo = cl_git_sandbox_init("empty_bare.git");
+
+ cl_assert(git_repository_path(repo) != NULL);
+ cl_assert(git__suffixcmp(git_repository_path(repo), "/") == 0);
+ cl_assert(git_repository_workdir(repo) == NULL);
+}
+
+void test_repo_open__standard_empty_repo_through_gitdir(void)
+{
+ git_repository *repo;
+
+ cl_git_pass(git_repository_open(&repo, cl_fixture("empty_standard_repo/.gitted")));
+
+ cl_assert(git_repository_path(repo) != NULL);
+ cl_assert(git__suffixcmp(git_repository_path(repo), "/") == 0);
+
+ cl_assert(git_repository_workdir(repo) != NULL);
+ cl_assert(git__suffixcmp(git_repository_workdir(repo), "/") == 0);
+
+ git_repository_free(repo);
+}
+
+void test_repo_open__standard_empty_repo_through_workdir(void)
+{
+ git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
+
+ cl_assert(git_repository_path(repo) != NULL);
+ cl_assert(git__suffixcmp(git_repository_path(repo), "/") == 0);
+
+ cl_assert(git_repository_workdir(repo) != NULL);
+ cl_assert(git__suffixcmp(git_repository_workdir(repo), "/") == 0);
+}
+
+
+void test_repo_open__open_with_discover(void)
+{
+ static const char *variants[] = {
+ "attr", "attr/", "attr/.git", "attr/.git/",
+ "attr/sub", "attr/sub/", "attr/sub/sub", "attr/sub/sub/",
+ NULL
+ };
+ git_repository *repo;
+ const char **scan;
+
+ cl_fixture_sandbox("attr");
+ cl_git_pass(p_rename("attr/.gitted", "attr/.git"));
+
+ for (scan = variants; *scan != NULL; scan++) {
+ cl_git_pass(git_repository_open_ext(&repo, *scan, 0, NULL));
+ cl_assert(git__suffixcmp(git_repository_path(repo), "attr/.git/") == 0);
+ cl_assert(git__suffixcmp(git_repository_workdir(repo), "attr/") == 0);
+ git_repository_free(repo);
+ }
+
+ cl_fixture_cleanup("attr");
+}
+
+static void make_gitlink_dir(const char *dir, const char *linktext)
+{
+ git_buf path = GIT_BUF_INIT;
+
+ cl_git_pass(git_futils_mkdir(dir, NULL, 0777, GIT_MKDIR_VERIFY_DIR));
+ cl_git_pass(git_buf_joinpath(&path, dir, ".git"));
+ cl_git_rewritefile(path.ptr, linktext);
+ git_buf_free(&path);
+}
+
+void test_repo_open__gitlinked(void)
+{
+ /* need to have both repo dir and workdir set up correctly */
+ git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
+ git_repository *repo2;
+
+ make_gitlink_dir("alternate", "gitdir: ../empty_standard_repo/.git");
+
+ cl_git_pass(git_repository_open(&repo2, "alternate"));
+
+ cl_assert(git_repository_path(repo2) != NULL);
+ cl_assert_(git__suffixcmp(git_repository_path(repo2), "empty_standard_repo/.git/") == 0, git_repository_path(repo2));
+ cl_assert_equal_s(git_repository_path(repo), git_repository_path(repo2));
+
+ cl_assert(git_repository_workdir(repo2) != NULL);
+ cl_assert_(git__suffixcmp(git_repository_workdir(repo2), "alternate/") == 0, git_repository_workdir(repo2));
+
+ git_repository_free(repo2);
+}
+
+void test_repo_open__from_git_new_workdir(void)
+{
+#ifndef GIT_WIN32
+ /* The git-new-workdir script that ships with git sets up a bunch of
+ * symlinks to create a second workdir that shares the object db with
+ * another checkout. Libgit2 can open a repo that has been configured
+ * this way.
+ */
+
+ git_repository *repo2;
+ git_buf link_tgt = GIT_BUF_INIT, link = GIT_BUF_INIT, body = GIT_BUF_INIT;
+ const char **scan;
+ int link_fd;
+ static const char *links[] = {
+ "config", "refs", "logs/refs", "objects", "info", "hooks",
+ "packed-refs", "remotes", "rr-cache", "svn", NULL
+ };
+ static const char *copies[] = {
+ "HEAD", NULL
+ };
+
+ cl_git_sandbox_init("empty_standard_repo");
+
+ cl_git_pass(p_mkdir("alternate", 0777));
+ cl_git_pass(p_mkdir("alternate/.git", 0777));
+
+ for (scan = links; *scan != NULL; scan++) {
+ git_buf_joinpath(&link_tgt, "empty_standard_repo/.git", *scan);
+ if (git_path_exists(link_tgt.ptr)) {
+ git_buf_joinpath(&link_tgt, "../../empty_standard_repo/.git", *scan);
+ git_buf_joinpath(&link, "alternate/.git", *scan);
+ if (strchr(*scan, '/'))
+ git_futils_mkpath2file(link.ptr, 0777);
+ cl_assert_(symlink(link_tgt.ptr, link.ptr) == 0, strerror(errno));
+ }
+ }
+ for (scan = copies; *scan != NULL; scan++) {
+ git_buf_joinpath(&link_tgt, "empty_standard_repo/.git", *scan);
+ if (git_path_exists(link_tgt.ptr)) {
+ git_buf_joinpath(&link, "alternate/.git", *scan);
+ cl_git_pass(git_futils_readbuffer(&body, link_tgt.ptr));
+
+ cl_assert((link_fd = git_futils_creat_withpath(link.ptr, 0777, 0666)) >= 0);
+ cl_must_pass(p_write(link_fd, body.ptr, body.size));
+ p_close(link_fd);
+ }
+ }
+
+ git_buf_free(&link_tgt);
+ git_buf_free(&link);
+ git_buf_free(&body);
+
+
+ cl_git_pass(git_repository_open(&repo2, "alternate"));
+
+ cl_assert(git_repository_path(repo2) != NULL);
+ cl_assert_(git__suffixcmp(git_repository_path(repo2), "alternate/.git/") == 0, git_repository_path(repo2));
+
+ cl_assert(git_repository_workdir(repo2) != NULL);
+ cl_assert_(git__suffixcmp(git_repository_workdir(repo2), "alternate/") == 0, git_repository_workdir(repo2));
+
+ git_repository_free(repo2);
+#endif
+}
+
+void test_repo_open__failures(void)
+{
+ git_repository *base, *repo;
+ git_buf ceiling = GIT_BUF_INIT;
+
+ base = cl_git_sandbox_init("attr");
+ cl_git_pass(git_buf_sets(&ceiling, git_repository_workdir(base)));
+
+ /* fail with no searching */
+ cl_git_fail(git_repository_open(&repo, "attr/sub"));
+ cl_git_fail(git_repository_open_ext(
+ &repo, "attr/sub", GIT_REPOSITORY_OPEN_NO_SEARCH, NULL));
+
+ /* fail with ceiling too low */
+ cl_git_pass(git_buf_joinpath(&ceiling, ceiling.ptr, "sub"));
+ cl_git_fail(git_repository_open_ext(&repo, "attr/sub", 0, ceiling.ptr));
+
+ /* fail with no repo */
+ cl_git_pass(p_mkdir("alternate", 0777));
+ cl_git_pass(p_mkdir("alternate/.git", 0777));
+ cl_git_fail(git_repository_open_ext(&repo, "alternate", 0, NULL));
+ cl_git_fail(git_repository_open_ext(&repo, "alternate/.git", 0, NULL));
+
+ git_buf_free(&ceiling);
+}
+
+void test_repo_open__bad_gitlinks(void)
+{
+ git_repository *repo;
+ static const char *bad_links[] = {
+ "garbage\n", "gitdir", "gitdir:\n", "gitdir: foobar",
+ "gitdir: ../invalid", "gitdir: ../invalid2",
+ "gitdir: ../attr/.git with extra stuff",
+ NULL
+ };
+ const char **scan;
+
+ cl_git_sandbox_init("attr");
+
+ cl_git_pass(p_mkdir("invalid", 0777));
+ cl_git_pass(git_futils_mkdir_r("invalid2/.git", NULL, 0777));
+
+ for (scan = bad_links; *scan != NULL; scan++) {
+ make_gitlink_dir("alternate", *scan);
+ cl_git_fail(git_repository_open_ext(&repo, "alternate", 0, NULL));
+ }
+
+ git_futils_rmdir_r("invalid", NULL, GIT_RMDIR_REMOVE_FILES);
+ git_futils_rmdir_r("invalid2", NULL, GIT_RMDIR_REMOVE_FILES);
+}
+
+#ifdef GIT_WIN32
+static void unposix_path(git_buf *path)
+{
+ char *src, *tgt;
+
+ src = tgt = path->ptr;
+
+ /* convert "/d/..." to "d:\..." */
+ if (src[0] == '/' && isalpha(src[1]) && src[2] == '/') {
+ *tgt++ = src[1];
+ *tgt++ = ':';
+ *tgt++ = '\\';
+ src += 3;
+ }
+
+ while (*src) {
+ *tgt++ = (*src == '/') ? '\\' : *src;
+ src++;
+ }
+
+ *tgt = '\0';
+}
+#endif
+
+void test_repo_open__win32_path(void)
+{
+#ifdef GIT_WIN32
+ git_repository *repo = cl_git_sandbox_init("empty_standard_repo"), *repo2;
+ git_buf winpath = GIT_BUF_INIT;
+ static const char *repo_path = "empty_standard_repo/.git/";
+ static const char *repo_wd = "empty_standard_repo/";
+
+ cl_assert(git__suffixcmp(git_repository_path(repo), repo_path) == 0);
+ cl_assert(git__suffixcmp(git_repository_workdir(repo), repo_wd) == 0);
+
+ cl_git_pass(git_buf_sets(&winpath, git_repository_path(repo)));
+ unposix_path(&winpath);
+ cl_git_pass(git_repository_open(&repo2, winpath.ptr));
+ cl_assert(git__suffixcmp(git_repository_path(repo2), repo_path) == 0);
+ cl_assert(git__suffixcmp(git_repository_workdir(repo2), repo_wd) == 0);
+ git_repository_free(repo2);
+
+ cl_git_pass(git_buf_sets(&winpath, git_repository_path(repo)));
+ git_buf_truncate(&winpath, winpath.size - 1); /* remove trailing '/' */
+ unposix_path(&winpath);
+ cl_git_pass(git_repository_open(&repo2, winpath.ptr));
+ cl_assert(git__suffixcmp(git_repository_path(repo2), repo_path) == 0);
+ cl_assert(git__suffixcmp(git_repository_workdir(repo2), repo_wd) == 0);
+ git_repository_free(repo2);
+
+ cl_git_pass(git_buf_sets(&winpath, git_repository_workdir(repo)));
+ unposix_path(&winpath);
+ cl_git_pass(git_repository_open(&repo2, winpath.ptr));
+ cl_assert(git__suffixcmp(git_repository_path(repo2), repo_path) == 0);
+ cl_assert(git__suffixcmp(git_repository_workdir(repo2), repo_wd) == 0);
+ git_repository_free(repo2);
+
+ cl_git_pass(git_buf_sets(&winpath, git_repository_workdir(repo)));
+ git_buf_truncate(&winpath, winpath.size - 1); /* remove trailing '/' */
+ unposix_path(&winpath);
+ cl_git_pass(git_repository_open(&repo2, winpath.ptr));
+ cl_assert(git__suffixcmp(git_repository_path(repo2), repo_path) == 0);
+ cl_assert(git__suffixcmp(git_repository_workdir(repo2), repo_wd) == 0);
+ git_repository_free(repo2);
+
+ git_buf_free(&winpath);
+#endif
+}
+
+void test_repo_open__opening_a_non_existing_repository_returns_ENOTFOUND(void)
+{
+ git_repository *repo;
+ cl_assert_equal_i(GIT_ENOTFOUND, git_repository_open(&repo, "i-do-not/exist"));
+}
+
+void test_repo_open__no_config(void)
+{
+ git_buf path = GIT_BUF_INIT;
+ git_repository *repo;
+ git_config *config;
+
+ cl_fixture_sandbox("empty_standard_repo");
+ cl_git_pass(cl_rename(
+ "empty_standard_repo/.gitted", "empty_standard_repo/.git"));
+
+ /* remove local config */
+ cl_git_pass(git_futils_rmdir_r(
+ "empty_standard_repo/.git/config", NULL, GIT_RMDIR_REMOVE_FILES));
+
+ /* isolate from system level configs */
+ cl_must_pass(p_mkdir("alternate", 0777));
+ cl_git_pass(git_path_prettify(&path, "alternate", NULL));
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr));
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr));
+ cl_git_pass(git_libgit2_opts(
+ GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr));
+
+ git_buf_free(&path);
+
+ cl_git_pass(git_repository_open(&repo, "empty_standard_repo"));
+ cl_git_pass(git_repository_config(&config, repo));
+
+ cl_git_pass(git_config_set_string(config, "test.set", "42"));
+
+ git_config_free(config);
+ git_repository_free(repo);
+ cl_fixture_cleanup("empty_standard_repo");
+
+ cl_sandbox_set_search_path_defaults();
+}
+
+void test_repo_open__force_bare(void)
+{
+ /* need to have both repo dir and workdir set up correctly */
+ git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
+ git_repository *barerepo;
+
+ make_gitlink_dir("alternate", "gitdir: ../empty_standard_repo/.git");
+
+ cl_assert(!git_repository_is_bare(repo));
+
+ cl_git_pass(git_repository_open(&barerepo, "alternate"));
+ cl_assert(!git_repository_is_bare(barerepo));
+ git_repository_free(barerepo);
+
+ cl_git_pass(git_repository_open_bare(
+ &barerepo, "empty_standard_repo/.git"));
+ cl_assert(git_repository_is_bare(barerepo));
+ git_repository_free(barerepo);
+
+ cl_git_fail(git_repository_open_bare(&barerepo, "alternate/.git"));
+
+ cl_git_pass(git_repository_open_ext(
+ &barerepo, "alternate/.git", GIT_REPOSITORY_OPEN_BARE, NULL));
+ cl_assert(git_repository_is_bare(barerepo));
+ git_repository_free(barerepo);
+
+ cl_git_pass(p_mkdir("empty_standard_repo/subdir", 0777));
+ cl_git_mkfile("empty_standard_repo/subdir/something.txt", "something");
+
+ cl_git_fail(git_repository_open_bare(
+ &barerepo, "empty_standard_repo/subdir"));
+
+ cl_git_pass(git_repository_open_ext(
+ &barerepo, "empty_standard_repo/subdir", GIT_REPOSITORY_OPEN_BARE, NULL));
+ cl_assert(git_repository_is_bare(barerepo));
+ git_repository_free(barerepo);
+
+ cl_git_pass(p_mkdir("alternate/subdir", 0777));
+ cl_git_pass(p_mkdir("alternate/subdir/sub2", 0777));
+ cl_git_mkfile("alternate/subdir/sub2/something.txt", "something");
+
+ cl_git_fail(git_repository_open_bare(&barerepo, "alternate/subdir/sub2"));
+
+ cl_git_pass(git_repository_open_ext(
+ &barerepo, "alternate/subdir/sub2", GIT_REPOSITORY_OPEN_BARE, NULL));
+ cl_assert(git_repository_is_bare(barerepo));
+ git_repository_free(barerepo);
+}
diff --git a/tests/repo/pathspec.c b/tests/repo/pathspec.c
new file mode 100644
index 000000000..5b86662bc
--- /dev/null
+++ b/tests/repo/pathspec.c
@@ -0,0 +1,385 @@
+#include "clar_libgit2.h"
+#include "git2/pathspec.h"
+
+static git_repository *g_repo;
+
+void test_repo_pathspec__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("status");
+}
+
+void test_repo_pathspec__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+ g_repo = NULL;
+}
+
+static char *str0[] = { "*_file", "new_file", "garbage" };
+static char *str1[] = { "*_FILE", "NEW_FILE", "GARBAGE" };
+static char *str2[] = { "staged_*" };
+static char *str3[] = { "!subdir", "*_file", "new_file" };
+static char *str4[] = { "*" };
+static char *str5[] = { "S*" };
+
+void test_repo_pathspec__workdir0(void)
+{
+ git_strarray s;
+ git_pathspec *ps;
+ git_pathspec_match_list *m;
+
+ /* { "*_file", "new_file", "garbage" } */
+ s.strings = str0; s.count = ARRAY_SIZE(str0);
+ cl_git_pass(git_pathspec_new(&ps, &s));
+
+ cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps));
+ cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m));
+ cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m));
+ git_pathspec_match_list_free(m);
+
+ cl_git_pass(git_pathspec_match_workdir(&m, g_repo,
+ GIT_PATHSPEC_FIND_FAILURES, ps));
+ cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m));
+ cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m));
+ cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 0));
+ git_pathspec_match_list_free(m);
+
+ cl_git_pass(git_pathspec_match_workdir(&m, g_repo,
+ GIT_PATHSPEC_FIND_FAILURES | GIT_PATHSPEC_FAILURES_ONLY, ps));
+ cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m));
+ cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m));
+ git_pathspec_match_list_free(m);
+
+ git_pathspec_free(ps);
+}
+
+void test_repo_pathspec__workdir1(void)
+{
+ git_strarray s;
+ git_pathspec *ps;
+ git_pathspec_match_list *m;
+
+ /* { "*_FILE", "NEW_FILE", "GARBAGE" } */
+ s.strings = str1; s.count = ARRAY_SIZE(str1);
+ cl_git_pass(git_pathspec_new(&ps, &s));
+
+ cl_git_pass(git_pathspec_match_workdir(&m, g_repo,
+ GIT_PATHSPEC_IGNORE_CASE, ps));
+ cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m));
+ git_pathspec_match_list_free(m);
+
+ cl_git_pass(git_pathspec_match_workdir(&m, g_repo,
+ GIT_PATHSPEC_USE_CASE, ps));
+ cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m));
+ git_pathspec_match_list_free(m);
+
+ cl_git_fail(git_pathspec_match_workdir(&m, g_repo,
+ GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_NO_MATCH_ERROR, ps));
+
+ cl_git_pass(git_pathspec_match_workdir(&m, g_repo,
+ GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps));
+ cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m));
+ cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m));
+ git_pathspec_match_list_free(m);
+
+ cl_git_pass(git_pathspec_match_workdir(&m, g_repo,
+ GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps));
+ cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m));
+ cl_assert_equal_sz(3, git_pathspec_match_list_failed_entrycount(m));
+ git_pathspec_match_list_free(m);
+
+ git_pathspec_free(ps);
+}
+
+void test_repo_pathspec__workdir2(void)
+{
+ git_strarray s;
+ git_pathspec *ps;
+ git_pathspec_match_list *m;
+
+ /* { "staged_*" } */
+ s.strings = str2; s.count = ARRAY_SIZE(str2);
+ cl_git_pass(git_pathspec_new(&ps, &s));
+
+ cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps));
+ cl_assert_equal_sz(5, git_pathspec_match_list_entrycount(m));
+ git_pathspec_match_list_free(m);
+
+ cl_git_pass(git_pathspec_match_workdir(&m, g_repo,
+ GIT_PATHSPEC_FIND_FAILURES, ps));
+ cl_assert_equal_sz(5, git_pathspec_match_list_entrycount(m));
+ cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m));
+ git_pathspec_match_list_free(m);
+
+ cl_git_fail(git_pathspec_match_workdir(&m, g_repo,
+ GIT_PATHSPEC_NO_GLOB | GIT_PATHSPEC_NO_MATCH_ERROR, ps));
+
+ cl_git_pass(git_pathspec_match_workdir(&m, g_repo,
+ GIT_PATHSPEC_NO_GLOB | GIT_PATHSPEC_FIND_FAILURES, ps));
+ cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m));
+ cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m));
+ git_pathspec_match_list_free(m);
+
+ git_pathspec_free(ps);
+}
+
+void test_repo_pathspec__workdir3(void)
+{
+ git_strarray s;
+ git_pathspec *ps;
+ git_pathspec_match_list *m;
+
+ /* { "!subdir", "*_file", "new_file" } */
+ s.strings = str3; s.count = ARRAY_SIZE(str3);
+ cl_git_pass(git_pathspec_new(&ps, &s));
+
+ cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps));
+ cl_assert_equal_sz(7, git_pathspec_match_list_entrycount(m));
+ git_pathspec_match_list_free(m);
+
+ cl_git_pass(git_pathspec_match_workdir(&m, g_repo,
+ GIT_PATHSPEC_FIND_FAILURES, ps));
+ cl_assert_equal_sz(7, git_pathspec_match_list_entrycount(m));
+ cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m));
+
+ cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0));
+ cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1));
+ cl_assert_equal_s("new_file", git_pathspec_match_list_entry(m, 2));
+ cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 3));
+ cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 4));
+ cl_assert_equal_s("staged_new_file", git_pathspec_match_list_entry(m, 5));
+ cl_assert_equal_s("staged_new_file_modified_file", git_pathspec_match_list_entry(m, 6));
+ cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 7));
+
+ git_pathspec_match_list_free(m);
+
+ git_pathspec_free(ps);
+}
+
+void test_repo_pathspec__workdir4(void)
+{
+ git_strarray s;
+ git_pathspec *ps;
+ git_pathspec_match_list *m;
+
+ /* { "*" } */
+ s.strings = str4; s.count = ARRAY_SIZE(str4);
+ cl_git_pass(git_pathspec_new(&ps, &s));
+
+ cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps));
+ cl_assert_equal_sz(13, git_pathspec_match_list_entrycount(m));
+ cl_assert_equal_s("\xE8\xBF\x99", git_pathspec_match_list_entry(m, 12));
+ git_pathspec_match_list_free(m);
+
+ git_pathspec_free(ps);
+}
+
+
+void test_repo_pathspec__index0(void)
+{
+ git_index *idx;
+ git_strarray s;
+ git_pathspec *ps;
+ git_pathspec_match_list *m;
+
+ cl_git_pass(git_repository_index(&idx, g_repo));
+
+ /* { "*_file", "new_file", "garbage" } */
+ s.strings = str0; s.count = ARRAY_SIZE(str0);
+ cl_git_pass(git_pathspec_new(&ps, &s));
+
+ cl_git_pass(git_pathspec_match_index(&m, idx, 0, ps));
+ cl_assert_equal_sz(9, git_pathspec_match_list_entrycount(m));
+ cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m));
+ cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0));
+ cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1));
+ cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 2));
+ cl_assert_equal_s("staged_new_file", git_pathspec_match_list_entry(m, 3));
+ cl_assert_equal_s("staged_new_file_deleted_file", git_pathspec_match_list_entry(m, 4));
+ cl_assert_equal_s("staged_new_file_modified_file", git_pathspec_match_list_entry(m, 5));
+ cl_assert_equal_s("subdir/current_file", git_pathspec_match_list_entry(m, 6));
+ cl_assert_equal_s("subdir/deleted_file", git_pathspec_match_list_entry(m, 7));
+ cl_assert_equal_s("subdir/modified_file", git_pathspec_match_list_entry(m, 8));
+ cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 9));
+ git_pathspec_match_list_free(m);
+
+ cl_git_pass(git_pathspec_match_index(&m, idx,
+ GIT_PATHSPEC_FIND_FAILURES, ps));
+ cl_assert_equal_sz(9, git_pathspec_match_list_entrycount(m));
+ cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m));
+ cl_assert_equal_s("new_file", git_pathspec_match_list_failed_entry(m, 0));
+ cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 1));
+ cl_assert_equal_s(NULL, git_pathspec_match_list_failed_entry(m, 2));
+ git_pathspec_match_list_free(m);
+
+ git_pathspec_free(ps);
+ git_index_free(idx);
+}
+
+void test_repo_pathspec__index1(void)
+{
+ /* Currently the USE_CASE and IGNORE_CASE flags don't work on the
+ * index because the index sort order for the index iterator is
+ * set by the index itself. I think the correct fix is for the
+ * index not to embed a global sort order but to support traversal
+ * in either case sensitive or insensitive order in a stateless
+ * manner.
+ *
+ * Anyhow, as it is, there is no point in doing this test.
+ */
+#if 0
+ git_index *idx;
+ git_strarray s;
+ git_pathspec *ps;
+ git_pathspec_match_list *m;
+
+ cl_git_pass(git_repository_index(&idx, g_repo));
+
+ /* { "*_FILE", "NEW_FILE", "GARBAGE" } */
+ s.strings = str1; s.count = ARRAY_SIZE(str1);
+ cl_git_pass(git_pathspec_new(&ps, &s));
+
+ cl_git_pass(git_pathspec_match_index(&m, idx,
+ GIT_PATHSPEC_USE_CASE, ps));
+ cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m));
+ cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m));
+ git_pathspec_match_list_free(m);
+
+ cl_git_pass(git_pathspec_match_index(&m, idx,
+ GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps));
+ cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m));
+ cl_assert_equal_sz(3, git_pathspec_match_list_failed_entrycount(m));
+ git_pathspec_match_list_free(m);
+
+ cl_git_pass(git_pathspec_match_index(&m, idx,
+ GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps));
+ cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m));
+ cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m));
+ git_pathspec_match_list_free(m);
+
+ git_pathspec_free(ps);
+ git_index_free(idx);
+#endif
+}
+
+void test_repo_pathspec__tree0(void)
+{
+ git_object *tree;
+ git_strarray s;
+ git_pathspec *ps;
+ git_pathspec_match_list *m;
+
+ /* { "*_file", "new_file", "garbage" } */
+ s.strings = str0; s.count = ARRAY_SIZE(str0);
+ cl_git_pass(git_pathspec_new(&ps, &s));
+
+ cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD~2^{tree}"));
+
+ cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree,
+ GIT_PATHSPEC_FIND_FAILURES, ps));
+ cl_assert_equal_sz(4, git_pathspec_match_list_entrycount(m));
+ cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0));
+ cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1));
+ cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 2));
+ cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 3));
+ cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 4));
+ cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m));
+ cl_assert_equal_s("new_file", git_pathspec_match_list_failed_entry(m, 0));
+ cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 1));
+ cl_assert_equal_s(NULL, git_pathspec_match_list_failed_entry(m, 2));
+ git_pathspec_match_list_free(m);
+
+ git_object_free(tree);
+
+ cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD^{tree}"));
+
+ cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree,
+ GIT_PATHSPEC_FIND_FAILURES, ps));
+ cl_assert_equal_sz(7, git_pathspec_match_list_entrycount(m));
+ cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0));
+ cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1));
+ cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 2));
+ cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 3));
+ cl_assert_equal_s("subdir/current_file", git_pathspec_match_list_entry(m, 4));
+ cl_assert_equal_s("subdir/deleted_file", git_pathspec_match_list_entry(m, 5));
+ cl_assert_equal_s("subdir/modified_file", git_pathspec_match_list_entry(m, 6));
+ cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 7));
+ cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m));
+ cl_assert_equal_s("new_file", git_pathspec_match_list_failed_entry(m, 0));
+ cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 1));
+ cl_assert_equal_s(NULL, git_pathspec_match_list_failed_entry(m, 2));
+ git_pathspec_match_list_free(m);
+
+ git_object_free(tree);
+
+ git_pathspec_free(ps);
+}
+
+void test_repo_pathspec__tree5(void)
+{
+ git_object *tree;
+ git_strarray s;
+ git_pathspec *ps;
+ git_pathspec_match_list *m;
+
+ /* { "S*" } */
+ s.strings = str5; s.count = ARRAY_SIZE(str5);
+ cl_git_pass(git_pathspec_new(&ps, &s));
+
+ cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD~2^{tree}"));
+
+ cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree,
+ GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps));
+ cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m));
+ cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m));
+ git_pathspec_match_list_free(m);
+
+ cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree,
+ GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps));
+ cl_assert_equal_sz(5, git_pathspec_match_list_entrycount(m));
+ cl_assert_equal_s("staged_changes", git_pathspec_match_list_entry(m, 0));
+ cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 4));
+ cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m));
+ git_pathspec_match_list_free(m);
+
+ git_object_free(tree);
+
+ cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD^{tree}"));
+
+ cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree,
+ GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps));
+ cl_assert_equal_sz(9, git_pathspec_match_list_entrycount(m));
+ cl_assert_equal_s("staged_changes", git_pathspec_match_list_entry(m, 0));
+ cl_assert_equal_s("subdir.txt", git_pathspec_match_list_entry(m, 5));
+ cl_assert_equal_s("subdir/current_file", git_pathspec_match_list_entry(m, 6));
+ cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m));
+ git_pathspec_match_list_free(m);
+
+ git_object_free(tree);
+
+ git_pathspec_free(ps);
+}
+
+void test_repo_pathspec__in_memory(void)
+{
+ static char *strings[] = { "one", "two*", "!three*", "*four" };
+ git_strarray s = { strings, ARRAY_SIZE(strings) };
+ git_pathspec *ps;
+
+ cl_git_pass(git_pathspec_new(&ps, &s));
+
+ cl_assert(git_pathspec_matches_path(ps, 0, "one"));
+ cl_assert(!git_pathspec_matches_path(ps, 0, "ONE"));
+ cl_assert(git_pathspec_matches_path(ps, GIT_PATHSPEC_IGNORE_CASE, "ONE"));
+ cl_assert(git_pathspec_matches_path(ps, 0, "two"));
+ cl_assert(git_pathspec_matches_path(ps, 0, "two.txt"));
+ cl_assert(!git_pathspec_matches_path(ps, 0, "three.txt"));
+ cl_assert(git_pathspec_matches_path(ps, 0, "anything.four"));
+ cl_assert(!git_pathspec_matches_path(ps, 0, "three.four"));
+ cl_assert(!git_pathspec_matches_path(ps, 0, "nomatch"));
+ cl_assert(!git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "two"));
+ cl_assert(git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "two*"));
+ cl_assert(!git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "anyfour"));
+ cl_assert(git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "*four"));
+
+ git_pathspec_free(ps);
+}
diff --git a/tests/repo/repo_helpers.c b/tests/repo/repo_helpers.c
new file mode 100644
index 000000000..7c5db4a81
--- /dev/null
+++ b/tests/repo/repo_helpers.c
@@ -0,0 +1,22 @@
+#include "clar_libgit2.h"
+#include "refs.h"
+#include "repo_helpers.h"
+#include "posix.h"
+
+void make_head_unborn(git_repository* repo, const char *target)
+{
+ git_reference *head;
+
+ cl_git_pass(git_reference_symbolic_create(&head, repo, GIT_HEAD_FILE, target, 1, NULL, NULL));
+ 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/repo/repo_helpers.h b/tests/repo/repo_helpers.h
new file mode 100644
index 000000000..6783d5701
--- /dev/null
+++ b/tests/repo/repo_helpers.h
@@ -0,0 +1,6 @@
+#include "common.h"
+
+#define NON_EXISTING_HEAD "refs/heads/hide/and/seek"
+
+extern void make_head_unborn(git_repository* repo, const char *target);
+extern void delete_head(git_repository* repo);
diff --git a/tests/repo/setters.c b/tests/repo/setters.c
new file mode 100644
index 000000000..f34f1e471
--- /dev/null
+++ b/tests/repo/setters.c
@@ -0,0 +1,107 @@
+#include "clar_libgit2.h"
+#include "git2/sys/repository.h"
+
+#include "buffer.h"
+#include "posix.h"
+#include "util.h"
+#include "path.h"
+#include "fileops.h"
+
+static git_repository *repo;
+
+void test_repo_setters__initialize(void)
+{
+ cl_fixture_sandbox("testrepo.git");
+ cl_git_pass(git_repository_open(&repo, "testrepo.git"));
+ cl_must_pass(p_mkdir("new_workdir", 0777));
+}
+
+void test_repo_setters__cleanup(void)
+{
+ git_repository_free(repo);
+ repo = NULL;
+
+ cl_fixture_cleanup("testrepo.git");
+ cl_fixture_cleanup("new_workdir");
+}
+
+void test_repo_setters__setting_a_workdir_turns_a_bare_repository_into_a_standard_one(void)
+{
+ cl_assert(git_repository_is_bare(repo) == 1);
+
+ cl_assert(git_repository_workdir(repo) == NULL);
+ cl_git_pass(git_repository_set_workdir(repo, "./new_workdir", false));
+
+ cl_assert(git_repository_workdir(repo) != NULL);
+ cl_assert(git_repository_is_bare(repo) == 0);
+}
+
+void test_repo_setters__setting_a_workdir_prettifies_its_path(void)
+{
+ cl_git_pass(git_repository_set_workdir(repo, "./new_workdir", false));
+
+ cl_assert(git__suffixcmp(git_repository_workdir(repo), "new_workdir/") == 0);
+}
+
+void test_repo_setters__setting_a_workdir_creates_a_gitlink(void)
+{
+ git_config *cfg;
+ const char *val;
+ git_buf content = GIT_BUF_INIT;
+
+ cl_git_pass(git_repository_set_workdir(repo, "./new_workdir", true));
+
+ cl_assert(git_path_isfile("./new_workdir/.git"));
+
+ cl_git_pass(git_futils_readbuffer(&content, "./new_workdir/.git"));
+ cl_assert(git__prefixcmp(git_buf_cstr(&content), "gitdir: ") == 0);
+ cl_assert(git__suffixcmp(git_buf_cstr(&content), "testrepo.git/") == 0);
+ git_buf_free(&content);
+
+ cl_git_pass(git_repository_config(&cfg, repo));
+ cl_git_pass(git_config_get_string(&val, cfg, "core.worktree"));
+ cl_assert(git__suffixcmp(val, "new_workdir/") == 0);
+ git_config_free(cfg);
+}
+
+void test_repo_setters__setting_a_new_index_on_a_repo_which_has_already_loaded_one_properly_honors_the_refcount(void)
+{
+ git_index *new_index;
+
+ cl_git_pass(git_index_open(&new_index, "./my-index"));
+ cl_assert(((git_refcount *)new_index)->refcount.val == 1);
+
+ git_repository_set_index(repo, new_index);
+ cl_assert(((git_refcount *)new_index)->refcount.val == 2);
+
+ git_repository_free(repo);
+ cl_assert(((git_refcount *)new_index)->refcount.val == 1);
+
+ git_index_free(new_index);
+
+ /*
+ * Ensure the cleanup method won't try to free the repo as it's already been taken care of
+ */
+ repo = NULL;
+}
+
+void test_repo_setters__setting_a_new_odb_on_a_repo_which_already_loaded_one_properly_honors_the_refcount(void)
+{
+ git_odb *new_odb;
+
+ cl_git_pass(git_odb_open(&new_odb, "./testrepo.git/objects"));
+ cl_assert(((git_refcount *)new_odb)->refcount.val == 1);
+
+ git_repository_set_odb(repo, new_odb);
+ cl_assert(((git_refcount *)new_odb)->refcount.val == 2);
+
+ git_repository_free(repo);
+ cl_assert(((git_refcount *)new_odb)->refcount.val == 1);
+
+ git_odb_free(new_odb);
+
+ /*
+ * Ensure the cleanup method won't try to free the repo as it's already been taken care of
+ */
+ repo = NULL;
+}
diff --git a/tests/repo/shallow.c b/tests/repo/shallow.c
new file mode 100644
index 000000000..5aeaf2def
--- /dev/null
+++ b/tests/repo/shallow.c
@@ -0,0 +1,39 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+
+static git_repository *g_repo;
+
+void test_repo_shallow__initialize(void)
+{
+}
+
+void test_repo_shallow__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_repo_shallow__no_shallow_file(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo.git");
+ cl_assert_equal_i(0, git_repository_is_shallow(g_repo));
+}
+
+void test_repo_shallow__empty_shallow_file(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo.git");
+ cl_git_mkfile("testrepo.git/shallow", "");
+ cl_assert_equal_i(0, git_repository_is_shallow(g_repo));
+}
+
+void test_repo_shallow__shallow_repo(void)
+{
+ g_repo = cl_git_sandbox_init("shallow.git");
+ cl_assert_equal_i(1, git_repository_is_shallow(g_repo));
+}
+
+void test_repo_shallow__clears_errors(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo.git");
+ cl_assert_equal_i(0, git_repository_is_shallow(g_repo));
+ cl_assert_equal_p(NULL, giterr_last());
+}
diff --git a/tests/repo/state.c b/tests/repo/state.c
new file mode 100644
index 000000000..2d6c780ee
--- /dev/null
+++ b/tests/repo/state.c
@@ -0,0 +1,114 @@
+#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, NULL, NULL));
+ 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);
+ cl_git_pass(git_repository_state_cleanup(_repo));
+ assert_repo_state(GIT_REPOSITORY_STATE_NONE);
+}
+
+void test_repo_state__revert(void)
+{
+ setup_simple_state(GIT_REVERT_HEAD_FILE);
+ assert_repo_state(GIT_REPOSITORY_STATE_REVERT);
+ cl_git_pass(git_repository_state_cleanup(_repo));
+ assert_repo_state(GIT_REPOSITORY_STATE_NONE);
+}
+
+void test_repo_state__cherry_pick(void)
+{
+ setup_simple_state(GIT_CHERRY_PICK_HEAD_FILE);
+ assert_repo_state(GIT_REPOSITORY_STATE_CHERRY_PICK);
+ cl_git_pass(git_repository_state_cleanup(_repo));
+ assert_repo_state(GIT_REPOSITORY_STATE_NONE);
+}
+
+void test_repo_state__bisect(void)
+{
+ setup_simple_state(GIT_BISECT_LOG_FILE);
+ assert_repo_state(GIT_REPOSITORY_STATE_BISECT);
+ cl_git_pass(git_repository_state_cleanup(_repo));
+ assert_repo_state(GIT_REPOSITORY_STATE_NONE);
+}
+
+void test_repo_state__rebase_interactive(void)
+{
+ setup_simple_state(GIT_REBASE_MERGE_INTERACTIVE_FILE);
+ assert_repo_state(GIT_REPOSITORY_STATE_REBASE_INTERACTIVE);
+ cl_git_pass(git_repository_state_cleanup(_repo));
+ assert_repo_state(GIT_REPOSITORY_STATE_NONE);
+}
+
+void test_repo_state__rebase_merge(void)
+{
+ setup_simple_state(GIT_REBASE_MERGE_DIR "whatever");
+ assert_repo_state(GIT_REPOSITORY_STATE_REBASE_MERGE);
+ cl_git_pass(git_repository_state_cleanup(_repo));
+ assert_repo_state(GIT_REPOSITORY_STATE_NONE);
+}
+
+void test_repo_state__rebase(void)
+{
+ setup_simple_state(GIT_REBASE_APPLY_REBASING_FILE);
+ assert_repo_state(GIT_REPOSITORY_STATE_REBASE);
+ cl_git_pass(git_repository_state_cleanup(_repo));
+ assert_repo_state(GIT_REPOSITORY_STATE_NONE);
+}
+
+void test_repo_state__apply_mailbox(void)
+{
+ setup_simple_state(GIT_REBASE_APPLY_APPLYING_FILE);
+ assert_repo_state(GIT_REPOSITORY_STATE_APPLY_MAILBOX);
+ cl_git_pass(git_repository_state_cleanup(_repo));
+ assert_repo_state(GIT_REPOSITORY_STATE_NONE);
+}
+
+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);
+ cl_git_pass(git_repository_state_cleanup(_repo));
+ assert_repo_state(GIT_REPOSITORY_STATE_NONE);
+}
diff --git a/tests/reset/default.c b/tests/reset/default.c
new file mode 100644
index 000000000..c76f14813
--- /dev/null
+++ b/tests/reset/default.c
@@ -0,0 +1,212 @@
+#include "clar_libgit2.h"
+#include "posix.h"
+#include "reset_helpers.h"
+#include "path.h"
+
+static git_repository *_repo;
+static git_object *_target;
+static git_strarray _pathspecs;
+static git_index *_index;
+
+static void initialize(const char *repo_name)
+{
+ _repo = cl_git_sandbox_init(repo_name);
+ cl_git_pass(git_repository_index(&_index, _repo));
+
+ _target = NULL;
+
+ _pathspecs.strings = NULL;
+ _pathspecs.count = 0;
+}
+
+void test_reset_default__initialize(void)
+{
+}
+
+void test_reset_default__cleanup(void)
+{
+ git_object_free(_target);
+ _target = NULL;
+
+ git_index_free(_index);
+ _index = NULL;
+
+ cl_git_sandbox_cleanup();
+}
+
+static void assert_content_in_index(
+ git_strarray *pathspecs,
+ bool should_exist,
+ git_strarray *expected_shas)
+{
+ size_t i, pos;
+ int error;
+
+ for (i = 0; i < pathspecs->count; i++) {
+ error = git_index_find(&pos, _index, pathspecs->strings[i]);
+
+ if (should_exist) {
+ const git_index_entry *entry;
+
+ cl_assert(error != GIT_ENOTFOUND);
+
+ entry = git_index_get_byindex(_index, pos);
+ cl_assert(entry != NULL);
+
+ if (!expected_shas)
+ continue;
+
+ cl_git_pass(git_oid_streq(&entry->id, expected_shas->strings[i]));
+ } else
+ cl_assert_equal_i(should_exist, error != GIT_ENOTFOUND);
+ }
+}
+
+void test_reset_default__resetting_filepaths_against_a_null_target_removes_them_from_the_index(void)
+{
+ char *paths[] = { "staged_changes", "staged_new_file" };
+
+ initialize("status");
+
+ _pathspecs.strings = paths;
+ _pathspecs.count = 2;
+
+ assert_content_in_index(&_pathspecs, true, NULL);
+
+ cl_git_pass(git_reset_default(_repo, NULL, &_pathspecs));
+
+ assert_content_in_index(&_pathspecs, false, NULL);
+}
+
+/*
+ * $ git ls-files --cached -s --abbrev=7 -- "staged*"
+ * 100644 55d316c 0 staged_changes
+ * 100644 a6be623 0 staged_changes_file_deleted
+ * ...
+ *
+ * $ git reset 0017bd4 -- staged_changes staged_changes_file_deleted
+ * Unstaged changes after reset:
+ * ...
+ *
+ * $ git ls-files --cached -s --abbrev=7 -- "staged*"
+ * 100644 32504b7 0 staged_changes
+ * 100644 061d42a 0 staged_changes_file_deleted
+ * ...
+ */
+void test_reset_default__resetting_filepaths_replaces_their_corresponding_index_entries(void)
+{
+ git_strarray before, after;
+
+ char *paths[] = { "staged_changes", "staged_changes_file_deleted" };
+ char *before_shas[] = { "55d316c9ba708999f1918e9677d01dfcae69c6b9",
+ "a6be623522ce87a1d862128ac42672604f7b468b" };
+ char *after_shas[] = { "32504b727382542f9f089e24fddac5e78533e96c",
+ "061d42a44cacde5726057b67558821d95db96f19" };
+
+ initialize("status");
+
+ _pathspecs.strings = paths;
+ _pathspecs.count = 2;
+ before.strings = before_shas;
+ before.count = 2;
+ after.strings = after_shas;
+ after.count = 2;
+
+ cl_git_pass(git_revparse_single(&_target, _repo, "0017bd4"));
+ assert_content_in_index(&_pathspecs, true, &before);
+
+ cl_git_pass(git_reset_default(_repo, _target, &_pathspecs));
+
+ assert_content_in_index(&_pathspecs, true, &after);
+}
+
+/*
+ * $ git ls-files --cached -s --abbrev=7 -- conflicts-one.txt
+ * 100644 1f85ca5 1 conflicts-one.txt
+ * 100644 6aea5f2 2 conflicts-one.txt
+ * 100644 516bd85 3 conflicts-one.txt
+ *
+ * $ git reset 9a05ccb -- conflicts-one.txt
+ * Unstaged changes after reset:
+ * ...
+ *
+ * $ git ls-files --cached -s --abbrev=7 -- conflicts-one.txt
+ * 100644 1f85ca5 0 conflicts-one.txt
+ *
+ */
+void test_reset_default__resetting_filepaths_clears_previous_conflicts(void)
+{
+ const git_index_entry *conflict_entry[3];
+ git_strarray after;
+
+ char *paths[] = { "conflicts-one.txt" };
+ char *after_shas[] = { "1f85ca51b8e0aac893a621b61a9c2661d6aa6d81" };
+
+ initialize("mergedrepo");
+
+ _pathspecs.strings = paths;
+ _pathspecs.count = 1;
+ after.strings = after_shas;
+ after.count = 1;
+
+ cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1],
+ &conflict_entry[2], _index, "conflicts-one.txt"));
+
+ cl_git_pass(git_revparse_single(&_target, _repo, "9a05ccb"));
+ cl_git_pass(git_reset_default(_repo, _target, &_pathspecs));
+
+ assert_content_in_index(&_pathspecs, true, &after);
+
+ cl_assert_equal_i(GIT_ENOTFOUND, git_index_conflict_get(&conflict_entry[0],
+ &conflict_entry[1], &conflict_entry[2], _index, "conflicts-one.txt"));
+}
+
+/*
+$ git reset HEAD -- "I_am_not_there.txt" "me_neither.txt"
+Unstaged changes after reset:
+...
+*/
+void test_reset_default__resetting_unknown_filepaths_does_not_fail(void)
+{
+ char *paths[] = { "I_am_not_there.txt", "me_neither.txt" };
+
+ initialize("status");
+
+ _pathspecs.strings = paths;
+ _pathspecs.count = 2;
+
+ assert_content_in_index(&_pathspecs, false, NULL);
+
+ cl_git_pass(git_revparse_single(&_target, _repo, "HEAD"));
+ cl_git_pass(git_reset_default(_repo, _target, &_pathspecs));
+
+ assert_content_in_index(&_pathspecs, false, NULL);
+}
+
+void test_reset_default__staged_rename_reset_delete(void)
+{
+ git_index_entry entry;
+ const git_index_entry *existing;
+ char *paths[] = { "new.txt" };
+
+ initialize("testrepo2");
+
+ existing = git_index_get_bypath(_index, "new.txt", 0);
+ cl_assert(existing);
+ memcpy(&entry, existing, sizeof(entry));
+
+ cl_git_pass(git_index_remove_bypath(_index, "new.txt"));
+
+ entry.path = "renamed.txt";
+ cl_git_pass(git_index_add(_index, &entry));
+
+ _pathspecs.strings = paths;
+ _pathspecs.count = 1;
+
+ assert_content_in_index(&_pathspecs, false, NULL);
+
+ cl_git_pass(git_revparse_single(&_target, _repo, "HEAD"));
+ cl_git_pass(git_reset_default(_repo, _target, &_pathspecs));
+
+ assert_content_in_index(&_pathspecs, true, NULL);
+}
diff --git a/tests/reset/hard.c b/tests/reset/hard.c
new file mode 100644
index 000000000..c6bf7a8ac
--- /dev/null
+++ b/tests/reset/hard.c
@@ -0,0 +1,232 @@
+#include "clar_libgit2.h"
+#include "posix.h"
+#include "reset_helpers.h"
+#include "path.h"
+#include "fileops.h"
+
+static git_repository *repo;
+static git_object *target;
+
+void test_reset_hard__initialize(void)
+{
+ repo = cl_git_sandbox_init("status");
+ target = NULL;
+}
+
+void test_reset_hard__cleanup(void)
+{
+ if (target != NULL) {
+ git_object_free(target);
+ target = NULL;
+ }
+
+ cl_git_sandbox_cleanup();
+}
+
+static int strequal_ignore_eol(const char *exp, const char *str)
+{
+ 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);
+}
+
+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",
+ NULL,
+ "staged_changes_modified_file\n"
+ };
+ const char *wd = git_repository_workdir(repo);
+
+ cl_assert(wd);
+
+ 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);
+ }
+
+ cl_git_pass(git_revparse_single(&target, repo, "26a125e"));
+
+ cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL, NULL));
+
+ 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(&content);
+ git_buf_free(&path);
+}
+
+void test_reset_hard__cannot_reset_in_a_bare_repository(void)
+{
+ git_repository *bare;
+
+ cl_git_pass(git_repository_open(&bare, cl_fixture("testrepo.git")));
+ cl_assert(git_repository_is_bare(bare) == true);
+
+ cl_git_pass(git_revparse_single(&target, bare, KNOWN_COMMIT_IN_BARE_REPO));
+
+ cl_assert_equal_i(GIT_EBAREREPO, git_reset(bare, target, GIT_RESET_HARD, NULL, NULL));
+
+ git_repository_free(bare);
+}
+
+static void index_entry_init(git_index *index, int side, git_oid *oid)
+{
+ git_index_entry entry;
+
+ memset(&entry, 0x0, sizeof(git_index_entry));
+
+ entry.path = "conflicting_file";
+ entry.flags = (side << GIT_IDXENTRY_STAGESHIFT);
+ entry.mode = 0100644;
+ git_oid_cpy(&entry.id, oid);
+
+ cl_git_pass(git_index_add(index, &entry));
+}
+
+static void unmerged_index_init(git_index *index, int entries)
+{
+ int write_ancestor = 1;
+ int write_ours = 2;
+ int write_theirs = 4;
+ git_oid ancestor, ours, theirs;
+
+ git_oid_fromstr(&ancestor, "6bb0d9f700543ba3d318ba7075fc3bd696b4287b");
+ git_oid_fromstr(&ours, "b19a1e93bec1317dc6097229e12afaffbfa74dc2");
+ git_oid_fromstr(&theirs, "950b81b7eee953d050aa05a641f8e056c85dd1bd");
+
+ cl_git_rewritefile("status/conflicting_file", "conflicting file\n");
+
+ if (entries & write_ancestor)
+ index_entry_init(index, 1, &ancestor);
+
+ if (entries & write_ours)
+ index_entry_init(index, 2, &ours);
+
+ if (entries & write_theirs)
+ index_entry_init(index, 3, &theirs);
+}
+
+void test_reset_hard__resetting_reverts_unmerged(void)
+{
+ git_index *index;
+ int entries;
+
+ /* Ensure every permutation of non-zero stage entries results in the
+ * path being cleaned up. */
+ for (entries = 1; entries < 8; entries++) {
+ cl_git_pass(git_repository_index(&index, repo));
+
+ unmerged_index_init(index, entries);
+ cl_git_pass(git_index_write(index));
+
+ cl_git_pass(git_revparse_single(&target, repo, "26a125e"));
+ cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL, NULL));
+
+ cl_assert(git_path_exists("status/conflicting_file") == 0);
+
+ git_object_free(target);
+ target = NULL;
+
+ git_index_free(index);
+ }
+}
+
+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_msg_path), "Merge commit 0017bd4ab1ec30440b17bae1680cff124ab5f1f6\n");
+
+ cl_git_pass(git_buf_joinpath(&merge_mode_path, git_repository_path(repo), "MERGE_MODE"));
+ cl_git_mkfile(git_buf_cstr(&merge_mode_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");
+
+ cl_git_pass(git_revparse_single(&target, repo, "0017bd4"));
+ cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL, NULL));
+
+ 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);
+}
+
+void test_reset_hard__reflog_is_correct(void)
+{
+ const char *exp_msg = "commit: Add a file which name should appear before the "
+ "\"subdir/\" folder while being dealt with by the treewalker";
+
+ reflog_check(repo, "HEAD", 3, "emeric.fermas@gmail.com", exp_msg);
+ reflog_check(repo, "refs/heads/master", 3, "emeric.fermas@gmail.com", exp_msg);
+
+ /* Branch not moving, no reflog entry */
+ cl_git_pass(git_revparse_single(&target, repo, "HEAD^{commit}"));
+ cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL, NULL));
+ reflog_check(repo, "HEAD", 3, "emeric.fermas@gmail.com", exp_msg);
+ reflog_check(repo, "refs/heads/master", 3, "emeric.fermas@gmail.com", exp_msg);
+
+ git_object_free(target);
+
+ /* Moved branch, expect default message */
+ exp_msg = "reset: moving";
+ cl_git_pass(git_revparse_single(&target, repo, "HEAD~^{commit}"));
+ cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL, NULL));
+ reflog_check(repo, "HEAD", 4, NULL, exp_msg);
+ reflog_check(repo, "refs/heads/master", 4, NULL, exp_msg);
+
+ git_object_free(target);
+
+ /* Moved branch, expect custom message */
+ exp_msg = "message1";
+ cl_git_pass(git_revparse_single(&target, repo, "HEAD~^{commit}"));
+ cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL, "message1"));
+ reflog_check(repo, "HEAD", 5, NULL, exp_msg);
+ reflog_check(repo, "refs/heads/master", 5, NULL, exp_msg);
+}
diff --git a/tests/reset/mixed.c b/tests/reset/mixed.c
new file mode 100644
index 000000000..55b8a2f88
--- /dev/null
+++ b/tests/reset/mixed.c
@@ -0,0 +1,83 @@
+#include "clar_libgit2.h"
+#include "posix.h"
+#include "reset_helpers.h"
+#include "path.h"
+
+static git_repository *repo;
+static git_object *target;
+
+void test_reset_mixed__initialize(void)
+{
+ repo = cl_git_sandbox_init("attr");
+ target = NULL;
+}
+
+void test_reset_mixed__cleanup(void)
+{
+ git_object_free(target);
+ target = NULL;
+
+ cl_git_sandbox_cleanup();
+}
+
+void test_reset_mixed__cannot_reset_in_a_bare_repository(void)
+{
+ git_repository *bare;
+
+ cl_git_pass(git_repository_open(&bare, cl_fixture("testrepo.git")));
+ cl_assert(git_repository_is_bare(bare) == true);
+
+ cl_git_pass(git_revparse_single(&target, bare, KNOWN_COMMIT_IN_BARE_REPO));
+
+ cl_assert_equal_i(GIT_EBAREREPO, git_reset(bare, target, GIT_RESET_MIXED, NULL, NULL));
+
+ git_repository_free(bare);
+}
+
+void test_reset_mixed__resetting_refreshes_the_index_to_the_commit_tree(void)
+{
+ unsigned int status;
+
+ cl_git_pass(git_status_file(&status, repo, "macro_bad"));
+ cl_assert(status == GIT_STATUS_CURRENT);
+ cl_git_pass(git_revparse_single(&target, repo, "605812a"));
+
+ cl_git_pass(git_reset(repo, target, GIT_RESET_MIXED, NULL, NULL));
+
+ cl_git_pass(git_status_file(&status, repo, "macro_bad"));
+ cl_assert(status == GIT_STATUS_WT_NEW);
+}
+
+void test_reset_mixed__reflog_is_correct(void)
+{
+ const char *exp_msg = "commit: Updating test data so we can test inter-hunk-context";
+
+ reflog_check(repo, "HEAD", 9, "yoram.harmelin@gmail.com", exp_msg);
+ reflog_check(repo, "refs/heads/master", 9, "yoram.harmelin@gmail.com", exp_msg);
+
+ /* Branch not moving, no reflog entry */
+ cl_git_pass(git_revparse_single(&target, repo, "HEAD^{commit}"));
+ cl_git_pass(git_reset(repo, target, GIT_RESET_MIXED, NULL, NULL));
+ reflog_check(repo, "HEAD", 9, "yoram.harmelin@gmail.com", exp_msg);
+ reflog_check(repo, "refs/heads/master", 9, "yoram.harmelin@gmail.com", exp_msg);
+
+ git_object_free(target);
+ target = NULL;
+
+ /* Moved branch, expect default message */
+ exp_msg = "reset: moving";
+ cl_git_pass(git_revparse_single(&target, repo, "HEAD~^{commit}"));
+ cl_git_pass(git_reset(repo, target, GIT_RESET_MIXED, NULL, NULL));
+ reflog_check(repo, "HEAD", 10, NULL, exp_msg);
+ reflog_check(repo, "refs/heads/master", 10, NULL, exp_msg);
+
+ git_object_free(target);
+ target = NULL;
+
+ /* Moved branch, expect custom message */
+ exp_msg = "message1";
+ cl_git_pass(git_revparse_single(&target, repo, "HEAD~^{commit}"));
+ cl_git_pass(git_reset(repo, target, GIT_RESET_MIXED, NULL, "message1"));
+ reflog_check(repo, "HEAD", 11, NULL, exp_msg);
+ reflog_check(repo, "refs/heads/master", 11, NULL, exp_msg);
+}
diff --git a/tests/reset/reset_helpers.c b/tests/reset/reset_helpers.c
new file mode 100644
index 000000000..7a335a600
--- /dev/null
+++ b/tests/reset/reset_helpers.c
@@ -0,0 +1,20 @@
+#include "clar_libgit2.h"
+#include "reset_helpers.h"
+
+void reflog_check(git_repository *repo, const char *refname,
+ size_t exp_count, const char *exp_email, const char *exp_msg)
+{
+ git_reflog *log;
+ const git_reflog_entry *entry;
+
+ cl_git_pass(git_reflog_read(&log, repo, refname));
+ cl_assert_equal_i(exp_count, git_reflog_entrycount(log));
+ entry = git_reflog_entry_byindex(log, 0);
+
+ if (exp_email)
+ cl_assert_equal_s(exp_email, git_reflog_entry_committer(entry)->email);
+ if (exp_msg)
+ cl_assert_equal_s(exp_msg, git_reflog_entry_message(entry));
+
+ git_reflog_free(log);
+}
diff --git a/tests/reset/reset_helpers.h b/tests/reset/reset_helpers.h
new file mode 100644
index 000000000..e7e048514
--- /dev/null
+++ b/tests/reset/reset_helpers.h
@@ -0,0 +1,7 @@
+#include "common.h"
+
+#define KNOWN_COMMIT_IN_BARE_REPO "e90810b8df3e80c413d903f631643c716887138d"
+#define KNOWN_COMMIT_IN_ATTR_REPO "217878ab49e1314388ea2e32dc6fdb58a1b969e0"
+
+void reflog_check(git_repository *repo, const char *refname,
+ size_t exp_count, const char *exp_email, const char *exp_msg);
diff --git a/tests/reset/soft.c b/tests/reset/soft.c
new file mode 100644
index 000000000..c889c0355
--- /dev/null
+++ b/tests/reset/soft.c
@@ -0,0 +1,182 @@
+#include "clar_libgit2.h"
+#include "posix.h"
+#include "reset_helpers.h"
+#include "path.h"
+#include "repo/repo_helpers.h"
+
+static git_repository *repo;
+static git_object *target;
+
+void test_reset_soft__initialize(void)
+{
+ repo = cl_git_sandbox_init("testrepo.git");
+}
+
+void test_reset_soft__cleanup(void)
+{
+ git_object_free(target);
+ target = NULL;
+
+ cl_git_sandbox_cleanup();
+}
+
+static void assert_reset_soft(bool should_be_detached)
+{
+ git_oid oid;
+
+ cl_git_pass(git_reference_name_to_id(&oid, repo, "HEAD"));
+ cl_git_fail(git_oid_streq(&oid, KNOWN_COMMIT_IN_BARE_REPO));
+ cl_git_pass(git_revparse_single(&target, repo, KNOWN_COMMIT_IN_BARE_REPO));
+
+ cl_assert(git_repository_head_detached(repo) == should_be_detached);
+
+ cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL, NULL));
+
+ cl_assert(git_repository_head_detached(repo) == should_be_detached);
+
+ cl_git_pass(git_reference_name_to_id(&oid, repo, "HEAD"));
+ cl_git_pass(git_oid_streq(&oid, KNOWN_COMMIT_IN_BARE_REPO));
+}
+
+void test_reset_soft__can_reset_the_non_detached_Head_to_the_specified_commit(void)
+{
+ assert_reset_soft(false);
+}
+
+void test_reset_soft__can_reset_the_detached_Head_to_the_specified_commit(void)
+{
+ git_repository_detach_head(repo, NULL, NULL);
+
+ assert_reset_soft(true);
+}
+
+void test_reset_soft__resetting_to_the_commit_pointed_at_by_the_Head_does_not_change_the_target_of_the_Head(void)
+{
+ git_oid oid;
+ char raw_head_oid[GIT_OID_HEXSZ + 1];
+
+ cl_git_pass(git_reference_name_to_id(&oid, repo, "HEAD"));
+ git_oid_fmt(raw_head_oid, &oid);
+ raw_head_oid[GIT_OID_HEXSZ] = '\0';
+
+ cl_git_pass(git_revparse_single(&target, repo, raw_head_oid));
+
+ cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL, NULL));
+
+ cl_git_pass(git_reference_name_to_id(&oid, repo, "HEAD"));
+ cl_git_pass(git_oid_streq(&oid, raw_head_oid));
+}
+
+void test_reset_soft__resetting_to_a_tag_sets_the_Head_to_the_peeled_commit(void)
+{
+ git_oid oid;
+
+ /* b25fa35 is a tag, pointing to another tag which points to commit e90810b */
+ cl_git_pass(git_revparse_single(&target, repo, "b25fa35"));
+
+ cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL, NULL));
+
+ cl_assert(git_repository_head_detached(repo) == false);
+ cl_git_pass(git_reference_name_to_id(&oid, repo, "HEAD"));
+ cl_git_pass(git_oid_streq(&oid, KNOWN_COMMIT_IN_BARE_REPO));
+}
+
+void test_reset_soft__cannot_reset_to_a_tag_not_pointing_at_a_commit(void)
+{
+ /* 53fc32d is the tree of commit e90810b */
+ cl_git_pass(git_revparse_single(&target, repo, "53fc32d"));
+
+ cl_git_fail(git_reset(repo, target, GIT_RESET_SOFT, NULL, NULL));
+ git_object_free(target);
+
+ /* 521d87c is an annotated tag pointing to a blob */
+ cl_git_pass(git_revparse_single(&target, repo, "521d87c"));
+ cl_git_fail(git_reset(repo, target, GIT_RESET_SOFT, NULL, NULL));
+}
+
+void test_reset_soft__resetting_against_an_unborn_head_repo_makes_the_head_no_longer_unborn(void)
+{
+ git_reference *head;
+
+ cl_git_pass(git_revparse_single(&target, repo, KNOWN_COMMIT_IN_BARE_REPO));
+
+ make_head_unborn(repo, NON_EXISTING_HEAD);
+
+ cl_assert_equal_i(true, git_repository_head_unborn(repo));
+
+ cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL, NULL));
+
+ cl_assert_equal_i(false, git_repository_head_unborn(repo));
+
+ cl_git_pass(git_reference_lookup(&head, repo, NON_EXISTING_HEAD));
+ cl_assert_equal_i(0, git_oid_streq(git_reference_target(head), KNOWN_COMMIT_IN_BARE_REPO));
+
+ 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, NULL, NULL));
+ 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_revparse_single(&target, repo, KNOWN_COMMIT_IN_BARE_REPO));
+
+ cl_assert_equal_i(GIT_EUNMERGED, git_reset(repo, target, GIT_RESET_SOFT, NULL, NULL));
+ cl_git_pass(p_unlink(git_buf_cstr(&merge_head_path)));
+
+ git_buf_free(&merge_head_path);
+}
+
+void test_reset_soft__fails_when_index_contains_conflicts_independently_of_MERGE_HEAD_file_existence(void)
+{
+ git_index *index;
+ git_reference *head;
+ git_buf merge_head_path = GIT_BUF_INIT;
+
+ cl_git_sandbox_cleanup();
+
+ repo = cl_git_sandbox_init("mergedrepo");
+
+ cl_git_pass(git_buf_joinpath(&merge_head_path, git_repository_path(repo), "MERGE_HEAD"));
+ cl_git_pass(p_unlink(git_buf_cstr(&merge_head_path)));
+ git_buf_free(&merge_head_path);
+
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_assert_equal_i(true, git_index_has_conflicts(index));
+ git_index_free(index);
+
+ cl_git_pass(git_repository_head(&head, repo));
+ cl_git_pass(git_reference_peel(&target, head, GIT_OBJ_COMMIT));
+ git_reference_free(head);
+
+ cl_assert_equal_i(GIT_EUNMERGED, git_reset(repo, target, GIT_RESET_SOFT, NULL, NULL));
+}
+
+void test_reset_soft_reflog_is_correct(void)
+{
+ const char *exp_msg = "commit: Updating test data so we can test inter-hunk-context";
+
+ reflog_check(repo, "HEAD", 9, "yoram.harmelin@gmail.com", exp_msg);
+ reflog_check(repo, "refs/heads/master", 9, "yoram.harmelin@gmail.com", exp_msg);
+
+ /* Branch not moving, no reflog entry */
+ cl_git_pass(git_revparse_single(&target, repo, "HEAD^{commit}"));
+ cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL, NULL));
+ reflog_check(repo, "HEAD", 9, "yoram.harmelin@gmail.com", exp_msg);
+ reflog_check(repo, "refs/heads/master", 9, "yoram.harmelin@gmail.com", exp_msg);
+
+ /* Moved branch, expect default message */
+ cl_git_pass(git_revparse_single(&target, repo, "HEAD~^{commit}"));
+ cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL, NULL));
+ reflog_check(repo, "HEAD", 9, "yoram.harmelin@gmail.com", exp_msg);
+ reflog_check(repo, "refs/heads/master", 10, NULL, "reset: moving");
+
+ /* Moved branch, expect custom message */
+ cl_git_pass(git_revparse_single(&target, repo, "HEAD~^{commit}"));
+ cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT, NULL, "message1"));
+ reflog_check(repo, "HEAD", 9, "yoram.harmelin@gmail.com", exp_msg);
+ reflog_check(repo, "refs/heads/master", 11, NULL, "message1");
+}
diff --git a/tests/resources/.gitattributes b/tests/resources/.gitattributes
new file mode 100644
index 000000000..556f8c827
--- /dev/null
+++ b/tests/resources/.gitattributes
@@ -0,0 +1 @@
+* binary
diff --git a/tests/resources/attr/.gitted/HEAD b/tests/resources/attr/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/attr/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/attr/.gitted/config b/tests/resources/attr/.gitted/config
new file mode 100644
index 000000000..af107929f
--- /dev/null
+++ b/tests/resources/attr/.gitted/config
@@ -0,0 +1,6 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
diff --git a/tests/resources/attr/.gitted/description b/tests/resources/attr/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/attr/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/attr/.gitted/index b/tests/resources/attr/.gitted/index
new file mode 100644
index 000000000..439ffb151
--- /dev/null
+++ b/tests/resources/attr/.gitted/index
Binary files differ
diff --git a/tests/resources/attr/.gitted/info/attributes b/tests/resources/attr/.gitted/info/attributes
new file mode 100644
index 000000000..5fe62a37a
--- /dev/null
+++ b/tests/resources/attr/.gitted/info/attributes
@@ -0,0 +1,4 @@
+* repoattr
+a* foo !bar -baz
+sub/*.txt reposub
+sub/sub/*.txt reposubsub
diff --git a/tests/resources/attr/.gitted/info/exclude b/tests/resources/attr/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/attr/.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/resources/attr/.gitted/logs/HEAD b/tests/resources/attr/.gitted/logs/HEAD
new file mode 100644
index 000000000..8ece39f37
--- /dev/null
+++ b/tests/resources/attr/.gitted/logs/HEAD
@@ -0,0 +1,9 @@
+0000000000000000000000000000000000000000 6bab5c79cd5140d0f800917f550eb2a3dc32b0da Russell Belfer <arrbee@arrbee.com> 1324416995 -0800 commit (initial): initial test data
+6bab5c79cd5140d0f800917f550eb2a3dc32b0da 605812ab7fe421fdd325a935d35cb06a9234a7d7 Russell Belfer <arrbee@arrbee.com> 1325143098 -0800 commit: latest test updates
+605812ab7fe421fdd325a935d35cb06a9234a7d7 a5d76cad53f66f1312bd995909a5bab3c0820770 Russell Belfer <arrbee@arrbee.com> 1325281762 -0800 commit: more macro tests
+a5d76cad53f66f1312bd995909a5bab3c0820770 370fe9ec224ce33e71f9e5ec2bd1142ce9937a6a Russell Belfer <arrbee@arrbee.com> 1327611749 -0800 commit: Updating files so we can do diffs
+370fe9ec224ce33e71f9e5ec2bd1142ce9937a6a f5b0af1fb4f5c0cd7aad880711d368a07333c307 Russell Belfer <arrbee@arrbee.com> 1327621027 -0800 commit: Updating test data
+f5b0af1fb4f5c0cd7aad880711d368a07333c307 a97cc019851d401a4f1d091cb91a15890a0dd1ba Russell Belfer <arrbee@arrbee.com> 1328653313 -0800 commit: Some whitespace only changes for testing purposes
+a97cc019851d401a4f1d091cb91a15890a0dd1ba 217878ab49e1314388ea2e32dc6fdb58a1b969e0 Russell Belfer <arrbee@arrbee.com> 1332734901 -0700 commit: added files in sub/sub
+217878ab49e1314388ea2e32dc6fdb58a1b969e0 24fa9a9fc4e202313e24b648087495441dab432b Russell Belfer <arrbee@arrbee.com> 1332735555 -0700 commit: adding more files in sub for tree status
+24fa9a9fc4e202313e24b648087495441dab432b 8d0b9df9bd30be7910ddda60548d485bc302b911 yorah <yoram.harmelin@gmail.com> 1341230701 +0200 commit: Updating test data so we can test inter-hunk-context
diff --git a/tests/resources/attr/.gitted/logs/refs/heads/master b/tests/resources/attr/.gitted/logs/refs/heads/master
new file mode 100644
index 000000000..8ece39f37
--- /dev/null
+++ b/tests/resources/attr/.gitted/logs/refs/heads/master
@@ -0,0 +1,9 @@
+0000000000000000000000000000000000000000 6bab5c79cd5140d0f800917f550eb2a3dc32b0da Russell Belfer <arrbee@arrbee.com> 1324416995 -0800 commit (initial): initial test data
+6bab5c79cd5140d0f800917f550eb2a3dc32b0da 605812ab7fe421fdd325a935d35cb06a9234a7d7 Russell Belfer <arrbee@arrbee.com> 1325143098 -0800 commit: latest test updates
+605812ab7fe421fdd325a935d35cb06a9234a7d7 a5d76cad53f66f1312bd995909a5bab3c0820770 Russell Belfer <arrbee@arrbee.com> 1325281762 -0800 commit: more macro tests
+a5d76cad53f66f1312bd995909a5bab3c0820770 370fe9ec224ce33e71f9e5ec2bd1142ce9937a6a Russell Belfer <arrbee@arrbee.com> 1327611749 -0800 commit: Updating files so we can do diffs
+370fe9ec224ce33e71f9e5ec2bd1142ce9937a6a f5b0af1fb4f5c0cd7aad880711d368a07333c307 Russell Belfer <arrbee@arrbee.com> 1327621027 -0800 commit: Updating test data
+f5b0af1fb4f5c0cd7aad880711d368a07333c307 a97cc019851d401a4f1d091cb91a15890a0dd1ba Russell Belfer <arrbee@arrbee.com> 1328653313 -0800 commit: Some whitespace only changes for testing purposes
+a97cc019851d401a4f1d091cb91a15890a0dd1ba 217878ab49e1314388ea2e32dc6fdb58a1b969e0 Russell Belfer <arrbee@arrbee.com> 1332734901 -0700 commit: added files in sub/sub
+217878ab49e1314388ea2e32dc6fdb58a1b969e0 24fa9a9fc4e202313e24b648087495441dab432b Russell Belfer <arrbee@arrbee.com> 1332735555 -0700 commit: adding more files in sub for tree status
+24fa9a9fc4e202313e24b648087495441dab432b 8d0b9df9bd30be7910ddda60548d485bc302b911 yorah <yoram.harmelin@gmail.com> 1341230701 +0200 commit: Updating test data so we can test inter-hunk-context
diff --git a/tests/resources/attr/.gitted/objects/10/8bb4e7fd7b16490dc33ff7d972151e73d7166e b/tests/resources/attr/.gitted/objects/10/8bb4e7fd7b16490dc33ff7d972151e73d7166e
new file mode 100644
index 000000000..edcf7520c
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/10/8bb4e7fd7b16490dc33ff7d972151e73d7166e
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/16/983da6643656bb44c43965ecb6855c6d574512 b/tests/resources/attr/.gitted/objects/16/983da6643656bb44c43965ecb6855c6d574512
new file mode 100644
index 000000000..e49c94acd
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/16/983da6643656bb44c43965ecb6855c6d574512
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/21/7878ab49e1314388ea2e32dc6fdb58a1b969e0 b/tests/resources/attr/.gitted/objects/21/7878ab49e1314388ea2e32dc6fdb58a1b969e0
new file mode 100644
index 000000000..b537899f2
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/21/7878ab49e1314388ea2e32dc6fdb58a1b969e0
@@ -0,0 +1,4 @@
+xŽQ
+Â0DýÎ)öên“ØDÄ#xƒmvƒ…ÖJ’Þ߀7ðcx0¼IۺΠ­¨‚óž-¹ÌÁñ+e"¼vù‚Á‡œâ˜ùpÑwŽcJH1x‡Ô%Œ”¦HL>Dd¡‰ ïíµxîµê²ÀC—¬®\ʤzÿᔶõdí0Z‘àˆ#¢émÿغþÏÚ°ˆ
+äyÑ
+óê>{Ì–qK² \ No newline at end of file
diff --git a/tests/resources/attr/.gitted/objects/24/fa9a9fc4e202313e24b648087495441dab432b b/tests/resources/attr/.gitted/objects/24/fa9a9fc4e202313e24b648087495441dab432b
new file mode 100644
index 000000000..e7099bbaa
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/24/fa9a9fc4e202313e24b648087495441dab432b
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/29/29de282ce999e95183aedac6451d3384559c4b b/tests/resources/attr/.gitted/objects/29/29de282ce999e95183aedac6451d3384559c4b
new file mode 100644
index 000000000..ad84f0854
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/29/29de282ce999e95183aedac6451d3384559c4b
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/2b/40c5aca159b04ea8d20ffe36cdf8b09369b14a b/tests/resources/attr/.gitted/objects/2b/40c5aca159b04ea8d20ffe36cdf8b09369b14a
new file mode 100644
index 000000000..0e2368069
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/2b/40c5aca159b04ea8d20ffe36cdf8b09369b14a
@@ -0,0 +1 @@
+xmPÑj„0ì«ùŠ=úP8Z…ÞS¾¤”c£ñ hR6{=¼¯obâ™Ò"šÙafvŒšœ‚÷×æéä#3‰ά=7Pÿ%[8<Heì`&]@?aFZª®ªç@!…÷.ÊÈù:±ù½§…•ldðÚLG­|K’7~XÃN8¤˜·IÏdìê}¿øˆqŸó2cG¾œ7l¾5ÔV_pEûŸ®’#lZ´ŽGMòt[JÌÈͽ¥&©h¸±uÌ][‰Öß4‰-3;ËCˆg˜4¿x`ZÀ»YÃŒ“錻ú€bÝ^>ï yNlÍ£¡>c¯;gÓ•¥kÇYXÄ9b|Dª~VØ—)…v¿øñÎÜ• \ No newline at end of file
diff --git a/tests/resources/attr/.gitted/objects/2c/66e14f77196ea763fb1e41612c1aa2bc2d8ed2 b/tests/resources/attr/.gitted/objects/2c/66e14f77196ea763fb1e41612c1aa2bc2d8ed2
new file mode 100644
index 000000000..4b75d50eb
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/2c/66e14f77196ea763fb1e41612c1aa2bc2d8ed2
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/2d/e7dfe3588f3c7e9ad59e7d50ba90e3329df9d9 b/tests/resources/attr/.gitted/objects/2d/e7dfe3588f3c7e9ad59e7d50ba90e3329df9d9
new file mode 100644
index 000000000..e0fd0468e
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/2d/e7dfe3588f3c7e9ad59e7d50ba90e3329df9d9
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/37/0fe9ec224ce33e71f9e5ec2bd1142ce9937a6a b/tests/resources/attr/.gitted/objects/37/0fe9ec224ce33e71f9e5ec2bd1142ce9937a6a
new file mode 100644
index 000000000..9c37c5946
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/37/0fe9ec224ce33e71f9e5ec2bd1142ce9937a6a
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/3a/6df026462ebafe455af9867d27eda20a9e0974 b/tests/resources/attr/.gitted/objects/3a/6df026462ebafe455af9867d27eda20a9e0974
new file mode 100644
index 000000000..c74add826
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/3a/6df026462ebafe455af9867d27eda20a9e0974
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/3b/74db7ab381105dc0d28f8295a77f6a82989292 b/tests/resources/attr/.gitted/objects/3b/74db7ab381105dc0d28f8295a77f6a82989292
new file mode 100644
index 000000000..e5cef35fa
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/3b/74db7ab381105dc0d28f8295a77f6a82989292
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/3e/42ffc54a663f9401cc25843d6c0e71a33e4249 b/tests/resources/attr/.gitted/objects/3e/42ffc54a663f9401cc25843d6c0e71a33e4249
new file mode 100644
index 000000000..091d79b14
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/3e/42ffc54a663f9401cc25843d6c0e71a33e4249
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/45/141a79a77842c59a63229403220a4e4be74e3d b/tests/resources/attr/.gitted/objects/45/141a79a77842c59a63229403220a4e4be74e3d
new file mode 100644
index 000000000..5b58ef024
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/45/141a79a77842c59a63229403220a4e4be74e3d
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/45/5a314fa848d52ae1f11d254da4f60858fc97f4 b/tests/resources/attr/.gitted/objects/45/5a314fa848d52ae1f11d254da4f60858fc97f4
new file mode 100644
index 000000000..f90f0d79c
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/45/5a314fa848d52ae1f11d254da4f60858fc97f4
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 b/tests/resources/attr/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057
new file mode 100644
index 000000000..7ca4ceed5
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/4d/713dc48e6b1bd75b0d61ad078ba9ca3a56745d b/tests/resources/attr/.gitted/objects/4d/713dc48e6b1bd75b0d61ad078ba9ca3a56745d
new file mode 100644
index 000000000..eb1e8d0c5
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/4d/713dc48e6b1bd75b0d61ad078ba9ca3a56745d
@@ -0,0 +1,2 @@
+xÁÁ € @ßWŶàÇ
+|ø§k 9n$¡}gŠ«à:‡îÂ;5°1¥e–4ˆ\k_]‘ÞƒŸÙ­hœD¡k›ý'~ \ No newline at end of file
diff --git a/tests/resources/attr/.gitted/objects/4e/49ba8c5b6c32ff28cd9dcb60be34df50fcc485 b/tests/resources/attr/.gitted/objects/4e/49ba8c5b6c32ff28cd9dcb60be34df50fcc485
new file mode 100644
index 000000000..6fcc549b4
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/4e/49ba8c5b6c32ff28cd9dcb60be34df50fcc485
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/55/6f8c827b8e4a02ad5cab77dca2bcb3e226b0b3 b/tests/resources/attr/.gitted/objects/55/6f8c827b8e4a02ad5cab77dca2bcb3e226b0b3
new file mode 100644
index 000000000..4bcff1faa
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/55/6f8c827b8e4a02ad5cab77dca2bcb3e226b0b3
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/58/19a185d77b03325aaf87cafc771db36f6ddca7 b/tests/resources/attr/.gitted/objects/58/19a185d77b03325aaf87cafc771db36f6ddca7
new file mode 100644
index 000000000..fe34eb63a
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/58/19a185d77b03325aaf87cafc771db36f6ddca7
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/60/5812ab7fe421fdd325a935d35cb06a9234a7d7 b/tests/resources/attr/.gitted/objects/60/5812ab7fe421fdd325a935d35cb06a9234a7d7
new file mode 100644
index 000000000..b0cc51ee6
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/60/5812ab7fe421fdd325a935d35cb06a9234a7d7
@@ -0,0 +1,2 @@
+xNÛ Â0ã;SÜ Ë»•BŒÀ—ËU ¥´JÓý °?¶lÙ–y™çgcáU RbaâÙcG;¸l²ã DqÖ Z©Ê«AH”<Ç‘³×3Nâ¨ãä=J2d3[“0“¢½=–
+÷}Û¤¸I™¤Â™jM"×x™/ ­é[ÇŽØçTwûÇÖãÿ´U¡&[ƒ/ìkþ(õtJL \ No newline at end of file
diff --git a/tests/resources/attr/.gitted/objects/6b/ab5c79cd5140d0f800917f550eb2a3dc32b0da b/tests/resources/attr/.gitted/objects/6b/ab5c79cd5140d0f800917f550eb2a3dc32b0da
new file mode 100644
index 000000000..f51e11ccc
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/6b/ab5c79cd5140d0f800917f550eb2a3dc32b0da
@@ -0,0 +1,3 @@
+xÛ Ã0 Eûí)´@‹d'~@(¥#tÅQ¨ÁiÀQö¯¡ôëÂánÞ·­(Pôm"Ř2æh°s L+d{—"{Zœ“`øÔ÷Þàu‡Ô
+O©«4˜¸µYäñ›[Þ·;³Ã@>¥®M§ýS»þOmʧhá
+*‡ÂÂÊæ ¿<- \ No newline at end of file
diff --git a/tests/resources/attr/.gitted/objects/6d/968d62c89c7d9ea23a4c9a7b665d017c3d8ffd b/tests/resources/attr/.gitted/objects/6d/968d62c89c7d9ea23a4c9a7b665d017c3d8ffd
new file mode 100644
index 000000000..e832241c9
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/6d/968d62c89c7d9ea23a4c9a7b665d017c3d8ffd
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/71/7fc31f6b84f9d6fc3a4edbca259d7fc92beee2 b/tests/resources/attr/.gitted/objects/71/7fc31f6b84f9d6fc3a4edbca259d7fc92beee2
new file mode 100644
index 000000000..a80265cac
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/71/7fc31f6b84f9d6fc3a4edbca259d7fc92beee2
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/8d/0b9df9bd30be7910ddda60548d485bc302b911 b/tests/resources/attr/.gitted/objects/8d/0b9df9bd30be7910ddda60548d485bc302b911
new file mode 100644
index 000000000..3dcf088e4
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/8d/0b9df9bd30be7910ddda60548d485bc302b911
@@ -0,0 +1 @@
+xŽKj1D³Ö)zolôiõŒ _"hiÚK2²L’ÛG!7Ȫ¯¨ÔJÉ,ù—ÑEÀPXÝÆDèÈSŒˆ ] /)Òê}¢Í/èUwîR§ˆ. Åj댋‘pÕë‚Á#š#:?ÇÞ:|·Î;¼þF9íÜ‹Ür=_ çÛ)µòÆ¡±N/ÚÀA[­ÕlçÃ!ÿqÕû}ã‘ë†<Lfx4øH\ÿº\çôqÖcj“¿†úƒTè \ No newline at end of file
diff --git a/tests/resources/attr/.gitted/objects/93/61f40bb97239cf55811892e14de2e344168ba1 b/tests/resources/attr/.gitted/objects/93/61f40bb97239cf55811892e14de2e344168ba1
new file mode 100644
index 000000000..4b57836cd
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/93/61f40bb97239cf55811892e14de2e344168ba1
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/94/da4faa0a6bfb8ee6ccf7153801a69202b31857 b/tests/resources/attr/.gitted/objects/94/da4faa0a6bfb8ee6ccf7153801a69202b31857
new file mode 100644
index 000000000..a9ddf5d20
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/94/da4faa0a6bfb8ee6ccf7153801a69202b31857
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/96/089fd31ce1d3ee2afb0ba09ba063066932f027 b/tests/resources/attr/.gitted/objects/96/089fd31ce1d3ee2afb0ba09ba063066932f027
new file mode 100644
index 000000000..efa62f912
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/96/089fd31ce1d3ee2afb0ba09ba063066932f027
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/99/eae476896f4907224978b88e5ecaa6c5bb67a9 b/tests/resources/attr/.gitted/objects/99/eae476896f4907224978b88e5ecaa6c5bb67a9
new file mode 100644
index 000000000..8f5acc70a
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/99/eae476896f4907224978b88e5ecaa6c5bb67a9
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/9e/5bdc47d6a80f2be0ea3049ad74231b94609242 b/tests/resources/attr/.gitted/objects/9e/5bdc47d6a80f2be0ea3049ad74231b94609242
new file mode 100644
index 000000000..d6385ec8d
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/9e/5bdc47d6a80f2be0ea3049ad74231b94609242
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/9f/b40b6675dde60b5697afceae91b66d908c02d9 b/tests/resources/attr/.gitted/objects/9f/b40b6675dde60b5697afceae91b66d908c02d9
new file mode 100644
index 000000000..7663ad0ad
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/9f/b40b6675dde60b5697afceae91b66d908c02d9
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/a0/f7217ae99f5ac3e88534f5cea267febc5fa85b b/tests/resources/attr/.gitted/objects/a0/f7217ae99f5ac3e88534f5cea267febc5fa85b
new file mode 100644
index 000000000..985c2e281
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/a0/f7217ae99f5ac3e88534f5cea267febc5fa85b
@@ -0,0 +1 @@
+x5Ž1Â0 E™}Š?–΀;•˜SâˆÔ®’”ŠÛ“Ðv´ýߢ8ŸO‡'FÈÈ:2r™ƒ)(¾ &¢Þ·«×9Z¼A Âð³¼Ñ¹r9Ýl¬ %¨˜ˆ„3ÑEo‚£.ÿV­Õi<Bñà F­©MÌb‰®+ÂÙŸ*vµªÛþìÖmõ÷¾¢ÞLK†Ý­D?+­N \ No newline at end of file
diff --git a/tests/resources/attr/.gitted/objects/a5/6bbcecaeac760cc26239384d2d4c614e7e4320 b/tests/resources/attr/.gitted/objects/a5/6bbcecaeac760cc26239384d2d4c614e7e4320
new file mode 100644
index 000000000..d898ae9b8
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/a5/6bbcecaeac760cc26239384d2d4c614e7e4320
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/a5/d76cad53f66f1312bd995909a5bab3c0820770 b/tests/resources/attr/.gitted/objects/a5/d76cad53f66f1312bd995909a5bab3c0820770
new file mode 100644
index 000000000..cd6a389f9
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/a5/d76cad53f66f1312bd995909a5bab3c0820770
@@ -0,0 +1,4 @@
+xŽ]
+‚!E{v³B>!"ZB;u¤à3Cmÿ í § ‡{.7µZŸ4âavfÈÖgBLÊEeP;NQÚ¬BŒLAnŲIÆç ÞÔù5ÁI»)MÑ6Z•œQ[
+h3Úe:
+ ùì}æ£u¸Æà}‡ï…;œ©÷È|ýÅ)µzµ&ô¦¼Óp”›”bÑõq®ú?¶¨­3TJ½Áä1‡ø3ÙJX \ No newline at end of file
diff --git a/tests/resources/attr/.gitted/objects/a9/7cc019851d401a4f1d091cb91a15890a0dd1ba b/tests/resources/attr/.gitted/objects/a9/7cc019851d401a4f1d091cb91a15890a0dd1ba
new file mode 100644
index 000000000..1a7ec0c55
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/a9/7cc019851d401a4f1d091cb91a15890a0dd1ba
@@ -0,0 +1,2 @@
+xŽQjÄ0 DûíSè[ähc;PJéÚ(²¼ $q°–Þ¾†Þ _3oàIÞ÷µÁàÜK+ªàâäBtƒ„I|œ”â»LìgçÆˆÖ ÅR4'=¤qFN6Í÷4
+JôÌ1ôÖFrÑ‘zÃW[r¯«VÝ6øÔ-i7.eVýø‹WÉû;X‚‰,Á ¢émwlÿÏÛ|ç]ṬMëÉ¢ídáã¡RwêC[œW9sÕj~’Wy \ No newline at end of file
diff --git a/tests/resources/attr/.gitted/objects/b4/35cd5689a0fb54afbeda4ac20368aa480e8f04 b/tests/resources/attr/.gitted/objects/b4/35cd5689a0fb54afbeda4ac20368aa480e8f04
new file mode 100644
index 000000000..ffe3473f4
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/b4/35cd5689a0fb54afbeda4ac20368aa480e8f04
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/c0/091889c0c77142b87a1fa5123a6398a61d33e7 b/tests/resources/attr/.gitted/objects/c0/091889c0c77142b87a1fa5123a6398a61d33e7
new file mode 100644
index 000000000..11dc63c79
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/c0/091889c0c77142b87a1fa5123a6398a61d33e7
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/c4/85abe35abd4aa6fd83b076a78bbea9e2e7e06c b/tests/resources/attr/.gitted/objects/c4/85abe35abd4aa6fd83b076a78bbea9e2e7e06c
new file mode 100644
index 000000000..58569ca0e
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/c4/85abe35abd4aa6fd83b076a78bbea9e2e7e06c
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/c7/aadd770d5907a8475c29e9ee21a27b88bf675d b/tests/resources/attr/.gitted/objects/c7/aadd770d5907a8475c29e9ee21a27b88bf675d
new file mode 100644
index 000000000..39aedb7d9
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/c7/aadd770d5907a8475c29e9ee21a27b88bf675d
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/c9/6bbb2c2557a8325ae1559e3ba79cdcecb23076 b/tests/resources/attr/.gitted/objects/c9/6bbb2c2557a8325ae1559e3ba79cdcecb23076
new file mode 100644
index 000000000..589f9ad31
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/c9/6bbb2c2557a8325ae1559e3ba79cdcecb23076
@@ -0,0 +1,2 @@
+x5A
+Â0D]ÿSÌεèoàÂuJ~L0ýͯ¡··)¸xÃcfªœp¹]OOΊcñB µ˜6‘»!뢘´²Ã³‚{,áU<C¿j˜¹[ÁE-ŠÜ-¢Ò˜ð&#š¯)~ëÇäÆ=˜;;{.öùe"3A \ No newline at end of file
diff --git a/tests/resources/attr/.gitted/objects/ce/39a97a7fb1fa90bcf5e711249c1e507476ae0e b/tests/resources/attr/.gitted/objects/ce/39a97a7fb1fa90bcf5e711249c1e507476ae0e
new file mode 100644
index 000000000..1005f944a
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/ce/39a97a7fb1fa90bcf5e711249c1e507476ae0e
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/d5/7da33c16b14326ecb05d19bbea908f5e4c47d9 b/tests/resources/attr/.gitted/objects/d5/7da33c16b14326ecb05d19bbea908f5e4c47d9
new file mode 100644
index 000000000..b96d40c24
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/d5/7da33c16b14326ecb05d19bbea908f5e4c47d9
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/d8/00886d9c86731ae5c4a62b0b77c437015e00d2 b/tests/resources/attr/.gitted/objects/d8/00886d9c86731ae5c4a62b0b77c437015e00d2
new file mode 100644
index 000000000..83f3b726d
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/d8/00886d9c86731ae5c4a62b0b77c437015e00d2
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/dc/cada462d3df8ac6de596fb8c896aba9344f941 b/tests/resources/attr/.gitted/objects/dc/cada462d3df8ac6de596fb8c896aba9344f941
new file mode 100644
index 000000000..ef62f8b9d
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/dc/cada462d3df8ac6de596fb8c896aba9344f941
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/de/863bff4976c9ed7e17a4da0fd524908dc84049 b/tests/resources/attr/.gitted/objects/de/863bff4976c9ed7e17a4da0fd524908dc84049
new file mode 100644
index 000000000..7d9b8551f
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/de/863bff4976c9ed7e17a4da0fd524908dc84049
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/e5/63cf4758f0d646f1b14b76016aa17fa9e549a4 b/tests/resources/attr/.gitted/objects/e5/63cf4758f0d646f1b14b76016aa17fa9e549a4
new file mode 100644
index 000000000..1bc1f0f0b
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/e5/63cf4758f0d646f1b14b76016aa17fa9e549a4
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/ec/b97df2a174987475ac816e3847fc8e9f6c596b b/tests/resources/attr/.gitted/objects/ec/b97df2a174987475ac816e3847fc8e9f6c596b
new file mode 100644
index 000000000..44d703b2e
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/ec/b97df2a174987475ac816e3847fc8e9f6c596b
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/ed/f3dcee4003d71f139777898882ccd097e34c53 b/tests/resources/attr/.gitted/objects/ed/f3dcee4003d71f139777898882ccd097e34c53
new file mode 100644
index 000000000..d28184670
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/ed/f3dcee4003d71f139777898882ccd097e34c53
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/f2/c6d717cf4a5a3e6b02684155ab07b766982165 b/tests/resources/attr/.gitted/objects/f2/c6d717cf4a5a3e6b02684155ab07b766982165
new file mode 100644
index 000000000..27a25dc86
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/f2/c6d717cf4a5a3e6b02684155ab07b766982165
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/f5/b0af1fb4f5c0cd7aad880711d368a07333c307 b/tests/resources/attr/.gitted/objects/f5/b0af1fb4f5c0cd7aad880711d368a07333c307
new file mode 100644
index 000000000..21faeb8a2
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/f5/b0af1fb4f5c0cd7aad880711d368a07333c307
@@ -0,0 +1,2 @@
+xN[j1 Ì·O¡ 4ÈRbÇPJÈ
+=€Ö;NûÂë½ ½A?†y 1“×y~7½žZ(¾¥2ªÏð£beàÁ8uå’Ja‰n³Š¥‘F.HÈ"— UD_®Ý£÷ÉHI£sv´×ZéûØwL=0Tú´ZàþGç¼Î_äUbßKèƒoÌ®§}cëçÿùv?Ûhí½<©aoÔµ¹_áEK \ No newline at end of file
diff --git a/tests/resources/attr/.gitted/objects/fb/5067b1aef3ac1ada4b379dbcb7d17255df7d78 b/tests/resources/attr/.gitted/objects/fb/5067b1aef3ac1ada4b379dbcb7d17255df7d78
new file mode 100644
index 000000000..6c8ff837e
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/fb/5067b1aef3ac1ada4b379dbcb7d17255df7d78
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/fe/773770c5a6cc7185580c9204b1ff18a33ff3fc b/tests/resources/attr/.gitted/objects/fe/773770c5a6cc7185580c9204b1ff18a33ff3fc
new file mode 100644
index 000000000..e6fcbc0b3
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/fe/773770c5a6cc7185580c9204b1ff18a33ff3fc
@@ -0,0 +1 @@
+x5ŽAÂ0 9ûû„xBÜAâœG¤vÕ¤Tüž¤Ð£åõÙ<ûãîÂ#¥Î1ÂUT釛*ÑMúWlÎOCR˜2dÖѵC.„T“©ËÈI¹lQH/öœmYÛ¬UN[àžª€ß¬¬¥þBÖ@t¶Üð8~˜†Õ‹¿}}R#Ä#kAØdD_=-H– \ No newline at end of file
diff --git a/tests/resources/attr/.gitted/objects/ff/69f8639ce2e6010b3f33a74160aad98b48da2b b/tests/resources/attr/.gitted/objects/ff/69f8639ce2e6010b3f33a74160aad98b48da2b
new file mode 100644
index 000000000..b736c0b2b
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/ff/69f8639ce2e6010b3f33a74160aad98b48da2b
Binary files differ
diff --git a/tests/resources/attr/.gitted/refs/heads/master b/tests/resources/attr/.gitted/refs/heads/master
new file mode 100644
index 000000000..b3abfff7d
--- /dev/null
+++ b/tests/resources/attr/.gitted/refs/heads/master
@@ -0,0 +1 @@
+8d0b9df9bd30be7910ddda60548d485bc302b911
diff --git a/tests/resources/attr/attr0 b/tests/resources/attr/attr0
new file mode 100644
index 000000000..556f8c827
--- /dev/null
+++ b/tests/resources/attr/attr0
@@ -0,0 +1 @@
+* binary
diff --git a/tests/resources/attr/attr1 b/tests/resources/attr/attr1
new file mode 100644
index 000000000..3b74db7ab
--- /dev/null
+++ b/tests/resources/attr/attr1
@@ -0,0 +1,29 @@
+# a comment followed by some blank lines
+
+
+
+ # another comment that is indented
+
+# variations on fnmatch
+
+pat0 attr0
+!pat1 attr1
+pat2/ attr2
+pat3dir/pat3file attr3
+pat4.* attr4
+ *.pat5 attr5
+pat6/pat6/*.pat6 attr6
+
+pat7[a-e]??[xyz] attr7 # with a comment on the line
+
+pat8\ with\ spaces attr8
+
+ invalid # attr with no assignments doesn't count
+
+also/invalid
+
+invalid.again/
+
+# next attr is at eof
+
+ pat9 attr9 \ No newline at end of file
diff --git a/tests/resources/attr/attr2 b/tests/resources/attr/attr2
new file mode 100644
index 000000000..2c66e14f7
--- /dev/null
+++ b/tests/resources/attr/attr2
@@ -0,0 +1,21 @@
+
+# variations on assignments
+
+pat0 simple
+pat1 -neg
+* notundef
+pat2 !notundef
+pat3 assigned=test-value
+pat4 rule-with-more-chars=value-with-more-chars
+pat5 empty=
+pat6 -negempty=
+pat7 multiple -single values=1 also=a-really-long-value/* happy=yes!
+# the next line has trailing spaces
+pat8 again= another=12321
+patbad0 # empty assignment does not count
+# next line will be another simple empty assign that should not count
+ patbad1
+
+# BTW I think there are 11 valid rules and two "invalid" empty ones
+
+pat9 -at-eof \ No newline at end of file
diff --git a/tests/resources/attr/attr3 b/tests/resources/attr/attr3
new file mode 100644
index 000000000..c485abe35
--- /dev/null
+++ b/tests/resources/attr/attr3
@@ -0,0 +1,4 @@
+# These are examples from the git-check-attr.1 man page
+*.java diff=java -crlf myAttr
+NoMyAttr.java !myAttr
+README caveat=unspecified
diff --git a/tests/resources/attr/binfile b/tests/resources/attr/binfile
new file mode 100644
index 000000000..d800886d9
--- /dev/null
+++ b/tests/resources/attr/binfile
@@ -0,0 +1 @@
+123 \ No newline at end of file
diff --git a/tests/resources/attr/dir/file b/tests/resources/attr/dir/file
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/resources/attr/dir/file
diff --git a/tests/resources/attr/file b/tests/resources/attr/file
new file mode 100644
index 000000000..45b983be3
--- /dev/null
+++ b/tests/resources/attr/file
@@ -0,0 +1 @@
+hi
diff --git a/tests/resources/attr/gitattributes b/tests/resources/attr/gitattributes
new file mode 100644
index 000000000..e038983ec
--- /dev/null
+++ b/tests/resources/attr/gitattributes
@@ -0,0 +1,29 @@
+* rootattr
+root_test2 -rootattr
+root_test3 !rootattr
+binfile binary
+abc foo bar baz
+does-not-exist foo=yes
+
+root_test2 multiattr
+root_test3 multi2=foo
+
+root_test3 multiattr=1 multiattr=2 multiattr=3 multi2=abc !multi2
+root_test2 multiattr=string -multiattr
+
+[attr]mymacro positive -negative !rootattr
+macro* mymacro another=77
+
+[attr]macro2 multi2 -multi2 multi3 !multi3 multi3=answer
+macro* macro2 macro2 macro2
+
+# let's try some malicious macro defs
+[attr]firstmacro -thirdmacro -secondmacro
+[attr]secondmacro firstmacro -firstmacro
+[attr]thirdmacro secondmacro=hahaha
+
+macro_bad firstmacro secondmacro thirdmacro
+
+# another test that Peff found was failing
+[attr]notest !test
+
diff --git a/tests/resources/attr/gitignore b/tests/resources/attr/gitignore
new file mode 100644
index 000000000..192967012
--- /dev/null
+++ b/tests/resources/attr/gitignore
@@ -0,0 +1,2 @@
+ign
+dir/
diff --git a/tests/resources/attr/ign b/tests/resources/attr/ign
new file mode 100644
index 000000000..592fd2594
--- /dev/null
+++ b/tests/resources/attr/ign
@@ -0,0 +1 @@
+ignore me
diff --git a/tests/resources/attr/macro_bad b/tests/resources/attr/macro_bad
new file mode 100644
index 000000000..5819a185d
--- /dev/null
+++ b/tests/resources/attr/macro_bad
@@ -0,0 +1 @@
+boo
diff --git a/tests/resources/attr/macro_test b/tests/resources/attr/macro_test
new file mode 100644
index 000000000..ff69f8639
--- /dev/null
+++ b/tests/resources/attr/macro_test
@@ -0,0 +1 @@
+Yo
diff --git a/tests/resources/attr/root_test1 b/tests/resources/attr/root_test1
new file mode 100644
index 000000000..45141a79a
--- /dev/null
+++ b/tests/resources/attr/root_test1
@@ -0,0 +1 @@
+Hello from the root
diff --git a/tests/resources/attr/root_test2 b/tests/resources/attr/root_test2
new file mode 100644
index 000000000..4d713dc48
--- /dev/null
+++ b/tests/resources/attr/root_test2
@@ -0,0 +1,6 @@
+Hello from the root
+
+Some additional lines
+
+Down here below
+
diff --git a/tests/resources/attr/root_test3 b/tests/resources/attr/root_test3
new file mode 100644
index 000000000..108bb4e7f
--- /dev/null
+++ b/tests/resources/attr/root_test3
@@ -0,0 +1,19 @@
+Some additional lines
+
+
+ Down here below the other lines
+
+
+With even more at the end
+
+
+And lots of good stuff
+
+
+Anywhere you want
+
+
+Don't you think
+
+
+
diff --git a/tests/resources/attr/root_test4.txt b/tests/resources/attr/root_test4.txt
new file mode 100644
index 000000000..a0f7217ae
--- /dev/null
+++ b/tests/resources/attr/root_test4.txt
@@ -0,0 +1,14 @@
+Here is some stuff at the start
+
+This should go in one hunk (first)
+
+Some additional lines
+
+Down here below the other lines
+
+With even more at the end
+
+Followed by a second hunk of stuff (second)
+
+That happens down here
+
diff --git a/tests/resources/attr/sub/.gitattributes b/tests/resources/attr/sub/.gitattributes
new file mode 100644
index 000000000..329c1c5b8
--- /dev/null
+++ b/tests/resources/attr/sub/.gitattributes
@@ -0,0 +1,7 @@
+* subattr=yes -negattr
+*.txt another=zero
+sub/*.txt another=one
+ab* merge=filfre
+abc -foo -bar
+*.c frotz
+deep/file deepdeep
diff --git a/tests/resources/attr/sub/abc b/tests/resources/attr/sub/abc
new file mode 100644
index 000000000..3e42ffc54
--- /dev/null
+++ b/tests/resources/attr/sub/abc
@@ -0,0 +1,37 @@
+# Test file from gitattributes(5) example:
+
+If you have these three gitattributes file:
+
+ (in $GIT_DIR/info/attributes)
+
+ a* foo !bar -baz
+
+ (in .gitattributes)
+ abc foo bar baz
+
+ (in t/.gitattributes)
+ ab* merge=filfre
+ abc -foo -bar
+ *.c frotz
+
+the attributes given to path t/abc are computed as follows:
+
+1. By examining t/.gitattributes (which is in the same directory as the path
+ in question), git finds that the first line matches. merge attribute is
+ set. It also finds that the second line matches, and attributes foo and
+ bar are unset.
+2. Then it examines .gitattributes (which is in the parent directory), and
+ finds that the first line matches, but t/.gitattributes file already
+ decided how merge, foo and bar attributes should be given to this path,
+ so it leaves foo and bar unset. Attribute baz is set.
+3. Finally it examines $GIT_DIR/info/attributes. This file is used to
+ override the in-tree settings. The first line is a match, and foo is set,
+ bar is reverted to unspecified state, and baz is unset.
+
+As the result, the attributes assignment to t/abc becomes:
+
+ foo set to true
+ bar unspecified
+ baz set to false
+ merge set to string value "filfre"
+ frotz unspecified
diff --git a/tests/resources/attr/sub/dir/file b/tests/resources/attr/sub/dir/file
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/resources/attr/sub/dir/file
diff --git a/tests/resources/attr/sub/file b/tests/resources/attr/sub/file
new file mode 100644
index 000000000..45b983be3
--- /dev/null
+++ b/tests/resources/attr/sub/file
@@ -0,0 +1 @@
+hi
diff --git a/tests/resources/attr/sub/ign/file b/tests/resources/attr/sub/ign/file
new file mode 100644
index 000000000..4dcd992e1
--- /dev/null
+++ b/tests/resources/attr/sub/ign/file
@@ -0,0 +1 @@
+in ignored dir
diff --git a/tests/resources/attr/sub/ign/sub/file b/tests/resources/attr/sub/ign/sub/file
new file mode 100644
index 000000000..88aca0164
--- /dev/null
+++ b/tests/resources/attr/sub/ign/sub/file
@@ -0,0 +1 @@
+below ignored dir
diff --git a/tests/resources/attr/sub/sub/.gitattributes b/tests/resources/attr/sub/sub/.gitattributes
new file mode 100644
index 000000000..55225e4d6
--- /dev/null
+++ b/tests/resources/attr/sub/sub/.gitattributes
@@ -0,0 +1,3 @@
+d/* test=a/b/d/*
+d/yes notest
+
diff --git a/tests/resources/attr/sub/sub/dir b/tests/resources/attr/sub/sub/dir
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/resources/attr/sub/sub/dir
diff --git a/tests/resources/attr/sub/sub/file b/tests/resources/attr/sub/sub/file
new file mode 100644
index 000000000..45b983be3
--- /dev/null
+++ b/tests/resources/attr/sub/sub/file
@@ -0,0 +1 @@
+hi
diff --git a/tests/resources/attr/sub/sub/subsub.txt b/tests/resources/attr/sub/sub/subsub.txt
new file mode 100644
index 000000000..9e5bdc47d
--- /dev/null
+++ b/tests/resources/attr/sub/sub/subsub.txt
@@ -0,0 +1 @@
+subsub
diff --git a/tests/resources/attr/sub/subdir_test1 b/tests/resources/attr/sub/subdir_test1
new file mode 100644
index 000000000..e563cf475
--- /dev/null
+++ b/tests/resources/attr/sub/subdir_test1
@@ -0,0 +1,2 @@
+Hello from the subdir
+
diff --git a/tests/resources/attr/sub/subdir_test2.txt b/tests/resources/attr/sub/subdir_test2.txt
new file mode 100644
index 000000000..fb5067b1a
--- /dev/null
+++ b/tests/resources/attr/sub/subdir_test2.txt
@@ -0,0 +1 @@
+Hello again
diff --git a/tests/resources/attr_index/.gitted/HEAD b/tests/resources/attr_index/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/attr_index/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/attr_index/.gitted/config b/tests/resources/attr_index/.gitted/config
new file mode 100644
index 000000000..af107929f
--- /dev/null
+++ b/tests/resources/attr_index/.gitted/config
@@ -0,0 +1,6 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
diff --git a/tests/resources/attr_index/.gitted/description b/tests/resources/attr_index/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/attr_index/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/attr_index/.gitted/index b/tests/resources/attr_index/.gitted/index
new file mode 100644
index 000000000..d87480332
--- /dev/null
+++ b/tests/resources/attr_index/.gitted/index
Binary files differ
diff --git a/tests/resources/attr_index/.gitted/info/exclude b/tests/resources/attr_index/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/attr_index/.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/resources/attr_index/.gitted/info/refs b/tests/resources/attr_index/.gitted/info/refs
new file mode 100644
index 000000000..60feca293
--- /dev/null
+++ b/tests/resources/attr_index/.gitted/info/refs
@@ -0,0 +1 @@
+58f7cf825b553ef7c26e5b9f8a23599c1a9ca296 refs/heads/master
diff --git a/tests/resources/attr_index/.gitted/logs/HEAD b/tests/resources/attr_index/.gitted/logs/HEAD
new file mode 100644
index 000000000..ffd298c04
--- /dev/null
+++ b/tests/resources/attr_index/.gitted/logs/HEAD
@@ -0,0 +1,4 @@
+0000000000000000000000000000000000000000 67c1640e91ccbaf0793591be09bf572cf40c9a53 Russell Belfer <rb@github.com> 1335817070 -0700 commit (initial): Initial commit
+67c1640e91ccbaf0793591be09bf572cf40c9a53 d441d7d88f52c28c2b23940ce4c33756748425f9 Russell Belfer <rb@github.com> 1335817296 -0700 commit: Adding some files in subtrees
+d441d7d88f52c28c2b23940ce4c33756748425f9 67c1640e91ccbaf0793591be09bf572cf40c9a53 Russell Belfer <rb@github.com> 1335817353 -0700 HEAD^: updating HEAD
+67c1640e91ccbaf0793591be09bf572cf40c9a53 58f7cf825b553ef7c26e5b9f8a23599c1a9ca296 Russell Belfer <rb@github.com> 1335817372 -0700 commit: Adding subtree data
diff --git a/tests/resources/attr_index/.gitted/logs/refs/heads/master b/tests/resources/attr_index/.gitted/logs/refs/heads/master
new file mode 100644
index 000000000..ffd298c04
--- /dev/null
+++ b/tests/resources/attr_index/.gitted/logs/refs/heads/master
@@ -0,0 +1,4 @@
+0000000000000000000000000000000000000000 67c1640e91ccbaf0793591be09bf572cf40c9a53 Russell Belfer <rb@github.com> 1335817070 -0700 commit (initial): Initial commit
+67c1640e91ccbaf0793591be09bf572cf40c9a53 d441d7d88f52c28c2b23940ce4c33756748425f9 Russell Belfer <rb@github.com> 1335817296 -0700 commit: Adding some files in subtrees
+d441d7d88f52c28c2b23940ce4c33756748425f9 67c1640e91ccbaf0793591be09bf572cf40c9a53 Russell Belfer <rb@github.com> 1335817353 -0700 HEAD^: updating HEAD
+67c1640e91ccbaf0793591be09bf572cf40c9a53 58f7cf825b553ef7c26e5b9f8a23599c1a9ca296 Russell Belfer <rb@github.com> 1335817372 -0700 commit: Adding subtree data
diff --git a/tests/resources/attr_index/.gitted/objects/38/12cfef36615db1788d4e63f90028007e17a348 b/tests/resources/attr_index/.gitted/objects/38/12cfef36615db1788d4e63f90028007e17a348
new file mode 100644
index 000000000..ee2991571
--- /dev/null
+++ b/tests/resources/attr_index/.gitted/objects/38/12cfef36615db1788d4e63f90028007e17a348
@@ -0,0 +1,3 @@
+x•Ž[
+Ã Eûí*Ü@‹ŽPJé
+]€š™&“`Ìþëúw¸œ 'o¥ÌM¸K«D’ ‚q•4¤ÊËì5DFË#šä!!ˆ=VZ›DÏ.³Lˆ†:ƒ%L}ƒ!dCŽ¬ˆg›¶*ßçqвÈ-LUÞkz~ç6é–·òÚ«íàWå”}í}­«ÿ>Åg˾Õ{fžâú%ñ ¡Gò \ No newline at end of file
diff --git a/tests/resources/attr_index/.gitted/objects/59/d942b8be2784bc96db9b22202c10815c9a077b b/tests/resources/attr_index/.gitted/objects/59/d942b8be2784bc96db9b22202c10815c9a077b
new file mode 100644
index 000000000..ff33737db
--- /dev/null
+++ b/tests/resources/attr_index/.gitted/objects/59/d942b8be2784bc96db9b22202c10815c9a077b
@@ -0,0 +1 @@
+x ÃÑ €0 @¿âÍà‡“¸@kR”’@ßÂ]½ã<¶K4±ÜnÕÔÅY‰á)l(a¨hF˜Hcƒcÿ^Ô \ No newline at end of file
diff --git a/tests/resources/attr_index/.gitted/objects/cd/f17ea3fe625ef812f4dce7f423f4f299287505 b/tests/resources/attr_index/.gitted/objects/cd/f17ea3fe625ef812f4dce7f423f4f299287505
new file mode 100644
index 000000000..2a410057e
--- /dev/null
+++ b/tests/resources/attr_index/.gitted/objects/cd/f17ea3fe625ef812f4dce7f423f4f299287505
Binary files differ
diff --git a/tests/resources/attr_index/.gitted/objects/f7/2502ddd01412bb20796ff812af56fd53b82b52 b/tests/resources/attr_index/.gitted/objects/f7/2502ddd01412bb20796ff812af56fd53b82b52
new file mode 100644
index 000000000..048928000
--- /dev/null
+++ b/tests/resources/attr_index/.gitted/objects/f7/2502ddd01412bb20796ff812af56fd53b82b52
Binary files differ
diff --git a/tests/resources/attr_index/.gitted/objects/info/packs b/tests/resources/attr_index/.gitted/objects/info/packs
new file mode 100644
index 000000000..559dc741c
--- /dev/null
+++ b/tests/resources/attr_index/.gitted/objects/info/packs
@@ -0,0 +1,2 @@
+P pack-4e6438607204ce78827e3885594b2c0bb4f13895.pack
+
diff --git a/tests/resources/attr_index/.gitted/objects/pack/pack-4e6438607204ce78827e3885594b2c0bb4f13895.idx b/tests/resources/attr_index/.gitted/objects/pack/pack-4e6438607204ce78827e3885594b2c0bb4f13895.idx
new file mode 100644
index 000000000..fbef4aa1d
--- /dev/null
+++ b/tests/resources/attr_index/.gitted/objects/pack/pack-4e6438607204ce78827e3885594b2c0bb4f13895.idx
Binary files differ
diff --git a/tests/resources/attr_index/.gitted/objects/pack/pack-4e6438607204ce78827e3885594b2c0bb4f13895.pack b/tests/resources/attr_index/.gitted/objects/pack/pack-4e6438607204ce78827e3885594b2c0bb4f13895.pack
new file mode 100644
index 000000000..09c9e06d9
--- /dev/null
+++ b/tests/resources/attr_index/.gitted/objects/pack/pack-4e6438607204ce78827e3885594b2c0bb4f13895.pack
Binary files differ
diff --git a/tests/resources/attr_index/.gitted/packed-refs b/tests/resources/attr_index/.gitted/packed-refs
new file mode 100644
index 000000000..6b3e4decf
--- /dev/null
+++ b/tests/resources/attr_index/.gitted/packed-refs
@@ -0,0 +1,2 @@
+# pack-refs with: peeled
+58f7cf825b553ef7c26e5b9f8a23599c1a9ca296 refs/heads/master
diff --git a/tests/resources/attr_index/.gitted/refs/heads/master b/tests/resources/attr_index/.gitted/refs/heads/master
new file mode 100644
index 000000000..9b7562931
--- /dev/null
+++ b/tests/resources/attr_index/.gitted/refs/heads/master
@@ -0,0 +1 @@
+3812cfef36615db1788d4e63f90028007e17a348
diff --git a/tests/resources/attr_index/README.md b/tests/resources/attr_index/README.md
new file mode 100644
index 000000000..59d942b8b
--- /dev/null
+++ b/tests/resources/attr_index/README.md
@@ -0,0 +1 @@
+This is contains tests for when the index and work dir differ
diff --git a/tests/resources/attr_index/README.txt b/tests/resources/attr_index/README.txt
new file mode 100644
index 000000000..874c12b79
--- /dev/null
+++ b/tests/resources/attr_index/README.txt
@@ -0,0 +1 @@
+This contains files for testing when the index and the workdir differ
diff --git a/tests/resources/attr_index/gitattributes b/tests/resources/attr_index/gitattributes
new file mode 100644
index 000000000..cdf17ea3f
--- /dev/null
+++ b/tests/resources/attr_index/gitattributes
@@ -0,0 +1,4 @@
+* bar
+*.txt -foo beep=10
+*.md blargh=goop -bar
+
diff --git a/tests/resources/attr_index/sub/sub/.gitattributes b/tests/resources/attr_index/sub/sub/.gitattributes
new file mode 100644
index 000000000..060c9a261
--- /dev/null
+++ b/tests/resources/attr_index/sub/sub/.gitattributes
@@ -0,0 +1,3 @@
+*.txt another=one again
+*.md bar=1234
+
diff --git a/tests/resources/attr_index/sub/sub/README.md b/tests/resources/attr_index/sub/sub/README.md
new file mode 100644
index 000000000..59652e349
--- /dev/null
+++ b/tests/resources/attr_index/sub/sub/README.md
@@ -0,0 +1 @@
+More testing
diff --git a/tests/resources/attr_index/sub/sub/README.txt b/tests/resources/attr_index/sub/sub/README.txt
new file mode 100644
index 000000000..59652e349
--- /dev/null
+++ b/tests/resources/attr_index/sub/sub/README.txt
@@ -0,0 +1 @@
+More testing
diff --git a/tests/resources/bad.index b/tests/resources/bad.index
new file mode 100644
index 000000000..53746549f
--- /dev/null
+++ b/tests/resources/bad.index
Binary files differ
diff --git a/tests/resources/bad_tag.git/HEAD b/tests/resources/bad_tag.git/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/bad_tag.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/bad_tag.git/config b/tests/resources/bad_tag.git/config
new file mode 100644
index 000000000..2f8958058
--- /dev/null
+++ b/tests/resources/bad_tag.git/config
@@ -0,0 +1,5 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = true
+ logallrefupdates = true
diff --git a/tests/resources/bad_tag.git/objects/pack/pack-7a28f4e000a17f49a41d7a79fc2f762a8a7d9164.idx b/tests/resources/bad_tag.git/objects/pack/pack-7a28f4e000a17f49a41d7a79fc2f762a8a7d9164.idx
new file mode 100644
index 000000000..c404aa15b
--- /dev/null
+++ b/tests/resources/bad_tag.git/objects/pack/pack-7a28f4e000a17f49a41d7a79fc2f762a8a7d9164.idx
Binary files differ
diff --git a/tests/resources/bad_tag.git/objects/pack/pack-7a28f4e000a17f49a41d7a79fc2f762a8a7d9164.pack b/tests/resources/bad_tag.git/objects/pack/pack-7a28f4e000a17f49a41d7a79fc2f762a8a7d9164.pack
new file mode 100644
index 000000000..90eac5032
--- /dev/null
+++ b/tests/resources/bad_tag.git/objects/pack/pack-7a28f4e000a17f49a41d7a79fc2f762a8a7d9164.pack
Binary files differ
diff --git a/tests/resources/bad_tag.git/packed-refs b/tests/resources/bad_tag.git/packed-refs
new file mode 100644
index 000000000..9da16459b
--- /dev/null
+++ b/tests/resources/bad_tag.git/packed-refs
@@ -0,0 +1,5 @@
+# pack-refs with: peeled
+eda9f45a2a98d4c17a09d681d88569fa4ea91755 refs/tags/e90810b
+^e90810b8df3e80c413d903f631643c716887138d
+d3bacb8d3ff25876a961b1963b6515170d0151ab refs/tags/hello
+^6dcf9bf7541ee10456529833502442f385010c3d \ No newline at end of file
diff --git a/tests/resources/bad_tag.git/refs/dummy-marker.txt b/tests/resources/bad_tag.git/refs/dummy-marker.txt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/resources/bad_tag.git/refs/dummy-marker.txt
diff --git a/tests/resources/binaryunicode/.gitted/HEAD b/tests/resources/binaryunicode/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/binaryunicode/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/binaryunicode/.gitted/config b/tests/resources/binaryunicode/.gitted/config
new file mode 100644
index 000000000..f9845fe7e
--- /dev/null
+++ b/tests/resources/binaryunicode/.gitted/config
@@ -0,0 +1,6 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ autocrlf = true
+ logallrefupdates = true
diff --git a/tests/resources/binaryunicode/.gitted/description b/tests/resources/binaryunicode/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/binaryunicode/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/binaryunicode/.gitted/index b/tests/resources/binaryunicode/.gitted/index
new file mode 100644
index 000000000..a216d2219
--- /dev/null
+++ b/tests/resources/binaryunicode/.gitted/index
Binary files differ
diff --git a/tests/resources/binaryunicode/.gitted/info/exclude b/tests/resources/binaryunicode/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/binaryunicode/.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/resources/binaryunicode/.gitted/info/refs b/tests/resources/binaryunicode/.gitted/info/refs
new file mode 100644
index 000000000..128eea7c9
--- /dev/null
+++ b/tests/resources/binaryunicode/.gitted/info/refs
@@ -0,0 +1,3 @@
+39e046d1416a208265b754124d0d197b4c9c0c47 refs/heads/branch1
+9e7d8bcd4d24dd57e3f1179aaf7afe648ff50e80 refs/heads/branch2
+d2a291469f4c11f387600d189313b927ddfe891c refs/heads/master
diff --git a/tests/resources/binaryunicode/.gitted/objects/info/packs b/tests/resources/binaryunicode/.gitted/objects/info/packs
new file mode 100644
index 000000000..c2de8f5cb
--- /dev/null
+++ b/tests/resources/binaryunicode/.gitted/objects/info/packs
@@ -0,0 +1,2 @@
+P pack-c5bfca875b4995d7aba6e5abf36241f3c397327d.pack
+
diff --git a/tests/resources/binaryunicode/.gitted/objects/pack/pack-c5bfca875b4995d7aba6e5abf36241f3c397327d.idx b/tests/resources/binaryunicode/.gitted/objects/pack/pack-c5bfca875b4995d7aba6e5abf36241f3c397327d.idx
new file mode 100644
index 000000000..8a05b2beb
--- /dev/null
+++ b/tests/resources/binaryunicode/.gitted/objects/pack/pack-c5bfca875b4995d7aba6e5abf36241f3c397327d.idx
Binary files differ
diff --git a/tests/resources/binaryunicode/.gitted/objects/pack/pack-c5bfca875b4995d7aba6e5abf36241f3c397327d.pack b/tests/resources/binaryunicode/.gitted/objects/pack/pack-c5bfca875b4995d7aba6e5abf36241f3c397327d.pack
new file mode 100644
index 000000000..6b5ddc414
--- /dev/null
+++ b/tests/resources/binaryunicode/.gitted/objects/pack/pack-c5bfca875b4995d7aba6e5abf36241f3c397327d.pack
Binary files differ
diff --git a/tests/resources/binaryunicode/.gitted/refs/heads/branch1 b/tests/resources/binaryunicode/.gitted/refs/heads/branch1
new file mode 100644
index 000000000..0595fbd31
--- /dev/null
+++ b/tests/resources/binaryunicode/.gitted/refs/heads/branch1
@@ -0,0 +1 @@
+39e046d1416a208265b754124d0d197b4c9c0c47
diff --git a/tests/resources/binaryunicode/.gitted/refs/heads/branch2 b/tests/resources/binaryunicode/.gitted/refs/heads/branch2
new file mode 100644
index 000000000..d86856687
--- /dev/null
+++ b/tests/resources/binaryunicode/.gitted/refs/heads/branch2
@@ -0,0 +1 @@
+9e7d8bcd4d24dd57e3f1179aaf7afe648ff50e80
diff --git a/tests/resources/binaryunicode/.gitted/refs/heads/master b/tests/resources/binaryunicode/.gitted/refs/heads/master
new file mode 100644
index 000000000..552d166da
--- /dev/null
+++ b/tests/resources/binaryunicode/.gitted/refs/heads/master
@@ -0,0 +1 @@
+d2a291469f4c11f387600d189313b927ddfe891c
diff --git a/tests/resources/binaryunicode/file.txt b/tests/resources/binaryunicode/file.txt
new file mode 100644
index 000000000..2255035d4
--- /dev/null
+++ b/tests/resources/binaryunicode/file.txt
@@ -0,0 +1 @@
+Master branch.
diff --git a/tests/resources/blametest.git/HEAD b/tests/resources/blametest.git/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/blametest.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/blametest.git/config b/tests/resources/blametest.git/config
new file mode 100644
index 000000000..c53d818dd
--- /dev/null
+++ b/tests/resources/blametest.git/config
@@ -0,0 +1,5 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = true
+ ignorecase = true
diff --git a/tests/resources/blametest.git/description b/tests/resources/blametest.git/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/blametest.git/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/blametest.git/objects/0c/bab4d45fd61e55a1c9697f9f9cb07a12e15448 b/tests/resources/blametest.git/objects/0c/bab4d45fd61e55a1c9697f9f9cb07a12e15448
new file mode 100644
index 000000000..90331cef9
--- /dev/null
+++ b/tests/resources/blametest.git/objects/0c/bab4d45fd61e55a1c9697f9f9cb07a12e15448
Binary files differ
diff --git a/tests/resources/blametest.git/objects/1a/ac69ae5d96461afc4d81d0066cb12f5b05a35b b/tests/resources/blametest.git/objects/1a/ac69ae5d96461afc4d81d0066cb12f5b05a35b
new file mode 100644
index 000000000..71890274a
--- /dev/null
+++ b/tests/resources/blametest.git/objects/1a/ac69ae5d96461afc4d81d0066cb12f5b05a35b
Binary files differ
diff --git a/tests/resources/blametest.git/objects/1b/5f0775af166331c854bd8d1bca3450eaf2532a b/tests/resources/blametest.git/objects/1b/5f0775af166331c854bd8d1bca3450eaf2532a
new file mode 100644
index 000000000..e66430637
--- /dev/null
+++ b/tests/resources/blametest.git/objects/1b/5f0775af166331c854bd8d1bca3450eaf2532a
Binary files differ
diff --git a/tests/resources/blametest.git/objects/48/2f2c370e35c2c314fc1f96db2beb33f955a26a b/tests/resources/blametest.git/objects/48/2f2c370e35c2c314fc1f96db2beb33f955a26a
new file mode 100644
index 000000000..7da4cf5d4
--- /dev/null
+++ b/tests/resources/blametest.git/objects/48/2f2c370e35c2c314fc1f96db2beb33f955a26a
Binary files differ
diff --git a/tests/resources/blametest.git/objects/63/d671eb32d250e4a83766ebbc60e818c1e1e93a b/tests/resources/blametest.git/objects/63/d671eb32d250e4a83766ebbc60e818c1e1e93a
new file mode 100644
index 000000000..5f41f2936
--- /dev/null
+++ b/tests/resources/blametest.git/objects/63/d671eb32d250e4a83766ebbc60e818c1e1e93a
@@ -0,0 +1,3 @@
+x•A E]sŠÙu¥Ê@bŒ±GðÀP뢭Azk<ù’—¼Ÿ×y~60­–I³“1S´’=“-6÷FfC.†ìcp½õŠµ, $b
+¶8MF
+ᨹO!1eïÈò]TqkÓZáV¸··çô¾>žmÚÒ)¯ó49dì-Ñ#ªîm­üg©áw©ºTK@Î \ No newline at end of file
diff --git a/tests/resources/blametest.git/objects/63/eb57322e363e18d460da5ea8284f3cd2340b36 b/tests/resources/blametest.git/objects/63/eb57322e363e18d460da5ea8284f3cd2340b36
new file mode 100644
index 000000000..c6c285eeb
--- /dev/null
+++ b/tests/resources/blametest.git/objects/63/eb57322e363e18d460da5ea8284f3cd2340b36
Binary files differ
diff --git a/tests/resources/blametest.git/objects/8b/137891791fe96927ad78e64b0aad7bded08bdc b/tests/resources/blametest.git/objects/8b/137891791fe96927ad78e64b0aad7bded08bdc
new file mode 100644
index 000000000..9d8f60531
--- /dev/null
+++ b/tests/resources/blametest.git/objects/8b/137891791fe96927ad78e64b0aad7bded08bdc
Binary files differ
diff --git a/tests/resources/blametest.git/objects/96/679d59cf9f74d69b3c920f258559b5e8c9a18a b/tests/resources/blametest.git/objects/96/679d59cf9f74d69b3c920f258559b5e8c9a18a
new file mode 100644
index 000000000..d716e3b03
--- /dev/null
+++ b/tests/resources/blametest.git/objects/96/679d59cf9f74d69b3c920f258559b5e8c9a18a
Binary files differ
diff --git a/tests/resources/blametest.git/objects/98/89d6e5557761aa8e3607e80c874a6dc51ada7c b/tests/resources/blametest.git/objects/98/89d6e5557761aa8e3607e80c874a6dc51ada7c
new file mode 100644
index 000000000..12407f662
--- /dev/null
+++ b/tests/resources/blametest.git/objects/98/89d6e5557761aa8e3607e80c874a6dc51ada7c
Binary files differ
diff --git a/tests/resources/blametest.git/objects/aa/06ecca6c4ad6432ab9313e556ca92ba4bcf9e9 b/tests/resources/blametest.git/objects/aa/06ecca6c4ad6432ab9313e556ca92ba4bcf9e9
new file mode 100644
index 000000000..bc13badb1
--- /dev/null
+++ b/tests/resources/blametest.git/objects/aa/06ecca6c4ad6432ab9313e556ca92ba4bcf9e9
@@ -0,0 +1 @@
+x•KÂ0 DYçÞu²ã6I%„P9'ÈÇ¡]ô£ÞŸ"NÀÌîI#½‰ë<O4ᩈYPè(O.SÛçÔÚÜ yÉÉ­ FV›/²TH^³å¾C¬“ÆĘÉv¡–£3ìrúÆ+¿×q-0ÈÏZüàÞ÷×TÇ=\â:߀ؠ%tÎÀ¢:èáVå¿•zü.5CóªŠA@ \ No newline at end of file
diff --git a/tests/resources/blametest.git/objects/b1/76dfc3a4dc8734e4c579f77236a9c8d0a965d2 b/tests/resources/blametest.git/objects/b1/76dfc3a4dc8734e4c579f77236a9c8d0a965d2
new file mode 100644
index 000000000..caf5cc1e3
--- /dev/null
+++ b/tests/resources/blametest.git/objects/b1/76dfc3a4dc8734e4c579f77236a9c8d0a965d2
Binary files differ
diff --git a/tests/resources/blametest.git/objects/b9/0bb887b7c03750ae6b352ffe76ab9d2e86ee7d b/tests/resources/blametest.git/objects/b9/0bb887b7c03750ae6b352ffe76ab9d2e86ee7d
new file mode 100644
index 000000000..d4f9ccaca
--- /dev/null
+++ b/tests/resources/blametest.git/objects/b9/0bb887b7c03750ae6b352ffe76ab9d2e86ee7d
Binary files differ
diff --git a/tests/resources/blametest.git/objects/b9/9f7ac0b88909253d829554c14af488c3b0f3a5 b/tests/resources/blametest.git/objects/b9/9f7ac0b88909253d829554c14af488c3b0f3a5
new file mode 100644
index 000000000..a428fd63b
--- /dev/null
+++ b/tests/resources/blametest.git/objects/b9/9f7ac0b88909253d829554c14af488c3b0f3a5
@@ -0,0 +1,2 @@
+x•ŽK
+1]ç½›•ÒIg2Yˆˆâ <A>­3‹L vîoÀ¸{õR-eÐΤ1ƒ#ŽóBÆ0©}¶s˜9xãí‹R6d1’S¡ËZÜx‡§´Ð#œãçúÞdíñ”j¹€&‡‹F§ Ñ#ªAGKø?KݧÇôgõ2r \ No newline at end of file
diff --git a/tests/resources/blametest.git/objects/bc/7c5ac2bafe828a68e9d1d460343718d6fbe136 b/tests/resources/blametest.git/objects/bc/7c5ac2bafe828a68e9d1d460343718d6fbe136
new file mode 100644
index 000000000..4e6ad159e
--- /dev/null
+++ b/tests/resources/blametest.git/objects/bc/7c5ac2bafe828a68e9d1d460343718d6fbe136
@@ -0,0 +1,3 @@
+x•AnÃ0 {Ö+tË))J´AÛ<!/ eºÉÁqàÊÿ¯¢Èu±³Àl[æùÞc*òÖW÷èÅ26t-DS¨V5C2ÀŠN#§Lá©«?zdy@7Jc*àY+ ÌnÖ¼bݧЅô¿¯
+ì­)·¬#gJjBH^
+7•dš­Mâtë·e_þˆ×¾êfñd?ß÷~Ûì½-ó9"1 BPžî.Ý_£Âåï‚Ãç!ü&íO \ No newline at end of file
diff --git a/tests/resources/blametest.git/objects/cf/e0e1e1e3ba18f149fd47f5e1aef6016b2260c3 b/tests/resources/blametest.git/objects/cf/e0e1e1e3ba18f149fd47f5e1aef6016b2260c3
new file mode 100644
index 000000000..2048e81eb
--- /dev/null
+++ b/tests/resources/blametest.git/objects/cf/e0e1e1e3ba18f149fd47f5e1aef6016b2260c3
Binary files differ
diff --git a/tests/resources/blametest.git/objects/d0/67729932057cdb7527a833d6799c4ddc520640 b/tests/resources/blametest.git/objects/d0/67729932057cdb7527a833d6799c4ddc520640
new file mode 100644
index 000000000..926c4bbb0
--- /dev/null
+++ b/tests/resources/blametest.git/objects/d0/67729932057cdb7527a833d6799c4ddc520640
@@ -0,0 +1 @@
+x+)JMU03c040031QHÔ+©(a˜Ñyíihyâª>3ö<í^¹G¥nÕ@$H­É\;ÍMêo㶜úѬ‹£Ƥ \ No newline at end of file
diff --git a/tests/resources/blametest.git/objects/da/237394e6132d20d30f175b9b73c8638fddddda b/tests/resources/blametest.git/objects/da/237394e6132d20d30f175b9b73c8638fddddda
new file mode 100644
index 000000000..e9e13833f
--- /dev/null
+++ b/tests/resources/blametest.git/objects/da/237394e6132d20d30f175b9b73c8638fddddda
@@ -0,0 +1,4 @@
+x•K
+Â0@]÷³ëJ™|'"¢ÞÀ$“ÔvÑVbz žÀíƒïÉ:ÏS­ðÐj)Ñif£Ñ‘äDNS ÆdOÌbs§Ñ[ìÞ±–¥Ab(
+¦Y;“ƒfç¬(‚˜„ƒ‰®‹[×
+·²À³Õ¸%8§Ïõ5µqK'Yç (ã‘zF8b@ìvº·µòŸÕÝKý£ÿc–?S \ No newline at end of file
diff --git a/tests/resources/blametest.git/objects/e5/b41c1ea533f87388ab69b13baf0b5a562d6243 b/tests/resources/blametest.git/objects/e5/b41c1ea533f87388ab69b13baf0b5a562d6243
new file mode 100644
index 000000000..7e5586c2b
--- /dev/null
+++ b/tests/resources/blametest.git/objects/e5/b41c1ea533f87388ab69b13baf0b5a562d6243
Binary files differ
diff --git a/tests/resources/blametest.git/objects/ef/32df4d259143933715c74951f932d9892364d1 b/tests/resources/blametest.git/objects/ef/32df4d259143933715c74951f932d9892364d1
new file mode 100644
index 000000000..d021ccfde
--- /dev/null
+++ b/tests/resources/blametest.git/objects/ef/32df4d259143933715c74951f932d9892364d1
Binary files differ
diff --git a/tests/resources/blametest.git/refs/heads/master b/tests/resources/blametest.git/refs/heads/master
new file mode 100644
index 000000000..b763025d8
--- /dev/null
+++ b/tests/resources/blametest.git/refs/heads/master
@@ -0,0 +1 @@
+bc7c5ac2bafe828a68e9d1d460343718d6fbe136
diff --git a/tests/resources/cherrypick/.gitted/HEAD b/tests/resources/cherrypick/.gitted/HEAD
new file mode 100644
index 000000000..656ac0e0a
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/automerge-branch
diff --git a/tests/resources/cherrypick/.gitted/config b/tests/resources/cherrypick/.gitted/config
new file mode 100644
index 000000000..6c9406b7d
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/config
@@ -0,0 +1,7 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
+ precomposeunicode = true
diff --git a/tests/resources/cherrypick/.gitted/index b/tests/resources/cherrypick/.gitted/index
new file mode 100644
index 000000000..7291006c8
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/index
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/info/exclude b/tests/resources/cherrypick/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/cherrypick/.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/resources/cherrypick/.gitted/objects/01/a2b453c2647c71ccfefc285f2266d1f00b8253 b/tests/resources/cherrypick/.gitted/objects/01/a2b453c2647c71ccfefc285f2266d1f00b8253
new file mode 100644
index 000000000..736a7f57b
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/01/a2b453c2647c71ccfefc285f2266d1f00b8253
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/02/67838e09bbc5969bba035be2d27c8a6de694d8 b/tests/resources/cherrypick/.gitted/objects/02/67838e09bbc5969bba035be2d27c8a6de694d8
new file mode 100644
index 000000000..4eacb26f5
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/02/67838e09bbc5969bba035be2d27c8a6de694d8
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/06/3fc9f01e6e9ec2a8d8f749885e931875e50d37 b/tests/resources/cherrypick/.gitted/objects/06/3fc9f01e6e9ec2a8d8f749885e931875e50d37
new file mode 100644
index 000000000..48fa6efcd
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/06/3fc9f01e6e9ec2a8d8f749885e931875e50d37
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/08/9ac03f76058b5ba0b44bb268f317f9242481e9 b/tests/resources/cherrypick/.gitted/objects/08/9ac03f76058b5ba0b44bb268f317f9242481e9
new file mode 100644
index 000000000..06d1c694e
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/08/9ac03f76058b5ba0b44bb268f317f9242481e9
@@ -0,0 +1,3 @@
+x¥ÎA
+Â0@Q×9Å왤“i"î—Þ`L§µB¬MÓ…··àܾÅç§)籂#ÞÕ¢
+^CNØä™”Îb+˜%¸˜¬Š÷¨bÞRôU!õ‰zŒ1Jh¸õ)JO}딼ëƒ b½‘µ>¦WIóª \´äqy¬ŸŽÏŸ 祖QªÒ”O`›ÈDÍ6{tˆfÓmµê_sÓy‹‚@Ö2¨ù("O- \ No newline at end of file
diff --git a/tests/resources/cherrypick/.gitted/objects/0d/447a6c2528b06616cde3b209a4b4ea3dcb8d65 b/tests/resources/cherrypick/.gitted/objects/0d/447a6c2528b06616cde3b209a4b4ea3dcb8d65
new file mode 100644
index 000000000..9a3ea3209
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/0d/447a6c2528b06616cde3b209a4b4ea3dcb8d65
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/11/24c2c1ae07b26fded662d6c3f3631d9dc16f88 b/tests/resources/cherrypick/.gitted/objects/11/24c2c1ae07b26fded662d6c3f3631d9dc16f88
new file mode 100644
index 000000000..62abc3c5b
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/11/24c2c1ae07b26fded662d6c3f3631d9dc16f88
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/12/905f4ea5b76f9d3fdcfe73e462201c06ae632a b/tests/resources/cherrypick/.gitted/objects/12/905f4ea5b76f9d3fdcfe73e462201c06ae632a
new file mode 100644
index 000000000..162844a70
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/12/905f4ea5b76f9d3fdcfe73e462201c06ae632a
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/19/c5c7207054604b69c84d08a7571ef9672bb5c2 b/tests/resources/cherrypick/.gitted/objects/19/c5c7207054604b69c84d08a7571ef9672bb5c2
new file mode 100644
index 000000000..d5cd6d3f2
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/19/c5c7207054604b69c84d08a7571ef9672bb5c2
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/1c/2116845780455ecf916538c1cc27c4222452af b/tests/resources/cherrypick/.gitted/objects/1c/2116845780455ecf916538c1cc27c4222452af
new file mode 100644
index 000000000..f9a841d4f
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/1c/2116845780455ecf916538c1cc27c4222452af
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/1c/c85eb4ff0a8438fde1b14274c6f87f891b36a0 b/tests/resources/cherrypick/.gitted/objects/1c/c85eb4ff0a8438fde1b14274c6f87f891b36a0
new file mode 100644
index 000000000..98b792b64
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/1c/c85eb4ff0a8438fde1b14274c6f87f891b36a0
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/1e/1cb7391d25dcd8daba88f1f627f3045982286c b/tests/resources/cherrypick/.gitted/objects/1e/1cb7391d25dcd8daba88f1f627f3045982286c
new file mode 100644
index 000000000..10a5be6fe
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/1e/1cb7391d25dcd8daba88f1f627f3045982286c
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/20/fc1a4c9d994021f43d33ab75e4252e27ca661d b/tests/resources/cherrypick/.gitted/objects/20/fc1a4c9d994021f43d33ab75e4252e27ca661d
new file mode 100644
index 000000000..c8b26cd01
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/20/fc1a4c9d994021f43d33ab75e4252e27ca661d
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/28/d9eb4208074ad1cc84e71ccc908b34573f05d2 b/tests/resources/cherrypick/.gitted/objects/28/d9eb4208074ad1cc84e71ccc908b34573f05d2
new file mode 100644
index 000000000..80363b016
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/28/d9eb4208074ad1cc84e71ccc908b34573f05d2
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/2a/26c7e88b285613b302ba76712bc998863f3cbc b/tests/resources/cherrypick/.gitted/objects/2a/26c7e88b285613b302ba76712bc998863f3cbc
new file mode 100644
index 000000000..283113999
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/2a/26c7e88b285613b302ba76712bc998863f3cbc
@@ -0,0 +1 @@
+x¥MjÃ0…»Ö)f_jôï„]¡ËÜ`$—*Nä1´·¯C=@—ï{ðñ^YZ›lLOÒ™Á”‚³¯Uz‡ub“·£/±âX1™ì"iu£ÎWN9ºêbÙÒ„ºèZŽS”&r4£mrY:¼Q¹o¼Â+÷6¯—í{…ÃÇ/{?­ÒgÊÒŽ`\ŠÞk-<k«µÚé>Uø_uæ+5ž ÎŸìùx9þ…a?õ¨Õ7˜W… \ No newline at end of file
diff --git a/tests/resources/cherrypick/.gitted/objects/2a/c3b376093de405b0a951bff578655b1c2b7fa1 b/tests/resources/cherrypick/.gitted/objects/2a/c3b376093de405b0a951bff578655b1c2b7fa1
new file mode 100644
index 000000000..a3294d764
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/2a/c3b376093de405b0a951bff578655b1c2b7fa1
@@ -0,0 +1 @@
+x•ÁJÅ0E]ç+f÷¢¤“Išw‚àGLÒÉk…´š¦ ÿÞÊóÜž‡{óVëÒG{×›*¤ˆA9¹ ˆÊÄšcô:ÊPÜ”ˆCJ’Bž¬ù”¦k‡\2ËÌ]}ˆj‰¥PQÉãD6b”Á9ú¼5x“üuè¯Úê²ÏÇ÷O7v}Ù{[¤ËcÞê3 Ž‘‹žàÞ¢µæ¤çÔ®ÿ#þEÌ»¶ëy³Éšg¸TÙÏà–µoPÕÃM™Í/X˜ \ No newline at end of file
diff --git a/tests/resources/cherrypick/.gitted/objects/2c/acbcaabf785f1ac231e8519849d4ad38692f2c b/tests/resources/cherrypick/.gitted/objects/2c/acbcaabf785f1ac231e8519849d4ad38692f2c
new file mode 100644
index 000000000..74b48dd6b
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/2c/acbcaabf785f1ac231e8519849d4ad38692f2c
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/35/cb210149022c7379b0a67b0dec13cc628ff87d b/tests/resources/cherrypick/.gitted/objects/35/cb210149022c7379b0a67b0dec13cc628ff87d
new file mode 100644
index 000000000..c0466f46a
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/35/cb210149022c7379b0a67b0dec13cc628ff87d
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/38/c05a857e831a7e759d83778bfc85d003e21c45 b/tests/resources/cherrypick/.gitted/objects/38/c05a857e831a7e759d83778bfc85d003e21c45
new file mode 100644
index 000000000..d4f1cf8ac
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/38/c05a857e831a7e759d83778bfc85d003e21c45
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/3f/9eed8946df9e2c737d3b8dc0b8e78959aacd92 b/tests/resources/cherrypick/.gitted/objects/3f/9eed8946df9e2c737d3b8dc0b8e78959aacd92
new file mode 100644
index 000000000..8c4d6d94f
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/3f/9eed8946df9e2c737d3b8dc0b8e78959aacd92
@@ -0,0 +1,5 @@
+xMjÃ0…»Ö)fj¤ñH– „ì]æÃdÔ¸Dq#ËÐÞ¾
+-tßåûáã=YJ™`HO­ª‚t.DòS´ä½JN.ø1ŠÁIÉ#góÁUo $ e›Râ8†É‡¨–gÊj/žÉFŒì¼á­]–
+¯,÷MW8j-ózÙ¾VxyÿñÞk«37d){pc
+Ôˆ°³h­énŸÚô
+îbNzã¢gÈóUÇ¡}6xÞÿ‰¡ŸzÄæï*V8 \ No newline at end of file
diff --git a/tests/resources/cherrypick/.gitted/objects/40/9a1bec58bf35348e8b62b72bb9c1f45cf5a587 b/tests/resources/cherrypick/.gitted/objects/40/9a1bec58bf35348e8b62b72bb9c1f45cf5a587
new file mode 100644
index 000000000..60d5dca4a
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/40/9a1bec58bf35348e8b62b72bb9c1f45cf5a587
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/44/cd2ed2052c9c68f9a439d208e9614dc2a55c70 b/tests/resources/cherrypick/.gitted/objects/44/cd2ed2052c9c68f9a439d208e9614dc2a55c70
new file mode 100644
index 000000000..8697c4e66
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/44/cd2ed2052c9c68f9a439d208e9614dc2a55c70
@@ -0,0 +1 @@
+x¥ÁNÄ0C9ç+推’É$M$´â†Ä‘XÍ&Úi!M%ø{ºð ÜìgɲÓZëÜÇp×› .É0¥˜c$¦ÍÖòetBèPpLì½Éꃛ,RITtŒ‘ƒõ£óA4E.TFr˜I lœâ½OkƒNŸ»lð,­ÎÛ´oðxýcoO[o3wÒZO`lôD.÷µV=¦vùW‰z•…«d(ó»Ø¡ux8ýš›ŽO·ô¼.çKã%MêÖ?ZØ \ No newline at end of file
diff --git a/tests/resources/cherrypick/.gitted/objects/48/7434cace79238a7091e2220611d4f20a765690 b/tests/resources/cherrypick/.gitted/objects/48/7434cace79238a7091e2220611d4f20a765690
new file mode 100644
index 000000000..a1fa599e1
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/48/7434cace79238a7091e2220611d4f20a765690
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/49/20ad2f17162dcc8823ad491444dcb87f5899c9 b/tests/resources/cherrypick/.gitted/objects/49/20ad2f17162dcc8823ad491444dcb87f5899c9
new file mode 100644
index 000000000..bf96fccad
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/49/20ad2f17162dcc8823ad491444dcb87f5899c9
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 b/tests/resources/cherrypick/.gitted/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904
new file mode 100644
index 000000000..adf64119a
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/4c/532774cc1fea37f6efc2256763a64d38c8cdde b/tests/resources/cherrypick/.gitted/objects/4c/532774cc1fea37f6efc2256763a64d38c8cdde
new file mode 100644
index 000000000..2e56d7403
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/4c/532774cc1fea37f6efc2256763a64d38c8cdde
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/51/145af30d411a50195b66517d825e69bf57ed22 b/tests/resources/cherrypick/.gitted/objects/51/145af30d411a50195b66517d825e69bf57ed22
new file mode 100644
index 000000000..3e01376bd
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/51/145af30d411a50195b66517d825e69bf57ed22
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/54/61de53ffadbf15be4dd6345997c15689573209 b/tests/resources/cherrypick/.gitted/objects/54/61de53ffadbf15be4dd6345997c15689573209
new file mode 100644
index 000000000..7d2b233a6
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/54/61de53ffadbf15be4dd6345997c15689573209
@@ -0,0 +1,4 @@
+xMjÃ0…»Ö)fj¤ñH– „ì]æÃdÔ¸Dq#ËÐÞ¾
+-tßåûáã=YJ™`HO­ª‚t.DòS´ä½JN.ø1ŠÁIÉ#góÁUo $ e›Râ8†É‡¨–gÊj/žÉFŒì¼á­]–
+¯,÷MW8j-ózÙ¾VxyÿñÞk«37d){pc
+Ôˆ°³h­énŸÚô?J¿sÒ=Cž¯:í³ÁóþO ýÔ#6ßïäV< \ No newline at end of file
diff --git a/tests/resources/cherrypick/.gitted/objects/54/784f10955e92ab27e4fa832e40cb2baf1edbdc b/tests/resources/cherrypick/.gitted/objects/54/784f10955e92ab27e4fa832e40cb2baf1edbdc
new file mode 100644
index 000000000..2a5bcec27
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/54/784f10955e92ab27e4fa832e40cb2baf1edbdc
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/56/3f6473a3858f99b80e5f93c660512ed38e1e6f b/tests/resources/cherrypick/.gitted/objects/56/3f6473a3858f99b80e5f93c660512ed38e1e6f
new file mode 100644
index 000000000..8847ed689
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/56/3f6473a3858f99b80e5f93c660512ed38e1e6f
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/58/a957ef0061c1a8ef995c855dfab4f5da8d6617 b/tests/resources/cherrypick/.gitted/objects/58/a957ef0061c1a8ef995c855dfab4f5da8d6617
new file mode 100644
index 000000000..f161a1941
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/58/a957ef0061c1a8ef995c855dfab4f5da8d6617
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/5d/c7e1f440ce74d5503a0dfbc6c30e091475f774 b/tests/resources/cherrypick/.gitted/objects/5d/c7e1f440ce74d5503a0dfbc6c30e091475f774
new file mode 100644
index 000000000..77deeaf0b
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/5d/c7e1f440ce74d5503a0dfbc6c30e091475f774
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/5e/2206cda1c56430ad107a6866a829c159e0b9ea b/tests/resources/cherrypick/.gitted/objects/5e/2206cda1c56430ad107a6866a829c159e0b9ea
new file mode 100644
index 000000000..aa30f501f
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/5e/2206cda1c56430ad107a6866a829c159e0b9ea
@@ -0,0 +1 @@
+x+)JMU044d040031QHËÌI5Ô+©(aøô¦§çãÊßU ÜE9sì‘ \uI‘X‘œÌvKYÕ;7níêøøMý3Kd“F’"c°¢ˆ•áï®x?3¦5ö×–¯·zÓÄѨ1* \ No newline at end of file
diff --git a/tests/resources/cherrypick/.gitted/objects/5f/77a2a13935ac62a629553f8944ad57b1ed8b4a b/tests/resources/cherrypick/.gitted/objects/5f/77a2a13935ac62a629553f8944ad57b1ed8b4a
new file mode 100644
index 000000000..5e622a1fa
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/5f/77a2a13935ac62a629553f8944ad57b1ed8b4a
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/63/c0d92b95253c4a40d3883f423a54be47d2c4c8 b/tests/resources/cherrypick/.gitted/objects/63/c0d92b95253c4a40d3883f423a54be47d2c4c8
new file mode 100644
index 000000000..eafe2c30a
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/63/c0d92b95253c4a40d3883f423a54be47d2c4c8
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/6c/e83eb5f0fd34a10c3d25c6b36d2ed7ec0d6ce7 b/tests/resources/cherrypick/.gitted/objects/6c/e83eb5f0fd34a10c3d25c6b36d2ed7ec0d6ce7
new file mode 100644
index 000000000..1c1f5034d
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/6c/e83eb5f0fd34a10c3d25c6b36d2ed7ec0d6ce7
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/6d/1c2afe5eeb9e497528e2780ac468a5465cbc96 b/tests/resources/cherrypick/.gitted/objects/6d/1c2afe5eeb9e497528e2780ac468a5465cbc96
new file mode 100644
index 000000000..a98378a70
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/6d/1c2afe5eeb9e497528e2780ac468a5465cbc96
@@ -0,0 +1 @@
+x¥Î=jÃ@@áÔ{Šé fvöLpgp“3ÌŽF–²œÕªðímðÒ¾âãɺ,sòþ«7Uàì$Ž ©¤š1 :HðEcÈ.d*1ŽQ«p5nzïà‚T²h}A"I.•ŠSÅAÅ:‘Hys ï}Z\YþvÝà¢m™·inpúý´ÛyëmæÎGY—o°®Ä`É$Dó®ïÕ®ÿBÌO{L|‡f^äðOA \ No newline at end of file
diff --git a/tests/resources/cherrypick/.gitted/objects/74/f06b5bfec6d33d7264f73606b57a7c0b963819 b/tests/resources/cherrypick/.gitted/objects/74/f06b5bfec6d33d7264f73606b57a7c0b963819
new file mode 100644
index 000000000..732011fce
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/74/f06b5bfec6d33d7264f73606b57a7c0b963819
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/82/8b08c52d2cba30952e0e008f60b25b5ba0d41a b/tests/resources/cherrypick/.gitted/objects/82/8b08c52d2cba30952e0e008f60b25b5ba0d41a
new file mode 100644
index 000000000..302014bff
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/82/8b08c52d2cba30952e0e008f60b25b5ba0d41a
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/85/36dd6f0ec3ddecb9f9b6c8c64c6d322cd01211 b/tests/resources/cherrypick/.gitted/objects/85/36dd6f0ec3ddecb9f9b6c8c64c6d322cd01211
new file mode 100644
index 000000000..db6faa9e2
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/85/36dd6f0ec3ddecb9f9b6c8c64c6d322cd01211
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/85/a4a1d791973644f24c72f5e89420d3064cc452 b/tests/resources/cherrypick/.gitted/objects/85/a4a1d791973644f24c72f5e89420d3064cc452
new file mode 100644
index 000000000..7fe69b6f8
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/85/a4a1d791973644f24c72f5e89420d3064cc452
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/8b/5c30499a71001189b647f4d5b57fa8f04897ce b/tests/resources/cherrypick/.gitted/objects/8b/5c30499a71001189b647f4d5b57fa8f04897ce
new file mode 100644
index 000000000..8b1638fbb
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/8b/5c30499a71001189b647f4d5b57fa8f04897ce
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/96/4ea3da044d9083181a88ba6701de9e35778bf4 b/tests/resources/cherrypick/.gitted/objects/96/4ea3da044d9083181a88ba6701de9e35778bf4
new file mode 100644
index 000000000..2dec33f69
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/96/4ea3da044d9083181a88ba6701de9e35778bf4
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/9c/c39fca3765a2facbe31157f7d60c2602193f36 b/tests/resources/cherrypick/.gitted/objects/9c/c39fca3765a2facbe31157f7d60c2602193f36
new file mode 100644
index 000000000..00314454f
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/9c/c39fca3765a2facbe31157f7d60c2602193f36
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/9c/cb9bf50c011fd58dcbaa65df917bf79539717f b/tests/resources/cherrypick/.gitted/objects/9c/cb9bf50c011fd58dcbaa65df917bf79539717f
new file mode 100644
index 000000000..1266aff36
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/9c/cb9bf50c011fd58dcbaa65df917bf79539717f
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/a1/0b59f4280491afe6e430c30654a7acc67d4a33 b/tests/resources/cherrypick/.gitted/objects/a1/0b59f4280491afe6e430c30654a7acc67d4a33
new file mode 100644
index 000000000..7aa0a5dcd
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/a1/0b59f4280491afe6e430c30654a7acc67d4a33
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/a2/1b4bfe7a04ab18024fb57f4ae9a52a1acef394 b/tests/resources/cherrypick/.gitted/objects/a2/1b4bfe7a04ab18024fb57f4ae9a52a1acef394
new file mode 100644
index 000000000..07b7195d2
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/a2/1b4bfe7a04ab18024fb57f4ae9a52a1acef394
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/a4/3a050c588d4e92f11a6b139680923e9728477d b/tests/resources/cherrypick/.gitted/objects/a4/3a050c588d4e92f11a6b139680923e9728477d
new file mode 100644
index 000000000..4713fb2db
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/a4/3a050c588d4e92f11a6b139680923e9728477d
@@ -0,0 +1 @@
+x¥ÎMjÃ0†á®uŠÙÂHŠþ ”BÜb4Ç.‘ÝJ2´· Gèöáãåã½”µƒqñ¥WÈ9¼ãl=#»`5GÎsDD5ê(ꋪlìXš!—„Æp°!e$2NÂÚ2{ç9†IÑÑ—½ÂøûW©emËñÛàõóÏîï­×•:y/o mò“‚7pBƒ¨†Ž«]þQ mw™`^¢Ïý§Ã¾A¡6ºê mT \ No newline at end of file
diff --git a/tests/resources/cherrypick/.gitted/objects/a5/8ca3fee5eb68b11adc2703e5843f968c9dad1e b/tests/resources/cherrypick/.gitted/objects/a5/8ca3fee5eb68b11adc2703e5843f968c9dad1e
new file mode 100644
index 000000000..1c3f2fb01
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/a5/8ca3fee5eb68b11adc2703e5843f968c9dad1e
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/a6/61b5dec1004e2c62654ded3762370c27cf266b b/tests/resources/cherrypick/.gitted/objects/a6/61b5dec1004e2c62654ded3762370c27cf266b
new file mode 100644
index 000000000..d94a9541f
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/a6/61b5dec1004e2c62654ded3762370c27cf266b
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/a6/9ef8fcbb9a2c509a7dbf4f23d257eb551d5610 b/tests/resources/cherrypick/.gitted/objects/a6/9ef8fcbb9a2c509a7dbf4f23d257eb551d5610
new file mode 100644
index 000000000..69feba205
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/a6/9ef8fcbb9a2c509a7dbf4f23d257eb551d5610
@@ -0,0 +1 @@
+x¥Î1ŠÃ0@Ñ­uŠéA¤,!] åÞ`,/hÈr‘ÛÇ°GHûŠÏÏK­s¤ðÕ›*heæ”Éæ§8ªÅèýèJ*(&ó¦rÉTlJIØ…è«¥$…JD%YF–ÁÙú}ip•üÜt…‹¶:¯÷íµÂ÷ï¿ÝÎko³t9楞`p)9ö­5»î«]?Š˜}nsS¨ÚnjÞGPOL \ No newline at end of file
diff --git a/tests/resources/cherrypick/.gitted/objects/a8/3c6f70297b805dedc549e6583582966f6ebcab b/tests/resources/cherrypick/.gitted/objects/a8/3c6f70297b805dedc549e6583582966f6ebcab
new file mode 100644
index 000000000..5a6db508e
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/a8/3c6f70297b805dedc549e6583582966f6ebcab
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/a9/020cd240774e4d672732bcb82d516d9685da76 b/tests/resources/cherrypick/.gitted/objects/a9/020cd240774e4d672732bcb82d516d9685da76
new file mode 100644
index 000000000..61741aff9
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/a9/020cd240774e4d672732bcb82d516d9685da76
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/ab/4115f808bc585b60f822da7020af86d20f62c8 b/tests/resources/cherrypick/.gitted/objects/ab/4115f808bc585b60f822da7020af86d20f62c8
new file mode 100644
index 000000000..08c4bef57
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/ab/4115f808bc585b60f822da7020af86d20f62c8
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/ab/e4603bc7cd5b8167a267e0e2418fd2348f8cff b/tests/resources/cherrypick/.gitted/objects/ab/e4603bc7cd5b8167a267e0e2418fd2348f8cff
new file mode 100644
index 000000000..4e4fe6f12
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/ab/e4603bc7cd5b8167a267e0e2418fd2348f8cff
@@ -0,0 +1,4 @@
+x¥KNÄ0DYç½›Ùî´?Bì8D»ÓžÉHNÀqÜžŒFœ€í«ª'•¬µÎÐƇÞT!‰`*Â<±+,YÑZ
+%LÞˆóÆÙ„ýðÅM—ì“–X$çÄNÈ$S.cq89
+š‰ìDÞš¿¾¦ìo{¢ìxŠFL)Æ”‰ÅÄÈÁ}ÞûemðÁò½ëïÚê¼]öŸ ^®wv~Ûz›¹ó³¬õ,&?Ži„GãŒz\ëú/Éð©í¬/rSåíž`^ú
+õ=Ý£áX¾fZ \ No newline at end of file
diff --git a/tests/resources/cherrypick/.gitted/objects/b8/26e9b36e22e949ec885e7a1f3db496bbab6cd0 b/tests/resources/cherrypick/.gitted/objects/b8/26e9b36e22e949ec885e7a1f3db496bbab6cd0
new file mode 100644
index 000000000..e3bf3a017
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/b8/26e9b36e22e949ec885e7a1f3db496bbab6cd0
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/ba/fbf6912c09505ac60575cd43d3f2aba3bd84d8 b/tests/resources/cherrypick/.gitted/objects/ba/fbf6912c09505ac60575cd43d3f2aba3bd84d8
new file mode 100644
index 000000000..956da8b71
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/ba/fbf6912c09505ac60575cd43d3f2aba3bd84d8
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/bb/14296ffa9dfbf935ec9ce2f9ed7808d952226b b/tests/resources/cherrypick/.gitted/objects/bb/14296ffa9dfbf935ec9ce2f9ed7808d952226b
new file mode 100644
index 000000000..b5583685a
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/bb/14296ffa9dfbf935ec9ce2f9ed7808d952226b
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/bc/4dd0744364d1db380a9811bd264c101065231e b/tests/resources/cherrypick/.gitted/objects/bc/4dd0744364d1db380a9811bd264c101065231e
new file mode 100644
index 000000000..01d88a283
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/bc/4dd0744364d1db380a9811bd264c101065231e
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/bd/65d4083845ed5ed4e1fe5feb85ac395d0760c8 b/tests/resources/cherrypick/.gitted/objects/bd/65d4083845ed5ed4e1fe5feb85ac395d0760c8
new file mode 100644
index 000000000..6a0eccb5e
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/bd/65d4083845ed5ed4e1fe5feb85ac395d0760c8
@@ -0,0 +1,2 @@
+x¥ÍA
+B!€áÖžböAŒ6ŠBD» [¨o^Ï@$ݾ¡í·øÿÜj-Ú›ƒtf ä]²#““ã":dKiõ‹ Æû51S@RqÊÖ:<b~Opç^ËØægÀåõ³çmH/Qâ)·z}ÎjCˆpDƒ¨vÝ×ÂEÔK»<È \ No newline at end of file
diff --git a/tests/resources/cherrypick/.gitted/objects/bd/6ffc8c6c41f0f85ff9e3d61c9479516bac0024 b/tests/resources/cherrypick/.gitted/objects/bd/6ffc8c6c41f0f85ff9e3d61c9479516bac0024
new file mode 100644
index 000000000..56f836779
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/bd/6ffc8c6c41f0f85ff9e3d61c9479516bac0024
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/bd/a51965cb36c0c5731c8cb50b80a36cac81018e b/tests/resources/cherrypick/.gitted/objects/bd/a51965cb36c0c5731c8cb50b80a36cac81018e
new file mode 100644
index 000000000..1187a7089
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/bd/a51965cb36c0c5731c8cb50b80a36cac81018e
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/ce/d8fb81b6ec534d5deaf2a48b4b96c799712507 b/tests/resources/cherrypick/.gitted/objects/ce/d8fb81b6ec534d5deaf2a48b4b96c799712507
new file mode 100644
index 000000000..569ee0c99
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/ce/d8fb81b6ec534d5deaf2a48b4b96c799712507
@@ -0,0 +1 @@
+x¥ÎÍJÅ0@a×yŠì…Ëä?] ¾Åd2i+7¦)èÛ[ðÜ~‹Ã¡ÞÚ6¥öîafY–’ÏÕH1„ì¼K%¢+E!…(>qð>¥q”µehMÁ„”}ÈP˜”!ò:ÖCxεùŽôuò!ßx´íXÏŸC>}üÙòṟáÄõö,•IÞç‚“ Ä¥×êäEÄë9{ã±0æ;KZq_®¾ËºÝYÝæ÷¿t3V \ No newline at end of file
diff --git a/tests/resources/cherrypick/.gitted/objects/cf/c4f0999a8367568e049af4f72e452d40828a15 b/tests/resources/cherrypick/.gitted/objects/cf/c4f0999a8367568e049af4f72e452d40828a15
new file mode 100644
index 000000000..d7deb0bff
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/cf/c4f0999a8367568e049af4f72e452d40828a15
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/d0/f21e17beb5b9d953b1d8349049818a4f2edd1e b/tests/resources/cherrypick/.gitted/objects/d0/f21e17beb5b9d953b1d8349049818a4f2edd1e
new file mode 100644
index 000000000..65c846fa4
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/d0/f21e17beb5b9d953b1d8349049818a4f2edd1e
@@ -0,0 +1 @@
+x¥ŽAjÃ0E»Ö)f_¶¤‘,¡]z‹‘<²]"«•ÇÐÞ¾‚!Ëÿø<^ª¥lÚá‹4fÀì=iM0HÉir: š<kiFGž§hI}Sã] õ™ã4FÇ qfÊšlÿÄà’Á¯è”µ6ø¤ôsòÜÊv¬çßׯ[Þi ]R-7è ÎÄ áuÐà:í©ÂOIÔû)µp[˜â!­´/=§î·;ë‹üŠú&éWY \ No newline at end of file
diff --git a/tests/resources/cherrypick/.gitted/objects/d3/d77487660ee3c0194ee01dc5eaf478782b1c7e b/tests/resources/cherrypick/.gitted/objects/d3/d77487660ee3c0194ee01dc5eaf478782b1c7e
new file mode 100644
index 000000000..b42df7e50
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/d3/d77487660ee3c0194ee01dc5eaf478782b1c7e
@@ -0,0 +1 @@
+x¥ÎKJ1€a×9Eí…!ïI@df%x‹ª¤ú!“Ž¦«AooƒGpû-~þÒ[[l O2˜AW﯋ 6‘ŽÑÄRÙ‘Õ=yFW ¥ƒúÄÁ›@Õ“5l®Ä(×™šœÏÚçdúÉr­†²ôïX¾ÞáG[÷åøÙáåãÏæÛ.cEÁKéíŒËÑ»²…gmµV§ž«ÂÿŠ¨û!½ñ˜éÁPÜæs§o0­vùõ *Wd \ No newline at end of file
diff --git a/tests/resources/cherrypick/.gitted/objects/e2/33b9ed408a95e9d4b65fec7fc34943a556deb2 b/tests/resources/cherrypick/.gitted/objects/e2/33b9ed408a95e9d4b65fec7fc34943a556deb2
new file mode 100644
index 000000000..b344c9cc8
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/e2/33b9ed408a95e9d4b65fec7fc34943a556deb2
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/e5/183bfd18e3a0a691fadde2f0d5610b73282d31 b/tests/resources/cherrypick/.gitted/objects/e5/183bfd18e3a0a691fadde2f0d5610b73282d31
new file mode 100644
index 000000000..fdc05714f
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/e5/183bfd18e3a0a691fadde2f0d5610b73282d31
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/e6/ae8889c40c77d7be02758235b5b3f7a4f2a129 b/tests/resources/cherrypick/.gitted/objects/e6/ae8889c40c77d7be02758235b5b3f7a4f2a129
new file mode 100644
index 000000000..3345907db
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/e6/ae8889c40c77d7be02758235b5b3f7a4f2a129
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/e7/811a2bc55635f182750f0420da5ad232c1af91 b/tests/resources/cherrypick/.gitted/objects/e7/811a2bc55635f182750f0420da5ad232c1af91
new file mode 100644
index 000000000..238873025
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/e7/811a2bc55635f182750f0420da5ad232c1af91
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/e9/b63f3655b2ad80c0ff587389b5a9589a3a7110 b/tests/resources/cherrypick/.gitted/objects/e9/b63f3655b2ad80c0ff587389b5a9589a3a7110
new file mode 100644
index 000000000..ab0a27f37
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/e9/b63f3655b2ad80c0ff587389b5a9589a3a7110
@@ -0,0 +1,2 @@
+x¥ÎMjÃ0@á®uŠÙŠ<Ë!E¡·É£ØÅ?‰$/rûz„n¿Åã¥}]çHÃG+ªzåè¼"*k
+¡×AºìÆHìc”èÓhÍ]Šn RN”-3Kp~è}PK,™ò€J=Ždéz#G›ö?’‡VøÖ²Îu:žN¿v»ÔVfiò•öõ cOÎ;‹ðiÑZóÖ÷jÓEÌu’í¦ Ëy^´š-˜P@ \ No newline at end of file
diff --git a/tests/resources/cherrypick/.gitted/objects/eb/da71fe44dcb60c53b8fbd53208a1204d32e959 b/tests/resources/cherrypick/.gitted/objects/eb/da71fe44dcb60c53b8fbd53208a1204d32e959
new file mode 100644
index 000000000..19d0c5288
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/eb/da71fe44dcb60c53b8fbd53208a1204d32e959
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/f0/5ed049854c1596a7cc0e957fab34961077f3ae b/tests/resources/cherrypick/.gitted/objects/f0/5ed049854c1596a7cc0e957fab34961077f3ae
new file mode 100644
index 000000000..ab454399e
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/f0/5ed049854c1596a7cc0e957fab34961077f3ae
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/f0/a4e1c66bb548cd2b22eebefda703872e969775 b/tests/resources/cherrypick/.gitted/objects/f0/a4e1c66bb548cd2b22eebefda703872e969775
new file mode 100644
index 000000000..558dd0a44
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/f0/a4e1c66bb548cd2b22eebefda703872e969775
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/f2/ec8c8cf1a9fb7aa047a25a4308bfe860237ad4 b/tests/resources/cherrypick/.gitted/objects/f2/ec8c8cf1a9fb7aa047a25a4308bfe860237ad4
new file mode 100644
index 000000000..a0117515c
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/f2/ec8c8cf1a9fb7aa047a25a4308bfe860237ad4
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/f5/684c96bf40c709877b56404cd8a5dd2d2a7978 b/tests/resources/cherrypick/.gitted/objects/f5/684c96bf40c709877b56404cd8a5dd2d2a7978
new file mode 100644
index 000000000..83bb5a0cc
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/f5/684c96bf40c709877b56404cd8a5dd2d2a7978
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/objects/f9/0f9dcbdac2cce5cc166346160e19cb693ef4e8 b/tests/resources/cherrypick/.gitted/objects/f9/0f9dcbdac2cce5cc166346160e19cb693ef4e8
new file mode 100644
index 000000000..71be9f834
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/objects/f9/0f9dcbdac2cce5cc166346160e19cb693ef4e8
Binary files differ
diff --git a/tests/resources/cherrypick/.gitted/refs/heads/automerge-branch b/tests/resources/cherrypick/.gitted/refs/heads/automerge-branch
new file mode 100644
index 000000000..9330ef3d1
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/refs/heads/automerge-branch
@@ -0,0 +1 @@
+d3d77487660ee3c0194ee01dc5eaf478782b1c7e
diff --git a/tests/resources/cherrypick/.gitted/refs/heads/master b/tests/resources/cherrypick/.gitted/refs/heads/master
new file mode 100644
index 000000000..aa8913beb
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/refs/heads/master
@@ -0,0 +1 @@
+2a26c7e88b285613b302ba76712bc998863f3cbc
diff --git a/tests/resources/cherrypick/.gitted/refs/heads/merge-branch b/tests/resources/cherrypick/.gitted/refs/heads/merge-branch
new file mode 100644
index 000000000..ea5b277ec
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/refs/heads/merge-branch
@@ -0,0 +1 @@
+abe4603bc7cd5b8167a267e0e2418fd2348f8cff
diff --git a/tests/resources/cherrypick/.gitted/refs/heads/merge-conflicts b/tests/resources/cherrypick/.gitted/refs/heads/merge-conflicts
new file mode 100644
index 000000000..f63f17efb
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/refs/heads/merge-conflicts
@@ -0,0 +1 @@
+bafbf6912c09505ac60575cd43d3f2aba3bd84d8
diff --git a/tests/resources/cherrypick/.gitted/refs/heads/merge-mainline b/tests/resources/cherrypick/.gitted/refs/heads/merge-mainline
new file mode 100644
index 000000000..0ec5e458c
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/refs/heads/merge-mainline
@@ -0,0 +1 @@
+cfc4f0999a8367568e049af4f72e452d40828a15
diff --git a/tests/resources/cherrypick/.gitted/refs/heads/orphan b/tests/resources/cherrypick/.gitted/refs/heads/orphan
new file mode 100644
index 000000000..f4d6a7467
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/refs/heads/orphan
@@ -0,0 +1 @@
+74f06b5bfec6d33d7264f73606b57a7c0b963819
diff --git a/tests/resources/cherrypick/.gitted/refs/heads/renames b/tests/resources/cherrypick/.gitted/refs/heads/renames
new file mode 100644
index 000000000..df5587173
--- /dev/null
+++ b/tests/resources/cherrypick/.gitted/refs/heads/renames
@@ -0,0 +1 @@
+44cd2ed2052c9c68f9a439d208e9614dc2a55c70
diff --git a/tests/resources/cherrypick/file1.txt b/tests/resources/cherrypick/file1.txt
new file mode 100644
index 000000000..38c05a857
--- /dev/null
+++ b/tests/resources/cherrypick/file1.txt
@@ -0,0 +1,15 @@
+!File 1
+File 1
+File 1
+File 1
+File 1
+File 1
+File 1
+File 1
+File 1
+File 1
+File 1
+File 1
+File 1
+File 1
+File 1
diff --git a/tests/resources/cherrypick/file2.txt b/tests/resources/cherrypick/file2.txt
new file mode 100644
index 000000000..a661b5dec
--- /dev/null
+++ b/tests/resources/cherrypick/file2.txt
@@ -0,0 +1,15 @@
+!File 2
+File 2
+File 2
+File 2
+File 2
+File 2
+File 2
+File 2
+File 2
+File 2
+File 2
+File 2
+File 2
+File 2
+File 2
diff --git a/tests/resources/cherrypick/file3.txt b/tests/resources/cherrypick/file3.txt
new file mode 100644
index 000000000..85a4a1d79
--- /dev/null
+++ b/tests/resources/cherrypick/file3.txt
@@ -0,0 +1,15 @@
+!File 3
+File 3
+File 3
+File 3
+File 3
+File 3
+File 3
+File 3
+File 3
+File 3
+File 3
+File 3
+File 3
+File 3
+File 3
diff --git a/tests/resources/config/config-include b/tests/resources/config/config-include
new file mode 100644
index 000000000..6b5e79de7
--- /dev/null
+++ b/tests/resources/config/config-include
@@ -0,0 +1,2 @@
+[include]
+ path = config-included
diff --git a/tests/resources/config/config-included b/tests/resources/config/config-included
new file mode 100644
index 000000000..089ca08a7
--- /dev/null
+++ b/tests/resources/config/config-included
@@ -0,0 +1,2 @@
+[foo "bar"]
+ baz = huzzah
diff --git a/tests/resources/config/config11 b/tests/resources/config/config11
new file mode 100644
index 000000000..1d8a74470
--- /dev/null
+++ b/tests/resources/config/config11
@@ -0,0 +1,5 @@
+[remote "ab"]
+ url = git://github.com/libgit2/libgit2
+ url = git://git.example.com/libgit2
+
+
diff --git a/tests/resources/config/config12 b/tests/resources/config/config12
new file mode 100644
index 000000000..b57a81b08
--- /dev/null
+++ b/tests/resources/config/config12
@@ -0,0 +1,7 @@
+[some "section"]
+ test = hi ; comment
+ other = "hello! \" ; ; ; " ; more test
+ multi = "hi, this is a ; \
+multiline comment # with ;\n special chars \
+and other stuff !@#"
+ back = "this is \ba phrase"
diff --git a/tests/resources/config/config13 b/tests/resources/config/config13
new file mode 100644
index 000000000..c1e0c5647
--- /dev/null
+++ b/tests/resources/config/config13
@@ -0,0 +1,2 @@
+[core]
+ editor = \"C:/Program Files/Nonsense/bah.exe\" \"--some option\"
diff --git a/tests/resources/config/config14 b/tests/resources/config/config14
new file mode 100644
index 000000000..ef2198c45
--- /dev/null
+++ b/tests/resources/config/config14
@@ -0,0 +1,4 @@
+[a]
+ b=c
+[d]
+ e = f
diff --git a/tests/resources/config/config15 b/tests/resources/config/config15
new file mode 100644
index 000000000..6d34f817b
--- /dev/null
+++ b/tests/resources/config/config15
@@ -0,0 +1,3 @@
+[core]
+ dummy2 = 7
+ global = 17
diff --git a/tests/resources/config/config16 b/tests/resources/config/config16
new file mode 100644
index 000000000..f25cdb728
--- /dev/null
+++ b/tests/resources/config/config16
@@ -0,0 +1,3 @@
+[core]
+ dummy2 = 28
+ system = 11
diff --git a/tests/resources/config/config17 b/tests/resources/config/config17
new file mode 100644
index 000000000..ca25a86af
--- /dev/null
+++ b/tests/resources/config/config17
@@ -0,0 +1,3 @@
+[core]
+ dummy2 = 7
+ global = 17 \ No newline at end of file
diff --git a/tests/resources/config/config18 b/tests/resources/config/config18
new file mode 100644
index 000000000..cb6fd5ebc
--- /dev/null
+++ b/tests/resources/config/config18
@@ -0,0 +1,5 @@
+[core]
+ int32global = 28
+ int64global = 9223372036854775803
+ boolglobal = true
+ stringglobal = I'm a global config value! \ No newline at end of file
diff --git a/tests/resources/config/config19 b/tests/resources/config/config19
new file mode 100644
index 000000000..f3ae5a640
--- /dev/null
+++ b/tests/resources/config/config19
@@ -0,0 +1,5 @@
+[core]
+ int32global = -1
+ int64global = -2
+ boolglobal = false
+ stringglobal = don't find me! \ No newline at end of file
diff --git a/tests/resources/config/config20 b/tests/resources/config/config20
new file mode 100644
index 000000000..8f0f12c4c
--- /dev/null
+++ b/tests/resources/config/config20
@@ -0,0 +1,11 @@
+[valid "[subsection]"]
+ something = a
+; we don't allow anything after closing "
+[sec "[subsec]/child"]
+ parent = grand
+[sec2 "[subsec2]/child2"]
+ type = dvcs
+[sec3 "escape\"quote"]
+ vcs = git
+[sec4 "escaping\\slash"]
+ lib = git2
diff --git a/tests/resources/config/config4 b/tests/resources/config/config4
index 741fa0ffd..9dd40419e 100644
--- a/tests/resources/config/config4
+++ b/tests/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/resources/config/config9 b/tests/resources/config/config9
index 34fdc07a5..fcaac424e 100644
--- a/tests/resources/config/config9
+++ b/tests/resources/config/config9
@@ -1,3 +1,9 @@
[core]
dummy2 = 42
+ verylong = 1
dummy = 1
+
+[remote "ab"]
+ url = http://example.com/git/ab
+[remote "abba"]
+ url = http://example.com/git/abba
diff --git a/tests/resources/crlf/.gitted/HEAD b/tests/resources/crlf/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/crlf/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/crlf/.gitted/config b/tests/resources/crlf/.gitted/config
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/resources/crlf/.gitted/config
diff --git a/tests/resources/crlf/.gitted/objects/04/de00b358f13389948756732158eaaaefa1448c b/tests/resources/crlf/.gitted/objects/04/de00b358f13389948756732158eaaaefa1448c
new file mode 100644
index 000000000..c3b7598c0
--- /dev/null
+++ b/tests/resources/crlf/.gitted/objects/04/de00b358f13389948756732158eaaaefa1448c
Binary files differ
diff --git a/tests/resources/crlf/.gitted/objects/0a/a76e474d259bd7c13eb726a1396c381db55c88 b/tests/resources/crlf/.gitted/objects/0a/a76e474d259bd7c13eb726a1396c381db55c88
new file mode 100644
index 000000000..e118d6656
--- /dev/null
+++ b/tests/resources/crlf/.gitted/objects/0a/a76e474d259bd7c13eb726a1396c381db55c88
Binary files differ
diff --git a/tests/resources/crlf/.gitted/objects/0d/06894e14df22e066763ae906e0ed3eb79c205f b/tests/resources/crlf/.gitted/objects/0d/06894e14df22e066763ae906e0ed3eb79c205f
new file mode 100644
index 000000000..b7a1f3290
--- /dev/null
+++ b/tests/resources/crlf/.gitted/objects/0d/06894e14df22e066763ae906e0ed3eb79c205f
Binary files differ
diff --git a/tests/resources/crlf/.gitted/objects/0f/f5a53f19bfd2b5eea1ba550295c47515678987 b/tests/resources/crlf/.gitted/objects/0f/f5a53f19bfd2b5eea1ba550295c47515678987
new file mode 100644
index 000000000..5366acd8c
--- /dev/null
+++ b/tests/resources/crlf/.gitted/objects/0f/f5a53f19bfd2b5eea1ba550295c47515678987
Binary files differ
diff --git a/tests/resources/crlf/.gitted/objects/12/faf3c1ea55f572473cec9052fca468c3584ccb b/tests/resources/crlf/.gitted/objects/12/faf3c1ea55f572473cec9052fca468c3584ccb
new file mode 100644
index 000000000..96d5b2f91
--- /dev/null
+++ b/tests/resources/crlf/.gitted/objects/12/faf3c1ea55f572473cec9052fca468c3584ccb
@@ -0,0 +1 @@
+x¥ŽÝ 1„}NÛ€²ù½,ˆøb6K6œ`.’‹Ø¾QìÀ·™o†ab-åÖA»ë0¡ódXš”•btnr:0¡cä¤yž(*´Y<Bãµå0‡¨q m-y«|TSöα6èGŸsáÙ—Úà’^¡%¸.µlu…#úQgþ?wˆµœ@jëµ”DöèÅ ãlç?gDl÷ ·¾‰7kP \ No newline at end of file
diff --git a/tests/resources/crlf/.gitted/objects/2c/9a868cfdf8e270d0ec68164433376c68fb1789 b/tests/resources/crlf/.gitted/objects/2c/9a868cfdf8e270d0ec68164433376c68fb1789
new file mode 100644
index 000000000..218e9d192
--- /dev/null
+++ b/tests/resources/crlf/.gitted/objects/2c/9a868cfdf8e270d0ec68164433376c68fb1789
Binary files differ
diff --git a/tests/resources/crlf/.gitted/objects/38/1cfe630df902bc29271a202d3277981180e4a6 b/tests/resources/crlf/.gitted/objects/38/1cfe630df902bc29271a202d3277981180e4a6
new file mode 100644
index 000000000..0cf707296
--- /dev/null
+++ b/tests/resources/crlf/.gitted/objects/38/1cfe630df902bc29271a202d3277981180e4a6
Binary files differ
diff --git a/tests/resources/crlf/.gitted/objects/79/9770d1cff46753a57db7a066159b5610da6e3a b/tests/resources/crlf/.gitted/objects/79/9770d1cff46753a57db7a066159b5610da6e3a
new file mode 100644
index 000000000..5c701b867
--- /dev/null
+++ b/tests/resources/crlf/.gitted/objects/79/9770d1cff46753a57db7a066159b5610da6e3a
Binary files differ
diff --git a/tests/resources/crlf/.gitted/objects/7c/ce67e58173e2b01f7db124ceaabe3183d19c49 b/tests/resources/crlf/.gitted/objects/7c/ce67e58173e2b01f7db124ceaabe3183d19c49
new file mode 100644
index 000000000..8e836aba1
--- /dev/null
+++ b/tests/resources/crlf/.gitted/objects/7c/ce67e58173e2b01f7db124ceaabe3183d19c49
Binary files differ
diff --git a/tests/resources/crlf/.gitted/objects/a9/a2e8913c1dbe2812fac5e6b4e0a4bd5d0d5966 b/tests/resources/crlf/.gitted/objects/a9/a2e8913c1dbe2812fac5e6b4e0a4bd5d0d5966
new file mode 100644
index 000000000..33d59f1f1
--- /dev/null
+++ b/tests/resources/crlf/.gitted/objects/a9/a2e8913c1dbe2812fac5e6b4e0a4bd5d0d5966
@@ -0,0 +1 @@
+xKÊÉOR02aH.ÊIãåÂ$œž  \ No newline at end of file
diff --git a/tests/resources/crlf/.gitted/objects/ba/aa042ab2976f8264e467988e6112ee518ec62e b/tests/resources/crlf/.gitted/objects/ba/aa042ab2976f8264e467988e6112ee518ec62e
new file mode 100644
index 000000000..4c544d5ef
--- /dev/null
+++ b/tests/resources/crlf/.gitted/objects/ba/aa042ab2976f8264e467988e6112ee518ec62e
Binary files differ
diff --git a/tests/resources/crlf/.gitted/objects/dc/88e3b917de821e25962bea7ec1f55c4ce2112c b/tests/resources/crlf/.gitted/objects/dc/88e3b917de821e25962bea7ec1f55c4ce2112c
new file mode 100644
index 000000000..3db13aa79
--- /dev/null
+++ b/tests/resources/crlf/.gitted/objects/dc/88e3b917de821e25962bea7ec1f55c4ce2112c
Binary files differ
diff --git a/tests/resources/crlf/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/tests/resources/crlf/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
new file mode 100644
index 000000000..711223894
--- /dev/null
+++ b/tests/resources/crlf/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
Binary files differ
diff --git a/tests/resources/crlf/.gitted/objects/ea/030d3c6cec212069eca698cabaa5b4550f1511 b/tests/resources/crlf/.gitted/objects/ea/030d3c6cec212069eca698cabaa5b4550f1511
new file mode 100644
index 000000000..117dc725a
--- /dev/null
+++ b/tests/resources/crlf/.gitted/objects/ea/030d3c6cec212069eca698cabaa5b4550f1511
Binary files differ
diff --git a/tests/resources/crlf/.gitted/objects/ef/0dcd356d77221e9c27f4f3928ad28e80b87ceb b/tests/resources/crlf/.gitted/objects/ef/0dcd356d77221e9c27f4f3928ad28e80b87ceb
new file mode 100644
index 000000000..33aceda12
--- /dev/null
+++ b/tests/resources/crlf/.gitted/objects/ef/0dcd356d77221e9c27f4f3928ad28e80b87ceb
Binary files differ
diff --git a/tests/resources/crlf/.gitted/objects/fe/085d9ace90cc675b87df15e1aeed0c3a31407f b/tests/resources/crlf/.gitted/objects/fe/085d9ace90cc675b87df15e1aeed0c3a31407f
new file mode 100644
index 000000000..2e8d10b76
--- /dev/null
+++ b/tests/resources/crlf/.gitted/objects/fe/085d9ace90cc675b87df15e1aeed0c3a31407f
Binary files differ
diff --git a/tests/resources/crlf/.gitted/refs/heads/master b/tests/resources/crlf/.gitted/refs/heads/master
new file mode 100644
index 000000000..cfdaaf37b
--- /dev/null
+++ b/tests/resources/crlf/.gitted/refs/heads/master
@@ -0,0 +1 @@
+2c9a868cfdf8e270d0ec68164433376c68fb1789
diff --git a/tests/resources/crlf/.gitted/refs/heads/utf8 b/tests/resources/crlf/.gitted/refs/heads/utf8
new file mode 100644
index 000000000..4b32f7f91
--- /dev/null
+++ b/tests/resources/crlf/.gitted/refs/heads/utf8
@@ -0,0 +1 @@
+baaa042ab2976f8264e467988e6112ee518ec62e
diff --git a/tests/resources/deprecated-mode.git/HEAD b/tests/resources/deprecated-mode.git/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/deprecated-mode.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/deprecated-mode.git/config b/tests/resources/deprecated-mode.git/config
new file mode 100644
index 000000000..f57351fd5
--- /dev/null
+++ b/tests/resources/deprecated-mode.git/config
@@ -0,0 +1,6 @@
+[core]
+ bare = true
+ repositoryformatversion = 0
+ filemode = false
+ logallrefupdates = true
+ ignorecase = true
diff --git a/tests/resources/deprecated-mode.git/description b/tests/resources/deprecated-mode.git/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/deprecated-mode.git/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/deprecated-mode.git/index b/tests/resources/deprecated-mode.git/index
new file mode 100644
index 000000000..682740603
--- /dev/null
+++ b/tests/resources/deprecated-mode.git/index
Binary files differ
diff --git a/tests/resources/deprecated-mode.git/info/exclude b/tests/resources/deprecated-mode.git/info/exclude
new file mode 100644
index 000000000..6d05881d3
--- /dev/null
+++ b/tests/resources/deprecated-mode.git/info/exclude
@@ -0,0 +1,2 @@
+# File patterns to ignore; see `git help ignore` for more information.
+# Lines that start with '#' are comments.
diff --git a/tests/resources/deprecated-mode.git/objects/06/262edc257418e9987caf999f9a7a3e1547adff b/tests/resources/deprecated-mode.git/objects/06/262edc257418e9987caf999f9a7a3e1547adff
new file mode 100644
index 000000000..a030cf7a7
--- /dev/null
+++ b/tests/resources/deprecated-mode.git/objects/06/262edc257418e9987caf999f9a7a3e1547adff
Binary files differ
diff --git a/tests/resources/deprecated-mode.git/objects/08/10fb7818088ff5ac41ee49199b51473b1bd6c7 b/tests/resources/deprecated-mode.git/objects/08/10fb7818088ff5ac41ee49199b51473b1bd6c7
new file mode 100644
index 000000000..52d56936a
--- /dev/null
+++ b/tests/resources/deprecated-mode.git/objects/08/10fb7818088ff5ac41ee49199b51473b1bd6c7
Binary files differ
diff --git a/tests/resources/deprecated-mode.git/objects/1b/05fdaa881ee45b48cbaa5e9b037d667a47745e b/tests/resources/deprecated-mode.git/objects/1b/05fdaa881ee45b48cbaa5e9b037d667a47745e
new file mode 100644
index 000000000..ae7765a70
--- /dev/null
+++ b/tests/resources/deprecated-mode.git/objects/1b/05fdaa881ee45b48cbaa5e9b037d667a47745e
Binary files differ
diff --git a/tests/resources/deprecated-mode.git/objects/3d/0970ec547fc41ef8a5882dde99c6adce65b021 b/tests/resources/deprecated-mode.git/objects/3d/0970ec547fc41ef8a5882dde99c6adce65b021
new file mode 100644
index 000000000..595283e89
--- /dev/null
+++ b/tests/resources/deprecated-mode.git/objects/3d/0970ec547fc41ef8a5882dde99c6adce65b021
Binary files differ
diff --git a/tests/resources/deprecated-mode.git/refs/heads/master b/tests/resources/deprecated-mode.git/refs/heads/master
new file mode 100644
index 000000000..1e106dfa4
--- /dev/null
+++ b/tests/resources/deprecated-mode.git/refs/heads/master
@@ -0,0 +1 @@
+06262edc257418e9987caf999f9a7a3e1547adff
diff --git a/tests/resources/diff/.gitted/HEAD b/tests/resources/diff/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/diff/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/diff/.gitted/config b/tests/resources/diff/.gitted/config
new file mode 100644
index 000000000..77a27ef1d
--- /dev/null
+++ b/tests/resources/diff/.gitted/config
@@ -0,0 +1,6 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = false
diff --git a/tests/resources/diff/.gitted/description b/tests/resources/diff/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/diff/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/diff/.gitted/index b/tests/resources/diff/.gitted/index
new file mode 100644
index 000000000..e1071874e
--- /dev/null
+++ b/tests/resources/diff/.gitted/index
Binary files differ
diff --git a/tests/resources/diff/.gitted/info/exclude b/tests/resources/diff/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/diff/.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/resources/diff/.gitted/logs/HEAD b/tests/resources/diff/.gitted/logs/HEAD
new file mode 100644
index 000000000..8c6f6fd18
--- /dev/null
+++ b/tests/resources/diff/.gitted/logs/HEAD
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 d70d245ed97ed2aa596dd1af6536e4bfdb047b69 Russell Belfer <rb@github.com> 1347559804 -0700 commit (initial): initial commit
+d70d245ed97ed2aa596dd1af6536e4bfdb047b69 7a9e0b02e63179929fed24f0a3e0f19168114d10 Russell Belfer <rb@github.com> 1347560491 -0700 commit: some changes
diff --git a/tests/resources/diff/.gitted/logs/refs/heads/master b/tests/resources/diff/.gitted/logs/refs/heads/master
new file mode 100644
index 000000000..8c6f6fd18
--- /dev/null
+++ b/tests/resources/diff/.gitted/logs/refs/heads/master
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 d70d245ed97ed2aa596dd1af6536e4bfdb047b69 Russell Belfer <rb@github.com> 1347559804 -0700 commit (initial): initial commit
+d70d245ed97ed2aa596dd1af6536e4bfdb047b69 7a9e0b02e63179929fed24f0a3e0f19168114d10 Russell Belfer <rb@github.com> 1347560491 -0700 commit: some changes
diff --git a/tests/resources/diff/.gitted/objects/29/ab7053bb4dde0298e03e2c179e890b7dd465a7 b/tests/resources/diff/.gitted/objects/29/ab7053bb4dde0298e03e2c179e890b7dd465a7
new file mode 100644
index 000000000..94f9a676d
--- /dev/null
+++ b/tests/resources/diff/.gitted/objects/29/ab7053bb4dde0298e03e2c179e890b7dd465a7
Binary files differ
diff --git a/tests/resources/diff/.gitted/objects/3e/5bcbad2a68e5bc60a53b8388eea53a1a7ab847 b/tests/resources/diff/.gitted/objects/3e/5bcbad2a68e5bc60a53b8388eea53a1a7ab847
new file mode 100644
index 000000000..9fed523dc
--- /dev/null
+++ b/tests/resources/diff/.gitted/objects/3e/5bcbad2a68e5bc60a53b8388eea53a1a7ab847
Binary files differ
diff --git a/tests/resources/diff/.gitted/objects/54/6c735f16a3b44d9784075c2c0dab2ac9bf1989 b/tests/resources/diff/.gitted/objects/54/6c735f16a3b44d9784075c2c0dab2ac9bf1989
new file mode 100644
index 000000000..d7df4d6a1
--- /dev/null
+++ b/tests/resources/diff/.gitted/objects/54/6c735f16a3b44d9784075c2c0dab2ac9bf1989
Binary files differ
diff --git a/tests/resources/diff/.gitted/objects/7a/9e0b02e63179929fed24f0a3e0f19168114d10 b/tests/resources/diff/.gitted/objects/7a/9e0b02e63179929fed24f0a3e0f19168114d10
new file mode 100644
index 000000000..9bc25eb34
--- /dev/null
+++ b/tests/resources/diff/.gitted/objects/7a/9e0b02e63179929fed24f0a3e0f19168114d10
Binary files differ
diff --git a/tests/resources/diff/.gitted/objects/7b/808f723a8ca90df319682c221187235af76693 b/tests/resources/diff/.gitted/objects/7b/808f723a8ca90df319682c221187235af76693
new file mode 100644
index 000000000..2fd266be6
--- /dev/null
+++ b/tests/resources/diff/.gitted/objects/7b/808f723a8ca90df319682c221187235af76693
Binary files differ
diff --git a/tests/resources/diff/.gitted/objects/88/789109439c1e1c3cd45224001edee5304ed53c b/tests/resources/diff/.gitted/objects/88/789109439c1e1c3cd45224001edee5304ed53c
new file mode 100644
index 000000000..7598b5914
--- /dev/null
+++ b/tests/resources/diff/.gitted/objects/88/789109439c1e1c3cd45224001edee5304ed53c
@@ -0,0 +1 @@
+x+)JMU07g040031QHÌË/ÉH-Ò+©(aÉ)Ž[¼Åwz {Œïj­“û%;¡ÊŠRSrSÁª4Wïö½Ç4ãŽø¼NîÚ+©Ë¶a \ No newline at end of file
diff --git a/tests/resources/diff/.gitted/objects/cb/8294e696339863df760b2ff5d1e275bee72455 b/tests/resources/diff/.gitted/objects/cb/8294e696339863df760b2ff5d1e275bee72455
new file mode 100644
index 000000000..86ebe04fe
--- /dev/null
+++ b/tests/resources/diff/.gitted/objects/cb/8294e696339863df760b2ff5d1e275bee72455
Binary files differ
diff --git a/tests/resources/diff/.gitted/objects/d7/0d245ed97ed2aa596dd1af6536e4bfdb047b69 b/tests/resources/diff/.gitted/objects/d7/0d245ed97ed2aa596dd1af6536e4bfdb047b69
new file mode 100644
index 000000000..99304c4aa
--- /dev/null
+++ b/tests/resources/diff/.gitted/objects/d7/0d245ed97ed2aa596dd1af6536e4bfdb047b69
@@ -0,0 +1 @@
+x•Û !óm·_ׄRB:XÝkVpWpµÿ© ¿‡™9±î{î ,^z#‚œôšŒ7JygÔš¬áA¦„« i1Y©Ù2úV¼ÇyR)𢒨Á½…ç'÷m„[¬û„ÒÑ;®áÊ-çl®ó¯Oô_“å#÷¼ø%Øœv8¤ \ No newline at end of file
diff --git a/tests/resources/diff/.gitted/refs/heads/master b/tests/resources/diff/.gitted/refs/heads/master
new file mode 100644
index 000000000..a83afc38b
--- /dev/null
+++ b/tests/resources/diff/.gitted/refs/heads/master
@@ -0,0 +1 @@
+7a9e0b02e63179929fed24f0a3e0f19168114d10
diff --git a/tests/resources/diff/another.txt b/tests/resources/diff/another.txt
new file mode 100644
index 000000000..d0e0bae4d
--- /dev/null
+++ b/tests/resources/diff/another.txt
@@ -0,0 +1,38 @@
+Git is fast. With Git, nearly all operations are performed locally, giving
+it an huge speed advantage on centralized systems that constantly have to
+communicate with a server somewh3r3.
+
+For testing, large AWS instances were set up in the same availability
+zone. Git and SVN were installed on both machines, the Ruby repository was
+copied to both Git and SVN servers, and common operations were performed on
+both.
+
+In some cases the commands don't match up exactly. Here, matching on the
+lowest common denominator was attempted. For example, the 'commit' tests
+also include the time to push for Git, though most of the time you would not
+actually be pushing to the server immediately after a commit where the two
+commands cannot be separated in SVN.
+
+Note that this is the best case scenario for SVN - a server with no load
+with an 80MB/s bandwidth connection to the client machine. Nearly all of
+these times would be even worse for SVN if that connection was slower, while
+many of the Git times would not be affected.
+
+Clearly, in many of these common version control operations, Git is one or
+two orders of magnitude faster than SVN, even under ideal conditions for
+SVN.
+
+Let's see how common operations stack up against Subversion, a common
+centralized version control system that is similar to CVS or
+Perforce. Smaller is faster.
+
+One place where Git is slower is in the initial clone operation. Here, Git
+One place where Git is slower is in the initial clone operation. Here, Git
+One place where Git is slower is in the initial clone operation. Here, Git
+seen in the above charts, it's not considerably slower for an operation that
+is only performed once.
+
+It's also interesting to note that the size of the data on the client side
+is very similar even though Git also has every version of every file for the
+entire history of the project. This illustrates how efficient it is at
+compressing and storing data on the client side. \ No newline at end of file
diff --git a/tests/resources/diff/readme.txt b/tests/resources/diff/readme.txt
new file mode 100644
index 000000000..beedf288d
--- /dev/null
+++ b/tests/resources/diff/readme.txt
@@ -0,0 +1,36 @@
+The Git feature that r3ally mak3s it stand apart from n3arly 3v3ry other SCM
+out there is its branching model.
+
+Git allows and encourages you to have multiple local branches that can be
+entirely independent of each other. The creation, merging, and deletion of
+those lines of development takes seconds.
+
+Git allows and encourages you to have multiple local branches that can be
+entirely independent of each other. The creation, merging, and deletion of
+those lines of development takes seconds.
+
+This means that you can do things like:
+
+Role-Bas3d Codelin3s. Have a branch that always contains only what goes to
+production, another that you merge work into for testing, and several
+smaller ones for day to day work.
+
+Feature Based Workflow. Create new branches for each new feature you're
+working on so you can seamlessly switch back and forth between them, then
+delete each branch when that feature gets merged into your main line.
+
+Disposable Experimentation. Create a branch to experiment in, realize it's
+not going to work, and just delete it - abandoning the work—with nobody else
+ever seeing it (even if you've pushed other branches in the meantime).
+
+Notably, when you push to a remote repository, you do not have to push all
+share it with others.
+
+Git allows and encourages you to have multiple local branches that can be
+entirely independent of each other. The creation, merging, and deletion of
+those lines of development takes seconds.
+
+There are ways to accomplish some of this with other systems, but the work
+involved is much more difficult and error-prone. Git makes this process
+incredibly easy and it changes the way most developers work when they learn
+it.!
diff --git a/tests/resources/diff_format_email/.gitted/HEAD b/tests/resources/diff_format_email/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/diff_format_email/.gitted/config b/tests/resources/diff_format_email/.gitted/config
new file mode 100644
index 000000000..6c9406b7d
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/config
@@ -0,0 +1,7 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
+ precomposeunicode = true
diff --git a/tests/resources/diff_format_email/.gitted/index b/tests/resources/diff_format_email/.gitted/index
new file mode 100644
index 000000000..f73027e56
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/index
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/info/exclude b/tests/resources/diff_format_email/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/diff_format_email/.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/resources/diff_format_email/.gitted/objects/0a/37045ca6d8503e9bcf06a12abbbc8e92664cce b/tests/resources/diff_format_email/.gitted/objects/0a/37045ca6d8503e9bcf06a12abbbc8e92664cce
new file mode 100644
index 000000000..1ece99cde
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/0a/37045ca6d8503e9bcf06a12abbbc8e92664cce
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/10/808fe9c9be5a190c0ba68d1a002233fb363508 b/tests/resources/diff_format_email/.gitted/objects/10/808fe9c9be5a190c0ba68d1a002233fb363508
new file mode 100644
index 000000000..90313feec
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/10/808fe9c9be5a190c0ba68d1a002233fb363508
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/13/ecf3d572dbc5e5b32c8ba067d1d1e0939572e8 b/tests/resources/diff_format_email/.gitted/objects/13/ecf3d572dbc5e5b32c8ba067d1d1e0939572e8
new file mode 100644
index 000000000..360dc3bc7
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/13/ecf3d572dbc5e5b32c8ba067d1d1e0939572e8
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/17/cfad36e93db7706b16bef5ef842ba1e5ca06ab b/tests/resources/diff_format_email/.gitted/objects/17/cfad36e93db7706b16bef5ef842ba1e5ca06ab
new file mode 100644
index 000000000..603aa496a
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/17/cfad36e93db7706b16bef5ef842ba1e5ca06ab
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/1a/9932083f96b0db42552103d40076f62fa8235e b/tests/resources/diff_format_email/.gitted/objects/1a/9932083f96b0db42552103d40076f62fa8235e
new file mode 100644
index 000000000..b6f04538e
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/1a/9932083f96b0db42552103d40076f62fa8235e
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/1a/e3be57f869687d983066a0f5d2aaea1b82ddc5 b/tests/resources/diff_format_email/.gitted/objects/1a/e3be57f869687d983066a0f5d2aaea1b82ddc5
new file mode 100644
index 000000000..be85c78ba
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/1a/e3be57f869687d983066a0f5d2aaea1b82ddc5
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/1b/525b0a6c5218b069b601ce91fce8eaf0a54e20 b/tests/resources/diff_format_email/.gitted/objects/1b/525b0a6c5218b069b601ce91fce8eaf0a54e20
new file mode 100644
index 000000000..e8145edfd
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/1b/525b0a6c5218b069b601ce91fce8eaf0a54e20
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/1e/82c3b234e37da82e5b23e0e2a70bca68ee12c6 b/tests/resources/diff_format_email/.gitted/objects/1e/82c3b234e37da82e5b23e0e2a70bca68ee12c6
new file mode 100644
index 000000000..3ae87cfa9
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/1e/82c3b234e37da82e5b23e0e2a70bca68ee12c6
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/1e/875da9b1e67f853b2eec3e202c21c867097234 b/tests/resources/diff_format_email/.gitted/objects/1e/875da9b1e67f853b2eec3e202c21c867097234
new file mode 100644
index 000000000..f80b3612c
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/1e/875da9b1e67f853b2eec3e202c21c867097234
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/20/609dbbc32bbfc827528eec3fcea2d024e6dd8a b/tests/resources/diff_format_email/.gitted/objects/20/609dbbc32bbfc827528eec3fcea2d024e6dd8a
new file mode 100644
index 000000000..190c3f272
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/20/609dbbc32bbfc827528eec3fcea2d024e6dd8a
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/23/f92946d3f38bd090f700d3e8e7b728ffc58264 b/tests/resources/diff_format_email/.gitted/objects/23/f92946d3f38bd090f700d3e8e7b728ffc58264
new file mode 100644
index 000000000..42f49ae8a
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/23/f92946d3f38bd090f700d3e8e7b728ffc58264
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/24/97c5249408494e66e25070a8c74e49eaeeb6c3 b/tests/resources/diff_format_email/.gitted/objects/24/97c5249408494e66e25070a8c74e49eaeeb6c3
new file mode 100644
index 000000000..e1ede9ae9
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/24/97c5249408494e66e25070a8c74e49eaeeb6c3
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/24/9a4263be23b4d1c02484cb840b6eca4c6cf74d b/tests/resources/diff_format_email/.gitted/objects/24/9a4263be23b4d1c02484cb840b6eca4c6cf74d
new file mode 100644
index 000000000..1b8d68951
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/24/9a4263be23b4d1c02484cb840b6eca4c6cf74d
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/25/2a3e19fd2c6fb7b20c111142c5bd5fb9ea6b8e b/tests/resources/diff_format_email/.gitted/objects/25/2a3e19fd2c6fb7b20c111142c5bd5fb9ea6b8e
new file mode 100644
index 000000000..10c34009d
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/25/2a3e19fd2c6fb7b20c111142c5bd5fb9ea6b8e
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/27/93544db9060bab4f9169e5b89c82f9fa7c7fa6 b/tests/resources/diff_format_email/.gitted/objects/27/93544db9060bab4f9169e5b89c82f9fa7c7fa6
new file mode 100644
index 000000000..689f5b9a1
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/27/93544db9060bab4f9169e5b89c82f9fa7c7fa6
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/29/1f1ff3cbb9a6f153678d9657679e3d4bf257df b/tests/resources/diff_format_email/.gitted/objects/29/1f1ff3cbb9a6f153678d9657679e3d4bf257df
new file mode 100644
index 000000000..6af5dda9d
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/29/1f1ff3cbb9a6f153678d9657679e3d4bf257df
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/2f/f7b811eee62a73959350b1f7349f6f4d0c882d b/tests/resources/diff_format_email/.gitted/objects/2f/f7b811eee62a73959350b1f7349f6f4d0c882d
new file mode 100644
index 000000000..d2b911d0e
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/2f/f7b811eee62a73959350b1f7349f6f4d0c882d
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/39/91dce9e71a0641ca49a6a4eea6c9e7ff402ed4 b/tests/resources/diff_format_email/.gitted/objects/39/91dce9e71a0641ca49a6a4eea6c9e7ff402ed4
new file mode 100644
index 000000000..69d213dcb
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/39/91dce9e71a0641ca49a6a4eea6c9e7ff402ed4
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/45/eef2a9317e179984649de247269e38cd5d99cf b/tests/resources/diff_format_email/.gitted/objects/45/eef2a9317e179984649de247269e38cd5d99cf
new file mode 100644
index 000000000..e5014565b
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/45/eef2a9317e179984649de247269e38cd5d99cf
@@ -0,0 +1,2 @@
+x¥ÎÁJÅ0…a×yŠÙ_I›IqwÁ¥o0Lo+$¹&©àÛ[õÜþŸ””öƒŸzU…à&ÒÁFY„,ñ:¯ÞYš¼CT·8B¥Õ ŽæÎUs¯H,BL‘Â)ËÈ(‚ër.cŒažÃ }+^Y>mpÕšö¶_ žÞÿÚí¥õºsçG)éì&k}˜G¸à€hÎzR»þëļi*Ÿ
+RrÿñsŽ ç›Âý—Ôö’›ùÐXã \ No newline at end of file
diff --git a/tests/resources/diff_format_email/.gitted/objects/4a/076277b884c519a932be67e346db2ac80a98fa b/tests/resources/diff_format_email/.gitted/objects/4a/076277b884c519a932be67e346db2ac80a98fa
new file mode 100644
index 000000000..b855408e8
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/4a/076277b884c519a932be67e346db2ac80a98fa
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/4c/3bd7182ad66ea7aa20ba47ae82812b710d169c b/tests/resources/diff_format_email/.gitted/objects/4c/3bd7182ad66ea7aa20ba47ae82812b710d169c
new file mode 100644
index 000000000..65740b42c
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/4c/3bd7182ad66ea7aa20ba47ae82812b710d169c
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/4c/a10087e696d2ba78d07b146a118e9a7096ed4f b/tests/resources/diff_format_email/.gitted/objects/4c/a10087e696d2ba78d07b146a118e9a7096ed4f
new file mode 100644
index 000000000..b05e7d634
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/4c/a10087e696d2ba78d07b146a118e9a7096ed4f
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/4d/de2b17d1c982cd988f21d24350a214401e4a1e b/tests/resources/diff_format_email/.gitted/objects/4d/de2b17d1c982cd988f21d24350a214401e4a1e
new file mode 100644
index 000000000..57a8dfed1
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/4d/de2b17d1c982cd988f21d24350a214401e4a1e
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/4f/31e0248ac800a1edc78b74f74e86f5eba90e87 b/tests/resources/diff_format_email/.gitted/objects/4f/31e0248ac800a1edc78b74f74e86f5eba90e87
new file mode 100644
index 000000000..0f31b97d6
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/4f/31e0248ac800a1edc78b74f74e86f5eba90e87
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/50/17c9456d013b2c7712d29aab73b681c880f509 b/tests/resources/diff_format_email/.gitted/objects/50/17c9456d013b2c7712d29aab73b681c880f509
new file mode 100644
index 000000000..5b96aa5ea
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/50/17c9456d013b2c7712d29aab73b681c880f509
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/50/438cfa585c1d15cf3650ed1bf641da937cc261 b/tests/resources/diff_format_email/.gitted/objects/50/438cfa585c1d15cf3650ed1bf641da937cc261
new file mode 100644
index 000000000..8db9090bc
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/50/438cfa585c1d15cf3650ed1bf641da937cc261
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/52/c3cd1ff6234b95fecbaf9ef13624da17697b8d b/tests/resources/diff_format_email/.gitted/objects/52/c3cd1ff6234b95fecbaf9ef13624da17697b8d
new file mode 100644
index 000000000..4bbc0ea43
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/52/c3cd1ff6234b95fecbaf9ef13624da17697b8d
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/55/0d730ba1b8c4937ea170b37c7ba91d792c0aaa b/tests/resources/diff_format_email/.gitted/objects/55/0d730ba1b8c4937ea170b37c7ba91d792c0aaa
new file mode 100644
index 000000000..680e48762
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/55/0d730ba1b8c4937ea170b37c7ba91d792c0aaa
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/66/81f1844dc677e5ff07ffd993461f5c441e6af5 b/tests/resources/diff_format_email/.gitted/objects/66/81f1844dc677e5ff07ffd993461f5c441e6af5
new file mode 100644
index 000000000..86a38289b
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/66/81f1844dc677e5ff07ffd993461f5c441e6af5
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/69/ddefb5c245e2f9ee62bd4cabd8ebe60a01e448 b/tests/resources/diff_format_email/.gitted/objects/69/ddefb5c245e2f9ee62bd4cabd8ebe60a01e448
new file mode 100644
index 000000000..81b606f4e
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/69/ddefb5c245e2f9ee62bd4cabd8ebe60a01e448
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/6b/6c2067c6d968f9bddb9b900ee1ab7e5b067430 b/tests/resources/diff_format_email/.gitted/objects/6b/6c2067c6d968f9bddb9b900ee1ab7e5b067430
new file mode 100644
index 000000000..aa9d7b0cd
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/6b/6c2067c6d968f9bddb9b900ee1ab7e5b067430
@@ -0,0 +1,2 @@
+x¥Î;
+1…aë¬âö‚ä ˆX)¸‹$sãDˆ3æº{.Áö+þsšsjÀ…Þµ‚ '£fg=Cm⤄çˆA §<p&m¨5\H²¹‚Ï:0¥• ThaŒC…—J[;̱¨<Î^)‰ëmY Ü\xu¬pÅ’S]ú§Âññ³û¹¶’\s‡°æ01†¸dTžrJÉÐqµá_rIム/P¿Ký \ No newline at end of file
diff --git a/tests/resources/diff_format_email/.gitted/objects/6b/ef49b206b29d9c46456e075722cd1a48b41e4c b/tests/resources/diff_format_email/.gitted/objects/6b/ef49b206b29d9c46456e075722cd1a48b41e4c
new file mode 100644
index 000000000..49e9d65f4
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/6b/ef49b206b29d9c46456e075722cd1a48b41e4c
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/6c/15659c036377aebf3b4569959ca1f5bedb551f b/tests/resources/diff_format_email/.gitted/objects/6c/15659c036377aebf3b4569959ca1f5bedb551f
new file mode 100644
index 000000000..e32471ba9
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/6c/15659c036377aebf3b4569959ca1f5bedb551f
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/6e/05acc5a5dab507d91a0a0cc0fb05a3dd98892d b/tests/resources/diff_format_email/.gitted/objects/6e/05acc5a5dab507d91a0a0cc0fb05a3dd98892d
new file mode 100644
index 000000000..26ee22429
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/6e/05acc5a5dab507d91a0a0cc0fb05a3dd98892d
@@ -0,0 +1,2 @@
+x¥ANÄ0 EYçÞ¨œ8“` f‡Ä’¸‰ËtD¦ºÜž"€åOúú¿,­Í!ñuUÈ1Õ£*Š…Cò•2G,2‰pžt+“{—®7)ŽœJªcEÅ,JT 1IâCˆ»›88Ùì²tx–ò±é
+OÚÛ¼^¶¯¯¿ìõ¼ZŸÅd(K;'Θ‘ ÝN÷©¦ÿ*q/z“¦¦ùMý`Ÿ÷§¿0ì§~´ûàW@ \ No newline at end of file
diff --git a/tests/resources/diff_format_email/.gitted/objects/74/6d514eae0c330261d37940cab33aa97fefbd93 b/tests/resources/diff_format_email/.gitted/objects/74/6d514eae0c330261d37940cab33aa97fefbd93
new file mode 100644
index 000000000..ee55d64d7
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/74/6d514eae0c330261d37940cab33aa97fefbd93
@@ -0,0 +1 @@
+x+)JMU01e040031QHËÌI5Ô+©(Ñ+JÍKÌMMaXY¾àB¸ØŒ¢î|ý²Ò-a'{"ÌŠw \ No newline at end of file
diff --git a/tests/resources/diff_format_email/.gitted/objects/74/a4d5394ebcfa7e9f445680897dfbc96586bc86 b/tests/resources/diff_format_email/.gitted/objects/74/a4d5394ebcfa7e9f445680897dfbc96586bc86
new file mode 100644
index 000000000..1bae4a6d6
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/74/a4d5394ebcfa7e9f445680897dfbc96586bc86
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/77/d0a3ed37236a7941d564f08d68d3b36462d231 b/tests/resources/diff_format_email/.gitted/objects/77/d0a3ed37236a7941d564f08d68d3b36462d231
new file mode 100644
index 000000000..db037caf9
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/77/d0a3ed37236a7941d564f08d68d3b36462d231
@@ -0,0 +1,2 @@
+x¥Aj1 E³ö)´/ìØ„®
+¹…$ËÍ<N<žEoŸ¡ÛÇçñŸ´ZçèãatU@ì)3‹Cæ" 㓪¸"J˜-z 9'2Oêº HÑ%J(5¸IÙ2:õ| ¼ï¹Lž\t†¶qon$¯MWøÖ^çõ¾ý®p~ü±Ÿë:úLƒ>¥Õ LîqŠÞGø°h­Ùé~uè¿$æ+g¨­+H[Æ^`ÞÈQW \ No newline at end of file
diff --git a/tests/resources/diff_format_email/.gitted/objects/7a/de76dd34bba4733cf9878079f9fd4a456a9189 b/tests/resources/diff_format_email/.gitted/objects/7a/de76dd34bba4733cf9878079f9fd4a456a9189
new file mode 100644
index 000000000..cf9bdaa5f
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/7a/de76dd34bba4733cf9878079f9fd4a456a9189
@@ -0,0 +1,3 @@
+x¥ŽA
+Â0E]ç³d’4M"î÷`šm…´5IÞÞ¢Gpû>¼ÿâœÒXÁ4aW³°nˆŒ3â y Œ–,[F­í½ôH­oD-”eªÐ
+:ŠÑ‘cêzš0F¼÷Ûb™C×ÊÖ:Ì®_«¸HNcÖwãóÇçRóH•qN'Ð6x­½F {4ˆj£[j•¿$ê¶0Uåû_Æy*ê&R \ No newline at end of file
diff --git a/tests/resources/diff_format_email/.gitted/objects/7a/ff11da95ca2be0bfb74b06e7cc1c480559dbe7 b/tests/resources/diff_format_email/.gitted/objects/7a/ff11da95ca2be0bfb74b06e7cc1c480559dbe7
new file mode 100644
index 000000000..d8c9934f7
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/7a/ff11da95ca2be0bfb74b06e7cc1c480559dbe7
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/7f/854619451620f7fbcec7ea171675e615ce92b6 b/tests/resources/diff_format_email/.gitted/objects/7f/854619451620f7fbcec7ea171675e615ce92b6
new file mode 100644
index 000000000..df4cbb322
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/7f/854619451620f7fbcec7ea171675e615ce92b6
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/87/3806f6f27e631eb0b23e4b56bea2bfac14a373 b/tests/resources/diff_format_email/.gitted/objects/87/3806f6f27e631eb0b23e4b56bea2bfac14a373
new file mode 100644
index 000000000..890abcd4a
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/87/3806f6f27e631eb0b23e4b56bea2bfac14a373
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/89/47a46e2097638ca6040ad4877246f4186ec3bd b/tests/resources/diff_format_email/.gitted/objects/89/47a46e2097638ca6040ad4877246f4186ec3bd
new file mode 100644
index 000000000..d4018bf8e
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/89/47a46e2097638ca6040ad4877246f4186ec3bd
@@ -0,0 +1,2 @@
+x•ŽAjÃ0E³Ö)´/„ÑŒ,ÉJw….{ƒ‘ò8 ;•åEo_C{nßâ½WÖZçnYÒ©7À:¤8\uÌ!NiÌ@0qaWRˆ4FožÚ°t›¢$
+S˜8"ˆC¦ÌŸ‡¡œ'-ΫD1º÷ûÚ쇖¯›}G«óvß¿7{yü²ÛÛÖÛ¬]Ïe­¯ÖÉ¢‘¼Øb"sÐcµãßöÎñŸÄ|bÑŠ«ùþûLÊ \ No newline at end of file
diff --git a/tests/resources/diff_format_email/.gitted/objects/89/7d3af16ca9e420cd071b1c4541bd2b91d04c8c b/tests/resources/diff_format_email/.gitted/objects/89/7d3af16ca9e420cd071b1c4541bd2b91d04c8c
new file mode 100644
index 000000000..1dce143b7
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/89/7d3af16ca9e420cd071b1c4541bd2b91d04c8c
@@ -0,0 +1 @@
+x¥ÎANÄ0 @QÖ9…÷HȵÓ$•šY!q '±™ éÒt1·g$ŽÀö-¾~ÙÖµ ŸžFWb[hñ¡²qÊ´ˆXY“Æ)™•9Qðî[ºÞ¤È ƒ£¨'͘‰Õç9dÊ&eò‘ã²uøòsèïÚ׶_Žû¯_öyÚGo2ä¥lëL¼D3§ÏHˆî¡Õ¡ÿŠ¸s­Z!·›ô;X»ªû"Pq \ No newline at end of file
diff --git a/tests/resources/diff_format_email/.gitted/objects/8d/7523f6fcb2404257889abe0d96f093d9f524f9 b/tests/resources/diff_format_email/.gitted/objects/8d/7523f6fcb2404257889abe0d96f093d9f524f9
new file mode 100644
index 000000000..903ec751c
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/8d/7523f6fcb2404257889abe0d96f093d9f524f9
@@ -0,0 +1 @@
+x¥ÎAJÅ0€a×9Åì™IÓ¤w‚à!f&_äõUÓtñnoÁ#¸ý¿nëÚø™F7JZ¹LÑòT$%ŒBQ¬ÎV—à…ÉfeŒ,î›»Ý,9•‰+EålÁ£L$¤a$ÅK¦‚Au|ŒËÖáõç°Þ¬¯m¿÷ž¿þÚçë>zãÁOº­/@SNOfGôˆî¬çê°!îc+­6+ íÆýµ]ÍýERÚ \ No newline at end of file
diff --git a/tests/resources/diff_format_email/.gitted/objects/8d/fa038554d5b682a51bda8ee3038cee6c63be76 b/tests/resources/diff_format_email/.gitted/objects/8d/fa038554d5b682a51bda8ee3038cee6c63be76
new file mode 100644
index 000000000..b5e08f901
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/8d/fa038554d5b682a51bda8ee3038cee6c63be76
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/92/64b96c6d104d0e07ae33d3007b6a48246c6f92 b/tests/resources/diff_format_email/.gitted/objects/92/64b96c6d104d0e07ae33d3007b6a48246c6f92
new file mode 100644
index 000000000..75b047a64
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/92/64b96c6d104d0e07ae33d3007b6a48246c6f92
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/94/350226b3aa14efac831c803a51f7a09f3fc31a b/tests/resources/diff_format_email/.gitted/objects/94/350226b3aa14efac831c803a51f7a09f3fc31a
new file mode 100644
index 000000000..a5286bc68
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/94/350226b3aa14efac831c803a51f7a09f3fc31a
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/94/75e21dcbc515af8f641576400e4b450e5f4c03 b/tests/resources/diff_format_email/.gitted/objects/94/75e21dcbc515af8f641576400e4b450e5f4c03
new file mode 100644
index 000000000..9e26e34cd
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/94/75e21dcbc515af8f641576400e4b450e5f4c03
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/94/aaae8954e8bb613de636071da663a621695911 b/tests/resources/diff_format_email/.gitted/objects/94/aaae8954e8bb613de636071da663a621695911
new file mode 100644
index 000000000..5fc167e79
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/94/aaae8954e8bb613de636071da663a621695911
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/9a/2d780ac2ea0aeabdb9d2a876e6bbfff17b2c44 b/tests/resources/diff_format_email/.gitted/objects/9a/2d780ac2ea0aeabdb9d2a876e6bbfff17b2c44
new file mode 100644
index 000000000..be48c275e
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/9a/2d780ac2ea0aeabdb9d2a876e6bbfff17b2c44
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/9a/c0329b8b7a4046210d8b8b02ac02055667de63 b/tests/resources/diff_format_email/.gitted/objects/9a/c0329b8b7a4046210d8b8b02ac02055667de63
new file mode 100644
index 000000000..f322a7ce4
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/9a/c0329b8b7a4046210d8b8b02ac02055667de63
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/9a/c35ff15cd8864aeafd889e4826a3150f0b06c4 b/tests/resources/diff_format_email/.gitted/objects/9a/c35ff15cd8864aeafd889e4826a3150f0b06c4
new file mode 100644
index 000000000..75cd71426
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/9a/c35ff15cd8864aeafd889e4826a3150f0b06c4
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/9b/997daca2a0beb5cc44b32c64f100a9a26d4d4b b/tests/resources/diff_format_email/.gitted/objects/9b/997daca2a0beb5cc44b32c64f100a9a26d4d4b
new file mode 100644
index 000000000..d84ab8dcc
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/9b/997daca2a0beb5cc44b32c64f100a9a26d4d4b
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/a3/ac918e3a6604294b239cb956363e83d71abb3b b/tests/resources/diff_format_email/.gitted/objects/a3/ac918e3a6604294b239cb956363e83d71abb3b
new file mode 100644
index 000000000..a693df5c8
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/a3/ac918e3a6604294b239cb956363e83d71abb3b
@@ -0,0 +1 @@
+x¥ÎMJ1@a×9Eí©TçDf7 xˆJRåD¦§5^Ìíðn¿ÅãÕm]ûòöih±çbSK5pâÂ)RDô.‘²V­¥Ù|óÛ„Ô¢§EƒÖBù˜Ræ"ØrPÌKËêÉi6|ÌË6àëÏ!;œe¬}¿÷^¿þìó´ÏÑyòKÝÖ7°KŽC ÏHˆæ¡Õ)ÿŠ˜­uíÒ ô;h¿ŠùÜüQK \ No newline at end of file
diff --git a/tests/resources/diff_format_email/.gitted/objects/a5/ac978d4f2a1784f847f41223a34c3e78934238 b/tests/resources/diff_format_email/.gitted/objects/a5/ac978d4f2a1784f847f41223a34c3e78934238
new file mode 100644
index 000000000..f0483943b
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/a5/ac978d4f2a1784f847f41223a34c3e78934238
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/a7/29eab45c84563135e8631d4010230bc0479f1f b/tests/resources/diff_format_email/.gitted/objects/a7/29eab45c84563135e8631d4010230bc0479f1f
new file mode 100644
index 000000000..5c1faf009
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/a7/29eab45c84563135e8631d4010230bc0479f1f
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/a9/7157a0d0571698728b6f2f7675b456c98c5961 b/tests/resources/diff_format_email/.gitted/objects/a9/7157a0d0571698728b6f2f7675b456c98c5961
new file mode 100644
index 000000000..3baf494be
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/a9/7157a0d0571698728b6f2f7675b456c98c5961
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/af/8f41d0cb7a3079a8f8e231ea2ab8b97837ce13 b/tests/resources/diff_format_email/.gitted/objects/af/8f41d0cb7a3079a8f8e231ea2ab8b97837ce13
new file mode 100644
index 000000000..f0dcaa9ac
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/af/8f41d0cb7a3079a8f8e231ea2ab8b97837ce13
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/b0/5cecf1949d192b6df852b3f71853ef820ee235 b/tests/resources/diff_format_email/.gitted/objects/b0/5cecf1949d192b6df852b3f71853ef820ee235
new file mode 100644
index 000000000..28e148f41
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/b0/5cecf1949d192b6df852b3f71853ef820ee235
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/b4/f457c219dbb3517be908d4e70f0ada2fd8b8f9 b/tests/resources/diff_format_email/.gitted/objects/b4/f457c219dbb3517be908d4e70f0ada2fd8b8f9
new file mode 100644
index 000000000..0c74e7696
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/b4/f457c219dbb3517be908d4e70f0ada2fd8b8f9
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/bd/474b2519cc15eab801ff851cc7d50f0dee49a1 b/tests/resources/diff_format_email/.gitted/objects/bd/474b2519cc15eab801ff851cc7d50f0dee49a1
new file mode 100644
index 000000000..2dc82087b
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/bd/474b2519cc15eab801ff851cc7d50f0dee49a1
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/bd/f7ba6bc5c4e57ca6595928dcbe6753c8a663ff b/tests/resources/diff_format_email/.gitted/objects/bd/f7ba6bc5c4e57ca6595928dcbe6753c8a663ff
new file mode 100644
index 000000000..af0232aa1
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/bd/f7ba6bc5c4e57ca6595928dcbe6753c8a663ff
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/cb/a89408dc016f4caddb6dc886fcb58f587a78df b/tests/resources/diff_format_email/.gitted/objects/cb/a89408dc016f4caddb6dc886fcb58f587a78df
new file mode 100644
index 000000000..e03c74935
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/cb/a89408dc016f4caddb6dc886fcb58f587a78df
@@ -0,0 +1,3 @@
+x¥ÎK
+Â0FaÇYE悤¹ÍCq&8t7É[!­¦éÀÝ[p N¿ÁáĹ”±IM~×* }ʬÈÓ'¬×lºØ´il´à¬xqÅÔ¤wä•Í6kK‚
+šÐcX‡Ì±ë™ ^Û0Wyãø^±È+j—aý,òôüÙã²´:rãCœËYvttZUOr¯´RbÓmµá¯ˆ¸câ‚$¾’ÏM‘ \ No newline at end of file
diff --git a/tests/resources/diff_format_email/.gitted/objects/cd/471f0d8770371e1bc78bcbb38db4c7e4106bd2 b/tests/resources/diff_format_email/.gitted/objects/cd/471f0d8770371e1bc78bcbb38db4c7e4106bd2
new file mode 100644
index 000000000..155b45273
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/cd/471f0d8770371e1bc78bcbb38db4c7e4106bd2
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/cd/ed722d05305c6b181f188c118d2d9810f39bb8 b/tests/resources/diff_format_email/.gitted/objects/cd/ed722d05305c6b181f188c118d2d9810f39bb8
new file mode 100644
index 000000000..fd9363612
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/cd/ed722d05305c6b181f188c118d2d9810f39bb8
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/ce/2792fcae8d704a56901754a0583a7418a21d8a b/tests/resources/diff_format_email/.gitted/objects/ce/2792fcae8d704a56901754a0583a7418a21d8a
new file mode 100644
index 000000000..5863cec1b
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/ce/2792fcae8d704a56901754a0583a7418a21d8a
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/d1/4aa252e52a709d03a3d3d0d965e177eb0a674e b/tests/resources/diff_format_email/.gitted/objects/d1/4aa252e52a709d03a3d3d0d965e177eb0a674e
new file mode 100644
index 000000000..a5d4d78e9
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/d1/4aa252e52a709d03a3d3d0d965e177eb0a674e
@@ -0,0 +1 @@
+x+)JMU01e040075UHËÌI5Ô+©(Ñ+JÍKÌMMaXY¾àB¸ØŒ¢î|ý²Ò-a'{"Íz \ No newline at end of file
diff --git a/tests/resources/diff_format_email/.gitted/objects/d7/bb447df12c6a8aba8727005482fb211f11297a b/tests/resources/diff_format_email/.gitted/objects/d7/bb447df12c6a8aba8727005482fb211f11297a
new file mode 100644
index 000000000..85eb8141c
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/d7/bb447df12c6a8aba8727005482fb211f11297a
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/db/e8727e4806ae88ccc3f0755cae8f8cb7efa2cc b/tests/resources/diff_format_email/.gitted/objects/db/e8727e4806ae88ccc3f0755cae8f8cb7efa2cc
new file mode 100644
index 000000000..8e250ece2
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/db/e8727e4806ae88ccc3f0755cae8f8cb7efa2cc
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/e1/2af77c510e8ce4c261a3758736109c2c2dd1f0 b/tests/resources/diff_format_email/.gitted/objects/e1/2af77c510e8ce4c261a3758736109c2c2dd1f0
new file mode 100644
index 000000000..92a89a2cf
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/e1/2af77c510e8ce4c261a3758736109c2c2dd1f0
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/tests/resources/diff_format_email/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
new file mode 100644
index 000000000..711223894
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/e9/091231467304a5ef112de02361d795ef051ee1 b/tests/resources/diff_format_email/.gitted/objects/e9/091231467304a5ef112de02361d795ef051ee1
new file mode 100644
index 000000000..43a006231
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/e9/091231467304a5ef112de02361d795ef051ee1
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/ee/251372f131d82e575f16fe51c778406d88f8c2 b/tests/resources/diff_format_email/.gitted/objects/ee/251372f131d82e575f16fe51c778406d88f8c2
new file mode 100644
index 000000000..ee1edad9a
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/ee/251372f131d82e575f16fe51c778406d88f8c2
@@ -0,0 +1,2 @@
+x¥ÎM
+Â0@a×9Eö‚dò3“€ˆ+o‘¦¡¶¦)èí-x·ßâñÒ4Ž¥Imp×*³Ô9Sç˜u$\0NuÉØ1Û^%ïu/æXùÙ$eï,B°P«L¹Kœˆ# 9Fp‰ƒîPĵ S•·˜^+/òÊu,Ë°~y|üì~^Z-±ÅCšÆ“H‘²hå^i¥Ä¦Ûjã¿"âRÞë,¾HrKï \ No newline at end of file
diff --git a/tests/resources/diff_format_email/.gitted/objects/f3/d35bd592fefd8280fc0c302fa9f27dbdd721a3 b/tests/resources/diff_format_email/.gitted/objects/f3/d35bd592fefd8280fc0c302fa9f27dbdd721a3
new file mode 100644
index 000000000..62d747cf6
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/f3/d35bd592fefd8280fc0c302fa9f27dbdd721a3
@@ -0,0 +1 @@
+x¥ÍA @Qלbö&fhi)‰1îŒÞb€A1b#LÞÞ&=‚Û¿x?Ì¥díìN*3x“Ì`C§]ô¾´õìpŠ†-&¤H]Š“Ÿ’S´Èc®p£ðY¸Á…kÉí±|Ÿ[»Ÿ›ÔLB‡0—èÞYûq4°ÇQ­u] ÿ…¨ë;K¦lšúH°B¿ \ No newline at end of file
diff --git a/tests/resources/diff_format_email/.gitted/objects/f4/07be01334e07bfb8f57cd2078f0ee3eb61e085 b/tests/resources/diff_format_email/.gitted/objects/f4/07be01334e07bfb8f57cd2078f0ee3eb61e085
new file mode 100644
index 000000000..d858a87b5
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/f4/07be01334e07bfb8f57cd2078f0ee3eb61e085
@@ -0,0 +1 @@
+x¥ÎMNÄ0 @aÖ9…÷”8®ÓH±CBâNâ™v íL~Üž‘8Ûoñôò±mk¤øЫ*pÒÅ„–Æ31M¬6L1'4'rJÙ\¥êÞ!„bÅkñ=KˆäÊÄt²sá¹øä™ zgdôå¨ð!ù6´Á»ÖmmËøiðrù³ó[ëu•.ÏùØ^ÁùК<Z´ÖÜõ¾Úõ_ó9¾ûú´Œý ò"ûY›ùû÷PR \ No newline at end of file
diff --git a/tests/resources/diff_format_email/.gitted/objects/f9/e215d309644e24fa50d6bd6e6eedba166e56bc b/tests/resources/diff_format_email/.gitted/objects/f9/e215d309644e24fa50d6bd6e6eedba166e56bc
new file mode 100644
index 000000000..b9919c225
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/f9/e215d309644e24fa50d6bd6e6eedba166e56bc
@@ -0,0 +1,2 @@
+x¥ÎM
+Â0@a×9Eö‚ÌLÚL"®¼ÅäG­[Óôö<‚Ûoñxq,ehšŒÝ´š³î9ú®· ЊÌH‰¼H`¬Ãè\{ðj’šŸMwÑ„ÄèH’µYX„ HÇ’9¤À ­J–v«¾H|-yÖç\Ë0ߗϬ÷ŸÝŽs«ƒ4Ùű4ÏÀÐé-€Zu]mù¯ˆ: ïeR_K§ \ No newline at end of file
diff --git a/tests/resources/diff_format_email/.gitted/objects/fc/a0c10eb9f1af6494a448d5733d283f5232a514 b/tests/resources/diff_format_email/.gitted/objects/fc/a0c10eb9f1af6494a448d5733d283f5232a514
new file mode 100644
index 000000000..776221d21
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/fc/a0c10eb9f1af6494a448d5733d283f5232a514
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/objects/ff/8d35b41494f7f0dc92f95d67f54fff274d3fcb b/tests/resources/diff_format_email/.gitted/objects/ff/8d35b41494f7f0dc92f95d67f54fff274d3fcb
new file mode 100644
index 000000000..92964252a
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/objects/ff/8d35b41494f7f0dc92f95d67f54fff274d3fcb
Binary files differ
diff --git a/tests/resources/diff_format_email/.gitted/refs/heads/binary b/tests/resources/diff_format_email/.gitted/refs/heads/binary
new file mode 100644
index 000000000..7e563c957
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/refs/heads/binary
@@ -0,0 +1 @@
+a3ac918e3a6604294b239cb956363e83d71abb3b
diff --git a/tests/resources/diff_format_email/.gitted/refs/heads/master b/tests/resources/diff_format_email/.gitted/refs/heads/master
new file mode 100644
index 000000000..f0f3f932a
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/refs/heads/master
@@ -0,0 +1 @@
+873806f6f27e631eb0b23e4b56bea2bfac14a373
diff --git a/tests/resources/diff_format_email/.gitted/refs/heads/multihunk b/tests/resources/diff_format_email/.gitted/refs/heads/multihunk
new file mode 100644
index 000000000..41bd37f39
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/refs/heads/multihunk
@@ -0,0 +1 @@
+cd471f0d8770371e1bc78bcbb38db4c7e4106bd2
diff --git a/tests/resources/diff_format_email/.gitted/refs/heads/rename b/tests/resources/diff_format_email/.gitted/refs/heads/rename
new file mode 100644
index 000000000..3025fbc64
--- /dev/null
+++ b/tests/resources/diff_format_email/.gitted/refs/heads/rename
@@ -0,0 +1 @@
+4ca10087e696d2ba78d07b146a118e9a7096ed4f
diff --git a/tests/resources/diff_format_email/file1.txt.renamed b/tests/resources/diff_format_email/file1.txt.renamed
new file mode 100755
index 000000000..a97157a0d
--- /dev/null
+++ b/tests/resources/diff_format_email/file1.txt.renamed
@@ -0,0 +1,17 @@
+file1.txt
+file1.txt
+_file1.txt_
+file1.txt
+file1.txt
+file1.txt_renamed
+file1.txt
+
+
+file1.txt
+file1.txt
+file1.txt_renamed
+file1.txt
+file1.txt
+_file1.txt_
+_file1.txt_
+file1.txt
diff --git a/tests/resources/diff_format_email/file2.txt b/tests/resources/diff_format_email/file2.txt
new file mode 100644
index 000000000..7aff11da9
--- /dev/null
+++ b/tests/resources/diff_format_email/file2.txt
@@ -0,0 +1,5 @@
+file2
+file2
+file2
+file2!
+file2
diff --git a/tests/resources/diff_format_email/file3.txt b/tests/resources/diff_format_email/file3.txt
new file mode 100644
index 000000000..9a2d780ac
--- /dev/null
+++ b/tests/resources/diff_format_email/file3.txt
@@ -0,0 +1,5 @@
+file3
+file3!
+file3
+file3
+file3
diff --git a/tests/resources/duplicate.git/COMMIT_EDITMSG b/tests/resources/duplicate.git/COMMIT_EDITMSG
new file mode 100644
index 000000000..01f9a2aac
--- /dev/null
+++ b/tests/resources/duplicate.git/COMMIT_EDITMSG
@@ -0,0 +1 @@
+commit
diff --git a/tests/resources/duplicate.git/HEAD b/tests/resources/duplicate.git/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/duplicate.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/duplicate.git/config b/tests/resources/duplicate.git/config
new file mode 100644
index 000000000..a4ef456cb
--- /dev/null
+++ b/tests/resources/duplicate.git/config
@@ -0,0 +1,5 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = true
+ logallrefupdates = true
diff --git a/tests/resources/duplicate.git/description b/tests/resources/duplicate.git/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/duplicate.git/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/duplicate.git/index b/tests/resources/duplicate.git/index
new file mode 100644
index 000000000..a61e1c5ca
--- /dev/null
+++ b/tests/resources/duplicate.git/index
Binary files differ
diff --git a/tests/resources/duplicate.git/info/exclude b/tests/resources/duplicate.git/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/duplicate.git/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/resources/duplicate.git/info/refs b/tests/resources/duplicate.git/info/refs
new file mode 100644
index 000000000..3b5bb03be
--- /dev/null
+++ b/tests/resources/duplicate.git/info/refs
@@ -0,0 +1 @@
+8d2f05c97ef29a4697b37c30fe81c248ef411a23 refs/heads/master
diff --git a/tests/resources/duplicate.git/logs/HEAD b/tests/resources/duplicate.git/logs/HEAD
new file mode 100644
index 000000000..be9b4c6cb
--- /dev/null
+++ b/tests/resources/duplicate.git/logs/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 8d2f05c97ef29a4697b37c30fe81c248ef411a23 Han-Wen Nienhuys <hanwen@xs4all.nl> 1336844322 -0300 commit (initial): commit
diff --git a/tests/resources/duplicate.git/logs/refs/heads/master b/tests/resources/duplicate.git/logs/refs/heads/master
new file mode 100644
index 000000000..be9b4c6cb
--- /dev/null
+++ b/tests/resources/duplicate.git/logs/refs/heads/master
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 8d2f05c97ef29a4697b37c30fe81c248ef411a23 Han-Wen Nienhuys <hanwen@xs4all.nl> 1336844322 -0300 commit (initial): commit
diff --git a/tests/resources/duplicate.git/objects/03/8d718da6a1ebbc6a7780a96ed75a70cc2ad6e2 b/tests/resources/duplicate.git/objects/03/8d718da6a1ebbc6a7780a96ed75a70cc2ad6e2
new file mode 100644
index 000000000..7350d98a2
--- /dev/null
+++ b/tests/resources/duplicate.git/objects/03/8d718da6a1ebbc6a7780a96ed75a70cc2ad6e2
Binary files differ
diff --git a/tests/resources/duplicate.git/objects/0d/deadede9e6d6ccddce0ee1e5749eed0485e5ea b/tests/resources/duplicate.git/objects/0d/deadede9e6d6ccddce0ee1e5749eed0485e5ea
new file mode 100644
index 000000000..47c2a631a
--- /dev/null
+++ b/tests/resources/duplicate.git/objects/0d/deadede9e6d6ccddce0ee1e5749eed0485e5ea
Binary files differ
diff --git a/tests/resources/duplicate.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a b/tests/resources/duplicate.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a
new file mode 100644
index 000000000..6802d4949
--- /dev/null
+++ b/tests/resources/duplicate.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a
Binary files differ
diff --git a/tests/resources/duplicate.git/objects/info/packs b/tests/resources/duplicate.git/objects/info/packs
new file mode 100644
index 000000000..d0fdf905e
--- /dev/null
+++ b/tests/resources/duplicate.git/objects/info/packs
@@ -0,0 +1,3 @@
+P pack-e87994ad581c9af946de0eb890175c08cd005f38.pack
+P pack-f4ef1aa326265de7d05018ee51acc0a8717fe1ea.pack
+
diff --git a/tests/resources/duplicate.git/objects/pack/pack-29a4896f0a0b9c9947b0927c57a5c03dcae052e3.idx b/tests/resources/duplicate.git/objects/pack/pack-29a4896f0a0b9c9947b0927c57a5c03dcae052e3.idx
new file mode 100644
index 000000000..acbed82b6
--- /dev/null
+++ b/tests/resources/duplicate.git/objects/pack/pack-29a4896f0a0b9c9947b0927c57a5c03dcae052e3.idx
Binary files differ
diff --git a/tests/resources/duplicate.git/objects/pack/pack-29a4896f0a0b9c9947b0927c57a5c03dcae052e3.pack b/tests/resources/duplicate.git/objects/pack/pack-29a4896f0a0b9c9947b0927c57a5c03dcae052e3.pack
new file mode 100644
index 000000000..652b0c91f
--- /dev/null
+++ b/tests/resources/duplicate.git/objects/pack/pack-29a4896f0a0b9c9947b0927c57a5c03dcae052e3.pack
Binary files differ
diff --git a/tests/resources/duplicate.git/objects/pack/pack-b18eeacbd65cbd30a365d7564b45a468e8bd43d6.idx b/tests/resources/duplicate.git/objects/pack/pack-b18eeacbd65cbd30a365d7564b45a468e8bd43d6.idx
new file mode 100644
index 000000000..fff685554
--- /dev/null
+++ b/tests/resources/duplicate.git/objects/pack/pack-b18eeacbd65cbd30a365d7564b45a468e8bd43d6.idx
Binary files differ
diff --git a/tests/resources/duplicate.git/objects/pack/pack-b18eeacbd65cbd30a365d7564b45a468e8bd43d6.pack b/tests/resources/duplicate.git/objects/pack/pack-b18eeacbd65cbd30a365d7564b45a468e8bd43d6.pack
new file mode 100644
index 000000000..e3e5f0e09
--- /dev/null
+++ b/tests/resources/duplicate.git/objects/pack/pack-b18eeacbd65cbd30a365d7564b45a468e8bd43d6.pack
Binary files differ
diff --git a/tests/resources/duplicate.git/objects/pack/pack-e87994ad581c9af946de0eb890175c08cd005f38.idx b/tests/resources/duplicate.git/objects/pack/pack-e87994ad581c9af946de0eb890175c08cd005f38.idx
new file mode 100644
index 000000000..fd8abee98
--- /dev/null
+++ b/tests/resources/duplicate.git/objects/pack/pack-e87994ad581c9af946de0eb890175c08cd005f38.idx
Binary files differ
diff --git a/tests/resources/duplicate.git/objects/pack/pack-e87994ad581c9af946de0eb890175c08cd005f38.pack b/tests/resources/duplicate.git/objects/pack/pack-e87994ad581c9af946de0eb890175c08cd005f38.pack
new file mode 100644
index 000000000..9879e0869
--- /dev/null
+++ b/tests/resources/duplicate.git/objects/pack/pack-e87994ad581c9af946de0eb890175c08cd005f38.pack
Binary files differ
diff --git a/tests/resources/duplicate.git/objects/pack/pack-f4ef1aa326265de7d05018ee51acc0a8717fe1ea.idx b/tests/resources/duplicate.git/objects/pack/pack-f4ef1aa326265de7d05018ee51acc0a8717fe1ea.idx
new file mode 100644
index 000000000..9f78f6e0f
--- /dev/null
+++ b/tests/resources/duplicate.git/objects/pack/pack-f4ef1aa326265de7d05018ee51acc0a8717fe1ea.idx
Binary files differ
diff --git a/tests/resources/duplicate.git/objects/pack/pack-f4ef1aa326265de7d05018ee51acc0a8717fe1ea.pack b/tests/resources/duplicate.git/objects/pack/pack-f4ef1aa326265de7d05018ee51acc0a8717fe1ea.pack
new file mode 100644
index 000000000..d1dd3b61a
--- /dev/null
+++ b/tests/resources/duplicate.git/objects/pack/pack-f4ef1aa326265de7d05018ee51acc0a8717fe1ea.pack
Binary files differ
diff --git a/tests/resources/duplicate.git/packed-refs b/tests/resources/duplicate.git/packed-refs
new file mode 100644
index 000000000..9f0d4e434
--- /dev/null
+++ b/tests/resources/duplicate.git/packed-refs
@@ -0,0 +1,2 @@
+# pack-refs with: peeled
+8d2f05c97ef29a4697b37c30fe81c248ef411a23 refs/heads/master
diff --git a/tests/resources/duplicate.git/refs/heads/dummy-marker.txt b/tests/resources/duplicate.git/refs/heads/dummy-marker.txt
new file mode 100644
index 000000000..421376db9
--- /dev/null
+++ b/tests/resources/duplicate.git/refs/heads/dummy-marker.txt
@@ -0,0 +1 @@
+dummy
diff --git a/tests/resources/filemodes/.gitted/HEAD b/tests/resources/filemodes/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/filemodes/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/filemodes/.gitted/config b/tests/resources/filemodes/.gitted/config
new file mode 100644
index 000000000..af107929f
--- /dev/null
+++ b/tests/resources/filemodes/.gitted/config
@@ -0,0 +1,6 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
diff --git a/tests/resources/filemodes/.gitted/description b/tests/resources/filemodes/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/filemodes/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/filemodes/.gitted/index b/tests/resources/filemodes/.gitted/index
new file mode 100644
index 000000000..b1b175a9b
--- /dev/null
+++ b/tests/resources/filemodes/.gitted/index
Binary files differ
diff --git a/tests/resources/filemodes/.gitted/info/exclude b/tests/resources/filemodes/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/filemodes/.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/resources/filemodes/.gitted/logs/HEAD b/tests/resources/filemodes/.gitted/logs/HEAD
new file mode 100644
index 000000000..1cb6a84c1
--- /dev/null
+++ b/tests/resources/filemodes/.gitted/logs/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 9962c8453ba6f0cf8dac7c5dcc2fa2897fa9964a Russell Belfer <rb@github.com> 1338847682 -0700 commit (initial): Initial commit of test data
diff --git a/tests/resources/filemodes/.gitted/logs/refs/heads/master b/tests/resources/filemodes/.gitted/logs/refs/heads/master
new file mode 100644
index 000000000..1cb6a84c1
--- /dev/null
+++ b/tests/resources/filemodes/.gitted/logs/refs/heads/master
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 9962c8453ba6f0cf8dac7c5dcc2fa2897fa9964a Russell Belfer <rb@github.com> 1338847682 -0700 commit (initial): Initial commit of test data
diff --git a/tests/resources/filemodes/.gitted/objects/99/62c8453ba6f0cf8dac7c5dcc2fa2897fa9964a b/tests/resources/filemodes/.gitted/objects/99/62c8453ba6f0cf8dac7c5dcc2fa2897fa9964a
new file mode 100644
index 000000000..cbd2b557a
--- /dev/null
+++ b/tests/resources/filemodes/.gitted/objects/99/62c8453ba6f0cf8dac7c5dcc2fa2897fa9964a
Binary files differ
diff --git a/tests/resources/filemodes/.gitted/objects/a5/c5dd0fc6c313159a69b1d19d7f61a9f978e8f1 b/tests/resources/filemodes/.gitted/objects/a5/c5dd0fc6c313159a69b1d19d7f61a9f978e8f1
new file mode 100644
index 000000000..a9eaf2cba
--- /dev/null
+++ b/tests/resources/filemodes/.gitted/objects/a5/c5dd0fc6c313159a69b1d19d7f61a9f978e8f1
Binary files differ
diff --git a/tests/resources/filemodes/.gitted/objects/e7/48d196331bcb20267eaaee4ff3326cb73b8182 b/tests/resources/filemodes/.gitted/objects/e7/48d196331bcb20267eaaee4ff3326cb73b8182
new file mode 100644
index 000000000..98066029c
--- /dev/null
+++ b/tests/resources/filemodes/.gitted/objects/e7/48d196331bcb20267eaaee4ff3326cb73b8182
Binary files differ
diff --git a/tests/resources/filemodes/.gitted/refs/heads/master b/tests/resources/filemodes/.gitted/refs/heads/master
new file mode 100644
index 000000000..9822d2d3f
--- /dev/null
+++ b/tests/resources/filemodes/.gitted/refs/heads/master
@@ -0,0 +1 @@
+9962c8453ba6f0cf8dac7c5dcc2fa2897fa9964a
diff --git a/tests/resources/filemodes/exec_off b/tests/resources/filemodes/exec_off
new file mode 100644
index 000000000..a5c5dd0fc
--- /dev/null
+++ b/tests/resources/filemodes/exec_off
@@ -0,0 +1 @@
+Howdy
diff --git a/tests/resources/filemodes/exec_off2on_staged b/tests/resources/filemodes/exec_off2on_staged
new file mode 100755
index 000000000..a5c5dd0fc
--- /dev/null
+++ b/tests/resources/filemodes/exec_off2on_staged
@@ -0,0 +1 @@
+Howdy
diff --git a/tests/resources/filemodes/exec_off2on_workdir b/tests/resources/filemodes/exec_off2on_workdir
new file mode 100755
index 000000000..a5c5dd0fc
--- /dev/null
+++ b/tests/resources/filemodes/exec_off2on_workdir
@@ -0,0 +1 @@
+Howdy
diff --git a/tests/resources/filemodes/exec_off_untracked b/tests/resources/filemodes/exec_off_untracked
new file mode 100644
index 000000000..a5c5dd0fc
--- /dev/null
+++ b/tests/resources/filemodes/exec_off_untracked
@@ -0,0 +1 @@
+Howdy
diff --git a/tests/resources/filemodes/exec_on b/tests/resources/filemodes/exec_on
new file mode 100755
index 000000000..a5c5dd0fc
--- /dev/null
+++ b/tests/resources/filemodes/exec_on
@@ -0,0 +1 @@
+Howdy
diff --git a/tests/resources/filemodes/exec_on2off_staged b/tests/resources/filemodes/exec_on2off_staged
new file mode 100644
index 000000000..a5c5dd0fc
--- /dev/null
+++ b/tests/resources/filemodes/exec_on2off_staged
@@ -0,0 +1 @@
+Howdy
diff --git a/tests/resources/filemodes/exec_on2off_workdir b/tests/resources/filemodes/exec_on2off_workdir
new file mode 100644
index 000000000..a5c5dd0fc
--- /dev/null
+++ b/tests/resources/filemodes/exec_on2off_workdir
@@ -0,0 +1 @@
+Howdy
diff --git a/tests/resources/filemodes/exec_on_untracked b/tests/resources/filemodes/exec_on_untracked
new file mode 100755
index 000000000..a5c5dd0fc
--- /dev/null
+++ b/tests/resources/filemodes/exec_on_untracked
@@ -0,0 +1 @@
+Howdy
diff --git a/tests/resources/icase/.gitted/HEAD b/tests/resources/icase/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/icase/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/icase/.gitted/config b/tests/resources/icase/.gitted/config
new file mode 100644
index 000000000..bb4d11c1f
--- /dev/null
+++ b/tests/resources/icase/.gitted/config
@@ -0,0 +1,7 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
+ precomposeunicode = false
diff --git a/tests/resources/icase/.gitted/description b/tests/resources/icase/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/icase/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/icase/.gitted/index b/tests/resources/icase/.gitted/index
new file mode 100644
index 000000000..f8288ec13
--- /dev/null
+++ b/tests/resources/icase/.gitted/index
Binary files differ
diff --git a/tests/resources/icase/.gitted/info/exclude b/tests/resources/icase/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/icase/.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/resources/icase/.gitted/logs/HEAD b/tests/resources/icase/.gitted/logs/HEAD
new file mode 100644
index 000000000..3b16bd163
--- /dev/null
+++ b/tests/resources/icase/.gitted/logs/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 76d6e1d231b1085fcce151427e9899335de74be6 Russell Belfer <rb@github.com> 1359157123 -0800 commit (initial): initial commit
diff --git a/tests/resources/icase/.gitted/logs/refs/heads/master b/tests/resources/icase/.gitted/logs/refs/heads/master
new file mode 100644
index 000000000..3b16bd163
--- /dev/null
+++ b/tests/resources/icase/.gitted/logs/refs/heads/master
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 76d6e1d231b1085fcce151427e9899335de74be6 Russell Belfer <rb@github.com> 1359157123 -0800 commit (initial): initial commit
diff --git a/tests/resources/icase/.gitted/objects/3e/257c57f136a1cb8f2b8e9a2e5bc8ec0258bdce b/tests/resources/icase/.gitted/objects/3e/257c57f136a1cb8f2b8e9a2e5bc8ec0258bdce
new file mode 100644
index 000000000..10691c788
--- /dev/null
+++ b/tests/resources/icase/.gitted/objects/3e/257c57f136a1cb8f2b8e9a2e5bc8ec0258bdce
Binary files differ
diff --git a/tests/resources/icase/.gitted/objects/4d/d6027d083575c7431396dc2a3174afeb393c93 b/tests/resources/icase/.gitted/objects/4d/d6027d083575c7431396dc2a3174afeb393c93
new file mode 100644
index 000000000..8ca70df17
--- /dev/null
+++ b/tests/resources/icase/.gitted/objects/4d/d6027d083575c7431396dc2a3174afeb393c93
Binary files differ
diff --git a/tests/resources/icase/.gitted/objects/62/e0af52c199ec731fe4ad230041cd3286192d49 b/tests/resources/icase/.gitted/objects/62/e0af52c199ec731fe4ad230041cd3286192d49
new file mode 100644
index 000000000..e264aeab3
--- /dev/null
+++ b/tests/resources/icase/.gitted/objects/62/e0af52c199ec731fe4ad230041cd3286192d49
Binary files differ
diff --git a/tests/resources/icase/.gitted/objects/76/d6e1d231b1085fcce151427e9899335de74be6 b/tests/resources/icase/.gitted/objects/76/d6e1d231b1085fcce151427e9899335de74be6
new file mode 100644
index 000000000..24a4b3ee3
--- /dev/null
+++ b/tests/resources/icase/.gitted/objects/76/d6e1d231b1085fcce151427e9899335de74be6
@@ -0,0 +1,3 @@
+x•Û 1EýNÓ€’c² ‹X‚dƉÈÈ&ý°/çœËußsãñÔ›8±è}2î SH–‚,Ñ
+am1ЋEÅÑ·Úà9ŽCJ‡”$ nîïÜ·A®û
+ÆábÐëଃÖj®ó¯Oô_SåOî9ø%Ô)Œ9š \ No newline at end of file
diff --git a/tests/resources/icase/.gitted/objects/d4/4e18fb93b7107b5cd1b95d601591d77869a1b6 b/tests/resources/icase/.gitted/objects/d4/4e18fb93b7107b5cd1b95d601591d77869a1b6
new file mode 100644
index 000000000..32d8c499f
--- /dev/null
+++ b/tests/resources/icase/.gitted/objects/d4/4e18fb93b7107b5cd1b95d601591d77869a1b6
Binary files differ
diff --git a/tests/resources/icase/.gitted/refs/heads/master b/tests/resources/icase/.gitted/refs/heads/master
new file mode 100644
index 000000000..37410ec2a
--- /dev/null
+++ b/tests/resources/icase/.gitted/refs/heads/master
@@ -0,0 +1 @@
+76d6e1d231b1085fcce151427e9899335de74be6
diff --git a/tests/resources/icase/B b/tests/resources/icase/B
new file mode 100644
index 000000000..d44e18fb9
--- /dev/null
+++ b/tests/resources/icase/B
@@ -0,0 +1 @@
+start
diff --git a/tests/resources/icase/D b/tests/resources/icase/D
new file mode 100644
index 000000000..d44e18fb9
--- /dev/null
+++ b/tests/resources/icase/D
@@ -0,0 +1 @@
+start
diff --git a/tests/resources/icase/F b/tests/resources/icase/F
new file mode 100644
index 000000000..d44e18fb9
--- /dev/null
+++ b/tests/resources/icase/F
@@ -0,0 +1 @@
+start
diff --git a/tests/resources/icase/H b/tests/resources/icase/H
new file mode 100644
index 000000000..d44e18fb9
--- /dev/null
+++ b/tests/resources/icase/H
@@ -0,0 +1 @@
+start
diff --git a/tests/resources/icase/J b/tests/resources/icase/J
new file mode 100644
index 000000000..d44e18fb9
--- /dev/null
+++ b/tests/resources/icase/J
@@ -0,0 +1 @@
+start
diff --git a/tests/resources/icase/L/1 b/tests/resources/icase/L/1
new file mode 100644
index 000000000..62e0af52c
--- /dev/null
+++ b/tests/resources/icase/L/1
@@ -0,0 +1 @@
+sub
diff --git a/tests/resources/icase/L/B b/tests/resources/icase/L/B
new file mode 100644
index 000000000..62e0af52c
--- /dev/null
+++ b/tests/resources/icase/L/B
@@ -0,0 +1 @@
+sub
diff --git a/tests/resources/icase/L/D b/tests/resources/icase/L/D
new file mode 100644
index 000000000..62e0af52c
--- /dev/null
+++ b/tests/resources/icase/L/D
@@ -0,0 +1 @@
+sub
diff --git a/tests/resources/icase/L/a b/tests/resources/icase/L/a
new file mode 100644
index 000000000..62e0af52c
--- /dev/null
+++ b/tests/resources/icase/L/a
@@ -0,0 +1 @@
+sub
diff --git a/tests/resources/icase/L/c b/tests/resources/icase/L/c
new file mode 100644
index 000000000..62e0af52c
--- /dev/null
+++ b/tests/resources/icase/L/c
@@ -0,0 +1 @@
+sub
diff --git a/tests/resources/icase/a b/tests/resources/icase/a
new file mode 100644
index 000000000..d44e18fb9
--- /dev/null
+++ b/tests/resources/icase/a
@@ -0,0 +1 @@
+start
diff --git a/tests/resources/icase/c b/tests/resources/icase/c
new file mode 100644
index 000000000..d44e18fb9
--- /dev/null
+++ b/tests/resources/icase/c
@@ -0,0 +1 @@
+start
diff --git a/tests/resources/icase/e b/tests/resources/icase/e
new file mode 100644
index 000000000..d44e18fb9
--- /dev/null
+++ b/tests/resources/icase/e
@@ -0,0 +1 @@
+start
diff --git a/tests/resources/icase/g b/tests/resources/icase/g
new file mode 100644
index 000000000..d44e18fb9
--- /dev/null
+++ b/tests/resources/icase/g
@@ -0,0 +1 @@
+start
diff --git a/tests/resources/icase/i b/tests/resources/icase/i
new file mode 100644
index 000000000..d44e18fb9
--- /dev/null
+++ b/tests/resources/icase/i
@@ -0,0 +1 @@
+start
diff --git a/tests/resources/icase/k/1 b/tests/resources/icase/k/1
new file mode 100644
index 000000000..62e0af52c
--- /dev/null
+++ b/tests/resources/icase/k/1
@@ -0,0 +1 @@
+sub
diff --git a/tests/resources/icase/k/B b/tests/resources/icase/k/B
new file mode 100644
index 000000000..62e0af52c
--- /dev/null
+++ b/tests/resources/icase/k/B
@@ -0,0 +1 @@
+sub
diff --git a/tests/resources/icase/k/D b/tests/resources/icase/k/D
new file mode 100644
index 000000000..62e0af52c
--- /dev/null
+++ b/tests/resources/icase/k/D
@@ -0,0 +1 @@
+sub
diff --git a/tests/resources/icase/k/a b/tests/resources/icase/k/a
new file mode 100644
index 000000000..62e0af52c
--- /dev/null
+++ b/tests/resources/icase/k/a
@@ -0,0 +1 @@
+sub
diff --git a/tests/resources/icase/k/c b/tests/resources/icase/k/c
new file mode 100644
index 000000000..62e0af52c
--- /dev/null
+++ b/tests/resources/icase/k/c
@@ -0,0 +1 @@
+sub
diff --git a/tests/resources/issue_1397/.gitted/HEAD b/tests/resources/issue_1397/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/issue_1397/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/issue_1397/.gitted/config b/tests/resources/issue_1397/.gitted/config
new file mode 100644
index 000000000..ba5bbde24
--- /dev/null
+++ b/tests/resources/issue_1397/.gitted/config
@@ -0,0 +1,6 @@
+[core]
+ bare = false
+ repositoryformatversion = 0
+ filemode = false
+ logallrefupdates = true
+ ignorecase = true
diff --git a/tests/resources/issue_1397/.gitted/index b/tests/resources/issue_1397/.gitted/index
new file mode 100644
index 000000000..fa0f541d6
--- /dev/null
+++ b/tests/resources/issue_1397/.gitted/index
Binary files differ
diff --git a/tests/resources/issue_1397/.gitted/objects/7f/483a738f867e5b21c8f377d70311f011eb48b5 b/tests/resources/issue_1397/.gitted/objects/7f/483a738f867e5b21c8f377d70311f011eb48b5
new file mode 100644
index 000000000..63bcb5d76
--- /dev/null
+++ b/tests/resources/issue_1397/.gitted/objects/7f/483a738f867e5b21c8f377d70311f011eb48b5
@@ -0,0 +1,3 @@
+xÎQNÃ0 €ažs
+¿£!'nâTBÓî°¸ŽC+Öe\Äv€ÿӯǾoÑËèfPƒ¦PL8FΆ‘ÒÌ%ª_Ä‹b4æIÜ—tk²°Uœ¸êLdeIÁòd<3/˜|0¥šjÈää1Ö£ÃõÛ\Gßô³c…wÛe»]ô~úùߊÁS
+)ÏGxEèôÿqØsµÓUÚ‡8šÁmkæ~yIæ \ No newline at end of file
diff --git a/tests/resources/issue_1397/.gitted/objects/83/12e0889a9cbab77c732b6bc39b51a683e3a318 b/tests/resources/issue_1397/.gitted/objects/83/12e0889a9cbab77c732b6bc39b51a683e3a318
new file mode 100644
index 000000000..06b59fede
--- /dev/null
+++ b/tests/resources/issue_1397/.gitted/objects/83/12e0889a9cbab77c732b6bc39b51a683e3a318
Binary files differ
diff --git a/tests/resources/issue_1397/.gitted/objects/8a/7ef047fc933edb62e84e7977b0612ec3f6f283 b/tests/resources/issue_1397/.gitted/objects/8a/7ef047fc933edb62e84e7977b0612ec3f6f283
new file mode 100644
index 000000000..19cfbeae2
--- /dev/null
+++ b/tests/resources/issue_1397/.gitted/objects/8a/7ef047fc933edb62e84e7977b0612ec3f6f283
Binary files differ
diff --git a/tests/resources/issue_1397/.gitted/objects/8e/8f80088a9274fd23584992f587083ca1bcbbac b/tests/resources/issue_1397/.gitted/objects/8e/8f80088a9274fd23584992f587083ca1bcbbac
new file mode 100644
index 000000000..f5c776b17
--- /dev/null
+++ b/tests/resources/issue_1397/.gitted/objects/8e/8f80088a9274fd23584992f587083ca1bcbbac
Binary files differ
diff --git a/tests/resources/issue_1397/.gitted/objects/f2/c62dea0372a0578e053697d5c1ba1ac05e774a b/tests/resources/issue_1397/.gitted/objects/f2/c62dea0372a0578e053697d5c1ba1ac05e774a
new file mode 100644
index 000000000..f932f3618
--- /dev/null
+++ b/tests/resources/issue_1397/.gitted/objects/f2/c62dea0372a0578e053697d5c1ba1ac05e774a
Binary files differ
diff --git a/tests/resources/issue_1397/.gitted/objects/ff/3578d64d199d5b48d92bbb569e0a273e411741 b/tests/resources/issue_1397/.gitted/objects/ff/3578d64d199d5b48d92bbb569e0a273e411741
new file mode 100644
index 000000000..fbd731727
--- /dev/null
+++ b/tests/resources/issue_1397/.gitted/objects/ff/3578d64d199d5b48d92bbb569e0a273e411741
Binary files differ
diff --git a/tests/resources/issue_1397/.gitted/refs/heads/master b/tests/resources/issue_1397/.gitted/refs/heads/master
new file mode 100644
index 000000000..285bc08ff
--- /dev/null
+++ b/tests/resources/issue_1397/.gitted/refs/heads/master
@@ -0,0 +1 @@
+7f483a738f867e5b21c8f377d70311f011eb48b5
diff --git a/tests/resources/issue_1397/crlf_file.txt b/tests/resources/issue_1397/crlf_file.txt
new file mode 100644
index 000000000..8312e0889
--- /dev/null
+++ b/tests/resources/issue_1397/crlf_file.txt
@@ -0,0 +1,3 @@
+first line
+second line
+both with crlf \ No newline at end of file
diff --git a/tests/resources/issue_1397/some_other_crlf_file.txt b/tests/resources/issue_1397/some_other_crlf_file.txt
new file mode 100644
index 000000000..8e8f80088
--- /dev/null
+++ b/tests/resources/issue_1397/some_other_crlf_file.txt
@@ -0,0 +1,3 @@
+first line
+second line with some change
+both with crlf \ No newline at end of file
diff --git a/tests/resources/issue_592/.gitted/COMMIT_EDITMSG b/tests/resources/issue_592/.gitted/COMMIT_EDITMSG
new file mode 100644
index 000000000..5852f4463
--- /dev/null
+++ b/tests/resources/issue_592/.gitted/COMMIT_EDITMSG
@@ -0,0 +1 @@
+Initial commit
diff --git a/tests/resources/issue_592/.gitted/HEAD b/tests/resources/issue_592/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/issue_592/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/issue_592/.gitted/config b/tests/resources/issue_592/.gitted/config
new file mode 100644
index 000000000..78387c50b
--- /dev/null
+++ b/tests/resources/issue_592/.gitted/config
@@ -0,0 +1,8 @@
+[core]
+ repositoryformatversion = 0
+ filemode = false
+ bare = false
+ logallrefupdates = true
+ symlinks = false
+ ignorecase = true
+ hideDotFiles = dotGitOnly
diff --git a/tests/resources/issue_592/.gitted/index b/tests/resources/issue_592/.gitted/index
new file mode 100644
index 000000000..be7a29d99
--- /dev/null
+++ b/tests/resources/issue_592/.gitted/index
Binary files differ
diff --git a/tests/resources/issue_592/.gitted/info/exclude b/tests/resources/issue_592/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/issue_592/.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/resources/issue_592/.gitted/logs/HEAD b/tests/resources/issue_592/.gitted/logs/HEAD
new file mode 100644
index 000000000..f19fe35a6
--- /dev/null
+++ b/tests/resources/issue_592/.gitted/logs/HEAD
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 4d383e87f0371ba8fa353f3912db6862b2625e85 nulltoken <emeric.fermas@gmail.com> 1331989635 +0100 commit (initial): Initial commit
+4d383e87f0371ba8fa353f3912db6862b2625e85 e38fcc7a6060f5eb5b876e836b52ae4769363f21 nulltoken <emeric.fermas@gmail.com> 1332227062 +0100 commit (amend): Initial commit
diff --git a/tests/resources/issue_592/.gitted/logs/refs/heads/master b/tests/resources/issue_592/.gitted/logs/refs/heads/master
new file mode 100644
index 000000000..f19fe35a6
--- /dev/null
+++ b/tests/resources/issue_592/.gitted/logs/refs/heads/master
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 4d383e87f0371ba8fa353f3912db6862b2625e85 nulltoken <emeric.fermas@gmail.com> 1331989635 +0100 commit (initial): Initial commit
+4d383e87f0371ba8fa353f3912db6862b2625e85 e38fcc7a6060f5eb5b876e836b52ae4769363f21 nulltoken <emeric.fermas@gmail.com> 1332227062 +0100 commit (amend): Initial commit
diff --git a/tests/resources/issue_592/.gitted/objects/06/07ee9d4ccce8e4c4fa13c2c7d727e7faba4e0e b/tests/resources/issue_592/.gitted/objects/06/07ee9d4ccce8e4c4fa13c2c7d727e7faba4e0e
new file mode 100644
index 000000000..05dec10f7
--- /dev/null
+++ b/tests/resources/issue_592/.gitted/objects/06/07ee9d4ccce8e4c4fa13c2c7d727e7faba4e0e
Binary files differ
diff --git a/tests/resources/issue_592/.gitted/objects/49/363a72a90d9424240258cd3759f23788ecf1d8 b/tests/resources/issue_592/.gitted/objects/49/363a72a90d9424240258cd3759f23788ecf1d8
new file mode 100644
index 000000000..e997e1b49
--- /dev/null
+++ b/tests/resources/issue_592/.gitted/objects/49/363a72a90d9424240258cd3759f23788ecf1d8
Binary files differ
diff --git a/tests/resources/issue_592/.gitted/objects/4d/383e87f0371ba8fa353f3912db6862b2625e85 b/tests/resources/issue_592/.gitted/objects/4d/383e87f0371ba8fa353f3912db6862b2625e85
new file mode 100644
index 000000000..c49a8be58
--- /dev/null
+++ b/tests/resources/issue_592/.gitted/objects/4d/383e87f0371ba8fa353f3912db6862b2625e85
@@ -0,0 +1,2 @@
+xM
+Â0]ço/”¤I›DÜzŒçë æbz ÞÀíÀÌHÍ9v2Ëtê =k¬›,pâ+£øÍ>ðƒ4ïýU•=¥^ß(tAF‹2´ÌŸÛ3sLƒÔ|%c­Y—u¶µÑZô˜vü©«{‰=r¢_G}KÈ>ˆ \ No newline at end of file
diff --git a/tests/resources/issue_592/.gitted/objects/71/44be264b61825fbff68046fe999bdfe96a1792 b/tests/resources/issue_592/.gitted/objects/71/44be264b61825fbff68046fe999bdfe96a1792
new file mode 100644
index 000000000..25d44d938
--- /dev/null
+++ b/tests/resources/issue_592/.gitted/objects/71/44be264b61825fbff68046fe999bdfe96a1792
Binary files differ
diff --git a/tests/resources/issue_592/.gitted/objects/be/de83ee10b5b3f00239660b00acec2d55fd0b84 b/tests/resources/issue_592/.gitted/objects/be/de83ee10b5b3f00239660b00acec2d55fd0b84
new file mode 100644
index 000000000..1d6e38d37
--- /dev/null
+++ b/tests/resources/issue_592/.gitted/objects/be/de83ee10b5b3f00239660b00acec2d55fd0b84
Binary files differ
diff --git a/tests/resources/issue_592/.gitted/objects/e3/8fcc7a6060f5eb5b876e836b52ae4769363f21 b/tests/resources/issue_592/.gitted/objects/e3/8fcc7a6060f5eb5b876e836b52ae4769363f21
new file mode 100644
index 000000000..36c5b9aab
--- /dev/null
+++ b/tests/resources/issue_592/.gitted/objects/e3/8fcc7a6060f5eb5b876e836b52ae4769363f21
Binary files differ
diff --git a/tests/resources/issue_592/.gitted/objects/f1/adef63cb08891a0942b76fc4b9c50c6c494bc7 b/tests/resources/issue_592/.gitted/objects/f1/adef63cb08891a0942b76fc4b9c50c6c494bc7
new file mode 100644
index 000000000..c08ecd5ed
--- /dev/null
+++ b/tests/resources/issue_592/.gitted/objects/f1/adef63cb08891a0942b76fc4b9c50c6c494bc7
Binary files differ
diff --git a/tests/resources/issue_592/.gitted/refs/heads/master b/tests/resources/issue_592/.gitted/refs/heads/master
new file mode 100644
index 000000000..1f6669628
--- /dev/null
+++ b/tests/resources/issue_592/.gitted/refs/heads/master
@@ -0,0 +1 @@
+e38fcc7a6060f5eb5b876e836b52ae4769363f21
diff --git a/tests/resources/issue_592/a.txt b/tests/resources/issue_592/a.txt
new file mode 100644
index 000000000..f1adef63c
--- /dev/null
+++ b/tests/resources/issue_592/a.txt
@@ -0,0 +1 @@
+nothing here
diff --git a/tests/resources/issue_592/c/a.txt b/tests/resources/issue_592/c/a.txt
new file mode 100644
index 000000000..f1adef63c
--- /dev/null
+++ b/tests/resources/issue_592/c/a.txt
@@ -0,0 +1 @@
+nothing here
diff --git a/tests/resources/issue_592/l.txt b/tests/resources/issue_592/l.txt
new file mode 100644
index 000000000..f1adef63c
--- /dev/null
+++ b/tests/resources/issue_592/l.txt
@@ -0,0 +1 @@
+nothing here
diff --git a/tests/resources/issue_592/t/a.txt b/tests/resources/issue_592/t/a.txt
new file mode 100644
index 000000000..f1adef63c
--- /dev/null
+++ b/tests/resources/issue_592/t/a.txt
@@ -0,0 +1 @@
+nothing here
diff --git a/tests/resources/issue_592/t/b.txt b/tests/resources/issue_592/t/b.txt
new file mode 100644
index 000000000..f1adef63c
--- /dev/null
+++ b/tests/resources/issue_592/t/b.txt
@@ -0,0 +1 @@
+nothing here
diff --git a/tests/resources/issue_592b/.gitted/HEAD b/tests/resources/issue_592b/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/issue_592b/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/issue_592b/.gitted/config b/tests/resources/issue_592b/.gitted/config
new file mode 100644
index 000000000..af107929f
--- /dev/null
+++ b/tests/resources/issue_592b/.gitted/config
@@ -0,0 +1,6 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
diff --git a/tests/resources/issue_592b/.gitted/description b/tests/resources/issue_592b/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/issue_592b/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/issue_592b/.gitted/index b/tests/resources/issue_592b/.gitted/index
new file mode 100644
index 000000000..596438216
--- /dev/null
+++ b/tests/resources/issue_592b/.gitted/index
Binary files differ
diff --git a/tests/resources/issue_592b/.gitted/info/exclude b/tests/resources/issue_592b/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/issue_592b/.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/resources/issue_592b/.gitted/logs/HEAD b/tests/resources/issue_592b/.gitted/logs/HEAD
new file mode 100644
index 000000000..6f3ba90cc
--- /dev/null
+++ b/tests/resources/issue_592b/.gitted/logs/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 3fbf1852f72fd268e36457b13a18cdd9a4c9ea35 Russell Belfer <rb@github.com> 1337205933 -0700 commit (initial): Initial commit
diff --git a/tests/resources/issue_592b/.gitted/logs/refs/heads/master b/tests/resources/issue_592b/.gitted/logs/refs/heads/master
new file mode 100644
index 000000000..6f3ba90cc
--- /dev/null
+++ b/tests/resources/issue_592b/.gitted/logs/refs/heads/master
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 3fbf1852f72fd268e36457b13a18cdd9a4c9ea35 Russell Belfer <rb@github.com> 1337205933 -0700 commit (initial): Initial commit
diff --git a/tests/resources/issue_592b/.gitted/objects/3f/bf1852f72fd268e36457b13a18cdd9a4c9ea35 b/tests/resources/issue_592b/.gitted/objects/3f/bf1852f72fd268e36457b13a18cdd9a4c9ea35
new file mode 100644
index 000000000..6eaf64b46
--- /dev/null
+++ b/tests/resources/issue_592b/.gitted/objects/3f/bf1852f72fd268e36457b13a18cdd9a4c9ea35
@@ -0,0 +1,2 @@
+x•K
+1]ç}%Bwn½A§íq‰™Îý x·ªz¼µVƃv É‚còžÑ&”%9¦@˜9x¤dÝëŒìÙÐÐuëðû.µÂ]ê".=ßÞEבO¼µ+¸ÐÛ˜B€£EkÍ\çŸNô_Ó<>E Uø%Ìû•9 \ No newline at end of file
diff --git a/tests/resources/issue_592b/.gitted/objects/6f/a891d3e578c83e1c03bdb9e0fdd8e6e934157f b/tests/resources/issue_592b/.gitted/objects/6f/a891d3e578c83e1c03bdb9e0fdd8e6e934157f
new file mode 100644
index 000000000..c4becfe2f
--- /dev/null
+++ b/tests/resources/issue_592b/.gitted/objects/6f/a891d3e578c83e1c03bdb9e0fdd8e6e934157f
Binary files differ
diff --git a/tests/resources/issue_592b/.gitted/objects/80/07d41d5794e6ce4d4d2c97e370d5a9aa6d5213 b/tests/resources/issue_592b/.gitted/objects/80/07d41d5794e6ce4d4d2c97e370d5a9aa6d5213
new file mode 100644
index 000000000..aea14f2af
--- /dev/null
+++ b/tests/resources/issue_592b/.gitted/objects/80/07d41d5794e6ce4d4d2c97e370d5a9aa6d5213
Binary files differ
diff --git a/tests/resources/issue_592b/.gitted/objects/a6/5fb6583a7c425284142f285bc359a2d6565513 b/tests/resources/issue_592b/.gitted/objects/a6/5fb6583a7c425284142f285bc359a2d6565513
new file mode 100644
index 000000000..9b7407221
--- /dev/null
+++ b/tests/resources/issue_592b/.gitted/objects/a6/5fb6583a7c425284142f285bc359a2d6565513
Binary files differ
diff --git a/tests/resources/issue_592b/.gitted/objects/ae/be7a55922c7097ef91ca3a7bc327a901d87c2c b/tests/resources/issue_592b/.gitted/objects/ae/be7a55922c7097ef91ca3a7bc327a901d87c2c
new file mode 100644
index 000000000..1494ed822
--- /dev/null
+++ b/tests/resources/issue_592b/.gitted/objects/ae/be7a55922c7097ef91ca3a7bc327a901d87c2c
Binary files differ
diff --git a/tests/resources/issue_592b/.gitted/objects/b3/44b055867fcdc1f01eaa75056a43e868eb4fbc b/tests/resources/issue_592b/.gitted/objects/b3/44b055867fcdc1f01eaa75056a43e868eb4fbc
new file mode 100644
index 000000000..7a6626636
--- /dev/null
+++ b/tests/resources/issue_592b/.gitted/objects/b3/44b055867fcdc1f01eaa75056a43e868eb4fbc
Binary files differ
diff --git a/tests/resources/issue_592b/.gitted/objects/f7/d75fbfad8b1d2e307ced287ea78aad403cdce3 b/tests/resources/issue_592b/.gitted/objects/f7/d75fbfad8b1d2e307ced287ea78aad403cdce3
new file mode 100644
index 000000000..65a1fd0d0
--- /dev/null
+++ b/tests/resources/issue_592b/.gitted/objects/f7/d75fbfad8b1d2e307ced287ea78aad403cdce3
Binary files differ
diff --git a/tests/resources/issue_592b/.gitted/refs/heads/master b/tests/resources/issue_592b/.gitted/refs/heads/master
new file mode 100644
index 000000000..c0a9ab495
--- /dev/null
+++ b/tests/resources/issue_592b/.gitted/refs/heads/master
@@ -0,0 +1 @@
+3fbf1852f72fd268e36457b13a18cdd9a4c9ea35
diff --git a/tests/resources/issue_592b/gitignore b/tests/resources/issue_592b/gitignore
new file mode 100644
index 000000000..8007d41d5
--- /dev/null
+++ b/tests/resources/issue_592b/gitignore
@@ -0,0 +1 @@
+ignored/
diff --git a/tests/resources/issue_592b/ignored/contained/ignored3.txt b/tests/resources/issue_592b/ignored/contained/ignored3.txt
new file mode 100644
index 000000000..b5dc7b073
--- /dev/null
+++ b/tests/resources/issue_592b/ignored/contained/ignored3.txt
@@ -0,0 +1 @@
+I'm ignored
diff --git a/tests/resources/issue_592b/ignored/contained/tracked3.txt b/tests/resources/issue_592b/ignored/contained/tracked3.txt
new file mode 100644
index 000000000..b344b0558
--- /dev/null
+++ b/tests/resources/issue_592b/ignored/contained/tracked3.txt
@@ -0,0 +1 @@
+You added me anyhow
diff --git a/tests/resources/issue_592b/ignored/ignored2.txt b/tests/resources/issue_592b/ignored/ignored2.txt
new file mode 100644
index 000000000..b5dc7b073
--- /dev/null
+++ b/tests/resources/issue_592b/ignored/ignored2.txt
@@ -0,0 +1 @@
+I'm ignored
diff --git a/tests/resources/issue_592b/ignored/tracked2.txt b/tests/resources/issue_592b/ignored/tracked2.txt
new file mode 100644
index 000000000..6fa891d3e
--- /dev/null
+++ b/tests/resources/issue_592b/ignored/tracked2.txt
@@ -0,0 +1 @@
+You like me
diff --git a/tests/resources/issue_592b/ignored1.txt b/tests/resources/issue_592b/ignored1.txt
new file mode 100644
index 000000000..b5dc7b073
--- /dev/null
+++ b/tests/resources/issue_592b/ignored1.txt
@@ -0,0 +1 @@
+I'm ignored
diff --git a/tests/resources/issue_592b/tracked1.txt b/tests/resources/issue_592b/tracked1.txt
new file mode 100644
index 000000000..6fa891d3e
--- /dev/null
+++ b/tests/resources/issue_592b/tracked1.txt
@@ -0,0 +1 @@
+You like me
diff --git a/tests/resources/merge-resolve/.gitted/COMMIT_EDITMSG b/tests/resources/merge-resolve/.gitted/COMMIT_EDITMSG
new file mode 100644
index 000000000..245b18a2c
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/COMMIT_EDITMSG
@@ -0,0 +1 @@
+rename conflict theirs
diff --git a/tests/resources/merge-resolve/.gitted/HEAD b/tests/resources/merge-resolve/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/merge-resolve/.gitted/ORIG_HEAD b/tests/resources/merge-resolve/.gitted/ORIG_HEAD
new file mode 100644
index 000000000..4092d428f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/ORIG_HEAD
@@ -0,0 +1 @@
+2392a2dacc9efb562b8635d6579fb458751c7c5b
diff --git a/tests/resources/merge-resolve/.gitted/config b/tests/resources/merge-resolve/.gitted/config
new file mode 100644
index 000000000..26c48426d
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/config
@@ -0,0 +1,8 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
+[submodule "submodule"]
+ url = ../submodule
diff --git a/tests/resources/merge-resolve/.gitted/description b/tests/resources/merge-resolve/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/merge-resolve/.gitted/index b/tests/resources/merge-resolve/.gitted/index
new file mode 100644
index 000000000..230eba9eb
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/index
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/logs/HEAD b/tests/resources/merge-resolve/.gitted/logs/HEAD
new file mode 100644
index 000000000..96cdb337e
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/HEAD
@@ -0,0 +1,236 @@
+0000000000000000000000000000000000000000 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1351563869 -0500 commit (initial): initial
+c607fc30883e335def28cd686b51f6cfa02b06ec c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1351563886 -0500 checkout: moving from master to branch
+c607fc30883e335def28cd686b51f6cfa02b06ec 7cb63eed597130ba4abb87b3e544b85021905520 Edward Thomson <ethomson@edwardthomson.com> 1351563965 -0500 commit: branch
+7cb63eed597130ba4abb87b3e544b85021905520 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1351563968 -0500 checkout: moving from branch to master
+c607fc30883e335def28cd686b51f6cfa02b06ec 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351564033 -0500 commit: master
+977c696519c5a3004c5f1d15d60c89dbeb8f235f 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351605785 -0500 checkout: moving from master to ff_branch
+977c696519c5a3004c5f1d15d60c89dbeb8f235f 33d500f588fbbe65901d82b4e6b008e549064be0 Edward Thomson <ethomson@edwardthomson.com> 1351605830 -0500 commit: fastforward
+33d500f588fbbe65901d82b4e6b008e549064be0 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351605889 -0500 checkout: moving from ff_branch to master
+977c696519c5a3004c5f1d15d60c89dbeb8f235f 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351874933 -0500 checkout: moving from master to octo1
+977c696519c5a3004c5f1d15d60c89dbeb8f235f 16f825815cfd20a07a75c71554e82d8eede0b061 Edward Thomson <ethomson@edwardthomson.com> 1351874954 -0500 commit: octo1
+16f825815cfd20a07a75c71554e82d8eede0b061 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351874957 -0500 checkout: moving from octo1 to master
+977c696519c5a3004c5f1d15d60c89dbeb8f235f 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351874960 -0500 checkout: moving from master to octo2
+977c696519c5a3004c5f1d15d60c89dbeb8f235f 158dc7bedb202f5b26502bf3574faa7f4238d56c Edward Thomson <ethomson@edwardthomson.com> 1351874974 -0500 commit: octo2
+158dc7bedb202f5b26502bf3574faa7f4238d56c 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351874976 -0500 checkout: moving from octo2 to master
+977c696519c5a3004c5f1d15d60c89dbeb8f235f 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351874980 -0500 checkout: moving from master to octo3
+977c696519c5a3004c5f1d15d60c89dbeb8f235f 50ce7d7d01217679e26c55939eef119e0c93e272 Edward Thomson <ethomson@edwardthomson.com> 1351874998 -0500 commit: octo3
+50ce7d7d01217679e26c55939eef119e0c93e272 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351875006 -0500 checkout: moving from octo3 to master
+977c696519c5a3004c5f1d15d60c89dbeb8f235f 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351875010 -0500 checkout: moving from master to octo4
+977c696519c5a3004c5f1d15d60c89dbeb8f235f 54269b3f6ec3d7d4ede24dd350dd5d605495c3ae Edward Thomson <ethomson@edwardthomson.com> 1351875023 -0500 commit: octo4
+54269b3f6ec3d7d4ede24dd350dd5d605495c3ae 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351875031 -0500 checkout: moving from octo4 to master
+977c696519c5a3004c5f1d15d60c89dbeb8f235f 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351875031 -0500 checkout: moving from master to octo5
+977c696519c5a3004c5f1d15d60c89dbeb8f235f e4f618a2c3ed0669308735727df5ebf2447f022f Edward Thomson <ethomson@edwardthomson.com> 1351875041 -0500 commit: octo5
+e4f618a2c3ed0669308735727df5ebf2447f022f 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351875046 -0500 checkout: moving from octo5 to master
+977c696519c5a3004c5f1d15d60c89dbeb8f235f 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351875046 -0500 checkout: moving from master to octo6
+977c696519c5a3004c5f1d15d60c89dbeb8f235f 4ca408a8c88655f7586a1b580be6fad138121e98 Edward Thomson <ethomson@edwardthomson.com> 1351875057 -0500 commit: octo5
+4ca408a8c88655f7586a1b580be6fad138121e98 b6f610aef53bd343e6c96227de874c66f00ee8e8 Edward Thomson <ethomson@edwardthomson.com> 1351875065 -0500 commit (amend): octo6
+b6f610aef53bd343e6c96227de874c66f00ee8e8 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351875071 -0500 checkout: moving from octo6 to master
+977c696519c5a3004c5f1d15d60c89dbeb8f235f 4e0d9401aee78eb345a8685a859d37c8c3c0bbed Edward Thomson <ethomson@edwardthomson.com> 1351875091 -0500 merge octo1 octo2 octo3 octo4: Merge made by the 'octopus' strategy.
+4e0d9401aee78eb345a8685a859d37c8c3c0bbed 54269b3f6ec3d7d4ede24dd350dd5d605495c3ae Edward Thomson <ethomson@edwardthomson.com> 1351875108 -0500 reset: moving to 54269b3f6ec3d7d4ede24dd350dd5d605495c3ae
+54269b3f6ec3d7d4ede24dd350dd5d605495c3ae 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351875584 -0500 reset: moving to 977c696519c5a3004c5f1d15d60c89dbeb8f235f
+bd593285fc7fe4ca18ccdbabf027f5d689101452 33d500f588fbbe65901d82b4e6b008e549064be0 Edward Thomson <ethomson@edwardthomson.com> 1351990193 -0500 checkout: moving from master to ff_branch
+33d500f588fbbe65901d82b4e6b008e549064be0 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1351990202 -0500 reset: moving to c607fc30883e335def28cd686b51f6cfa02b06ec
+c607fc30883e335def28cd686b51f6cfa02b06ec bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1351990205 -0500 merge master: Fast-forward
+bd593285fc7fe4ca18ccdbabf027f5d689101452 fd89f8cffb663ac89095a0f9764902e93ceaca6a Edward Thomson <ethomson@edwardthomson.com> 1351990229 -0500 commit: fastforward
+fd89f8cffb663ac89095a0f9764902e93ceaca6a bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1351990233 -0500 checkout: moving from ff_branch to master
+bd593285fc7fe4ca18ccdbabf027f5d689101452 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352091703 -0600 checkout: moving from master to trivial-2alt
+c607fc30883e335def28cd686b51f6cfa02b06ec c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352092411 -0600 checkout: moving from trivial-2alt to trivial-2alt-branch
+c607fc30883e335def28cd686b51f6cfa02b06ec c9174cef549ec94ecbc43ef03cdc775b4950becb Edward Thomson <ethomson@edwardthomson.com> 1352092434 -0600 commit: 2alt-branch
+c9174cef549ec94ecbc43ef03cdc775b4950becb c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352092440 -0600 checkout: moving from trivial-2alt-branch to trivial-2alt
+c607fc30883e335def28cd686b51f6cfa02b06ec 566ab53c220a2eafc1212af1a024513230280ab9 Edward Thomson <ethomson@edwardthomson.com> 1352092452 -0600 commit: 2alt
+bd593285fc7fe4ca18ccdbabf027f5d689101452 566ab53c220a2eafc1212af1a024513230280ab9 Edward Thomson <ethomson@edwardthomson.com> 1352094476 -0600 checkout: moving from master to trivial-3alt
+566ab53c220a2eafc1212af1a024513230280ab9 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352094547 -0600 reset: moving to c607fc30883e335def28cd686b51f6cfa02b06ec
+c607fc30883e335def28cd686b51f6cfa02b06ec 5459c89aa0026d543ce8343bd89871bce543f9c2 Edward Thomson <ethomson@edwardthomson.com> 1352094580 -0600 commit: 3alt
+5459c89aa0026d543ce8343bd89871bce543f9c2 4c9fac0707f8d4195037ae5a681aa48626491541 Edward Thomson <ethomson@edwardthomson.com> 1352094610 -0600 commit: 3alt-branch
+4c9fac0707f8d4195037ae5a681aa48626491541 bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1352094620 -0600 checkout: moving from trivial-3alt to master
+bd593285fc7fe4ca18ccdbabf027f5d689101452 566ab53c220a2eafc1212af1a024513230280ab9 Edward Thomson <ethomson@edwardthomson.com> 1352094752 -0600 checkout: moving from master to trivial-4
+566ab53c220a2eafc1212af1a024513230280ab9 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352094764 -0600 reset: moving to c607fc30883e335def28cd686b51f6cfa02b06ec
+c607fc30883e335def28cd686b51f6cfa02b06ec cc3e3009134cb88014129fc8858d1101359e5e2f Edward Thomson <ethomson@edwardthomson.com> 1352094815 -0600 commit: trivial-4
+cc3e3009134cb88014129fc8858d1101359e5e2f c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352094843 -0600 checkout: moving from trivial-4 to trivial-4-branch
+c607fc30883e335def28cd686b51f6cfa02b06ec 183310e30fb1499af8c619108ffea4d300b5e778 Edward Thomson <ethomson@edwardthomson.com> 1352094856 -0600 commit: trivial-4-branch
+183310e30fb1499af8c619108ffea4d300b5e778 bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1352094860 -0600 checkout: moving from trivial-4-branch to master
+bd593285fc7fe4ca18ccdbabf027f5d689101452 cc3e3009134cb88014129fc8858d1101359e5e2f Edward Thomson <ethomson@edwardthomson.com> 1352096588 -0600 checkout: moving from master to trivial-4
+cc3e3009134cb88014129fc8858d1101359e5e2f c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352096612 -0600 checkout: moving from trivial-4 to trivial-5alt-1
+c607fc30883e335def28cd686b51f6cfa02b06ec 4fe93c0ec83eb6305cbace3dace88ecee1b63cb6 Edward Thomson <ethomson@edwardthomson.com> 1352096643 -0600 commit: 5alt-1
+4fe93c0ec83eb6305cbace3dace88ecee1b63cb6 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352096661 -0600 checkout: moving from trivial-5alt-1 to trivial-5alt-1-branch
+c607fc30883e335def28cd686b51f6cfa02b06ec 4fe93c0ec83eb6305cbace3dace88ecee1b63cb6 Edward Thomson <ethomson@edwardthomson.com> 1352096671 -0600 checkout: moving from trivial-5alt-1-branch to trivial-5alt-1
+4fe93c0ec83eb6305cbace3dace88ecee1b63cb6 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352096678 -0600 checkout: moving from trivial-5alt-1 to trivial-5alt-1-branch
+c607fc30883e335def28cd686b51f6cfa02b06ec 478172cb2f5ff9b514bc9d04d3bd5ef5840cb3b2 Edward Thomson <ethomson@edwardthomson.com> 1352096689 -0600 commit: 5alt-1-branch
+478172cb2f5ff9b514bc9d04d3bd5ef5840cb3b2 4fe93c0ec83eb6305cbace3dace88ecee1b63cb6 Edward Thomson <ethomson@edwardthomson.com> 1352096701 -0600 checkout: moving from trivial-5alt-1-branch to trivial-5alt-1
+4fe93c0ec83eb6305cbace3dace88ecee1b63cb6 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352096715 -0600 checkout: moving from trivial-5alt-1 to trivial-5alt-2
+c607fc30883e335def28cd686b51f6cfa02b06ec ebc09d0137cfb0c26697aed0109fb943ad906f3f Edward Thomson <ethomson@edwardthomson.com> 1352096764 -0600 commit: existing file
+ebc09d0137cfb0c26697aed0109fb943ad906f3f 3b47b031b3e55ae11e14a05260b1c3ffd6838d55 Edward Thomson <ethomson@edwardthomson.com> 1352096815 -0600 commit: 5alt-2
+3b47b031b3e55ae11e14a05260b1c3ffd6838d55 ebc09d0137cfb0c26697aed0109fb943ad906f3f Edward Thomson <ethomson@edwardthomson.com> 1352096840 -0600 checkout: moving from trivial-5alt-2 to trivial-5alt-2-branch
+ebc09d0137cfb0c26697aed0109fb943ad906f3f f48097eb340dc5a7cae55aabcf1faf4548aa821f Edward Thomson <ethomson@edwardthomson.com> 1352096855 -0600 commit: 5alt-2-branch
+f48097eb340dc5a7cae55aabcf1faf4548aa821f bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1352096858 -0600 checkout: moving from trivial-5alt-2-branch to master
+bd593285fc7fe4ca18ccdbabf027f5d689101452 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352097377 -0600 checkout: moving from master to trivial-6
+c607fc30883e335def28cd686b51f6cfa02b06ec f7c332bd4d4d4b777366cae4d24d1687477576bf Edward Thomson <ethomson@edwardthomson.com> 1352097389 -0600 commit: 6
+f7c332bd4d4d4b777366cae4d24d1687477576bf 99b4f7e4f24470fa06b980bc21f1095c2a9425c0 Edward Thomson <ethomson@edwardthomson.com> 1352097404 -0600 commit: trivial-6
+99b4f7e4f24470fa06b980bc21f1095c2a9425c0 f7c332bd4d4d4b777366cae4d24d1687477576bf Edward Thomson <ethomson@edwardthomson.com> 1352097420 -0600 checkout: moving from trivial-6 to trivial-6-branch
+f7c332bd4d4d4b777366cae4d24d1687477576bf a43150a738849c59376cf30bb2a68348a83c8f48 Edward Thomson <ethomson@edwardthomson.com> 1352097431 -0600 commit: 6-branch
+a43150a738849c59376cf30bb2a68348a83c8f48 bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1352097442 -0600 checkout: moving from trivial-6-branch to master
+bd593285fc7fe4ca18ccdbabf027f5d689101452 99b4f7e4f24470fa06b980bc21f1095c2a9425c0 Edward Thomson <ethomson@edwardthomson.com> 1352098040 -0600 checkout: moving from master to trivial-6
+99b4f7e4f24470fa06b980bc21f1095c2a9425c0 bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1352098057 -0600 checkout: moving from trivial-6 to master
+bd593285fc7fe4ca18ccdbabf027f5d689101452 cc3e3009134cb88014129fc8858d1101359e5e2f Edward Thomson <ethomson@edwardthomson.com> 1352098792 -0600 checkout: moving from master to trivial-4
+cc3e3009134cb88014129fc8858d1101359e5e2f c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352098818 -0600 checkout: moving from trivial-4 to trivial-8
+c607fc30883e335def28cd686b51f6cfa02b06ec 75a811bf6bc57694adb3fe604786f3a4efd1cd1b Edward Thomson <ethomson@edwardthomson.com> 1352098884 -0600 commit: trivial-8
+75a811bf6bc57694adb3fe604786f3a4efd1cd1b 75a811bf6bc57694adb3fe604786f3a4efd1cd1b Edward Thomson <ethomson@edwardthomson.com> 1352098947 -0600 checkout: moving from trivial-8 to trivial-8-branch
+75a811bf6bc57694adb3fe604786f3a4efd1cd1b 52d8bc572af2b6d4ee0d5e62ed5d1fbad92210a9 Edward Thomson <ethomson@edwardthomson.com> 1352098979 -0600 commit: trivial-8-branch
+52d8bc572af2b6d4ee0d5e62ed5d1fbad92210a9 75a811bf6bc57694adb3fe604786f3a4efd1cd1b Edward Thomson <ethomson@edwardthomson.com> 1352098982 -0600 checkout: moving from trivial-8-branch to trivial-8
+75a811bf6bc57694adb3fe604786f3a4efd1cd1b 3575826c96a975031d2c14368529cc5c4353a8fd Edward Thomson <ethomson@edwardthomson.com> 1352099000 -0600 commit: trivial-8
+3575826c96a975031d2c14368529cc5c4353a8fd bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1352099008 -0600 checkout: moving from trivial-8 to master
+bd593285fc7fe4ca18ccdbabf027f5d689101452 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352099776 -0600 checkout: moving from master to trivial-7
+c607fc30883e335def28cd686b51f6cfa02b06ec 092ce8682d7f3a2a3a769a6daca58950168ba5c4 Edward Thomson <ethomson@edwardthomson.com> 1352099790 -0600 commit: trivial-7
+092ce8682d7f3a2a3a769a6daca58950168ba5c4 092ce8682d7f3a2a3a769a6daca58950168ba5c4 Edward Thomson <ethomson@edwardthomson.com> 1352099799 -0600 checkout: moving from trivial-7 to trivial-7-branch
+092ce8682d7f3a2a3a769a6daca58950168ba5c4 73cbfdc4fe843169e5b2af8dcad03cbf3acf306c Edward Thomson <ethomson@edwardthomson.com> 1352099812 -0600 commit: trivial-7-branch
+73cbfdc4fe843169e5b2af8dcad03cbf3acf306c 092ce8682d7f3a2a3a769a6daca58950168ba5c4 Edward Thomson <ethomson@edwardthomson.com> 1352099815 -0600 checkout: moving from trivial-7-branch to trivial-7
+092ce8682d7f3a2a3a769a6daca58950168ba5c4 73cbfdc4fe843169e5b2af8dcad03cbf3acf306c Edward Thomson <ethomson@edwardthomson.com> 1352099838 -0600 checkout: moving from trivial-7 to trivial-7-branch
+73cbfdc4fe843169e5b2af8dcad03cbf3acf306c 092ce8682d7f3a2a3a769a6daca58950168ba5c4 Edward Thomson <ethomson@edwardthomson.com> 1352099874 -0600 reset: moving to 092ce8682d7f3a2a3a769a6daca58950168ba5c4
+092ce8682d7f3a2a3a769a6daca58950168ba5c4 009b9cab6fdac02915a88ecd078b7a792ed802d8 Edward Thomson <ethomson@edwardthomson.com> 1352099921 -0600 commit: removed in 7
+009b9cab6fdac02915a88ecd078b7a792ed802d8 5195a1b480f66691b667f10a9e41e70115a78351 Edward Thomson <ethomson@edwardthomson.com> 1352099927 -0600 commit (amend): trivial-7-branch
+5195a1b480f66691b667f10a9e41e70115a78351 092ce8682d7f3a2a3a769a6daca58950168ba5c4 Edward Thomson <ethomson@edwardthomson.com> 1352099937 -0600 checkout: moving from trivial-7-branch to trivial-7
+092ce8682d7f3a2a3a769a6daca58950168ba5c4 d874671ef5b20184836cb983bb273e5280384d0b Edward Thomson <ethomson@edwardthomson.com> 1352099947 -0600 commit: trivial-7
+d874671ef5b20184836cb983bb273e5280384d0b bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1352099949 -0600 checkout: moving from trivial-7 to master
+bd593285fc7fe4ca18ccdbabf027f5d689101452 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352100174 -0600 checkout: moving from master to trivial-10
+c607fc30883e335def28cd686b51f6cfa02b06ec 53825f41ac8d640612f9423a2f03a69f3d96809a Edward Thomson <ethomson@edwardthomson.com> 1352100193 -0600 commit: trivial-10
+53825f41ac8d640612f9423a2f03a69f3d96809a 53825f41ac8d640612f9423a2f03a69f3d96809a Edward Thomson <ethomson@edwardthomson.com> 1352100200 -0600 checkout: moving from trivial-10 to trivial-10-branch
+53825f41ac8d640612f9423a2f03a69f3d96809a 11f4f3c08b737f5fd896cbefa1425ee63b21b2fa Edward Thomson <ethomson@edwardthomson.com> 1352100211 -0600 commit: trivial-10-branch
+11f4f3c08b737f5fd896cbefa1425ee63b21b2fa 53825f41ac8d640612f9423a2f03a69f3d96809a Edward Thomson <ethomson@edwardthomson.com> 1352100214 -0600 checkout: moving from trivial-10-branch to trivial-10
+53825f41ac8d640612f9423a2f03a69f3d96809a 0ec5f433959cd46177f745903353efb5be08d151 Edward Thomson <ethomson@edwardthomson.com> 1352100223 -0600 commit: trivial-10
+0ec5f433959cd46177f745903353efb5be08d151 bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1352100225 -0600 checkout: moving from trivial-10 to master
+bd593285fc7fe4ca18ccdbabf027f5d689101452 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352100270 -0600 checkout: moving from master to trivial-9
+c607fc30883e335def28cd686b51f6cfa02b06ec f0053b8060bb3f0be5cbcc3147a07ece26bf097e Edward Thomson <ethomson@edwardthomson.com> 1352100304 -0600 commit: trivial-9
+f0053b8060bb3f0be5cbcc3147a07ece26bf097e f0053b8060bb3f0be5cbcc3147a07ece26bf097e Edward Thomson <ethomson@edwardthomson.com> 1352100310 -0600 checkout: moving from trivial-9 to trivial-9-branch
+f0053b8060bb3f0be5cbcc3147a07ece26bf097e 13d1be4ea52a6ced1d7a1d832f0ee3c399348e5e Edward Thomson <ethomson@edwardthomson.com> 1352100317 -0600 commit: trivial-9-branch
+13d1be4ea52a6ced1d7a1d832f0ee3c399348e5e f0053b8060bb3f0be5cbcc3147a07ece26bf097e Edward Thomson <ethomson@edwardthomson.com> 1352100319 -0600 checkout: moving from trivial-9-branch to trivial-9
+f0053b8060bb3f0be5cbcc3147a07ece26bf097e c35dee9bcc0e989f3b0c40f68372a9a51b6c4e6a Edward Thomson <ethomson@edwardthomson.com> 1352100333 -0600 commit: trivial-9
+c35dee9bcc0e989f3b0c40f68372a9a51b6c4e6a bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1352100335 -0600 checkout: moving from trivial-9 to master
+bd593285fc7fe4ca18ccdbabf027f5d689101452 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352100576 -0600 checkout: moving from master to trivial-13
+c607fc30883e335def28cd686b51f6cfa02b06ec 8f4433f8593ddd65b7dd43dd4564d841f4d9c8aa Edward Thomson <ethomson@edwardthomson.com> 1352100589 -0600 commit: trivial-13
+8f4433f8593ddd65b7dd43dd4564d841f4d9c8aa 8f4433f8593ddd65b7dd43dd4564d841f4d9c8aa Edward Thomson <ethomson@edwardthomson.com> 1352100604 -0600 checkout: moving from trivial-13 to trivial-13-branch
+8f4433f8593ddd65b7dd43dd4564d841f4d9c8aa 05f3c1a2a56ca95c3d2ef28dc9ddf32b5cd6c91c Edward Thomson <ethomson@edwardthomson.com> 1352100610 -0600 commit: trivial-13-branch
+05f3c1a2a56ca95c3d2ef28dc9ddf32b5cd6c91c 8f4433f8593ddd65b7dd43dd4564d841f4d9c8aa Edward Thomson <ethomson@edwardthomson.com> 1352100612 -0600 checkout: moving from trivial-13-branch to trivial-13
+8f4433f8593ddd65b7dd43dd4564d841f4d9c8aa a3fabece9eb8748da810e1e08266fef9b7136ad4 Edward Thomson <ethomson@edwardthomson.com> 1352100625 -0600 commit: trivial-13
+a3fabece9eb8748da810e1e08266fef9b7136ad4 bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1352100627 -0600 checkout: moving from trivial-13 to master
+bd593285fc7fe4ca18ccdbabf027f5d689101452 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352100936 -0600 checkout: moving from master to trivial-11
+c607fc30883e335def28cd686b51f6cfa02b06ec 35632e43612c06a3ea924bfbacd48333da874c29 Edward Thomson <ethomson@edwardthomson.com> 1352100958 -0600 commit: trivial-11
+35632e43612c06a3ea924bfbacd48333da874c29 35632e43612c06a3ea924bfbacd48333da874c29 Edward Thomson <ethomson@edwardthomson.com> 1352100964 -0600 checkout: moving from trivial-11 to trivial-11-branch
+35632e43612c06a3ea924bfbacd48333da874c29 6718a45909532d1fcf5600d0877f7fe7e78f0b86 Edward Thomson <ethomson@edwardthomson.com> 1352100978 -0600 commit: trivial-11-branch
+6718a45909532d1fcf5600d0877f7fe7e78f0b86 35632e43612c06a3ea924bfbacd48333da874c29 Edward Thomson <ethomson@edwardthomson.com> 1352100981 -0600 checkout: moving from trivial-11-branch to trivial-11
+35632e43612c06a3ea924bfbacd48333da874c29 3168dca1a561889b045a6441909f4c56145e666d Edward Thomson <ethomson@edwardthomson.com> 1352100992 -0600 commit: trivial-11
+3168dca1a561889b045a6441909f4c56145e666d bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1352100996 -0600 checkout: moving from trivial-11 to master
+bd593285fc7fe4ca18ccdbabf027f5d689101452 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352101098 -0600 checkout: moving from master to trivial-14
+c607fc30883e335def28cd686b51f6cfa02b06ec 596803b523203a4851c824c07366906f8353f4ad Edward Thomson <ethomson@edwardthomson.com> 1352101113 -0600 commit: trivial-14
+596803b523203a4851c824c07366906f8353f4ad 596803b523203a4851c824c07366906f8353f4ad Edward Thomson <ethomson@edwardthomson.com> 1352101117 -0600 checkout: moving from trivial-14 to trivial-14-branch
+596803b523203a4851c824c07366906f8353f4ad 8187117062b750eed4f93fd7e899f17b52ce554d Edward Thomson <ethomson@edwardthomson.com> 1352101132 -0600 commit: trivial-14-branch
+8187117062b750eed4f93fd7e899f17b52ce554d 596803b523203a4851c824c07366906f8353f4ad Edward Thomson <ethomson@edwardthomson.com> 1352101135 -0600 checkout: moving from trivial-14-branch to trivial-14
+596803b523203a4851c824c07366906f8353f4ad 7e2d058d5fedf8329db44db4fac610d6b1a89159 Edward Thomson <ethomson@edwardthomson.com> 1352101141 -0600 commit: trivial-14
+7e2d058d5fedf8329db44db4fac610d6b1a89159 bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1352101145 -0600 checkout: moving from trivial-14 to master
+bd593285fc7fe4ca18ccdbabf027f5d689101452 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1353177749 -0600 checkout: moving from master to renames1
+c607fc30883e335def28cd686b51f6cfa02b06ec 412b32fb66137366147f1801ecc962452757d48a Edward Thomson <ethomson@edwardthomson.com> 1353177886 -0600 commit: renames
+412b32fb66137366147f1801ecc962452757d48a bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1353794607 -0600 checkout: moving from renames1 to master
+bd593285fc7fe4ca18ccdbabf027f5d689101452 bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1353794647 -0600 checkout: moving from master to renames2
+bd593285fc7fe4ca18ccdbabf027f5d689101452 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1353794677 -0600 reset: moving to c607fc30883e335def28cd686b51f6cfa02b06ec
+c607fc30883e335def28cd686b51f6cfa02b06ec ab40af3cb8a3ed2e2843e96d9aa7871336b94573 Edward Thomson <ethomson@edwardthomson.com> 1353794852 -0600 commit: renames2
+ab40af3cb8a3ed2e2843e96d9aa7871336b94573 bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1353794883 -0600 checkout: moving from renames2 to master
+bd593285fc7fe4ca18ccdbabf027f5d689101452 bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1354574697 -0600 checkout: moving from master to df_side1
+bd593285fc7fe4ca18ccdbabf027f5d689101452 d4207f77243500bec335ab477f9227fcdb1e271a Edward Thomson <ethomson@edwardthomson.com> 1354574962 -0600 commit: df_ancestor
+d4207f77243500bec335ab477f9227fcdb1e271a c94b27e41064c521120627e07e2035cca1d24ffa Edward Thomson <ethomson@edwardthomson.com> 1354575027 -0600 commit: df_side1
+c94b27e41064c521120627e07e2035cca1d24ffa d4207f77243500bec335ab477f9227fcdb1e271a Edward Thomson <ethomson@edwardthomson.com> 1354575070 -0600 checkout: moving from df_side1 to df_side2
+d4207f77243500bec335ab477f9227fcdb1e271a f8958bdf4d365a84a9a178b1f5f35ff1dacbd884 Edward Thomson <ethomson@edwardthomson.com> 1354575206 -0600 commit: df_side2
+f8958bdf4d365a84a9a178b1f5f35ff1dacbd884 bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1354575381 -0600 checkout: moving from df_side2 to master
+bd593285fc7fe4ca18ccdbabf027f5d689101452 c94b27e41064c521120627e07e2035cca1d24ffa Edward Thomson <ethomson@edwardthomson.com> 1355017614 -0600 checkout: moving from master to df_side1
+c94b27e41064c521120627e07e2035cca1d24ffa a90bc3fb6f15181972a2959a921429efbd81a473 Edward Thomson <ethomson@edwardthomson.com> 1355017650 -0600 commit: df_added
+a90bc3fb6f15181972a2959a921429efbd81a473 c94b27e41064c521120627e07e2035cca1d24ffa Edward Thomson <ethomson@edwardthomson.com> 1355017673 -0600 checkout: moving from df_side1 to c94b27e
+c94b27e41064c521120627e07e2035cca1d24ffa d4207f77243500bec335ab477f9227fcdb1e271a Edward Thomson <ethomson@edwardthomson.com> 1355017673 -0600 rebase -i (squash): updating HEAD
+d4207f77243500bec335ab477f9227fcdb1e271a 005b6fcc8fec71d2550bef8462d169b3c26aa14b Edward Thomson <ethomson@edwardthomson.com> 1355017673 -0600 rebase -i (squash): df_side1
+005b6fcc8fec71d2550bef8462d169b3c26aa14b 005b6fcc8fec71d2550bef8462d169b3c26aa14b Edward Thomson <ethomson@edwardthomson.com> 1355017676 -0600 rebase -i (finish): returning to refs/heads/df_side1
+005b6fcc8fec71d2550bef8462d169b3c26aa14b f8958bdf4d365a84a9a178b1f5f35ff1dacbd884 Edward Thomson <ethomson@edwardthomson.com> 1355017715 -0600 reset: moving to df_side2
+f8958bdf4d365a84a9a178b1f5f35ff1dacbd884 8c749d9968d4b10dcfb06c9f97d0e5d92d337071 Edward Thomson <ethomson@edwardthomson.com> 1355017744 -0600 commit: df_added
+8c749d9968d4b10dcfb06c9f97d0e5d92d337071 f8958bdf4d365a84a9a178b1f5f35ff1dacbd884 Edward Thomson <ethomson@edwardthomson.com> 1355017754 -0600 checkout: moving from df_side1 to f8958bd
+f8958bdf4d365a84a9a178b1f5f35ff1dacbd884 d4207f77243500bec335ab477f9227fcdb1e271a Edward Thomson <ethomson@edwardthomson.com> 1355017754 -0600 rebase -i (squash): updating HEAD
+d4207f77243500bec335ab477f9227fcdb1e271a 0204a84f822acbf6386b36d33f1f6bc68bbbf858 Edward Thomson <ethomson@edwardthomson.com> 1355017754 -0600 rebase -i (squash): df_side2
+0204a84f822acbf6386b36d33f1f6bc68bbbf858 0204a84f822acbf6386b36d33f1f6bc68bbbf858 Edward Thomson <ethomson@edwardthomson.com> 1355017756 -0600 rebase -i (finish): returning to refs/heads/df_side1
+0204a84f822acbf6386b36d33f1f6bc68bbbf858 005b6fcc8fec71d2550bef8462d169b3c26aa14b Edward Thomson <ethomson@edwardthomson.com> 1355017793 -0600 reset: moving to 005b6fcc8fec71d2550bef8462d169b3c26aa14b
+005b6fcc8fec71d2550bef8462d169b3c26aa14b 0204a84f822acbf6386b36d33f1f6bc68bbbf858 Edward Thomson <ethomson@edwardthomson.com> 1355017826 -0600 reset: moving to 0204a84
+0204a84f822acbf6386b36d33f1f6bc68bbbf858 bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1355017847 -0600 checkout: moving from df_side1 to master
+bd593285fc7fe4ca18ccdbabf027f5d689101452 0204a84f822acbf6386b36d33f1f6bc68bbbf858 Edward Thomson <ethomson@edwardthomson.com> 1355168677 -0600 checkout: moving from master to df_side1
+005b6fcc8fec71d2550bef8462d169b3c26aa14b 005b6fcc8fec71d2550bef8462d169b3c26aa14b Edward Thomson <ethomson@edwardthomson.com> 1355168829 -0600 checkout: moving from df_side1 to df_side1
+005b6fcc8fec71d2550bef8462d169b3c26aa14b 005b6fcc8fec71d2550bef8462d169b3c26aa14b Edward Thomson <ethomson@edwardthomson.com> 1355168838 -0600 checkout: moving from df_side1 to df_side1
+005b6fcc8fec71d2550bef8462d169b3c26aa14b e8107f24196736b870a318a0e28f048e29f6feff Edward Thomson <ethomson@edwardthomson.com> 1355169065 -0600 commit: df_side1
+e8107f24196736b870a318a0e28f048e29f6feff 005b6fcc8fec71d2550bef8462d169b3c26aa14b Edward Thomson <ethomson@edwardthomson.com> 1355169081 -0600 checkout: moving from df_side1 to 005b6fc
+005b6fcc8fec71d2550bef8462d169b3c26aa14b d4207f77243500bec335ab477f9227fcdb1e271a Edward Thomson <ethomson@edwardthomson.com> 1355169081 -0600 rebase -i (squash): updating HEAD
+d4207f77243500bec335ab477f9227fcdb1e271a 80a8fbb3abb1ba423d554e9630b8fc2e5698f86b Edward Thomson <ethomson@edwardthomson.com> 1355169081 -0600 rebase -i (squash): df_side1
+80a8fbb3abb1ba423d554e9630b8fc2e5698f86b 80a8fbb3abb1ba423d554e9630b8fc2e5698f86b Edward Thomson <ethomson@edwardthomson.com> 1355169084 -0600 rebase -i (finish): returning to refs/heads/df_side1
+80a8fbb3abb1ba423d554e9630b8fc2e5698f86b 0204a84f822acbf6386b36d33f1f6bc68bbbf858 Edward Thomson <ethomson@edwardthomson.com> 1355169141 -0600 checkout: moving from df_side1 to df_side2
+0204a84f822acbf6386b36d33f1f6bc68bbbf858 944f5dd1a867cab4c2bbcb896493435cae1dcc1a Edward Thomson <ethomson@edwardthomson.com> 1355169174 -0600 commit: both
+944f5dd1a867cab4c2bbcb896493435cae1dcc1a 0204a84f822acbf6386b36d33f1f6bc68bbbf858 Edward Thomson <ethomson@edwardthomson.com> 1355169182 -0600 checkout: moving from df_side2 to 0204a84
+0204a84f822acbf6386b36d33f1f6bc68bbbf858 d4207f77243500bec335ab477f9227fcdb1e271a Edward Thomson <ethomson@edwardthomson.com> 1355169182 -0600 rebase -i (squash): updating HEAD
+d4207f77243500bec335ab477f9227fcdb1e271a 57079a46233ae2b6df62e9ade71c4948512abefb Edward Thomson <ethomson@edwardthomson.com> 1355169182 -0600 rebase -i (squash): df_side2
+57079a46233ae2b6df62e9ade71c4948512abefb 57079a46233ae2b6df62e9ade71c4948512abefb Edward Thomson <ethomson@edwardthomson.com> 1355169185 -0600 rebase -i (finish): returning to refs/heads/df_side2
+57079a46233ae2b6df62e9ade71c4948512abefb 80a8fbb3abb1ba423d554e9630b8fc2e5698f86b Edward Thomson <ethomson@edwardthomson.com> 1355169241 -0600 checkout: moving from df_side2 to df_side1
+80a8fbb3abb1ba423d554e9630b8fc2e5698f86b e65a9bb2af9f4c2d1c375dd0f8f8a46cf9c68812 Edward Thomson <ethomson@edwardthomson.com> 1355169419 -0600 commit: side1
+e65a9bb2af9f4c2d1c375dd0f8f8a46cf9c68812 80a8fbb3abb1ba423d554e9630b8fc2e5698f86b Edward Thomson <ethomson@edwardthomson.com> 1355169431 -0600 checkout: moving from df_side1 to 80a8fbb
+80a8fbb3abb1ba423d554e9630b8fc2e5698f86b d4207f77243500bec335ab477f9227fcdb1e271a Edward Thomson <ethomson@edwardthomson.com> 1355169431 -0600 rebase -i (squash): updating HEAD
+d4207f77243500bec335ab477f9227fcdb1e271a 5dc1018e90b19654bee986b7a0c268804d39659d Edward Thomson <ethomson@edwardthomson.com> 1355169431 -0600 rebase -i (squash): df_side1
+5dc1018e90b19654bee986b7a0c268804d39659d 5dc1018e90b19654bee986b7a0c268804d39659d Edward Thomson <ethomson@edwardthomson.com> 1355169435 -0600 rebase -i (finish): returning to refs/heads/df_side1
+5dc1018e90b19654bee986b7a0c268804d39659d 57079a46233ae2b6df62e9ade71c4948512abefb Edward Thomson <ethomson@edwardthomson.com> 1355169439 -0600 checkout: moving from df_side1 to df_side2
+57079a46233ae2b6df62e9ade71c4948512abefb 58e853f66699fd02629fd50bde08082bc005933a Edward Thomson <ethomson@edwardthomson.com> 1355169460 -0600 commit: side2
+58e853f66699fd02629fd50bde08082bc005933a 57079a46233ae2b6df62e9ade71c4948512abefb Edward Thomson <ethomson@edwardthomson.com> 1355169469 -0600 checkout: moving from df_side2 to 57079a4
+57079a46233ae2b6df62e9ade71c4948512abefb d4207f77243500bec335ab477f9227fcdb1e271a Edward Thomson <ethomson@edwardthomson.com> 1355169469 -0600 rebase -i (squash): updating HEAD
+d4207f77243500bec335ab477f9227fcdb1e271a fada9356aa3f74622327a3038ae9c6f92e1c5c1d Edward Thomson <ethomson@edwardthomson.com> 1355169469 -0600 rebase -i (squash): df_side2
+fada9356aa3f74622327a3038ae9c6f92e1c5c1d fada9356aa3f74622327a3038ae9c6f92e1c5c1d Edward Thomson <ethomson@edwardthomson.com> 1355169471 -0600 rebase -i (finish): returning to refs/heads/df_side2
+fada9356aa3f74622327a3038ae9c6f92e1c5c1d 5dc1018e90b19654bee986b7a0c268804d39659d Edward Thomson <ethomson@edwardthomson.com> 1355169494 -0600 checkout: moving from df_side2 to df_side1
+5dc1018e90b19654bee986b7a0c268804d39659d d4207f77243500bec335ab477f9227fcdb1e271a Edward Thomson <ethomson@edwardthomson.com> 1355169663 -0600 checkout: moving from df_side1 to d4207f77243500bec335ab477f9227fcdb1e271a
+d4207f77243500bec335ab477f9227fcdb1e271a 849619b03ae540acee4d1edec96b86993da6b497 Edward Thomson <ethomson@edwardthomson.com> 1355169683 -0600 commit: both_dirs
+849619b03ae540acee4d1edec96b86993da6b497 d4207f77243500bec335ab477f9227fcdb1e271a Edward Thomson <ethomson@edwardthomson.com> 1355169691 -0600 checkout: moving from 849619b03ae540acee4d1edec96b86993da6b497 to d4207f7
+d4207f77243500bec335ab477f9227fcdb1e271a bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1355169691 -0600 rebase -i (squash): updating HEAD
+bd593285fc7fe4ca18ccdbabf027f5d689101452 a765fb87eb2f7a1920b73b2d5a057f8f8476a42b Edward Thomson <ethomson@edwardthomson.com> 1355169691 -0600 rebase -i (squash): df_ancestor
+a765fb87eb2f7a1920b73b2d5a057f8f8476a42b 5dc1018e90b19654bee986b7a0c268804d39659d Edward Thomson <ethomson@edwardthomson.com> 1355169706 -0600 checkout: moving from a765fb87eb2f7a1920b73b2d5a057f8f8476a42b to df_side1
+5dc1018e90b19654bee986b7a0c268804d39659d a765fb87eb2f7a1920b73b2d5a057f8f8476a42b Edward Thomson <ethomson@edwardthomson.com> 1355169715 -0600 checkout: moving from df_side1 to a765fb87eb2f7a1920b73b2d5a057f8f8476a42b^0
+a765fb87eb2f7a1920b73b2d5a057f8f8476a42b bc744705e1d8a019993cf88f62bc4020f1b80919 Edward Thomson <ethomson@edwardthomson.com> 1355169801 -0600 commit: df_side1
+bc744705e1d8a019993cf88f62bc4020f1b80919 bc744705e1d8a019993cf88f62bc4020f1b80919 Edward Thomson <ethomson@edwardthomson.com> 1355169822 -0600 checkout: moving from bc744705e1d8a019993cf88f62bc4020f1b80919 to df_side1
+bc744705e1d8a019993cf88f62bc4020f1b80919 fada9356aa3f74622327a3038ae9c6f92e1c5c1d Edward Thomson <ethomson@edwardthomson.com> 1355169826 -0600 checkout: moving from df_side1 to df_side2
+fada9356aa3f74622327a3038ae9c6f92e1c5c1d a765fb87eb2f7a1920b73b2d5a057f8f8476a42b Edward Thomson <ethomson@edwardthomson.com> 1355169866 -0600 checkout: moving from df_side2 to a765fb87eb2f7a1920b73b2d5a057f8f8476a42b^0
+a765fb87eb2f7a1920b73b2d5a057f8f8476a42b 95646149ab6b6ba6edc83cff678582538b457b2b Edward Thomson <ethomson@edwardthomson.com> 1355169897 -0600 rebase: df_side2
+95646149ab6b6ba6edc83cff678582538b457b2b 95646149ab6b6ba6edc83cff678582538b457b2b Edward Thomson <ethomson@edwardthomson.com> 1355169897 -0600 rebase finished: returning to refs/heads/df_side2
+95646149ab6b6ba6edc83cff678582538b457b2b bc744705e1d8a019993cf88f62bc4020f1b80919 Edward Thomson <ethomson@edwardthomson.com> 1355169949 -0600 checkout: moving from df_side2 to df_side1
+bc744705e1d8a019993cf88f62bc4020f1b80919 bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1355170046 -0600 checkout: moving from df_side1 to master
+bd593285fc7fe4ca18ccdbabf027f5d689101452 bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1355181639 -0600 checkout: moving from master to df_ancestor
+bd593285fc7fe4ca18ccdbabf027f5d689101452 2da538570bc1e5b2c3e855bf702f35248ad0735f Edward Thomson <ethomson@edwardthomson.com> 1355181673 -0600 commit: df_ancestor
+2da538570bc1e5b2c3e855bf702f35248ad0735f a7dbfcbfc1a60709cb80b5ca24539008456531d0 Edward Thomson <ethomson@edwardthomson.com> 1355181715 -0600 commit: df_side1
+a7dbfcbfc1a60709cb80b5ca24539008456531d0 a7dbfcbfc1a60709cb80b5ca24539008456531d0 Edward Thomson <ethomson@edwardthomson.com> 1355181743 -0600 checkout: moving from df_ancestor to df_ancestor
+a7dbfcbfc1a60709cb80b5ca24539008456531d0 9a301fbe6fada7dcb74fcd7c20269b5c743459a7 Edward Thomson <ethomson@edwardthomson.com> 1355181775 -0600 commit: df_side2
+9a301fbe6fada7dcb74fcd7c20269b5c743459a7 a7dbfcbfc1a60709cb80b5ca24539008456531d0 Edward Thomson <ethomson@edwardthomson.com> 1355181793 -0600 checkout: moving from df_ancestor to df_side1
+a7dbfcbfc1a60709cb80b5ca24539008456531d0 9a301fbe6fada7dcb74fcd7c20269b5c743459a7 Edward Thomson <ethomson@edwardthomson.com> 1355181797 -0600 checkout: moving from df_side1 to df_side2
+9a301fbe6fada7dcb74fcd7c20269b5c743459a7 9a301fbe6fada7dcb74fcd7c20269b5c743459a7 Edward Thomson <ethomson@edwardthomson.com> 1355182062 -0600 checkout: moving from df_side2 to df_ancestor
+9a301fbe6fada7dcb74fcd7c20269b5c743459a7 2da538570bc1e5b2c3e855bf702f35248ad0735f Edward Thomson <ethomson@edwardthomson.com> 1355182067 -0600 reset: moving to 2da538570bc1e5b2c3e855bf702f35248ad0735f
+2da538570bc1e5b2c3e855bf702f35248ad0735f 2da538570bc1e5b2c3e855bf702f35248ad0735f Edward Thomson <ethomson@edwardthomson.com> 1355182087 -0600 checkout: moving from df_ancestor to df_side2
+2da538570bc1e5b2c3e855bf702f35248ad0735f fc90237dc4891fa6c69827fc465632225e391618 Edward Thomson <ethomson@edwardthomson.com> 1355182104 -0600 commit: df_side2
+fc90237dc4891fa6c69827fc465632225e391618 a7dbfcbfc1a60709cb80b5ca24539008456531d0 Edward Thomson <ethomson@edwardthomson.com> 1355182111 -0600 checkout: moving from df_side2 to df_side1
+a7dbfcbfc1a60709cb80b5ca24539008456531d0 fc90237dc4891fa6c69827fc465632225e391618 Edward Thomson <ethomson@edwardthomson.com> 1355182115 -0600 checkout: moving from df_side1 to df_side2
+fc90237dc4891fa6c69827fc465632225e391618 bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1355182122 -0600 checkout: moving from df_side2 to master
+bd593285fc7fe4ca18ccdbabf027f5d689101452 d6cf6c7741b3316826af1314042550c97ded1d50 Edward Thomson <ethomson@edwardthomson.com> 1358997543 -0600 checkout: moving from master to unrelated
+d6cf6c7741b3316826af1314042550c97ded1d50 55b4e4687e7a0d9ca367016ed930f385d4022e6f Edward Thomson <ethomson@edwardthomson.com> 1358997664 -0600 commit: conflicting changes
+55b4e4687e7a0d9ca367016ed930f385d4022e6f bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1358997675 -0600 checkout: moving from unrelated to master
+bd593285fc7fe4ca18ccdbabf027f5d689101452 88e185910a15cd13bdf44854ad037f4842b03b29 Edward Thomson <ethomson@microsoft.com> 1365714471 -0500 checkout: moving from master to rename_conflict_ours
+88e185910a15cd13bdf44854ad037f4842b03b29 bef6e37b3ee632ba74159168836f382fed21d77d Edward Thomson <ethomson@microsoft.com> 1365714516 -0500 checkout: moving from rename_conflict_ours to bef6e37b3ee632ba74159168836f382fed21d77d
+bef6e37b3ee632ba74159168836f382fed21d77d 01f149e1b8f84bd8896aaff6d6b22af88459ded0 Edward Thomson <ethomson@microsoft.com> 1365714831 -0500 commit: rename ancestor
+0000000000000000000000000000000000000000 2392a2dacc9efb562b8635d6579fb458751c7c5b Edward Thomson <ethomson@microsoft.com> 1365714958 -0500 commit (initial): rename conflict ancestor
+2392a2dacc9efb562b8635d6579fb458751c7c5b 88e185910a15cd13bdf44854ad037f4842b03b29 Edward Thomson <ethomson@microsoft.com> 1365714980 -0500 checkout: moving from rename_conflict_ancestor to rename_conflict_ours
+88e185910a15cd13bdf44854ad037f4842b03b29 7c2c5228c9e90170d4a35e6558e47163daf092e5 Edward Thomson <ethomson@microsoft.com> 1365715250 -0500 commit: rename conflict ours
+7c2c5228c9e90170d4a35e6558e47163daf092e5 2f4024ce528d36d8670c289cce5a7963e625bb0c Edward Thomson <ethomson@microsoft.com> 1365715274 -0500 checkout: moving from rename_conflict_ours to rename_conflict_theirs
+2f4024ce528d36d8670c289cce5a7963e625bb0c 56a638b76b75e068590ac999c2f8621e7f3e264c Edward Thomson <ethomson@microsoft.com> 1365715362 -0500 commit: rename conflict theirs
+56a638b76b75e068590ac999c2f8621e7f3e264c 2392a2dacc9efb562b8635d6579fb458751c7c5b Edward Thomson <ethomson@microsoft.com> 1365715368 -0500 checkout: moving from rename_conflict_theirs to rename_conflict_ancestor
+2392a2dacc9efb562b8635d6579fb458751c7c5b 56a638b76b75e068590ac999c2f8621e7f3e264c Edward Thomson <ethomson@microsoft.com> 1365715371 -0500 checkout: moving from rename_conflict_ancestor to rename_conflict_theirs
+56a638b76b75e068590ac999c2f8621e7f3e264c 2392a2dacc9efb562b8635d6579fb458751c7c5b Edward Thomson <ethomson@microsoft.com> 1365715404 -0500 checkout: moving from rename_conflict_theirs to rename_conflict_ancestor
+2392a2dacc9efb562b8635d6579fb458751c7c5b 2392a2dacc9efb562b8635d6579fb458751c7c5b Edward Thomson <ethomson@microsoft.com> 1365715438 -0500 checkout: moving from rename_conflict_ancestor to rename_conflict_ours
+2392a2dacc9efb562b8635d6579fb458751c7c5b 2392a2dacc9efb562b8635d6579fb458751c7c5b Edward Thomson <ethomson@microsoft.com> 1365715480 -0500 checkout: moving from rename_conflict_ours to rename_conflict_ancestor
+2392a2dacc9efb562b8635d6579fb458751c7c5b 2392a2dacc9efb562b8635d6579fb458751c7c5b Edward Thomson <ethomson@microsoft.com> 1365715486 -0500 checkout: moving from rename_conflict_ancestor to rename_conflict_ours
+2392a2dacc9efb562b8635d6579fb458751c7c5b f3293571dcd708b6a3faf03818cd2844d000e198 Edward Thomson <ethomson@microsoft.com> 1365715538 -0500 commit: rename conflict ours
+f3293571dcd708b6a3faf03818cd2844d000e198 2392a2dacc9efb562b8635d6579fb458751c7c5b Edward Thomson <ethomson@microsoft.com> 1365715546 -0500 checkout: moving from rename_conflict_ours to rename_conflict_ancestor
+2392a2dacc9efb562b8635d6579fb458751c7c5b 2392a2dacc9efb562b8635d6579fb458751c7c5b Edward Thomson <ethomson@microsoft.com> 1365715550 -0500 checkout: moving from rename_conflict_ancestor to rename_conflict_thiers
+2392a2dacc9efb562b8635d6579fb458751c7c5b 2392a2dacc9efb562b8635d6579fb458751c7c5b Edward Thomson <ethomson@microsoft.com> 1365715554 -0500 checkout: moving from rename_conflict_thiers to rename_conflict_ancestor
+2392a2dacc9efb562b8635d6579fb458751c7c5b 2392a2dacc9efb562b8635d6579fb458751c7c5b Edward Thomson <ethomson@microsoft.com> 1365715557 -0500 checkout: moving from rename_conflict_ancestor to rename_conflict_theirs
+2392a2dacc9efb562b8635d6579fb458751c7c5b a802e06f1782a9645b9851bc7202cee74a8a4972 Edward Thomson <ethomson@microsoft.com> 1365715572 -0500 commit: rename conflict theirs
+a802e06f1782a9645b9851bc7202cee74a8a4972 bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@microsoft.com> 1365715620 -0500 checkout: moving from rename_conflict_theirs to master
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/branch
new file mode 100644
index 000000000..8b0acb702
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/branch
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1351563886 -0500 branch: Created from HEAD
+c607fc30883e335def28cd686b51f6cfa02b06ec 7cb63eed597130ba4abb87b3e544b85021905520 Edward Thomson <ethomson@edwardthomson.com> 1351563965 -0500 commit: branch
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/df_ancestor b/tests/resources/merge-resolve/.gitted/logs/refs/heads/df_ancestor
new file mode 100644
index 000000000..df7695a66
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/df_ancestor
@@ -0,0 +1,5 @@
+0000000000000000000000000000000000000000 bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1355181639 -0600 branch: Created from HEAD
+bd593285fc7fe4ca18ccdbabf027f5d689101452 2da538570bc1e5b2c3e855bf702f35248ad0735f Edward Thomson <ethomson@edwardthomson.com> 1355181673 -0600 commit: df_ancestor
+2da538570bc1e5b2c3e855bf702f35248ad0735f a7dbfcbfc1a60709cb80b5ca24539008456531d0 Edward Thomson <ethomson@edwardthomson.com> 1355181715 -0600 commit: df_side1
+a7dbfcbfc1a60709cb80b5ca24539008456531d0 9a301fbe6fada7dcb74fcd7c20269b5c743459a7 Edward Thomson <ethomson@edwardthomson.com> 1355181775 -0600 commit: df_side2
+9a301fbe6fada7dcb74fcd7c20269b5c743459a7 2da538570bc1e5b2c3e855bf702f35248ad0735f Edward Thomson <ethomson@edwardthomson.com> 1355182067 -0600 reset: moving to 2da538570bc1e5b2c3e855bf702f35248ad0735f
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/df_side1 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/df_side1
new file mode 100644
index 000000000..a504ad610
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/df_side1
@@ -0,0 +1,14 @@
+0000000000000000000000000000000000000000 bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1354574697 -0600 branch: Created from HEAD
+bd593285fc7fe4ca18ccdbabf027f5d689101452 d4207f77243500bec335ab477f9227fcdb1e271a Edward Thomson <ethomson@edwardthomson.com> 1354574962 -0600 commit: df_ancestor
+d4207f77243500bec335ab477f9227fcdb1e271a c94b27e41064c521120627e07e2035cca1d24ffa Edward Thomson <ethomson@edwardthomson.com> 1354575027 -0600 commit: df_side1
+c94b27e41064c521120627e07e2035cca1d24ffa a90bc3fb6f15181972a2959a921429efbd81a473 Edward Thomson <ethomson@edwardthomson.com> 1355017650 -0600 commit: df_added
+a90bc3fb6f15181972a2959a921429efbd81a473 005b6fcc8fec71d2550bef8462d169b3c26aa14b Edward Thomson <ethomson@edwardthomson.com> 1355017676 -0600 rebase -i (finish): refs/heads/df_side1 onto c94b27e
+005b6fcc8fec71d2550bef8462d169b3c26aa14b f8958bdf4d365a84a9a178b1f5f35ff1dacbd884 Edward Thomson <ethomson@edwardthomson.com> 1355017715 -0600 reset: moving to df_side2
+f8958bdf4d365a84a9a178b1f5f35ff1dacbd884 8c749d9968d4b10dcfb06c9f97d0e5d92d337071 Edward Thomson <ethomson@edwardthomson.com> 1355017744 -0600 commit: df_added
+8c749d9968d4b10dcfb06c9f97d0e5d92d337071 0204a84f822acbf6386b36d33f1f6bc68bbbf858 Edward Thomson <ethomson@edwardthomson.com> 1355017756 -0600 rebase -i (finish): refs/heads/df_side1 onto f8958bd
+0204a84f822acbf6386b36d33f1f6bc68bbbf858 005b6fcc8fec71d2550bef8462d169b3c26aa14b Edward Thomson <ethomson@edwardthomson.com> 1355017793 -0600 reset: moving to 005b6fcc8fec71d2550bef8462d169b3c26aa14b
+005b6fcc8fec71d2550bef8462d169b3c26aa14b 0204a84f822acbf6386b36d33f1f6bc68bbbf858 Edward Thomson <ethomson@edwardthomson.com> 1355017826 -0600 reset: moving to 0204a84
+005b6fcc8fec71d2550bef8462d169b3c26aa14b e8107f24196736b870a318a0e28f048e29f6feff Edward Thomson <ethomson@edwardthomson.com> 1355169065 -0600 commit: df_side1
+e8107f24196736b870a318a0e28f048e29f6feff 80a8fbb3abb1ba423d554e9630b8fc2e5698f86b Edward Thomson <ethomson@edwardthomson.com> 1355169084 -0600 rebase -i (finish): refs/heads/df_side1 onto 005b6fc
+80a8fbb3abb1ba423d554e9630b8fc2e5698f86b e65a9bb2af9f4c2d1c375dd0f8f8a46cf9c68812 Edward Thomson <ethomson@edwardthomson.com> 1355169419 -0600 commit: side1
+e65a9bb2af9f4c2d1c375dd0f8f8a46cf9c68812 5dc1018e90b19654bee986b7a0c268804d39659d Edward Thomson <ethomson@edwardthomson.com> 1355169435 -0600 rebase -i (finish): refs/heads/df_side1 onto 80a8fbb
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/df_side2 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/df_side2
new file mode 100644
index 000000000..27d833eda
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/df_side2
@@ -0,0 +1,9 @@
+0000000000000000000000000000000000000000 d4207f77243500bec335ab477f9227fcdb1e271a Edward Thomson <ethomson@edwardthomson.com> 1354575051 -0600 branch: Created from d4207f77243500bec335ab477f9227fcdb1e271a
+d4207f77243500bec335ab477f9227fcdb1e271a f8958bdf4d365a84a9a178b1f5f35ff1dacbd884 Edward Thomson <ethomson@edwardthomson.com> 1354575206 -0600 commit: df_side2
+0204a84f822acbf6386b36d33f1f6bc68bbbf858 944f5dd1a867cab4c2bbcb896493435cae1dcc1a Edward Thomson <ethomson@edwardthomson.com> 1355169174 -0600 commit: both
+944f5dd1a867cab4c2bbcb896493435cae1dcc1a 57079a46233ae2b6df62e9ade71c4948512abefb Edward Thomson <ethomson@edwardthomson.com> 1355169185 -0600 rebase -i (finish): refs/heads/df_side2 onto 0204a84
+57079a46233ae2b6df62e9ade71c4948512abefb 58e853f66699fd02629fd50bde08082bc005933a Edward Thomson <ethomson@edwardthomson.com> 1355169460 -0600 commit: side2
+58e853f66699fd02629fd50bde08082bc005933a fada9356aa3f74622327a3038ae9c6f92e1c5c1d Edward Thomson <ethomson@edwardthomson.com> 1355169471 -0600 rebase -i (finish): refs/heads/df_side2 onto 57079a4
+fada9356aa3f74622327a3038ae9c6f92e1c5c1d 95646149ab6b6ba6edc83cff678582538b457b2b Edward Thomson <ethomson@edwardthomson.com> 1355169897 -0600 rebase finished: refs/heads/df_side2 onto a765fb87eb2f7a1920b73b2d5a057f8f8476a42b
+0000000000000000000000000000000000000000 2da538570bc1e5b2c3e855bf702f35248ad0735f Edward Thomson <ethomson@edwardthomson.com> 1355182087 -0600 branch: Created from HEAD
+2da538570bc1e5b2c3e855bf702f35248ad0735f fc90237dc4891fa6c69827fc465632225e391618 Edward Thomson <ethomson@edwardthomson.com> 1355182104 -0600 commit: df_side2
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/ff_branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/ff_branch
new file mode 100644
index 000000000..c4706175d
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/ff_branch
@@ -0,0 +1,5 @@
+0000000000000000000000000000000000000000 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351605785 -0500 branch: Created from HEAD
+977c696519c5a3004c5f1d15d60c89dbeb8f235f 33d500f588fbbe65901d82b4e6b008e549064be0 Edward Thomson <ethomson@edwardthomson.com> 1351605830 -0500 commit: fastforward
+33d500f588fbbe65901d82b4e6b008e549064be0 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1351990202 -0500 reset: moving to c607fc30883e335def28cd686b51f6cfa02b06ec
+c607fc30883e335def28cd686b51f6cfa02b06ec bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1351990205 -0500 merge master: Fast-forward
+bd593285fc7fe4ca18ccdbabf027f5d689101452 fd89f8cffb663ac89095a0f9764902e93ceaca6a Edward Thomson <ethomson@edwardthomson.com> 1351990229 -0500 commit: fastforward
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/master b/tests/resources/merge-resolve/.gitted/logs/refs/heads/master
new file mode 100644
index 000000000..60475992a
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/master
@@ -0,0 +1,5 @@
+0000000000000000000000000000000000000000 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1351563869 -0500 commit (initial): initial
+c607fc30883e335def28cd686b51f6cfa02b06ec 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351564033 -0500 commit: master
+977c696519c5a3004c5f1d15d60c89dbeb8f235f 4e0d9401aee78eb345a8685a859d37c8c3c0bbed Edward Thomson <ethomson@edwardthomson.com> 1351875091 -0500 merge octo1 octo2 octo3 octo4: Merge made by the 'octopus' strategy.
+4e0d9401aee78eb345a8685a859d37c8c3c0bbed 54269b3f6ec3d7d4ede24dd350dd5d605495c3ae Edward Thomson <ethomson@edwardthomson.com> 1351875108 -0500 reset: moving to 54269b3f6ec3d7d4ede24dd350dd5d605495c3ae
+54269b3f6ec3d7d4ede24dd350dd5d605495c3ae 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351875584 -0500 reset: moving to 977c696519c5a3004c5f1d15d60c89dbeb8f235f
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo1 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo1
new file mode 100644
index 000000000..0b6c9214a
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo1
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351874933 -0500 branch: Created from HEAD
+977c696519c5a3004c5f1d15d60c89dbeb8f235f 16f825815cfd20a07a75c71554e82d8eede0b061 Edward Thomson <ethomson@edwardthomson.com> 1351874954 -0500 commit: octo1
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo2 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo2
new file mode 100644
index 000000000..5392a4f86
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo2
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351874960 -0500 branch: Created from HEAD
+977c696519c5a3004c5f1d15d60c89dbeb8f235f 158dc7bedb202f5b26502bf3574faa7f4238d56c Edward Thomson <ethomson@edwardthomson.com> 1351874974 -0500 commit: octo2
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo3 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo3
new file mode 100644
index 000000000..7db5617c8
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo3
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351874980 -0500 branch: Created from HEAD
+977c696519c5a3004c5f1d15d60c89dbeb8f235f 50ce7d7d01217679e26c55939eef119e0c93e272 Edward Thomson <ethomson@edwardthomson.com> 1351874998 -0500 commit: octo3
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo4 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo4
new file mode 100644
index 000000000..b0f9e42ef
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo4
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351875010 -0500 branch: Created from HEAD
+977c696519c5a3004c5f1d15d60c89dbeb8f235f 54269b3f6ec3d7d4ede24dd350dd5d605495c3ae Edward Thomson <ethomson@edwardthomson.com> 1351875023 -0500 commit: octo4
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo5 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo5
new file mode 100644
index 000000000..614563edf
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo5
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351875031 -0500 branch: Created from HEAD
+977c696519c5a3004c5f1d15d60c89dbeb8f235f e4f618a2c3ed0669308735727df5ebf2447f022f Edward Thomson <ethomson@edwardthomson.com> 1351875041 -0500 commit: octo5
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo6 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo6
new file mode 100644
index 000000000..4c812eacc
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/octo6
@@ -0,0 +1,3 @@
+0000000000000000000000000000000000000000 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson <ethomson@edwardthomson.com> 1351875046 -0500 branch: Created from HEAD
+977c696519c5a3004c5f1d15d60c89dbeb8f235f 4ca408a8c88655f7586a1b580be6fad138121e98 Edward Thomson <ethomson@edwardthomson.com> 1351875057 -0500 commit: octo5
+4ca408a8c88655f7586a1b580be6fad138121e98 b6f610aef53bd343e6c96227de874c66f00ee8e8 Edward Thomson <ethomson@edwardthomson.com> 1351875065 -0500 commit (amend): octo6
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/renames1 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/renames1
new file mode 100644
index 000000000..58a7e0565
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/renames1
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1353177745 -0600 branch: Created from c607fc30883e335def28cd686b51f6cfa02b06ec
+c607fc30883e335def28cd686b51f6cfa02b06ec 412b32fb66137366147f1801ecc962452757d48a Edward Thomson <ethomson@edwardthomson.com> 1353177886 -0600 commit: renames
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/renames2 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/renames2
new file mode 100644
index 000000000..5645ecee7
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/renames2
@@ -0,0 +1,3 @@
+0000000000000000000000000000000000000000 bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson <ethomson@edwardthomson.com> 1353794647 -0600 branch: Created from HEAD
+bd593285fc7fe4ca18ccdbabf027f5d689101452 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1353794677 -0600 reset: moving to c607fc30883e335def28cd686b51f6cfa02b06ec
+c607fc30883e335def28cd686b51f6cfa02b06ec ab40af3cb8a3ed2e2843e96d9aa7871336b94573 Edward Thomson <ethomson@edwardthomson.com> 1353794852 -0600 commit: renames2
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-10 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-10
new file mode 100644
index 000000000..b6bd247e7
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-10
@@ -0,0 +1,3 @@
+0000000000000000000000000000000000000000 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352100171 -0600 branch: Created from c607fc30883e335def28cd686b51f6cfa02b06ec
+c607fc30883e335def28cd686b51f6cfa02b06ec 53825f41ac8d640612f9423a2f03a69f3d96809a Edward Thomson <ethomson@edwardthomson.com> 1352100193 -0600 commit: trivial-10
+53825f41ac8d640612f9423a2f03a69f3d96809a 0ec5f433959cd46177f745903353efb5be08d151 Edward Thomson <ethomson@edwardthomson.com> 1352100223 -0600 commit: trivial-10
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-10-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-10-branch
new file mode 100644
index 000000000..14ce9e545
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-10-branch
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 53825f41ac8d640612f9423a2f03a69f3d96809a Edward Thomson <ethomson@edwardthomson.com> 1352100200 -0600 branch: Created from HEAD
+53825f41ac8d640612f9423a2f03a69f3d96809a 11f4f3c08b737f5fd896cbefa1425ee63b21b2fa Edward Thomson <ethomson@edwardthomson.com> 1352100211 -0600 commit: trivial-10-branch
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-11 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-11
new file mode 100644
index 000000000..3e6b77437
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-11
@@ -0,0 +1,3 @@
+0000000000000000000000000000000000000000 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352100930 -0600 branch: Created from c607fc30883e335def28cd686b51f6cfa02b06ec
+c607fc30883e335def28cd686b51f6cfa02b06ec 35632e43612c06a3ea924bfbacd48333da874c29 Edward Thomson <ethomson@edwardthomson.com> 1352100958 -0600 commit: trivial-11
+35632e43612c06a3ea924bfbacd48333da874c29 3168dca1a561889b045a6441909f4c56145e666d Edward Thomson <ethomson@edwardthomson.com> 1352100992 -0600 commit: trivial-11
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-11-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-11-branch
new file mode 100644
index 000000000..30d5ec7a3
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-11-branch
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 35632e43612c06a3ea924bfbacd48333da874c29 Edward Thomson <ethomson@edwardthomson.com> 1352100964 -0600 branch: Created from HEAD
+35632e43612c06a3ea924bfbacd48333da874c29 6718a45909532d1fcf5600d0877f7fe7e78f0b86 Edward Thomson <ethomson@edwardthomson.com> 1352100978 -0600 commit: trivial-11-branch
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-13 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-13
new file mode 100644
index 000000000..3a7302dea
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-13
@@ -0,0 +1,3 @@
+0000000000000000000000000000000000000000 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352100559 -0600 branch: Created from c607fc30883e335def28cd686b51f6cfa02b06ec
+c607fc30883e335def28cd686b51f6cfa02b06ec 8f4433f8593ddd65b7dd43dd4564d841f4d9c8aa Edward Thomson <ethomson@edwardthomson.com> 1352100589 -0600 commit: trivial-13
+8f4433f8593ddd65b7dd43dd4564d841f4d9c8aa a3fabece9eb8748da810e1e08266fef9b7136ad4 Edward Thomson <ethomson@edwardthomson.com> 1352100625 -0600 commit: trivial-13
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-13-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-13-branch
new file mode 100644
index 000000000..bb2604244
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-13-branch
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 8f4433f8593ddd65b7dd43dd4564d841f4d9c8aa Edward Thomson <ethomson@edwardthomson.com> 1352100604 -0600 branch: Created from HEAD
+8f4433f8593ddd65b7dd43dd4564d841f4d9c8aa 05f3c1a2a56ca95c3d2ef28dc9ddf32b5cd6c91c Edward Thomson <ethomson@edwardthomson.com> 1352100610 -0600 commit: trivial-13-branch
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-14 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-14
new file mode 100644
index 000000000..4b70d2898
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-14
@@ -0,0 +1,3 @@
+0000000000000000000000000000000000000000 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352101083 -0600 branch: Created from c607fc30883e335def28cd686b51f6cfa02b06ec
+c607fc30883e335def28cd686b51f6cfa02b06ec 596803b523203a4851c824c07366906f8353f4ad Edward Thomson <ethomson@edwardthomson.com> 1352101113 -0600 commit: trivial-14
+596803b523203a4851c824c07366906f8353f4ad 7e2d058d5fedf8329db44db4fac610d6b1a89159 Edward Thomson <ethomson@edwardthomson.com> 1352101141 -0600 commit: trivial-14
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-14-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-14-branch
new file mode 100644
index 000000000..8e491ca68
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-14-branch
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 596803b523203a4851c824c07366906f8353f4ad Edward Thomson <ethomson@edwardthomson.com> 1352101117 -0600 branch: Created from HEAD
+596803b523203a4851c824c07366906f8353f4ad 8187117062b750eed4f93fd7e899f17b52ce554d Edward Thomson <ethomson@edwardthomson.com> 1352101132 -0600 commit: trivial-14-branch
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-2alt b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-2alt
new file mode 100644
index 000000000..a2a28d401
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-2alt
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352091695 -0600 branch: Created from c607fc30883e335def28cd686b51f6cfa02b06ec
+c607fc30883e335def28cd686b51f6cfa02b06ec 566ab53c220a2eafc1212af1a024513230280ab9 Edward Thomson <ethomson@edwardthomson.com> 1352092452 -0600 commit: 2alt
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-2alt-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-2alt-branch
new file mode 100644
index 000000000..a0a48ae35
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-2alt-branch
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352092411 -0600 branch: Created from HEAD
+c607fc30883e335def28cd686b51f6cfa02b06ec c9174cef549ec94ecbc43ef03cdc775b4950becb Edward Thomson <ethomson@edwardthomson.com> 1352092434 -0600 commit: 2alt-branch
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-3alt b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-3alt
new file mode 100644
index 000000000..4374d3888
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-3alt
@@ -0,0 +1,3 @@
+566ab53c220a2eafc1212af1a024513230280ab9 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352094547 -0600 reset: moving to c607fc30883e335def28cd686b51f6cfa02b06ec
+c607fc30883e335def28cd686b51f6cfa02b06ec 5459c89aa0026d543ce8343bd89871bce543f9c2 Edward Thomson <ethomson@edwardthomson.com> 1352094580 -0600 commit: 3alt
+5459c89aa0026d543ce8343bd89871bce543f9c2 4c9fac0707f8d4195037ae5a681aa48626491541 Edward Thomson <ethomson@edwardthomson.com> 1352094610 -0600 commit: 3alt-branch
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-3alt-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-3alt-branch
new file mode 100644
index 000000000..7a2e6f822
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-3alt-branch
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352094594 -0600 branch: Created from c607fc30883e335def28cd686b51f6cfa02b06ec
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-4 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-4
new file mode 100644
index 000000000..3ee6d2503
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-4
@@ -0,0 +1,2 @@
+566ab53c220a2eafc1212af1a024513230280ab9 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352094764 -0600 reset: moving to c607fc30883e335def28cd686b51f6cfa02b06ec
+c607fc30883e335def28cd686b51f6cfa02b06ec cc3e3009134cb88014129fc8858d1101359e5e2f Edward Thomson <ethomson@edwardthomson.com> 1352094815 -0600 commit: trivial-4
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-4-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-4-branch
new file mode 100644
index 000000000..51f8a9290
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-4-branch
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352094830 -0600 branch: Created from c607fc30883e335def28cd686b51f6cfa02b06ec
+c607fc30883e335def28cd686b51f6cfa02b06ec 183310e30fb1499af8c619108ffea4d300b5e778 Edward Thomson <ethomson@edwardthomson.com> 1352094856 -0600 commit: trivial-4-branch
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-1 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-1
new file mode 100644
index 000000000..14497029a
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-1
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352096606 -0600 branch: Created from c607fc30883e335def28cd686b51f6cfa02b06ec
+c607fc30883e335def28cd686b51f6cfa02b06ec 4fe93c0ec83eb6305cbace3dace88ecee1b63cb6 Edward Thomson <ethomson@edwardthomson.com> 1352096643 -0600 commit: 5alt-1
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-1-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-1-branch
new file mode 100644
index 000000000..4cff83526
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-1-branch
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352096657 -0600 branch: Created from c607fc30883e335def28cd686b51f6cfa02b06ec
+c607fc30883e335def28cd686b51f6cfa02b06ec 478172cb2f5ff9b514bc9d04d3bd5ef5840cb3b2 Edward Thomson <ethomson@edwardthomson.com> 1352096689 -0600 commit: 5alt-1-branch
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-2 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-2
new file mode 100644
index 000000000..3ca077b29
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-2
@@ -0,0 +1,3 @@
+0000000000000000000000000000000000000000 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352096711 -0600 branch: Created from c607fc30883e335def28cd686b51f6cfa02b06ec
+c607fc30883e335def28cd686b51f6cfa02b06ec ebc09d0137cfb0c26697aed0109fb943ad906f3f Edward Thomson <ethomson@edwardthomson.com> 1352096764 -0600 commit: existing file
+ebc09d0137cfb0c26697aed0109fb943ad906f3f 3b47b031b3e55ae11e14a05260b1c3ffd6838d55 Edward Thomson <ethomson@edwardthomson.com> 1352096815 -0600 commit: 5alt-2
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-2-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-2-branch
new file mode 100644
index 000000000..e7bb901f2
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-5alt-2-branch
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 ebc09d0137cfb0c26697aed0109fb943ad906f3f Edward Thomson <ethomson@edwardthomson.com> 1352096833 -0600 branch: Created from ebc09d0
+ebc09d0137cfb0c26697aed0109fb943ad906f3f f48097eb340dc5a7cae55aabcf1faf4548aa821f Edward Thomson <ethomson@edwardthomson.com> 1352096855 -0600 commit: 5alt-2-branch
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-6 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-6
new file mode 100644
index 000000000..7c717a210
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-6
@@ -0,0 +1,3 @@
+0000000000000000000000000000000000000000 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352097371 -0600 branch: Created from c607fc30883e335def28cd686b51f6cfa02b06ec
+c607fc30883e335def28cd686b51f6cfa02b06ec f7c332bd4d4d4b777366cae4d24d1687477576bf Edward Thomson <ethomson@edwardthomson.com> 1352097389 -0600 commit: 6
+f7c332bd4d4d4b777366cae4d24d1687477576bf 99b4f7e4f24470fa06b980bc21f1095c2a9425c0 Edward Thomson <ethomson@edwardthomson.com> 1352097404 -0600 commit: trivial-6
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-6-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-6-branch
new file mode 100644
index 000000000..715f3ae1c
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-6-branch
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 f7c332bd4d4d4b777366cae4d24d1687477576bf Edward Thomson <ethomson@edwardthomson.com> 1352097414 -0600 branch: Created from f7c332bd4d4d4b777366cae4d24d1687477576bf
+f7c332bd4d4d4b777366cae4d24d1687477576bf a43150a738849c59376cf30bb2a68348a83c8f48 Edward Thomson <ethomson@edwardthomson.com> 1352097431 -0600 commit: 6-branch
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-7 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-7
new file mode 100644
index 000000000..a014f1722
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-7
@@ -0,0 +1,3 @@
+0000000000000000000000000000000000000000 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352099765 -0600 branch: Created from c607fc30883e335def28cd686b51f6cfa02b06ec
+c607fc30883e335def28cd686b51f6cfa02b06ec 092ce8682d7f3a2a3a769a6daca58950168ba5c4 Edward Thomson <ethomson@edwardthomson.com> 1352099790 -0600 commit: trivial-7
+092ce8682d7f3a2a3a769a6daca58950168ba5c4 d874671ef5b20184836cb983bb273e5280384d0b Edward Thomson <ethomson@edwardthomson.com> 1352099947 -0600 commit: trivial-7
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-7-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-7-branch
new file mode 100644
index 000000000..22331d78c
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-7-branch
@@ -0,0 +1,5 @@
+0000000000000000000000000000000000000000 092ce8682d7f3a2a3a769a6daca58950168ba5c4 Edward Thomson <ethomson@edwardthomson.com> 1352099799 -0600 branch: Created from HEAD
+092ce8682d7f3a2a3a769a6daca58950168ba5c4 73cbfdc4fe843169e5b2af8dcad03cbf3acf306c Edward Thomson <ethomson@edwardthomson.com> 1352099812 -0600 commit: trivial-7-branch
+73cbfdc4fe843169e5b2af8dcad03cbf3acf306c 092ce8682d7f3a2a3a769a6daca58950168ba5c4 Edward Thomson <ethomson@edwardthomson.com> 1352099874 -0600 reset: moving to 092ce8682d7f3a2a3a769a6daca58950168ba5c4
+092ce8682d7f3a2a3a769a6daca58950168ba5c4 009b9cab6fdac02915a88ecd078b7a792ed802d8 Edward Thomson <ethomson@edwardthomson.com> 1352099921 -0600 commit: removed in 7
+009b9cab6fdac02915a88ecd078b7a792ed802d8 5195a1b480f66691b667f10a9e41e70115a78351 Edward Thomson <ethomson@edwardthomson.com> 1352099927 -0600 commit (amend): trivial-7-branch
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-8 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-8
new file mode 100644
index 000000000..7670c3506
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-8
@@ -0,0 +1,3 @@
+0000000000000000000000000000000000000000 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352098816 -0600 branch: Created from c607fc30883e335def28cd686b51f6cfa02b06ec
+c607fc30883e335def28cd686b51f6cfa02b06ec 75a811bf6bc57694adb3fe604786f3a4efd1cd1b Edward Thomson <ethomson@edwardthomson.com> 1352098884 -0600 commit: trivial-8
+75a811bf6bc57694adb3fe604786f3a4efd1cd1b 3575826c96a975031d2c14368529cc5c4353a8fd Edward Thomson <ethomson@edwardthomson.com> 1352099000 -0600 commit: trivial-8
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-8-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-8-branch
new file mode 100644
index 000000000..c4d68edcf
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-8-branch
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 75a811bf6bc57694adb3fe604786f3a4efd1cd1b Edward Thomson <ethomson@edwardthomson.com> 1352098947 -0600 branch: Created from HEAD
+75a811bf6bc57694adb3fe604786f3a4efd1cd1b 52d8bc572af2b6d4ee0d5e62ed5d1fbad92210a9 Edward Thomson <ethomson@edwardthomson.com> 1352098979 -0600 commit: trivial-8-branch
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-9 b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-9
new file mode 100644
index 000000000..09a343bdb
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-9
@@ -0,0 +1,3 @@
+0000000000000000000000000000000000000000 c607fc30883e335def28cd686b51f6cfa02b06ec Edward Thomson <ethomson@edwardthomson.com> 1352100268 -0600 branch: Created from c607fc30883e335def28cd686b51f6cfa02b06ec
+c607fc30883e335def28cd686b51f6cfa02b06ec f0053b8060bb3f0be5cbcc3147a07ece26bf097e Edward Thomson <ethomson@edwardthomson.com> 1352100304 -0600 commit: trivial-9
+f0053b8060bb3f0be5cbcc3147a07ece26bf097e c35dee9bcc0e989f3b0c40f68372a9a51b6c4e6a Edward Thomson <ethomson@edwardthomson.com> 1352100333 -0600 commit: trivial-9
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-9-branch b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-9-branch
new file mode 100644
index 000000000..1b126fb7b
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/trivial-9-branch
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 f0053b8060bb3f0be5cbcc3147a07ece26bf097e Edward Thomson <ethomson@edwardthomson.com> 1352100310 -0600 branch: Created from HEAD
+f0053b8060bb3f0be5cbcc3147a07ece26bf097e 13d1be4ea52a6ced1d7a1d832f0ee3c399348e5e Edward Thomson <ethomson@edwardthomson.com> 1352100317 -0600 commit: trivial-9-branch
diff --git a/tests/resources/merge-resolve/.gitted/logs/refs/heads/unrelated b/tests/resources/merge-resolve/.gitted/logs/refs/heads/unrelated
new file mode 100644
index 000000000..a83ffc26a
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/logs/refs/heads/unrelated
@@ -0,0 +1 @@
+d6cf6c7741b3316826af1314042550c97ded1d50 55b4e4687e7a0d9ca367016ed930f385d4022e6f Edward Thomson <ethomson@edwardthomson.com> 1358997664 -0600 commit: conflicting changes
diff --git a/tests/resources/merge-resolve/.gitted/modules/submodule/HEAD b/tests/resources/merge-resolve/.gitted/modules/submodule/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/modules/submodule/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/merge-resolve/.gitted/modules/submodule/ORIG_HEAD b/tests/resources/merge-resolve/.gitted/modules/submodule/ORIG_HEAD
new file mode 100644
index 000000000..d1bfcf0f4
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/modules/submodule/ORIG_HEAD
@@ -0,0 +1 @@
+ae39c77c70cb6bad18bb471912460c4e1ba0f586
diff --git a/tests/resources/merge-resolve/.gitted/modules/submodule/config b/tests/resources/merge-resolve/.gitted/modules/submodule/config
new file mode 100644
index 000000000..575cc8599
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/modules/submodule/config
@@ -0,0 +1,15 @@
+[core]
+ repositoryformatversion = 0
+ filemode = false
+ bare = false
+ logallrefupdates = true
+ worktree = ../../../submodule
+ symlinks = false
+ ignorecase = true
+ hideDotFiles = dotGitOnly
+[remote "origin"]
+ url = c:/Temp/TestRepos/submodule
+ fetch = +refs/heads/*:refs/remotes/origin/*
+[branch "master"]
+ remote = origin
+ merge = refs/heads/master
diff --git a/tests/resources/merge-resolve/.gitted/modules/submodule/index b/tests/resources/merge-resolve/.gitted/modules/submodule/index
new file mode 100644
index 000000000..e948afb27
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/modules/submodule/index
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/modules/submodule/info/exclude b/tests/resources/merge-resolve/.gitted/modules/submodule/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/modules/submodule/info/exclude
@@ -0,0 +1,6 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
diff --git a/tests/resources/merge-resolve/.gitted/modules/submodule/objects/18/fae1354bba0a5f1e6a531f9988369142c24a9e b/tests/resources/merge-resolve/.gitted/modules/submodule/objects/18/fae1354bba0a5f1e6a531f9988369142c24a9e
new file mode 100644
index 000000000..fcf1c6381
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/modules/submodule/objects/18/fae1354bba0a5f1e6a531f9988369142c24a9e
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/modules/submodule/objects/29/7aa6cd028b3336c7802c7a6f49143da4e1602d b/tests/resources/merge-resolve/.gitted/modules/submodule/objects/29/7aa6cd028b3336c7802c7a6f49143da4e1602d
new file mode 100644
index 000000000..aa9fc5006
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/modules/submodule/objects/29/7aa6cd028b3336c7802c7a6f49143da4e1602d
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/modules/submodule/objects/38/6c80dc813b89d719797668f40c1be0a6efa996 b/tests/resources/merge-resolve/.gitted/modules/submodule/objects/38/6c80dc813b89d719797668f40c1be0a6efa996
new file mode 100644
index 000000000..bc9a32ebc
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/modules/submodule/objects/38/6c80dc813b89d719797668f40c1be0a6efa996
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/modules/submodule/objects/ab/435a147bae6d5906ecfd0916a570c4ab3eeea8 b/tests/resources/merge-resolve/.gitted/modules/submodule/objects/ab/435a147bae6d5906ecfd0916a570c4ab3eeea8
new file mode 100644
index 000000000..65a8d759f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/modules/submodule/objects/ab/435a147bae6d5906ecfd0916a570c4ab3eeea8
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/modules/submodule/objects/ad/16e0a7684ea95bf892980a2ee412293ae979cc b/tests/resources/merge-resolve/.gitted/modules/submodule/objects/ad/16e0a7684ea95bf892980a2ee412293ae979cc
new file mode 100644
index 000000000..49e1aafeb
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/modules/submodule/objects/ad/16e0a7684ea95bf892980a2ee412293ae979cc
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/modules/submodule/objects/ae/39c77c70cb6bad18bb471912460c4e1ba0f586 b/tests/resources/merge-resolve/.gitted/modules/submodule/objects/ae/39c77c70cb6bad18bb471912460c4e1ba0f586
new file mode 100644
index 000000000..6ceffdd4e
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/modules/submodule/objects/ae/39c77c70cb6bad18bb471912460c4e1ba0f586
@@ -0,0 +1,2 @@
+x¥ŽË !@=S hfù%Æx±†!ëÅ Æö]kðöò/{k©-âa] xW«s =,lÄP…‰
+Šë#g0KÈêIC¶© –ž–,5ù“1¡– ™9;aãlB«è=×>ô­|h}_{{õMŸe·?º¶þêuž¸·‹6˜Àšˆô"€Úí>:å„ʃ6^Õ¤±Kd \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/modules/submodule/objects/c2/0765f6e24e8bbb63a648d0d11d84da63170190 b/tests/resources/merge-resolve/.gitted/modules/submodule/objects/c2/0765f6e24e8bbb63a648d0d11d84da63170190
new file mode 100644
index 000000000..14781032f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/modules/submodule/objects/c2/0765f6e24e8bbb63a648d0d11d84da63170190
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/modules/submodule/objects/d3/d806a4bef96889117fd7ebac0e3cb5ec152932 b/tests/resources/merge-resolve/.gitted/modules/submodule/objects/d3/d806a4bef96889117fd7ebac0e3cb5ec152932
new file mode 100644
index 000000000..8df72a45c
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/modules/submodule/objects/d3/d806a4bef96889117fd7ebac0e3cb5ec152932
@@ -0,0 +1,3 @@
+x¥A
+Â0E]çse¦mÚDÜx½À$™Ò‚é@šâõgpóø¼ÅQs^+·§ZD€ÃÐ[¦a
+,c²G‰sBO#Û ãÀ¡v†ºhGúpIðZ4ïºÁUšý­{^cÑ]çz‰šo@½ÇŽ\çÎèM³-\å ó<BÖt¼æµÌ·ÈA› \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/modules/submodule/objects/f1/065ff5593604072837fecaad3e2e268cb0147b b/tests/resources/merge-resolve/.gitted/modules/submodule/objects/f1/065ff5593604072837fecaad3e2e268cb0147b
new file mode 100644
index 000000000..e0d73e1c2
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/modules/submodule/objects/f1/065ff5593604072837fecaad3e2e268cb0147b
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/modules/submodule/packed-refs b/tests/resources/merge-resolve/.gitted/modules/submodule/packed-refs
new file mode 100644
index 000000000..992c4eec6
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/modules/submodule/packed-refs
@@ -0,0 +1,3 @@
+# pack-refs with: peeled
+297aa6cd028b3336c7802c7a6f49143da4e1602d refs/remotes/origin/master
+ae39c77c70cb6bad18bb471912460c4e1ba0f586 refs/remotes/origin/submodule-branch
diff --git a/tests/resources/merge-resolve/.gitted/modules/submodule/refs/heads/master b/tests/resources/merge-resolve/.gitted/modules/submodule/refs/heads/master
new file mode 100644
index 000000000..fe282569b
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/modules/submodule/refs/heads/master
@@ -0,0 +1 @@
+297aa6cd028b3336c7802c7a6f49143da4e1602d
diff --git a/tests/resources/merge-resolve/.gitted/modules/submodule/refs/remotes/origin/HEAD b/tests/resources/merge-resolve/.gitted/modules/submodule/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..6efe28fff
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/modules/submodule/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+ref: refs/remotes/origin/master
diff --git a/tests/resources/merge-resolve/.gitted/objects/00/5b6fcc8fec71d2550bef8462d169b3c26aa14b b/tests/resources/merge-resolve/.gitted/objects/00/5b6fcc8fec71d2550bef8462d169b3c26aa14b
new file mode 100644
index 000000000..82a8da597
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/00/5b6fcc8fec71d2550bef8462d169b3c26aa14b
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/00/9b9cab6fdac02915a88ecd078b7a792ed802d8 b/tests/resources/merge-resolve/.gitted/objects/00/9b9cab6fdac02915a88ecd078b7a792ed802d8
new file mode 100644
index 000000000..f663a3c51
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/00/9b9cab6fdac02915a88ecd078b7a792ed802d8
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/00/c7d33f1ffa79d19c2272b370fcaeaadba49c08 b/tests/resources/merge-resolve/.gitted/objects/00/c7d33f1ffa79d19c2272b370fcaeaadba49c08
new file mode 100644
index 000000000..72698dc3d
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/00/c7d33f1ffa79d19c2272b370fcaeaadba49c08
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/01/f149e1b8f84bd8896aaff6d6b22af88459ded0 b/tests/resources/merge-resolve/.gitted/objects/01/f149e1b8f84bd8896aaff6d6b22af88459ded0
new file mode 100644
index 000000000..aa6336d3f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/01/f149e1b8f84bd8896aaff6d6b22af88459ded0
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/02/04a84f822acbf6386b36d33f1f6bc68bbbf858 b/tests/resources/merge-resolve/.gitted/objects/02/04a84f822acbf6386b36d33f1f6bc68bbbf858
new file mode 100644
index 000000000..2f0a0e1bb
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/02/04a84f822acbf6386b36d33f1f6bc68bbbf858
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/02/251f990ca8e92e7ae61d3426163fa821c64001 b/tests/resources/merge-resolve/.gitted/objects/02/251f990ca8e92e7ae61d3426163fa821c64001
new file mode 100644
index 000000000..d623117c5
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/02/251f990ca8e92e7ae61d3426163fa821c64001
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/03/21415405cb906c46869919af56d51dbbe5e85c b/tests/resources/merge-resolve/.gitted/objects/03/21415405cb906c46869919af56d51dbbe5e85c
new file mode 100644
index 000000000..277bdcff5
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/03/21415405cb906c46869919af56d51dbbe5e85c
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/03/2ebc5ab85d9553bb187d3cd40875ff23a63ed0 b/tests/resources/merge-resolve/.gitted/objects/03/2ebc5ab85d9553bb187d3cd40875ff23a63ed0
new file mode 100644
index 000000000..e5404d838
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/03/2ebc5ab85d9553bb187d3cd40875ff23a63ed0
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/03/b87706555accbf874ccd410dbda01e8e70a67f b/tests/resources/merge-resolve/.gitted/objects/03/b87706555accbf874ccd410dbda01e8e70a67f
new file mode 100644
index 000000000..0befcd735
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/03/b87706555accbf874ccd410dbda01e8e70a67f
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/03/dad1005e5d06d418f50b12e0bcd48ff2306a03 b/tests/resources/merge-resolve/.gitted/objects/03/dad1005e5d06d418f50b12e0bcd48ff2306a03
new file mode 100644
index 000000000..04011a2ce
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/03/dad1005e5d06d418f50b12e0bcd48ff2306a03
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/05/1ffd7901a442faf56b226161649074f15c7c47 b/tests/resources/merge-resolve/.gitted/objects/05/1ffd7901a442faf56b226161649074f15c7c47
new file mode 100644
index 000000000..65fa6894f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/05/1ffd7901a442faf56b226161649074f15c7c47
@@ -0,0 +1 @@
+x+)JMU06·`040031QH,-ÉÏM-JOMLÊIÕ+©(aH:,»:ÎCÉýúô:ÞË ²o>ZC'g$楧¦èfæé&%æ%g€5¬ÎqYôÂeÒZoÇÝÙkÚMíæ2­éÆÔ›X\’ZDPC~^ZNfrIf^:Xéõ›ZHÙŠž1O(_œ,'º× jvn~JfZ&Ä5†&ȚؽùÁ„ +gÆz¬Ÿ¥•4íú3Ž^¨¦¢ÔÜü2 ÜüI{•|þ¹÷ 2m»gÜ˾‹©É1ÖËåüŠ5Ó¿Ü,\“µ})TC)0XÀ¡vÿ‰ùzÖ›¦9–¤×Mü°úÕ…'6óbó#— \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/05/8541fc37114bfc1dddf6bd6bffc7fae5c2e6fe b/tests/resources/merge-resolve/.gitted/objects/05/8541fc37114bfc1dddf6bd6bffc7fae5c2e6fe
new file mode 100644
index 000000000..d79dc30ba
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/05/8541fc37114bfc1dddf6bd6bffc7fae5c2e6fe
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/05/f3c1a2a56ca95c3d2ef28dc9ddf32b5cd6c91c b/tests/resources/merge-resolve/.gitted/objects/05/f3c1a2a56ca95c3d2ef28dc9ddf32b5cd6c91c
new file mode 100644
index 000000000..7b4b152f3
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/05/f3c1a2a56ca95c3d2ef28dc9ddf32b5cd6c91c
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/07/a759da919f737221791d542f176ab49c88837f b/tests/resources/merge-resolve/.gitted/objects/07/a759da919f737221791d542f176ab49c88837f
new file mode 100644
index 000000000..a34b6c235
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/07/a759da919f737221791d542f176ab49c88837f
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/07/c514b04698e068892b31c8d352b85813b99c6e b/tests/resources/merge-resolve/.gitted/objects/07/c514b04698e068892b31c8d352b85813b99c6e
new file mode 100644
index 000000000..23ab92171
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/07/c514b04698e068892b31c8d352b85813b99c6e
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/09/055301463b7f2f8ee5d368f8ed5c0a40ad8515 b/tests/resources/merge-resolve/.gitted/objects/09/055301463b7f2f8ee5d368f8ed5c0a40ad8515
new file mode 100644
index 000000000..bf5b0fcc5
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/09/055301463b7f2f8ee5d368f8ed5c0a40ad8515
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/09/17bb159596aea4d295f4857da77e8f96b3c7dc b/tests/resources/merge-resolve/.gitted/objects/09/17bb159596aea4d295f4857da77e8f96b3c7dc
new file mode 100644
index 000000000..9fb640dd5
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/09/17bb159596aea4d295f4857da77e8f96b3c7dc
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/09/2ce8682d7f3a2a3a769a6daca58950168ba5c4 b/tests/resources/merge-resolve/.gitted/objects/09/2ce8682d7f3a2a3a769a6daca58950168ba5c4
new file mode 100644
index 000000000..b709cf461
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/09/2ce8682d7f3a2a3a769a6daca58950168ba5c4
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/09/3bebf072dd4bbba88833667d6ffe454df199e1 b/tests/resources/merge-resolve/.gitted/objects/09/3bebf072dd4bbba88833667d6ffe454df199e1
new file mode 100644
index 000000000..ae13207d7
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/09/3bebf072dd4bbba88833667d6ffe454df199e1
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/09/768bed22680cdb0859683fa9677ccc8d5a25c1 b/tests/resources/merge-resolve/.gitted/objects/09/768bed22680cdb0859683fa9677ccc8d5a25c1
new file mode 100644
index 000000000..5f4b4dab1
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/09/768bed22680cdb0859683fa9677ccc8d5a25c1
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/0a/75d9aac1dc84fb5aa51f7325c0ab53242ddef7 b/tests/resources/merge-resolve/.gitted/objects/0a/75d9aac1dc84fb5aa51f7325c0ab53242ddef7
new file mode 100644
index 000000000..d5377341a
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/0a/75d9aac1dc84fb5aa51f7325c0ab53242ddef7
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/0c/fd6c54ef6532d862408f562309dc9c74a401e8 b/tests/resources/merge-resolve/.gitted/objects/0c/fd6c54ef6532d862408f562309dc9c74a401e8
new file mode 100644
index 000000000..40f628f89
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/0c/fd6c54ef6532d862408f562309dc9c74a401e8
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/0d/52e3a556e189ba0948ae56780918011c1b167d b/tests/resources/merge-resolve/.gitted/objects/0d/52e3a556e189ba0948ae56780918011c1b167d
new file mode 100644
index 000000000..4b633e504
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/0d/52e3a556e189ba0948ae56780918011c1b167d
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/0d/872f8e871a30208305978ecbf9e66d864f1638 b/tests/resources/merge-resolve/.gitted/objects/0d/872f8e871a30208305978ecbf9e66d864f1638
new file mode 100644
index 000000000..4cbc18e84
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/0d/872f8e871a30208305978ecbf9e66d864f1638
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/0e/c5f433959cd46177f745903353efb5be08d151 b/tests/resources/merge-resolve/.gitted/objects/0e/c5f433959cd46177f745903353efb5be08d151
new file mode 100644
index 000000000..1bee56c14
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/0e/c5f433959cd46177f745903353efb5be08d151
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/0f/3fc5dddc8964b9ac1040d0e957f9eb02d9efb3 b/tests/resources/merge-resolve/.gitted/objects/0f/3fc5dddc8964b9ac1040d0e957f9eb02d9efb3
new file mode 100644
index 000000000..d0ca42dad
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/0f/3fc5dddc8964b9ac1040d0e957f9eb02d9efb3
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/11/aeee27ac45a8402c2fd5b875d66dd844e5df00 b/tests/resources/merge-resolve/.gitted/objects/11/aeee27ac45a8402c2fd5b875d66dd844e5df00
new file mode 100644
index 000000000..90e729f6d
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/11/aeee27ac45a8402c2fd5b875d66dd844e5df00
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/11/deab00b2d3a6f5a3073988ac050c2d7b6655e2 b/tests/resources/merge-resolve/.gitted/objects/11/deab00b2d3a6f5a3073988ac050c2d7b6655e2
new file mode 100644
index 000000000..857b23686
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/11/deab00b2d3a6f5a3073988ac050c2d7b6655e2
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/11/f4f3c08b737f5fd896cbefa1425ee63b21b2fa b/tests/resources/merge-resolve/.gitted/objects/11/f4f3c08b737f5fd896cbefa1425ee63b21b2fa
new file mode 100644
index 000000000..6555194cb
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/11/f4f3c08b737f5fd896cbefa1425ee63b21b2fa
@@ -0,0 +1 @@
+x¥ŽQ Dýæ\ fw)cüñ^` Û´‰-Q¯/oàßÌ›Ìdb^×¥j²´«EDC²$†­u‚>Œ ¡÷,Ö z@Œ8¢’ºq‘­jk<Ù©GŽ>¹Òz2Lva2)¸VeÅ:ç¢ÏéÅ%éËœ×{ÞôAý¨“|ƒŸÛǼ5K@ˆº mg«ü9£jYž _;„n,¼ÅY½y²P” \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/13/d1be4ea52a6ced1d7a1d832f0ee3c399348e5e b/tests/resources/merge-resolve/.gitted/objects/13/d1be4ea52a6ced1d7a1d832f0ee3c399348e5e
new file mode 100644
index 000000000..4e4e175e8
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/13/d1be4ea52a6ced1d7a1d832f0ee3c399348e5e
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/14/39088f509b79b1535b64193137d3ce4b240734 b/tests/resources/merge-resolve/.gitted/objects/14/39088f509b79b1535b64193137d3ce4b240734
new file mode 100644
index 000000000..51ddf6dcb
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/14/39088f509b79b1535b64193137d3ce4b240734
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/15/8dc7bedb202f5b26502bf3574faa7f4238d56c b/tests/resources/merge-resolve/.gitted/objects/15/8dc7bedb202f5b26502bf3574faa7f4238d56c
new file mode 100644
index 000000000..064423d0c
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/15/8dc7bedb202f5b26502bf3574faa7f4238d56c
@@ -0,0 +1,2 @@
+x¥ŽK!D]sŠ¾€¦ùCbŒoà i2. b¼¾£ñîªÞK*E½µÛep73UƒÓ¾*NYYôI²Ô”)–j¼ŠL:8§<‹{¼NˆÞ“‹ÎÊH6iDC¶Ê"mqH!–Ì9T¥mé9—>àR^i¸.½=ú
+GÞè'ù+~í@½@j+ƒ7ÑØ£EÝÎNþsFtš]‰7bN) \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/16/f825815cfd20a07a75c71554e82d8eede0b061 b/tests/resources/merge-resolve/.gitted/objects/16/f825815cfd20a07a75c71554e82d8eede0b061
new file mode 100644
index 000000000..82d65253b
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/16/f825815cfd20a07a75c71554e82d8eede0b061
@@ -0,0 +1 @@
+x¥ŽK!D]sŠ¾€†šObŒoà Œ ƒ¯ïh¼»ª÷’Jqoí6A¹›£°JÙ€ˆ¬“T1h¢3§£·Î'LÕ ó.‰{eœc,a`ŠZJÃT1#e+Ù‡œJòUiª">çÒ\ò+Ž ×¥·G_áX6úIçò¿vàÞN€šÐ;ÈÀ^’”b£ÛÙYþœgGñ—MMµ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/17/8940b450f238a56c0d75b7955cb57b38191982 b/tests/resources/merge-resolve/.gitted/objects/17/8940b450f238a56c0d75b7955cb57b38191982
new file mode 100644
index 000000000..94e571e65
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/17/8940b450f238a56c0d75b7955cb57b38191982
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/18/3310e30fb1499af8c619108ffea4d300b5e778 b/tests/resources/merge-resolve/.gitted/objects/18/3310e30fb1499af8c619108ffea4d300b5e778
new file mode 100644
index 000000000..1c4010d04
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/18/3310e30fb1499af8c619108ffea4d300b5e778
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/18/cb316b1cefa0f8a6946f0e201a8e1a6f845ab9 b/tests/resources/merge-resolve/.gitted/objects/18/cb316b1cefa0f8a6946f0e201a8e1a6f845ab9
new file mode 100644
index 000000000..30f3110f1
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/18/cb316b1cefa0f8a6946f0e201a8e1a6f845ab9
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/19/b7ac485269b672a101060894de3ba9c2a24dd1 b/tests/resources/merge-resolve/.gitted/objects/19/b7ac485269b672a101060894de3ba9c2a24dd1
new file mode 100644
index 000000000..e34ccb855
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/19/b7ac485269b672a101060894de3ba9c2a24dd1
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/1a/010b1c0f081b2e8901d55307a15c29ff30af0e b/tests/resources/merge-resolve/.gitted/objects/1a/010b1c0f081b2e8901d55307a15c29ff30af0e
new file mode 100644
index 000000000..6039df00e
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/1a/010b1c0f081b2e8901d55307a15c29ff30af0e
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/1c/51d885170f57a0c4e8c69ff6363d91a5b51f85 b/tests/resources/merge-resolve/.gitted/objects/1c/51d885170f57a0c4e8c69ff6363d91a5b51f85
new file mode 100644
index 000000000..9a21e26c0
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/1c/51d885170f57a0c4e8c69ff6363d91a5b51f85
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/1c/ff9ec6a47a537380dedfdd17c9e76d74259a2b b/tests/resources/merge-resolve/.gitted/objects/1c/ff9ec6a47a537380dedfdd17c9e76d74259a2b
new file mode 100644
index 000000000..30802bcec
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/1c/ff9ec6a47a537380dedfdd17c9e76d74259a2b
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/1e/4ff029aee68d0d69ef9eb6efa6cbf1ec732f99 b/tests/resources/merge-resolve/.gitted/objects/1e/4ff029aee68d0d69ef9eb6efa6cbf1ec732f99
new file mode 100644
index 000000000..5183b8360
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/1e/4ff029aee68d0d69ef9eb6efa6cbf1ec732f99
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/1f/81433e3161efbf250576c58fede7f6b836f3d3 b/tests/resources/merge-resolve/.gitted/objects/1f/81433e3161efbf250576c58fede7f6b836f3d3
new file mode 100644
index 000000000..970855675
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/1f/81433e3161efbf250576c58fede7f6b836f3d3
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/20/91d94c8bd3eb0835dc5220de5e8bb310fa1513 b/tests/resources/merge-resolve/.gitted/objects/20/91d94c8bd3eb0835dc5220de5e8bb310fa1513
new file mode 100644
index 000000000..a843890c0
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/20/91d94c8bd3eb0835dc5220de5e8bb310fa1513
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/21/671e290278286fb2ce4c63d01699b67adce331 b/tests/resources/merge-resolve/.gitted/objects/21/671e290278286fb2ce4c63d01699b67adce331
new file mode 100644
index 000000000..b656d0001
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/21/671e290278286fb2ce4c63d01699b67adce331
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/22/7792b52aaa0b238bea00ec7e509b02623f168c b/tests/resources/merge-resolve/.gitted/objects/22/7792b52aaa0b238bea00ec7e509b02623f168c
new file mode 100644
index 000000000..3bb19bb77
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/22/7792b52aaa0b238bea00ec7e509b02623f168c
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/23/3c0919c998ed110a4b6ff36f353aec8b713487 b/tests/resources/merge-resolve/.gitted/objects/23/3c0919c998ed110a4b6ff36f353aec8b713487
new file mode 100644
index 000000000..d0c8c9e1d
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/23/3c0919c998ed110a4b6ff36f353aec8b713487
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/23/92a2dacc9efb562b8635d6579fb458751c7c5b b/tests/resources/merge-resolve/.gitted/objects/23/92a2dacc9efb562b8635d6579fb458751c7c5b
new file mode 100644
index 000000000..86127a344
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/23/92a2dacc9efb562b8635d6579fb458751c7c5b
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/23/ed141a6ae1e798b2f721afedbe947c119111ba b/tests/resources/merge-resolve/.gitted/objects/23/ed141a6ae1e798b2f721afedbe947c119111ba
new file mode 100644
index 000000000..06dee3b23
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/23/ed141a6ae1e798b2f721afedbe947c119111ba
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/24/1a1005cd9b980732741b74385b891142bcba28 b/tests/resources/merge-resolve/.gitted/objects/24/1a1005cd9b980732741b74385b891142bcba28
new file mode 100644
index 000000000..9b65f666f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/24/1a1005cd9b980732741b74385b891142bcba28
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/24/2591eb280ee9eeb2ce63524b9a8b9bc4cb515d b/tests/resources/merge-resolve/.gitted/objects/24/2591eb280ee9eeb2ce63524b9a8b9bc4cb515d
new file mode 100644
index 000000000..74a01373f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/24/2591eb280ee9eeb2ce63524b9a8b9bc4cb515d
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/24/90b9f1a079420870027deefb49f51d6656cf74 b/tests/resources/merge-resolve/.gitted/objects/24/90b9f1a079420870027deefb49f51d6656cf74
new file mode 100644
index 000000000..60497caa5
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/24/90b9f1a079420870027deefb49f51d6656cf74
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/25/9d08ca43af9200e9ea9a098e44a5a350ebd9b3 b/tests/resources/merge-resolve/.gitted/objects/25/9d08ca43af9200e9ea9a098e44a5a350ebd9b3
new file mode 100644
index 000000000..2bae66998
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/25/9d08ca43af9200e9ea9a098e44a5a350ebd9b3
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/25/c40b7660c08c8fb581f770312f41b9b03119d1 b/tests/resources/merge-resolve/.gitted/objects/25/c40b7660c08c8fb581f770312f41b9b03119d1
new file mode 100644
index 000000000..185214727
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/25/c40b7660c08c8fb581f770312f41b9b03119d1
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/26/153a3ff3649b6c2bb652d3f06878c6e0a172f9 b/tests/resources/merge-resolve/.gitted/objects/26/153a3ff3649b6c2bb652d3f06878c6e0a172f9
new file mode 100644
index 000000000..4fcaa07e2
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/26/153a3ff3649b6c2bb652d3f06878c6e0a172f9
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/27/133da702ba3c60af2a01e96c2555ff4045d692 b/tests/resources/merge-resolve/.gitted/objects/27/133da702ba3c60af2a01e96c2555ff4045d692
new file mode 100644
index 000000000..08e61f844
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/27/133da702ba3c60af2a01e96c2555ff4045d692
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/27/4bbe983022fb4c02f8a2bf2ebe8da4fe130054 b/tests/resources/merge-resolve/.gitted/objects/27/4bbe983022fb4c02f8a2bf2ebe8da4fe130054
new file mode 100644
index 000000000..c7afad2a8
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/27/4bbe983022fb4c02f8a2bf2ebe8da4fe130054
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/2b/0de5dc27505dcdd83a75c8bf1fcd9462cd7add b/tests/resources/merge-resolve/.gitted/objects/2b/0de5dc27505dcdd83a75c8bf1fcd9462cd7add
new file mode 100644
index 000000000..a95f926f8
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/2b/0de5dc27505dcdd83a75c8bf1fcd9462cd7add
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/2b/5f1f181ee3b58ea751f5dd5d8f9b445520a136 b/tests/resources/merge-resolve/.gitted/objects/2b/5f1f181ee3b58ea751f5dd5d8f9b445520a136
new file mode 100644
index 000000000..d24231eda
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/2b/5f1f181ee3b58ea751f5dd5d8f9b445520a136
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/2b/d0a343aeef7a2cf0d158478966a6e587ff3863 b/tests/resources/merge-resolve/.gitted/objects/2b/d0a343aeef7a2cf0d158478966a6e587ff3863
new file mode 100644
index 000000000..d10ca636b
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/2b/d0a343aeef7a2cf0d158478966a6e587ff3863
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/2b/fdd7e1b6c6ae993f23dfe8e84a8e06a772fa2a b/tests/resources/merge-resolve/.gitted/objects/2b/fdd7e1b6c6ae993f23dfe8e84a8e06a772fa2a
new file mode 100644
index 000000000..c86edfb68
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/2b/fdd7e1b6c6ae993f23dfe8e84a8e06a772fa2a
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/2d/a538570bc1e5b2c3e855bf702f35248ad0735f b/tests/resources/merge-resolve/.gitted/objects/2d/a538570bc1e5b2c3e855bf702f35248ad0735f
new file mode 100644
index 000000000..83253f81c
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/2d/a538570bc1e5b2c3e855bf702f35248ad0735f
@@ -0,0 +1,2 @@
+x¥ŽK
+1D]ç¹€ÒùN"n¼{ét:Œ‹L$ñúŽâ ÜU½EQ«õ>¤¶~7:³L ðÙD [´±5—¬É‡,Øy2e®ÐTت@”¦z*.û(ë´Àç˜[——üžåunum‹<òF?éÌ_ñkjõ$•qNå'#÷àÄF·³ƒÿœ¹Üp!^Gëâ 9+Q. \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/2f/2e37b7ebbae467978610896ca3aafcdad2ee67 b/tests/resources/merge-resolve/.gitted/objects/2f/2e37b7ebbae467978610896ca3aafcdad2ee67
new file mode 100644
index 000000000..7adffb165
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/2f/2e37b7ebbae467978610896ca3aafcdad2ee67
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/2f/4024ce528d36d8670c289cce5a7963e625bb0c b/tests/resources/merge-resolve/.gitted/objects/2f/4024ce528d36d8670c289cce5a7963e625bb0c
new file mode 100644
index 000000000..0100fd70e
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/2f/4024ce528d36d8670c289cce5a7963e625bb0c
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/2f/56120107d680129a5d9791b521cb1e73a2ed31 b/tests/resources/merge-resolve/.gitted/objects/2f/56120107d680129a5d9791b521cb1e73a2ed31
new file mode 100644
index 000000000..1f5f597b9
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/2f/56120107d680129a5d9791b521cb1e73a2ed31
@@ -0,0 +1,3 @@
+xí[
+Â@ EýžUdR臸žy¤öBÌ”LJ·oÔnBð/pî#Ü"½Ðí>_|Å Ât@„
+“ñapg%hò•aãJYÁÕ®Aâ8Õ©í› fçà²ûN©ì¦¯4À;œ”h[º%cÍO¦ÆÎÕÑuJÑ÷çWÖyÎ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/2f/598248eeccfc27e5ca44d9d96383f6dfea7b16 b/tests/resources/merge-resolve/.gitted/objects/2f/598248eeccfc27e5ca44d9d96383f6dfea7b16
new file mode 100644
index 000000000..1d9f226e2
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/2f/598248eeccfc27e5ca44d9d96383f6dfea7b16
@@ -0,0 +1 @@
+x+)JMU067c040031QH,-ÉÏM-JOMLÊIÕ+©(aH:,»:ÎCÉýúô:ÞË ²o>ZC'g$楧¦èfæé&%æ%g€5¬ÎqYôÂeÒZoÇÝÙkÚMíæ2­éÆÔ›X\’ZDPC~^ZNfrIf^:Xéõ›ZHÙŠž1O(_œ,'º× jvQjn~Ä1–ÈÎÑ×3ßþzדôém9‹Wý¹ué]:¦$÷ßüI{•|þ¹÷ 2m»gÜ˾‹©Éý1ÖËåüŠ5Ó¿Ü,\“µ})TC)0PÀavý‰ùzÖ›¦9–¤×Mü°úÕ…'6óbˆ—¢ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/31/68dca1a561889b045a6441909f4c56145e666d b/tests/resources/merge-resolve/.gitted/objects/31/68dca1a561889b045a6441909f4c56145e666d
new file mode 100644
index 000000000..2de1c5a79
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/31/68dca1a561889b045a6441909f4c56145e666d
@@ -0,0 +1,2 @@
+x¥ŽQ
+Â0DýÎ)rJ²›MñÇxM²¥ÛHŒz}«xÿfÞƒaRY–¹ip´kUD $Ï1f¢Q2qê-Ó=Y£ëÁ3R7®²6äÄ¡·Œgàâ9e7 bæ¡w ‚âG›JÕçüâšõe*˽¬ú ý¤“|ůíSYŽÚ"5&Ðñƨng›ü9£ZŸ3_;kÕ¬dO¼ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/31/d5472536041a83d986829240bbbdc897c6f8a6 b/tests/resources/merge-resolve/.gitted/objects/31/d5472536041a83d986829240bbbdc897c6f8a6
new file mode 100644
index 000000000..5ec5acb59
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/31/d5472536041a83d986829240bbbdc897c6f8a6
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/32/21dd512b7e2dc4b5bd03046df6c81b2ab2070b b/tests/resources/merge-resolve/.gitted/objects/32/21dd512b7e2dc4b5bd03046df6c81b2ab2070b
new file mode 100644
index 000000000..d36138d79
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/32/21dd512b7e2dc4b5bd03046df6c81b2ab2070b
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/33/46d64325b39e5323733492cd55f808994a2475 b/tests/resources/merge-resolve/.gitted/objects/33/46d64325b39e5323733492cd55f808994a2475
new file mode 100644
index 000000000..11546cea4
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/33/46d64325b39e5323733492cd55f808994a2475
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/33/d500f588fbbe65901d82b4e6b008e549064be0 b/tests/resources/merge-resolve/.gitted/objects/33/d500f588fbbe65901d82b4e6b008e549064be0
new file mode 100644
index 000000000..061a031b6
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/33/d500f588fbbe65901d82b4e6b008e549064be0
@@ -0,0 +1,2 @@
+x¥ŽA E]s
+. hbŒoত.Z Åx}[ã Üýÿ~òó¤Îó£këü¡·œµCAŒž<:km`d”„Ì‘d,!:𦳤žÜòÒõ‚øÁ“„œP1ÉPò qHcc±HEñ«Oµé[zsKú>Õy­‹>çî隿ï¤ÎmŒŠú 6ºÉöüç*¼öRÛn¢>úåOÇ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/34/8dcd41e2b467991578e92bedd16971b877ef1e b/tests/resources/merge-resolve/.gitted/objects/34/8dcd41e2b467991578e92bedd16971b877ef1e
new file mode 100644
index 000000000..fd61b6ce5
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/34/8dcd41e2b467991578e92bedd16971b877ef1e
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/34/bfafff88eaf118402b44e6f3e2dbbf1a582b05 b/tests/resources/merge-resolve/.gitted/objects/34/bfafff88eaf118402b44e6f3e2dbbf1a582b05
new file mode 100644
index 000000000..c653cec50
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/34/bfafff88eaf118402b44e6f3e2dbbf1a582b05
@@ -0,0 +1 @@
+x•ŽKj1D½Ö)ú úµ>`Œ7¾A. µ$<`MŒ¯orlŠ¢ ¸÷m‚¶þ4G­›·&údVdƒ[j2ÖµËJÉâ›C«ÑŠgu_GuÒ%ÅÚ2:ƒ3XúزÅàQ‘'Ì"½æÜÊ;?wîïp®kým×¾ÑàƒÛü&îPf!¢ ð%QJ±Ö%:ëûCˆeœzâ½=6šÀ¯qˆ;iOè \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/35/0c6eb3010efc403a6bed682332635314e9ed58 b/tests/resources/merge-resolve/.gitted/objects/35/0c6eb3010efc403a6bed682332635314e9ed58
new file mode 100644
index 000000000..2eee60233
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/35/0c6eb3010efc403a6bed682332635314e9ed58
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/35/411bfb77cd2cc431f3a03a2b4976ed94b5d241 b/tests/resources/merge-resolve/.gitted/objects/35/411bfb77cd2cc431f3a03a2b4976ed94b5d241
new file mode 100644
index 000000000..ea024ccd9
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/35/411bfb77cd2cc431f3a03a2b4976ed94b5d241
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/35/4704d3613ad4228e4786fc76656b11e98236c4 b/tests/resources/merge-resolve/.gitted/objects/35/4704d3613ad4228e4786fc76656b11e98236c4
new file mode 100644
index 000000000..1dd13c44a
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/35/4704d3613ad4228e4786fc76656b11e98236c4
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/35/632e43612c06a3ea924bfbacd48333da874c29 b/tests/resources/merge-resolve/.gitted/objects/35/632e43612c06a3ea924bfbacd48333da874c29
new file mode 100644
index 000000000..be7684f19
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/35/632e43612c06a3ea924bfbacd48333da874c29
@@ -0,0 +1 @@
+x¥NË !õLÓÀšd–MŒñb60ÀÝă¨í‹Æ¼½^,ëº40;·iUFf+)›³ˆ1vòBÁ939fG–(ôDIݸʵA$s´è½k]’l|Lä{IgŠ™Ñ$‰Šm.NéÅ5Áy.ë½\a/]ý £|ÛƲ@[g4âä< Hˆª«ýl“?gT«ËsáË µzCøP˜ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/35/75826c96a975031d2c14368529cc5c4353a8fd b/tests/resources/merge-resolve/.gitted/objects/35/75826c96a975031d2c14368529cc5c4353a8fd
new file mode 100644
index 000000000..24e33bc41
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/35/75826c96a975031d2c14368529cc5c4353a8fd
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/36/219b49367146cb2e6a1555b5a9ebd4d0328495 b/tests/resources/merge-resolve/.gitted/objects/36/219b49367146cb2e6a1555b5a9ebd4d0328495
new file mode 100644
index 000000000..7f8044372
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/36/219b49367146cb2e6a1555b5a9ebd4d0328495
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/36/4bbe4ce80c7bd31e6307dce77d46e3e1759fb3 b/tests/resources/merge-resolve/.gitted/objects/36/4bbe4ce80c7bd31e6307dce77d46e3e1759fb3
new file mode 100644
index 000000000..90fd9651f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/36/4bbe4ce80c7bd31e6307dce77d46e3e1759fb3
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/37/48859b001c6e627e712a07951aee40afd19b41 b/tests/resources/merge-resolve/.gitted/objects/37/48859b001c6e627e712a07951aee40afd19b41
new file mode 100644
index 000000000..6a0c389e4
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/37/48859b001c6e627e712a07951aee40afd19b41
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/38/5c8a0f26ddf79e9041e15e17dc352ed2c4cced b/tests/resources/merge-resolve/.gitted/objects/38/5c8a0f26ddf79e9041e15e17dc352ed2c4cced
new file mode 100644
index 000000000..e95ff3a88
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/38/5c8a0f26ddf79e9041e15e17dc352ed2c4cced
@@ -0,0 +1,2 @@
+x-MK
+1 uS¼YÍRÄ…ñ™6C±…6Ò뛪ð¼okn÷ÇåYt àEpª iÅDûØCd§Éœ³dLõB+8ø%¨q‚±ƒk µÿú+Þe™Þ6¢ï‡©fHüBü·¯1J©ÕÓ4ùFà1l \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/3b/47b031b3e55ae11e14a05260b1c3ffd6838d55 b/tests/resources/merge-resolve/.gitted/objects/3b/47b031b3e55ae11e14a05260b1c3ffd6838d55
new file mode 100644
index 000000000..82086466f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/3b/47b031b3e55ae11e14a05260b1c3ffd6838d55
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/3b/bf0bf59b20df5d5fc58b9fc1dc07be637c301f b/tests/resources/merge-resolve/.gitted/objects/3b/bf0bf59b20df5d5fc58b9fc1dc07be637c301f
new file mode 100644
index 000000000..723a9ae4c
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/3b/bf0bf59b20df5d5fc58b9fc1dc07be637c301f
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/3e/f4d30382ca33fdeba9fda895a99e0891ba37aa b/tests/resources/merge-resolve/.gitted/objects/3e/f4d30382ca33fdeba9fda895a99e0891ba37aa
new file mode 100644
index 000000000..49ee15239
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/3e/f4d30382ca33fdeba9fda895a99e0891ba37aa
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/3e/f9bfe82f9635518ae89152322f3b46fd4ba25b b/tests/resources/merge-resolve/.gitted/objects/3e/f9bfe82f9635518ae89152322f3b46fd4ba25b
new file mode 100644
index 000000000..3b5998ca6
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/3e/f9bfe82f9635518ae89152322f3b46fd4ba25b
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/40/2784a46a4a3982294231594cbeb431f506d22c b/tests/resources/merge-resolve/.gitted/objects/40/2784a46a4a3982294231594cbeb431f506d22c
new file mode 100644
index 000000000..a17e05d0f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/40/2784a46a4a3982294231594cbeb431f506d22c
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/41/2b32fb66137366147f1801ecc962452757d48a b/tests/resources/merge-resolve/.gitted/objects/41/2b32fb66137366147f1801ecc962452757d48a
new file mode 100644
index 000000000..b183dd782
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/41/2b32fb66137366147f1801ecc962452757d48a
@@ -0,0 +1,2 @@
+x¥ŽK
+1D]ç¹€ÒIO>"n¼èt:Œ‹™‰x}£xwU¯ xÜj½um'»ë«ˆ.®Ì9»=y 6Ø$@T8ÌÀ‰&Lhf4êA«Ü»f¡0BŒ(ˆ.K±‘³>9S<›À +zö¥­ú’_´f}]ZÝÚ]eÐO:Ëwøµ·zÒš†‹ÞƒPƒÙ.Þ¨aNU6õÎOÖ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/42/18670ab81cc219a9f94befb5c5dad90ec52648 b/tests/resources/merge-resolve/.gitted/objects/42/18670ab81cc219a9f94befb5c5dad90ec52648
new file mode 100644
index 000000000..33ead6112
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/42/18670ab81cc219a9f94befb5c5dad90ec52648
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/43/aafd43bea779ec74317dc361f45ae3f532a505 b/tests/resources/merge-resolve/.gitted/objects/43/aafd43bea779ec74317dc361f45ae3f532a505
new file mode 100644
index 000000000..ac86823b6
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/43/aafd43bea779ec74317dc361f45ae3f532a505
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/43/c338656342227a3a3cd3aa85cbf784061f5425 b/tests/resources/merge-resolve/.gitted/objects/43/c338656342227a3a3cd3aa85cbf784061f5425
new file mode 100644
index 000000000..d9773118b
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/43/c338656342227a3a3cd3aa85cbf784061f5425
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/45/299c1ca5e07bba1fd90843056fb559f96b1f5a b/tests/resources/merge-resolve/.gitted/objects/45/299c1ca5e07bba1fd90843056fb559f96b1f5a
new file mode 100644
index 000000000..2093b4410
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/45/299c1ca5e07bba1fd90843056fb559f96b1f5a
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/46/6daf8552b891e5c22bc58c9d7fc1a2eb8f0289 b/tests/resources/merge-resolve/.gitted/objects/46/6daf8552b891e5c22bc58c9d7fc1a2eb8f0289
new file mode 100644
index 000000000..c39b53aa8
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/46/6daf8552b891e5c22bc58c9d7fc1a2eb8f0289
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/47/6dbb3e207313d1d8aaa120c6ad204bf1295e53 b/tests/resources/merge-resolve/.gitted/objects/47/6dbb3e207313d1d8aaa120c6ad204bf1295e53
new file mode 100644
index 000000000..3e5f66e55
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/47/6dbb3e207313d1d8aaa120c6ad204bf1295e53
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/47/8172cb2f5ff9b514bc9d04d3bd5ef5840cb3b2 b/tests/resources/merge-resolve/.gitted/objects/47/8172cb2f5ff9b514bc9d04d3bd5ef5840cb3b2
new file mode 100644
index 000000000..d9e250e66
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/47/8172cb2f5ff9b514bc9d04d3bd5ef5840cb3b2
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/49/130a28ef567af9a6a6104c38773fedfa5f9742 b/tests/resources/merge-resolve/.gitted/objects/49/130a28ef567af9a6a6104c38773fedfa5f9742
new file mode 100644
index 000000000..e2c49f5c4
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/49/130a28ef567af9a6a6104c38773fedfa5f9742
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/49/9df817155e4bdd3c6ee192a72c52f481818230 b/tests/resources/merge-resolve/.gitted/objects/49/9df817155e4bdd3c6ee192a72c52f481818230
new file mode 100644
index 000000000..9c7e471dd
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/49/9df817155e4bdd3c6ee192a72c52f481818230
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/49/fd9edac79d15c8fbfca2d481cbb900beba22a6 b/tests/resources/merge-resolve/.gitted/objects/49/fd9edac79d15c8fbfca2d481cbb900beba22a6
new file mode 100644
index 000000000..d808d9fd9
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/49/fd9edac79d15c8fbfca2d481cbb900beba22a6
@@ -0,0 +1,3 @@
+xUÁ
+Â0D=ç+æ¼èÅ
+‚…þÆf»5dIŽ~¼ÑEhße3Ìó©xœ¯—ÓÍÀ2?ž®‡Ø°Å$Éô%+¢"SëRAºÂWRîm Kýn¸ä\t XZ/µŸhôº¥ÈÝMƱߙìþg2©»ñj># \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/4a/9550ebcc97ce22b22f45af7b829bb030d003f5 b/tests/resources/merge-resolve/.gitted/objects/4a/9550ebcc97ce22b22f45af7b829bb030d003f5
new file mode 100644
index 000000000..6ec674adc
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/4a/9550ebcc97ce22b22f45af7b829bb030d003f5
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/4b/253da36a0ae8bfce63aeabd8c5b58429925594 b/tests/resources/merge-resolve/.gitted/objects/4b/253da36a0ae8bfce63aeabd8c5b58429925594
new file mode 100644
index 000000000..1a4072794
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/4b/253da36a0ae8bfce63aeabd8c5b58429925594
@@ -0,0 +1,2 @@
+x ÅA
+€0 @ÏûŠ¾AðAILm l¡¦ÿ×¹ŒæÔvGxÑ#ÿ6Ù3¬‚tW BØ6—§”ßÐ%´h” \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/4b/48deed3a433909bfd6b6ab3d4b91348b6af464 b/tests/resources/merge-resolve/.gitted/objects/4b/48deed3a433909bfd6b6ab3d4b91348b6af464
new file mode 100644
index 000000000..328c8506e
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/4b/48deed3a433909bfd6b6ab3d4b91348b6af464
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 b/tests/resources/merge-resolve/.gitted/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904
new file mode 100644
index 000000000..adf64119a
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/4c/9fac0707f8d4195037ae5a681aa48626491541 b/tests/resources/merge-resolve/.gitted/objects/4c/9fac0707f8d4195037ae5a681aa48626491541
new file mode 100644
index 000000000..6b8c85e2b
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/4c/9fac0707f8d4195037ae5a681aa48626491541
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/4c/a408a8c88655f7586a1b580be6fad138121e98 b/tests/resources/merge-resolve/.gitted/objects/4c/a408a8c88655f7586a1b580be6fad138121e98
new file mode 100644
index 000000000..15cb7f29a
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/4c/a408a8c88655f7586a1b580be6fad138121e98
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/4e/0d9401aee78eb345a8685a859d37c8c3c0bbed b/tests/resources/merge-resolve/.gitted/objects/4e/0d9401aee78eb345a8685a859d37c8c3c0bbed
new file mode 100644
index 000000000..57f7eb68c
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/4e/0d9401aee78eb345a8685a859d37c8c3c0bbed
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/4e/886e602529caa9ab11d71f86634bd1b6e0de10 b/tests/resources/merge-resolve/.gitted/objects/4e/886e602529caa9ab11d71f86634bd1b6e0de10
new file mode 100644
index 000000000..53168a038
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/4e/886e602529caa9ab11d71f86634bd1b6e0de10
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/4e/b04c9e79e88f6640d01ff5b25ca2a60764f216 b/tests/resources/merge-resolve/.gitted/objects/4e/b04c9e79e88f6640d01ff5b25ca2a60764f216
new file mode 100644
index 000000000..f4ec0efec
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/4e/b04c9e79e88f6640d01ff5b25ca2a60764f216
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/4f/e93c0ec83eb6305cbace3dace88ecee1b63cb6 b/tests/resources/merge-resolve/.gitted/objects/4f/e93c0ec83eb6305cbace3dace88ecee1b63cb6
new file mode 100644
index 000000000..67dc6842f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/4f/e93c0ec83eb6305cbace3dace88ecee1b63cb6
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/50/12fd565b1393bdfda1805d4ec38ce6619e1fd1 b/tests/resources/merge-resolve/.gitted/objects/50/12fd565b1393bdfda1805d4ec38ce6619e1fd1
new file mode 100644
index 000000000..d629a23a1
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/50/12fd565b1393bdfda1805d4ec38ce6619e1fd1
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/50/4f75ac95a71ef98051817618576a68505b92f9 b/tests/resources/merge-resolve/.gitted/objects/50/4f75ac95a71ef98051817618576a68505b92f9
new file mode 100644
index 000000000..1b24c721a
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/50/4f75ac95a71ef98051817618576a68505b92f9
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/50/84fc2a88b6bdba8db93bd3953a8f4fdb470238 b/tests/resources/merge-resolve/.gitted/objects/50/84fc2a88b6bdba8db93bd3953a8f4fdb470238
new file mode 100644
index 000000000..84c9987ce
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/50/84fc2a88b6bdba8db93bd3953a8f4fdb470238
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/50/ce7d7d01217679e26c55939eef119e0c93e272 b/tests/resources/merge-resolve/.gitted/objects/50/ce7d7d01217679e26c55939eef119e0c93e272
new file mode 100644
index 000000000..e2f9f67fd
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/50/ce7d7d01217679e26c55939eef119e0c93e272
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/51/95a1b480f66691b667f10a9e41e70115a78351 b/tests/resources/merge-resolve/.gitted/objects/51/95a1b480f66691b667f10a9e41e70115a78351
new file mode 100644
index 000000000..088ee5498
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/51/95a1b480f66691b667f10a9e41e70115a78351
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/52/d8bc572af2b6d4ee0d5e62ed5d1fbad92210a9 b/tests/resources/merge-resolve/.gitted/objects/52/d8bc572af2b6d4ee0d5e62ed5d1fbad92210a9
new file mode 100644
index 000000000..6522209bd
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/52/d8bc572af2b6d4ee0d5e62ed5d1fbad92210a9
@@ -0,0 +1,3 @@
+x¥Ž]
+Â0„}Î)rÊnóÓD|ñ^`“ÝЂm$F½¾U¼o3ßä²,sÓ½Ã]«" #€ÇàÙbÈ"ö1± 9÷<Q7ª²6=8
+ˆ1û˜ÜàGKMv>²’cTôhS©úÌ/ª¬/SYîeÕÙè'ä+~mŸÊrÔh\c‡QwàÔF·³MþœQ­ÎÏ™®]èb¥5Mê ¨ÚRŸ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/53/825f41ac8d640612f9423a2f03a69f3d96809a b/tests/resources/merge-resolve/.gitted/objects/53/825f41ac8d640612f9423a2f03a69f3d96809a
new file mode 100644
index 000000000..08cb0b66f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/53/825f41ac8d640612f9423a2f03a69f3d96809a
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/54/269b3f6ec3d7d4ede24dd350dd5d605495c3ae b/tests/resources/merge-resolve/.gitted/objects/54/269b3f6ec3d7d4ede24dd350dd5d605495c3ae
new file mode 100644
index 000000000..4a2415339
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/54/269b3f6ec3d7d4ede24dd350dd5d605495c3ae
@@ -0,0 +1,2 @@
+x¥ŽK
+1D]ç¹€ÒI¦ó7ÞÀ LºÓŒ‹L$F¼¾£xwUïAQÔj½ m'Ø^Šv9 dÁ”-° £Æœ„ „\¼ äÀˆºÏ½¬C§È'&Î`"ÃÙÅĹä(Ö¡¨ù9–Öõ…_sg}]Z}´UËF?é\¾â×ÔêI‡&ëô@mt;;ÊŸ3ªÑh“z“ÎNÀ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/54/59c89aa0026d543ce8343bd89871bce543f9c2 b/tests/resources/merge-resolve/.gitted/objects/54/59c89aa0026d543ce8343bd89871bce543f9c2
new file mode 100644
index 000000000..178b833e8
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/54/59c89aa0026d543ce8343bd89871bce543f9c2
@@ -0,0 +1,3 @@
+x¥ŽÑ !Dý¦
+Ð,,,ãØÀ,9O blßÓØ3ï%“ÉmY.C[L›ÑEtd–`\ %aBH%TƒvB G%QphÐqªêÎ]nCg‚P3BŒ(ˆ¾Hµ1Š4yS)W;IVüsëúT^Ü‹>Ïmy´›ÞËJ?é(_ñk»Ü–ƒ6è-$ç#è-€ZézvÈŸ3
+ù:ÔNqMB \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/54/7607c690372fe81fab8e3bb44c530e129118fd b/tests/resources/merge-resolve/.gitted/objects/54/7607c690372fe81fab8e3bb44c530e129118fd
new file mode 100644
index 000000000..dccd22006
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/54/7607c690372fe81fab8e3bb44c530e129118fd
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/55/b4e4687e7a0d9ca367016ed930f385d4022e6f b/tests/resources/merge-resolve/.gitted/objects/55/b4e4687e7a0d9ca367016ed930f385d4022e6f
new file mode 100644
index 000000000..fb157a214
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/55/b4e4687e7a0d9ca367016ed930f385d4022e6f
@@ -0,0 +1 @@
+x¥ŽQjÃ0DûíSìZv¥ÕJ‚ú“ä²´Š µ•\¿Né ú7ó“û¶­Œã·±«‚Ôd-{á¹LìXçÀÖ'ÅŒ ÊçéžvmŠä*Ù{¦ÙZ’`$U²ÄÈÆ9ÌÑ-TNé{,}‡Ky¦½ÀuéÛ£78éA_éS‡¿ö‘ûv².ÄèEÞQ§ƒg‡þSsxZýZóXÛ ò’ÚMÓNRi \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/56/6ab53c220a2eafc1212af1a024513230280ab9 b/tests/resources/merge-resolve/.gitted/objects/56/6ab53c220a2eafc1212af1a024513230280ab9
new file mode 100644
index 000000000..a8855ae67
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/56/6ab53c220a2eafc1212af1a024513230280ab9
@@ -0,0 +1,3 @@
+x¥ŽÑ !Dý¦
+Ð,˱‰1þØ ,°äLD blßÓØ3ï%“I­ÖËÐhÃft Ù¡XvŽÄøÂäYÍ‚ñ`L2ÑМջ܆NsI¼·b­ËRЧLž¢3…RaÀ$Iñs,­ëS~qÏú¼´úh7½—•~ÒQ¾â×v©Õƒ6Ö!œê-€ZézvÈŸ3
+ù:Ô9âM& \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/56/a638b76b75e068590ac999c2f8621e7f3e264c b/tests/resources/merge-resolve/.gitted/objects/56/a638b76b75e068590ac999c2f8621e7f3e264c
new file mode 100644
index 000000000..36289bf7a
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/56/a638b76b75e068590ac999c2f8621e7f3e264c
@@ -0,0 +1 @@
+x¥ŽAj!E³öu vuW©B6sƒ¹€–%ÝÛÁ6äúqÎÝçÁ<iµp o£«BNŒ¼D'LâJfç ­„”¸ lŽ<Æ4“yÆ®ç<–Íâ&JèóÊÙ³³‚>È$Ñ^•‘R²bâÏØ[‡{þ=Ãcoõj'|褯õUéíje¼K«Ÿ°¬Ln¡•n–¬5“ÎСÿP˜Y«‚´³|2`ìzôËü—zQ{ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/57/079a46233ae2b6df62e9ade71c4948512abefb b/tests/resources/merge-resolve/.gitted/objects/57/079a46233ae2b6df62e9ade71c4948512abefb
new file mode 100644
index 000000000..c7eabc46b
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/57/079a46233ae2b6df62e9ade71c4948512abefb
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/58/43febcb23480df0b5edb22a21c59c772bb8e29 b/tests/resources/merge-resolve/.gitted/objects/58/43febcb23480df0b5edb22a21c59c772bb8e29
new file mode 100644
index 000000000..f6b2a2bfe
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/58/43febcb23480df0b5edb22a21c59c772bb8e29
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/58/87a5e516c53bd58efb0f02ec6aa031b6fe9ad7 b/tests/resources/merge-resolve/.gitted/objects/58/87a5e516c53bd58efb0f02ec6aa031b6fe9ad7
new file mode 100644
index 000000000..550d288d4
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/58/87a5e516c53bd58efb0f02ec6aa031b6fe9ad7
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/58/e853f66699fd02629fd50bde08082bc005933a b/tests/resources/merge-resolve/.gitted/objects/58/e853f66699fd02629fd50bde08082bc005933a
new file mode 100644
index 000000000..cf6db633c
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/58/e853f66699fd02629fd50bde08082bc005933a
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/59/6803b523203a4851c824c07366906f8353f4ad b/tests/resources/merge-resolve/.gitted/objects/59/6803b523203a4851c824c07366906f8353f4ad
new file mode 100644
index 000000000..cbc8cbef3
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/59/6803b523203a4851c824c07366906f8353f4ad
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/5c/2411f8075f48a6b2fdb85ebc0d371747c4df15 b/tests/resources/merge-resolve/.gitted/objects/5c/2411f8075f48a6b2fdb85ebc0d371747c4df15
new file mode 100644
index 000000000..7b41413da
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/5c/2411f8075f48a6b2fdb85ebc0d371747c4df15
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/5c/341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d b/tests/resources/merge-resolve/.gitted/objects/5c/341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d
new file mode 100644
index 000000000..63c86bd33
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/5c/341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/5c/3b68a71fc4fa5d362fd3875e53137c6a5ab7a5 b/tests/resources/merge-resolve/.gitted/objects/5c/3b68a71fc4fa5d362fd3875e53137c6a5ab7a5
new file mode 100644
index 000000000..541001456
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/5c/3b68a71fc4fa5d362fd3875e53137c6a5ab7a5
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/5d/c1018e90b19654bee986b7a0c268804d39659d b/tests/resources/merge-resolve/.gitted/objects/5d/c1018e90b19654bee986b7a0c268804d39659d
new file mode 100644
index 000000000..7500b9914
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/5d/c1018e90b19654bee986b7a0c268804d39659d
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/5d/dd0fe66f990dc0e5cf9fec6d9b465240e9537f b/tests/resources/merge-resolve/.gitted/objects/5d/dd0fe66f990dc0e5cf9fec6d9b465240e9537f
new file mode 100644
index 000000000..9d8691eb2
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/5d/dd0fe66f990dc0e5cf9fec6d9b465240e9537f
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/5e/b7bb6a146eb3c7fd3990b240a2308eceb1cf8d b/tests/resources/merge-resolve/.gitted/objects/5e/b7bb6a146eb3c7fd3990b240a2308eceb1cf8d
new file mode 100644
index 000000000..aca2666cf
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/5e/b7bb6a146eb3c7fd3990b240a2308eceb1cf8d
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/5f/bfbdc04b4eca46f54f4853a3c5a1dce28f5165 b/tests/resources/merge-resolve/.gitted/objects/5f/bfbdc04b4eca46f54f4853a3c5a1dce28f5165
new file mode 100644
index 000000000..aec3867c8
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/5f/bfbdc04b4eca46f54f4853a3c5a1dce28f5165
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/60/61fe116ecba0800c26113ea1a7dfac2e16eeaf b/tests/resources/merge-resolve/.gitted/objects/60/61fe116ecba0800c26113ea1a7dfac2e16eeaf
new file mode 100644
index 000000000..3f266f6df
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/60/61fe116ecba0800c26113ea1a7dfac2e16eeaf
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/60/91fc2c036a382a69489e3f518ee5aae9a4e567 b/tests/resources/merge-resolve/.gitted/objects/60/91fc2c036a382a69489e3f518ee5aae9a4e567
new file mode 100644
index 000000000..fa63afba1
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/60/91fc2c036a382a69489e3f518ee5aae9a4e567
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/61/340eeed7340fa6a8792def9a5938bb5d4434bb b/tests/resources/merge-resolve/.gitted/objects/61/340eeed7340fa6a8792def9a5938bb5d4434bb
new file mode 100644
index 000000000..e830cafe5
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/61/340eeed7340fa6a8792def9a5938bb5d4434bb
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/61/78885b38fe96e825ac0f492c0a941f288b37f6 b/tests/resources/merge-resolve/.gitted/objects/61/78885b38fe96e825ac0f492c0a941f288b37f6
new file mode 100644
index 000000000..bedc5f27e
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/61/78885b38fe96e825ac0f492c0a941f288b37f6
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/62/12c31dab5e482247d7977e4f0dd3601decf13b b/tests/resources/merge-resolve/.gitted/objects/62/12c31dab5e482247d7977e4f0dd3601decf13b
new file mode 100644
index 000000000..b6f0607bb
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/62/12c31dab5e482247d7977e4f0dd3601decf13b
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/62/269111c3b02a9355badcb9da8678b1bf41787b b/tests/resources/merge-resolve/.gitted/objects/62/269111c3b02a9355badcb9da8678b1bf41787b
new file mode 100644
index 000000000..0edf65994
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/62/269111c3b02a9355badcb9da8678b1bf41787b
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/62/33c6a0670228627f93c01cef32485a30403670 b/tests/resources/merge-resolve/.gitted/objects/62/33c6a0670228627f93c01cef32485a30403670
new file mode 100644
index 000000000..81428dd62
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/62/33c6a0670228627f93c01cef32485a30403670
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/62/c4f6533c9a3894191fdcb96a3be935ade63f1a b/tests/resources/merge-resolve/.gitted/objects/62/c4f6533c9a3894191fdcb96a3be935ade63f1a
new file mode 100644
index 000000000..c0f822d2c
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/62/c4f6533c9a3894191fdcb96a3be935ade63f1a
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/63/247125386de9ec90a27ad36169307bf8a11a38 b/tests/resources/merge-resolve/.gitted/objects/63/247125386de9ec90a27ad36169307bf8a11a38
new file mode 100644
index 000000000..bc2d7384d
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/63/247125386de9ec90a27ad36169307bf8a11a38
@@ -0,0 +1 @@
+xÝ;1 D©}Šé¶A‹Vâô\ÀÙ8¬¥àHIVp|²?‰‚ÐLãyO“ÃuN7C] Í¥ƒËlãt¦:iAÐ(xiŒp‚,ÆOñÄæ¡;•æo†7 …UYZ B‡½ß÷ý]ÆdUmÔyk©ô[…½Úc©ñþÍ¥)©ñ!êX{¢¿Zó±ö \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/67/110d77886b2af6309b9212961e72b8583e5fa9 b/tests/resources/merge-resolve/.gitted/objects/67/110d77886b2af6309b9212961e72b8583e5fa9
new file mode 100644
index 000000000..877bad703
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/67/110d77886b2af6309b9212961e72b8583e5fa9
@@ -0,0 +1 @@
+x¥=N1 „©÷î^rœ !J:.`'ûŠlP^×gâT3ú43Ò”Ñûuåp·¦*´Z£ %°ælÙÚ4irœÇH‰·žz,ê³¥ä[‰M]a“J©ÂÒbó5¤lÐ8OùX$XÕ³EaÇ")ŠUïœ$d2zO¸ñçÚÇ„—úųÂÛ>úmð¨'ýqÏýZ渶ÊèO`lF“³Oî1!n'=-ýÇÄöªó]A&e‡Ë¯^¶o––^Ý \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/67/18a45909532d1fcf5600d0877f7fe7e78f0b86 b/tests/resources/merge-resolve/.gitted/objects/67/18a45909532d1fcf5600d0877f7fe7e78f0b86
new file mode 100644
index 000000000..ffda698f0
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/67/18a45909532d1fcf5600d0877f7fe7e78f0b86
@@ -0,0 +1 @@
+x¥ŽÑM1 DùNi`‘ǹDBè~è€ǧ]‰Ý ƒöYð7óž4íû¾M8<ÌaæF䬵@Ò˜r*‹Ü85ÆV«±eV÷.Î鉋”0($!“b½UÑ35É—¨¡8¹ÏµÿÒ¾d4ÿºöý£þÉNú“®ö+þÚ£öýÙ#q@€rÉ~àNzžöÏ7Çö¹ÉÛ‚¸Ô!‡®î÷uQu \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/68/c6c84b091926c7d90aa6a79b2bc3bb6adccd8e b/tests/resources/merge-resolve/.gitted/objects/68/c6c84b091926c7d90aa6a79b2bc3bb6adccd8e
new file mode 100644
index 000000000..1e4b07574
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/68/c6c84b091926c7d90aa6a79b2bc3bb6adccd8e
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/69/f570c57b24ea7c086e94c5e574964798321435 b/tests/resources/merge-resolve/.gitted/objects/69/f570c57b24ea7c086e94c5e574964798321435
new file mode 100644
index 000000000..6975f0bab
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/69/f570c57b24ea7c086e94c5e574964798321435
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/6a/e1a3967031a42cf955d9d5c2395211ac82f6cf b/tests/resources/merge-resolve/.gitted/objects/6a/e1a3967031a42cf955d9d5c2395211ac82f6cf
new file mode 100644
index 000000000..3b5713cbc
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/6a/e1a3967031a42cf955d9d5c2395211ac82f6cf
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/6b/7e37be8ce0b897093f2878a9dcd8f396beda2c b/tests/resources/merge-resolve/.gitted/objects/6b/7e37be8ce0b897093f2878a9dcd8f396beda2c
new file mode 100644
index 000000000..c39318683
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/6b/7e37be8ce0b897093f2878a9dcd8f396beda2c
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/6c/06dcd163587c2cc18be44857e0b71116382aeb b/tests/resources/merge-resolve/.gitted/objects/6c/06dcd163587c2cc18be44857e0b71116382aeb
new file mode 100644
index 000000000..2f54be818
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/6c/06dcd163587c2cc18be44857e0b71116382aeb
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/6e/3b9eb35214d4e31ed5789afc7d520ac798ce55 b/tests/resources/merge-resolve/.gitted/objects/6e/3b9eb35214d4e31ed5789afc7d520ac798ce55
new file mode 100644
index 000000000..c6100cb01
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/6e/3b9eb35214d4e31ed5789afc7d520ac798ce55
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/6f/32739c3724d1d5f855299309f388606f407468 b/tests/resources/merge-resolve/.gitted/objects/6f/32739c3724d1d5f855299309f388606f407468
new file mode 100644
index 000000000..6741aa4d5
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/6f/32739c3724d1d5f855299309f388606f407468
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/6f/a33014764bf1120a454eb8437ae098238e409b b/tests/resources/merge-resolve/.gitted/objects/6f/a33014764bf1120a454eb8437ae098238e409b
new file mode 100644
index 000000000..973a4f646
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/6f/a33014764bf1120a454eb8437ae098238e409b
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/6f/be9fb85c86d7d1435f728da418bdff52c640a9 b/tests/resources/merge-resolve/.gitted/objects/6f/be9fb85c86d7d1435f728da418bdff52c640a9
new file mode 100644
index 000000000..a2c8d93ad
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/6f/be9fb85c86d7d1435f728da418bdff52c640a9
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/71/17467b18605a660ebe5586df69e2311ed5609f b/tests/resources/merge-resolve/.gitted/objects/71/17467b18605a660ebe5586df69e2311ed5609f
new file mode 100644
index 000000000..02e183144
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/71/17467b18605a660ebe5586df69e2311ed5609f
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/71/2ebba6669ea847d9829e4f1059d6c830c8b531 b/tests/resources/merge-resolve/.gitted/objects/71/2ebba6669ea847d9829e4f1059d6c830c8b531
new file mode 100644
index 000000000..dd7d58f1f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/71/2ebba6669ea847d9829e4f1059d6c830c8b531
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/71/add2d7b93d55bf3600f8a1582beceebbd050c8 b/tests/resources/merge-resolve/.gitted/objects/71/add2d7b93d55bf3600f8a1582beceebbd050c8
new file mode 100644
index 000000000..221afa3c8
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/71/add2d7b93d55bf3600f8a1582beceebbd050c8
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/72/cdb057b340205164478565e91eb71647e66891 b/tests/resources/merge-resolve/.gitted/objects/72/cdb057b340205164478565e91eb71647e66891
new file mode 100644
index 000000000..84aa8336b
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/72/cdb057b340205164478565e91eb71647e66891
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/72/ea499e108df5ff0a4a913e7655bbeeb1fb69f2 b/tests/resources/merge-resolve/.gitted/objects/72/ea499e108df5ff0a4a913e7655bbeeb1fb69f2
new file mode 100644
index 000000000..4886e492e
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/72/ea499e108df5ff0a4a913e7655bbeeb1fb69f2
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/74/df13f0793afdaa972150bba976f7de8284914e b/tests/resources/merge-resolve/.gitted/objects/74/df13f0793afdaa972150bba976f7de8284914e
new file mode 100644
index 000000000..cb50e6757
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/74/df13f0793afdaa972150bba976f7de8284914e
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/75/a811bf6bc57694adb3fe604786f3a4efd1cd1b b/tests/resources/merge-resolve/.gitted/objects/75/a811bf6bc57694adb3fe604786f3a4efd1cd1b
new file mode 100644
index 000000000..477fd87ec
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/75/a811bf6bc57694adb3fe604786f3a4efd1cd1b
@@ -0,0 +1,2 @@
+x¥ŽK
+1D]ç¹ÀHçk "n¼Hº;Ì€3‘õúŽâ ÜU½EQç©këý¦7 Ž€ !²7Xdc2±ÇR¬ƒ˜À©[j²tMv… :q.°‹Äc¦D* l†(¤Ò£µé3¿Rc}ë|¯‹>ÈJ?é$_ñk[ªóQ,ìÑë"€Zéz¶ËŸ3ª·é9¥ë€ê LŽOÊ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/76/63fce0130db092936b137cabd693ec234eb060 b/tests/resources/merge-resolve/.gitted/objects/76/63fce0130db092936b137cabd693ec234eb060
new file mode 100644
index 000000000..f578a4a68
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/76/63fce0130db092936b137cabd693ec234eb060
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/76/ab0e2868197ec158ddd6c78d8a0d2fd73d38f9 b/tests/resources/merge-resolve/.gitted/objects/76/ab0e2868197ec158ddd6c78d8a0d2fd73d38f9
new file mode 100644
index 000000000..4d41ad8cd
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/76/ab0e2868197ec158ddd6c78d8a0d2fd73d38f9
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/7a/a3edf2bcfee22398e6b55295aa56366b7aaf76 b/tests/resources/merge-resolve/.gitted/objects/7a/a3edf2bcfee22398e6b55295aa56366b7aaf76
new file mode 100644
index 000000000..09f1e4d3a
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/7a/a3edf2bcfee22398e6b55295aa56366b7aaf76
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/7a/f14d9c679baaef35555095f4f5d33e9a569ab9 b/tests/resources/merge-resolve/.gitted/objects/7a/f14d9c679baaef35555095f4f5d33e9a569ab9
new file mode 100644
index 000000000..b4c4ef734
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/7a/f14d9c679baaef35555095f4f5d33e9a569ab9
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/7c/04ca611203ed320c5f495b9813054dd23be3be b/tests/resources/merge-resolve/.gitted/objects/7c/04ca611203ed320c5f495b9813054dd23be3be
new file mode 100644
index 000000000..e3ba6056d
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/7c/04ca611203ed320c5f495b9813054dd23be3be
@@ -0,0 +1,2 @@
+x¥ŽQ Dýæ{ -,tcüñ^`%õƒb(ÆëKÏà|ͼd&k)¯³±§ÞDÀsžl¢è<fɇ4a¶“1BŒŽ8zs“­CvƒÅ˜„ºEëQüd¡˜ÅO>åEñ§¯µÁ#}¹%x®µìuƒ« z¸{yÅV÷šû%ÖrƒÉžÇ†·pÖ¨µtíòÇ„
+·¸ÎêUrL \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/7c/2c5228c9e90170d4a35e6558e47163daf092e5 b/tests/resources/merge-resolve/.gitted/objects/7c/2c5228c9e90170d4a35e6558e47163daf092e5
new file mode 100644
index 000000000..52fde92a1
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/7c/2c5228c9e90170d4a35e6558e47163daf092e5
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/7c/b63eed597130ba4abb87b3e544b85021905520 b/tests/resources/merge-resolve/.gitted/objects/7c/b63eed597130ba4abb87b3e544b85021905520
new file mode 100644
index 000000000..769f29c6e
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/7c/b63eed597130ba4abb87b3e544b85021905520
@@ -0,0 +1,3 @@
+x¥ŽK
+1D]ç¹€Òùt'"n¼H:ÆÅL$F¼¾£xwUïAQÜ–å6´õf7ºˆI“T*zë±J
+–1#;@r´X]¬êžº¬C3A¨ì F'Îa‘j#Š”ÑTâšÀf a•žcn]_Ê+õ¢¯s[mÕGÙè'å+~íÀm9iãÐ ¹‰PïÔF·³CþœQ¹§•gõe"NÕ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/7e/2d058d5fedf8329db44db4fac610d6b1a89159 b/tests/resources/merge-resolve/.gitted/objects/7e/2d058d5fedf8329db44db4fac610d6b1a89159
new file mode 100644
index 000000000..d12d7b4a7
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/7e/2d058d5fedf8329db44db4fac610d6b1a89159
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/7f/7a2da58126226986d71c6ddfab4afba693280d b/tests/resources/merge-resolve/.gitted/objects/7f/7a2da58126226986d71c6ddfab4afba693280d
new file mode 100644
index 000000000..2f833c292
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/7f/7a2da58126226986d71c6ddfab4afba693280d
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/80/a8fbb3abb1ba423d554e9630b8fc2e5698f86b b/tests/resources/merge-resolve/.gitted/objects/80/a8fbb3abb1ba423d554e9630b8fc2e5698f86b
new file mode 100644
index 000000000..3daf6c3e0
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/80/a8fbb3abb1ba423d554e9630b8fc2e5698f86b
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/81/1c70fcb6d5bbd022d04cc31836d30b436f9551 b/tests/resources/merge-resolve/.gitted/objects/81/1c70fcb6d5bbd022d04cc31836d30b436f9551
new file mode 100644
index 000000000..6d8702404
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/81/1c70fcb6d5bbd022d04cc31836d30b436f9551
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/81/87117062b750eed4f93fd7e899f17b52ce554d b/tests/resources/merge-resolve/.gitted/objects/81/87117062b750eed4f93fd7e899f17b52ce554d
new file mode 100644
index 000000000..19cac9faf
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/81/87117062b750eed4f93fd7e899f17b52ce554d
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/83/07d93a155903a5c49576583f0ce1f6ff897c0e b/tests/resources/merge-resolve/.gitted/objects/83/07d93a155903a5c49576583f0ce1f6ff897c0e
new file mode 100644
index 000000000..5a96a4e4e
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/83/07d93a155903a5c49576583f0ce1f6ff897c0e
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/83/6b8b82b26cab22eaaed8820877c76d6c8bca19 b/tests/resources/merge-resolve/.gitted/objects/83/6b8b82b26cab22eaaed8820877c76d6c8bca19
new file mode 100644
index 000000000..99f828649
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/83/6b8b82b26cab22eaaed8820877c76d6c8bca19
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/83/824a8c6658768e2013905219cc8c64cc3d9a2e b/tests/resources/merge-resolve/.gitted/objects/83/824a8c6658768e2013905219cc8c64cc3d9a2e
new file mode 100644
index 000000000..066190fb8
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/83/824a8c6658768e2013905219cc8c64cc3d9a2e
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/84/9619b03ae540acee4d1edec96b86993da6b497 b/tests/resources/merge-resolve/.gitted/objects/84/9619b03ae540acee4d1edec96b86993da6b497
new file mode 100644
index 000000000..67271ac50
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/84/9619b03ae540acee4d1edec96b86993da6b497
@@ -0,0 +1,3 @@
+x¥ŽK
+1D]ç¹€Òùv7ÞÀ½t’3‹L$ñúŽâ ÜU½E¥Vë<¤¶v7:³"xç¥K@›R¶
+rÌŠ#Ç"Ôy2[ Xµ5 r2ÆQ´ˆå¨5–”£bŠ=ÇÔº¼æõ,oS«k[ä‰7úIþŠ_;¤VÏRç”?ú`ä<€ØèvvðŸ3"¶1ÝóÜWñEÇP \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/84/de84f8f3a6d63e636ee9ad81f4b80512fa9bbe b/tests/resources/merge-resolve/.gitted/objects/84/de84f8f3a6d63e636ee9ad81f4b80512fa9bbe
new file mode 100644
index 000000000..32f1461d4
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/84/de84f8f3a6d63e636ee9ad81f4b80512fa9bbe
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/86/088dae8bade454995b21a1c88107b0e1accdab b/tests/resources/merge-resolve/.gitted/objects/86/088dae8bade454995b21a1c88107b0e1accdab
new file mode 100644
index 000000000..623a747f0
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/86/088dae8bade454995b21a1c88107b0e1accdab
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/87/b4926260d77a3b851e71ecce06839bd650b231 b/tests/resources/merge-resolve/.gitted/objects/87/b4926260d77a3b851e71ecce06839bd650b231
new file mode 100644
index 000000000..91944ffb5
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/87/b4926260d77a3b851e71ecce06839bd650b231
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/88/e185910a15cd13bdf44854ad037f4842b03b29 b/tests/resources/merge-resolve/.gitted/objects/88/e185910a15cd13bdf44854ad037f4842b03b29
new file mode 100644
index 000000000..ae1c5e242
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/88/e185910a15cd13bdf44854ad037f4842b03b29
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/8a/ad9d0ea334951da47b621a475b39cc6ed759bf b/tests/resources/merge-resolve/.gitted/objects/8a/ad9d0ea334951da47b621a475b39cc6ed759bf
new file mode 100644
index 000000000..5e2c94321
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/8a/ad9d0ea334951da47b621a475b39cc6ed759bf
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/8a/ae714f7d939309d7f132b30646d96743134a9f b/tests/resources/merge-resolve/.gitted/objects/8a/ae714f7d939309d7f132b30646d96743134a9f
new file mode 100644
index 000000000..34ff560e3
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/8a/ae714f7d939309d7f132b30646d96743134a9f
@@ -0,0 +1 @@
+x+)JMU06³`040031QH,-ÉÏM-JOMLÊIÕ+©(aH:,»:ÎCÉýúô:ÞË ²o>ZC'g$楧¦èfæé&%æ%g€5¬ÎqYôÂeÒZoÇÝÙkÚMíæ2­éÆÔ›X\’ZDPC~^ZNfrIf^:Xéõ›ZHÙŠž1O(_œ,'º× jv^j9È!Ɖ9%`¥<sBÞ§ÝHrèSæ¼3§d ã ¨Ò¢ÔÜü2 wßüI{•|þ¹÷ 2m»gÜ˾‹©ÉÝ1ÖËåüŠ5Ó¿Ü,\“µ})TC)00ÀavʉùzÖ›¦9–¤×Mü°úÕ…'6óbÊå’G \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/8b/095d8fd01594f4d14454d073e3ac57b9ce485f b/tests/resources/merge-resolve/.gitted/objects/8b/095d8fd01594f4d14454d073e3ac57b9ce485f
new file mode 100644
index 000000000..4ec013881
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/8b/095d8fd01594f4d14454d073e3ac57b9ce485f
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/8b/5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a b/tests/resources/merge-resolve/.gitted/objects/8b/5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a
new file mode 100644
index 000000000..f4249c23d
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/8b/5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a
@@ -0,0 +1 @@
+xí± À0 S{Š"2ŽŒd,0²‘^?&S¤HóÕÝŸ[Ï8ï눪E›`Ñ„ËrƒZŠ*êdŒ¥­ÆrlŒ,©± ÙcbF/ ·“¶÷'¿ûågв \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/8b/7cd60d49ce3a1a770ece43b7d29b5cf462a33a b/tests/resources/merge-resolve/.gitted/objects/8b/7cd60d49ce3a1a770ece43b7d29b5cf462a33a
new file mode 100644
index 000000000..790750c0f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/8b/7cd60d49ce3a1a770ece43b7d29b5cf462a33a
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/8b/fb012a6d809e499bd8d3e194a3929bc8995b93 b/tests/resources/merge-resolve/.gitted/objects/8b/fb012a6d809e499bd8d3e194a3929bc8995b93
new file mode 100644
index 000000000..a90ee08ce
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/8b/fb012a6d809e499bd8d3e194a3929bc8995b93
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/8c/749d9968d4b10dcfb06c9f97d0e5d92d337071 b/tests/resources/merge-resolve/.gitted/objects/8c/749d9968d4b10dcfb06c9f97d0e5d92d337071
new file mode 100644
index 000000000..e42393cf7
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/8c/749d9968d4b10dcfb06c9f97d0e5d92d337071
@@ -0,0 +1,2 @@
+x¥ŽAB!C]s
+. a€ùcÜx÷f`†|_ b¼¾h¼»ö5mšëº^»¶ÞmzÑÙL“`ð”}$26#"8°ÅÆ`s.`Ԛܺ.!bH\<» i´"Á,K¦œ8¯èÙ—Úô‰_ÔXŸ—º>êMïeÐ:Ê7ø¹]®ëAƒC40ÏÞë­™ŒQƒŽ³]þœQ\.Ä,¬ÞVO  \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/8f/4433f8593ddd65b7dd43dd4564d841f4d9c8aa b/tests/resources/merge-resolve/.gitted/objects/8f/4433f8593ddd65b7dd43dd4564d841f4d9c8aa
new file mode 100644
index 000000000..d2de777cc
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/8f/4433f8593ddd65b7dd43dd4564d841f4d9c8aa
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/90/a336c7dacbe295159413559b0043b8bdc60d57 b/tests/resources/merge-resolve/.gitted/objects/90/a336c7dacbe295159413559b0043b8bdc60d57
new file mode 100644
index 000000000..35453ebfd
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/90/a336c7dacbe295159413559b0043b8bdc60d57
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/91/2b2d7819cf9c1029e414883857ed61d597a1a5 b/tests/resources/merge-resolve/.gitted/objects/91/2b2d7819cf9c1029e414883857ed61d597a1a5
new file mode 100644
index 000000000..d5df393e9
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/91/2b2d7819cf9c1029e414883857ed61d597a1a5
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/91/8bb3e09090a9995d48af9a2a6296d7e6088d1c b/tests/resources/merge-resolve/.gitted/objects/91/8bb3e09090a9995d48af9a2a6296d7e6088d1c
new file mode 100644
index 000000000..c214ab206
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/91/8bb3e09090a9995d48af9a2a6296d7e6088d1c
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/91/f44111cb1cb1358ac6944ad356ca1738813ea1 b/tests/resources/merge-resolve/.gitted/objects/91/f44111cb1cb1358ac6944ad356ca1738813ea1
new file mode 100644
index 000000000..51a456f42
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/91/f44111cb1cb1358ac6944ad356ca1738813ea1
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/92/7d4943cdbdc9a667db8e62cfd0a41870235c51 b/tests/resources/merge-resolve/.gitted/objects/92/7d4943cdbdc9a667db8e62cfd0a41870235c51
new file mode 100644
index 000000000..b6b92c842
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/92/7d4943cdbdc9a667db8e62cfd0a41870235c51
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/93/77fccdb210540b8c0520cc6e80eb632c20bd25 b/tests/resources/merge-resolve/.gitted/objects/93/77fccdb210540b8c0520cc6e80eb632c20bd25
new file mode 100644
index 000000000..4b2d93b07
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/93/77fccdb210540b8c0520cc6e80eb632c20bd25
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/94/4f5dd1a867cab4c2bbcb896493435cae1dcc1a b/tests/resources/merge-resolve/.gitted/objects/94/4f5dd1a867cab4c2bbcb896493435cae1dcc1a
new file mode 100644
index 000000000..143093831
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/94/4f5dd1a867cab4c2bbcb896493435cae1dcc1a
@@ -0,0 +1,2 @@
+x¥ŽK!D]s
+. zšÄ7ÞÀ Ð|2. b¼¾h¼»ªWÉKÅVëmH0~7zÎÒ"¦PÈ9`ò:»ÀÑQôi)QÈLEyq=oC*P6-"4„l0StAŽHÌ<u$Âs¬­ËKz…žäumõÑ6yÌ“~Ò9‡_;ÄVORç4z½X¹W¨”˜tžùOà6Vññ4NF \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/94/8ba6e701c1edab0c2d394fb7c5538334129793 b/tests/resources/merge-resolve/.gitted/objects/94/8ba6e701c1edab0c2d394fb7c5538334129793
new file mode 100644
index 000000000..b3e3ef985
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/94/8ba6e701c1edab0c2d394fb7c5538334129793
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/95/646149ab6b6ba6edc83cff678582538b457b2b b/tests/resources/merge-resolve/.gitted/objects/95/646149ab6b6ba6edc83cff678582538b457b2b
new file mode 100644
index 000000000..de9ba2894
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/95/646149ab6b6ba6edc83cff678582538b457b2b
@@ -0,0 +1,3 @@
+x•ŽMÂ …]s
+. ™ÃObŒoàÞ eH»h1ãõEÓ ¸{?y_ÞX–enR}h•Y* üHFS
+€S ž!$À1…¨Å“*¯M’³˜£wUv4ôIt:ª„è²ÏÞ8KFEA¯6•*oéM5ÉûT–­¬òÌ=ýª+ÿŠÝƲ\ä Ñ CVÁˆžö³ÿÆà`ƒnLj”ÛœX‰iO\ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/95/9de65e568274120fdf9e3af9f77b1550122149 b/tests/resources/merge-resolve/.gitted/objects/95/9de65e568274120fdf9e3af9f77b1550122149
new file mode 100644
index 000000000..e998de849
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/95/9de65e568274120fdf9e3af9f77b1550122149
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/96/8ca794a4597f7f6abbb2b8d940b4078a0f3fd4 b/tests/resources/merge-resolve/.gitted/objects/96/8ca794a4597f7f6abbb2b8d940b4078a0f3fd4
new file mode 100644
index 000000000..359e43a88
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/96/8ca794a4597f7f6abbb2b8d940b4078a0f3fd4
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/96/bca8d4f05cc4c5e33e4389f80a1309e86fe054 b/tests/resources/merge-resolve/.gitted/objects/96/bca8d4f05cc4c5e33e4389f80a1309e86fe054
new file mode 100644
index 000000000..8938d3e56
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/96/bca8d4f05cc4c5e33e4389f80a1309e86fe054
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/97/7c696519c5a3004c5f1d15d60c89dbeb8f235f b/tests/resources/merge-resolve/.gitted/objects/97/7c696519c5a3004c5f1d15d60c89dbeb8f235f
new file mode 100644
index 000000000..e561b473f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/97/7c696519c5a3004c5f1d15d60c89dbeb8f235f
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/98/ba4205fcf31f5dd93c916d35fe3f3b3d0e6714 b/tests/resources/merge-resolve/.gitted/objects/98/ba4205fcf31f5dd93c916d35fe3f3b3d0e6714
new file mode 100644
index 000000000..6f5e97978
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/98/ba4205fcf31f5dd93c916d35fe3f3b3d0e6714
@@ -0,0 +1 @@
+x-ŒÁ Ã0 ûÖ ÐþºG% È…­"ë×Mú!@yÉjÂýñ¼½Šv¬j‚¢:ïAÜÁ‹ÂAÇèM~dú¹­ãÐ{.3Ñ);ÔlÂç]vi›ú6Á„D%þ «¯¦9fú|.z \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/98/d52d07c0b0bbf2b46548f6aa521295c2cb55db b/tests/resources/merge-resolve/.gitted/objects/98/d52d07c0b0bbf2b46548f6aa521295c2cb55db
new file mode 100644
index 000000000..c8d636e8b
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/98/d52d07c0b0bbf2b46548f6aa521295c2cb55db
@@ -0,0 +1,3 @@
+xíA
+€0=÷y„}NË®taÝJ[ï
+ú/^r“’´$ŒÓ<ô, ‡¨"1*[\™ †žYj Ñ‹(;Ôóm£9ƒ oNŒxcëz"1ï(»7„áy÷Û—.øõ®þ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/99/b4f7e4f24470fa06b980bc21f1095c2a9425c0 b/tests/resources/merge-resolve/.gitted/objects/99/b4f7e4f24470fa06b980bc21f1095c2a9425c0
new file mode 100644
index 000000000..01ad66eaa
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/99/b4f7e4f24470fa06b980bc21f1095c2a9425c0
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/9a/301fbe6fada7dcb74fcd7c20269b5c743459a7 b/tests/resources/merge-resolve/.gitted/objects/9a/301fbe6fada7dcb74fcd7c20269b5c743459a7
new file mode 100644
index 000000000..f413cc5f7
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/9a/301fbe6fada7dcb74fcd7c20269b5c743459a7
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/9a/f731fa116d1eb9a6c0109562472cfee6f5a979 b/tests/resources/merge-resolve/.gitted/objects/9a/f731fa116d1eb9a6c0109562472cfee6f5a979
new file mode 100644
index 000000000..53233c4f1
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/9a/f731fa116d1eb9a6c0109562472cfee6f5a979
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/9c/0b6c34ef379a42d858f03fef38630f476b9102 b/tests/resources/merge-resolve/.gitted/objects/9c/0b6c34ef379a42d858f03fef38630f476b9102
new file mode 100644
index 000000000..e6f850079
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/9c/0b6c34ef379a42d858f03fef38630f476b9102
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/9e/7f4359c469f309b6057febf4c6e80742cbed5b b/tests/resources/merge-resolve/.gitted/objects/9e/7f4359c469f309b6057febf4c6e80742cbed5b
new file mode 100644
index 000000000..72b7c49fc
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/9e/7f4359c469f309b6057febf4c6e80742cbed5b
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/9e/fe7723802d4305142eee177e018fee1572c4f4 b/tests/resources/merge-resolve/.gitted/objects/9e/fe7723802d4305142eee177e018fee1572c4f4
new file mode 100644
index 000000000..c63fc2c96
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/9e/fe7723802d4305142eee177e018fee1572c4f4
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/9f/74397a3397b3585faf09e9926b110d7f654254 b/tests/resources/merge-resolve/.gitted/objects/9f/74397a3397b3585faf09e9926b110d7f654254
new file mode 100644
index 000000000..e7ec3973a
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/9f/74397a3397b3585faf09e9926b110d7f654254
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/a0/31a28ae70e33a641ce4b8a8f6317f1ab79dee4 b/tests/resources/merge-resolve/.gitted/objects/a0/31a28ae70e33a641ce4b8a8f6317f1ab79dee4
new file mode 100644
index 000000000..a6c05d182
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/a0/31a28ae70e33a641ce4b8a8f6317f1ab79dee4
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/a3/9a620dae5bc8b4e771cd4d251b7d080401a21e b/tests/resources/merge-resolve/.gitted/objects/a3/9a620dae5bc8b4e771cd4d251b7d080401a21e
new file mode 100644
index 000000000..4d22586eb
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/a3/9a620dae5bc8b4e771cd4d251b7d080401a21e
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/a3/fabece9eb8748da810e1e08266fef9b7136ad4 b/tests/resources/merge-resolve/.gitted/objects/a3/fabece9eb8748da810e1e08266fef9b7136ad4
new file mode 100644
index 000000000..24d7dbc2e
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/a3/fabece9eb8748da810e1e08266fef9b7136ad4
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/a4/1b1bb6d0be3c22fb654234c33b428e15c8cc27 b/tests/resources/merge-resolve/.gitted/objects/a4/1b1bb6d0be3c22fb654234c33b428e15c8cc27
new file mode 100644
index 000000000..60789ee36
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/a4/1b1bb6d0be3c22fb654234c33b428e15c8cc27
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/a4/3150a738849c59376cf30bb2a68348a83c8f48 b/tests/resources/merge-resolve/.gitted/objects/a4/3150a738849c59376cf30bb2a68348a83c8f48
new file mode 100644
index 000000000..06ae09eb6
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/a4/3150a738849c59376cf30bb2a68348a83c8f48
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/a5/563304ddf6caba25cb50323a2ea6f7dbfcadca b/tests/resources/merge-resolve/.gitted/objects/a5/563304ddf6caba25cb50323a2ea6f7dbfcadca
new file mode 100644
index 000000000..a831878f8
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/a5/563304ddf6caba25cb50323a2ea6f7dbfcadca
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/a7/08b253bd507417ec42d1467a7fd2d7519c4956 b/tests/resources/merge-resolve/.gitted/objects/a7/08b253bd507417ec42d1467a7fd2d7519c4956
new file mode 100644
index 000000000..bae752a09
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/a7/08b253bd507417ec42d1467a7fd2d7519c4956
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/a7/65fb87eb2f7a1920b73b2d5a057f8f8476a42b b/tests/resources/merge-resolve/.gitted/objects/a7/65fb87eb2f7a1920b73b2d5a057f8f8476a42b
new file mode 100644
index 000000000..30abd8b4d
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/a7/65fb87eb2f7a1920b73b2d5a057f8f8476a42b
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/a7/7a56a49f8f3ae242e02717f18ebbc60c5cc543 b/tests/resources/merge-resolve/.gitted/objects/a7/7a56a49f8f3ae242e02717f18ebbc60c5cc543
new file mode 100644
index 000000000..76dd5f91b
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/a7/7a56a49f8f3ae242e02717f18ebbc60c5cc543
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/a7/dbfcbfc1a60709cb80b5ca24539008456531d0 b/tests/resources/merge-resolve/.gitted/objects/a7/dbfcbfc1a60709cb80b5ca24539008456531d0
new file mode 100644
index 000000000..67126c90b
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/a7/dbfcbfc1a60709cb80b5ca24539008456531d0
@@ -0,0 +1 @@
+x¥NË !õL4 a˜ÀÄ/vàÝð²{`1,ÆöEcÞÞ?/ÖR–.õ„»Þ˜%3$“ÁL15fe53'4Á2ÅÃ7^G1yBGV…LAGä Ù*‘ôä|R) ÿìsmòš^¾%y›kÙê*O<Ôºð×ø±C¬å,‰À’{e”Cg;ÿ9#R¾oKboœ³NÀ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/a8/02e06f1782a9645b9851bc7202cee74a8a4972 b/tests/resources/merge-resolve/.gitted/objects/a8/02e06f1782a9645b9851bc7202cee74a8a4972
new file mode 100644
index 000000000..d39034b82
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/a8/02e06f1782a9645b9851bc7202cee74a8a4972
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/a8/87dd39ad3edd610fc9083dcb61e40ab50673d1 b/tests/resources/merge-resolve/.gitted/objects/a8/87dd39ad3edd610fc9083dcb61e40ab50673d1
new file mode 100644
index 000000000..968c42ae2
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/a8/87dd39ad3edd610fc9083dcb61e40ab50673d1
@@ -0,0 +1 @@
+x+)JMU067a040031QH,-ÉÏM-JOMLÊIÕ+©(aH:,»:ÎCÉýúô:ÞË ²o>ZC'g$楧¦èfæé&%æ%g€5¬ÎqYôÂeÒZoÇÝÙkÚMíæ2­éÆÔ›X\’ZDPC~^ZNfrIf^:Xéõ›ZHÙŠž1O(_œ,'º× jv^j¹nb^ŠnJfZZjQj^ XÃû#3ƒ|>²^Uó:þ'äAÝÔ2¿†R¨†¢ÔÜü2 ×ßüI{•|þ¹÷ 2m»gÜ˾‹©Éõ1ÖËåüŠ5Ó¿Ü,\“µ})TC)0HÀ!vʉùzÖ›¦9–¤×Mü°úÕ…'6óbG–x \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/a9/0bc3fb6f15181972a2959a921429efbd81a473 b/tests/resources/merge-resolve/.gitted/objects/a9/0bc3fb6f15181972a2959a921429efbd81a473
new file mode 100644
index 000000000..91113ee8e
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/a9/0bc3fb6f15181972a2959a921429efbd81a473
@@ -0,0 +1,2 @@
+x¥ŽK
+1D]ç}¥;ÿ7ÞÀ½dÒ=Œ‹‰¯ooà®^UQT®¥\;hk6½‰@¦™Â‰gŒ §5r’èƒÑ“–Œ]uOMnòdgÄz›&ÒècÆ圈õ¨'•ž}­ NüJá¼Öò¨7ØËp?ê(ßàG»\ËÈ8‡¼CØ¢GTÃg»ü9£x¹$faõxN" \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/ab/40af3cb8a3ed2e2843e96d9aa7871336b94573 b/tests/resources/merge-resolve/.gitted/objects/ab/40af3cb8a3ed2e2843e96d9aa7871336b94573
new file mode 100644
index 000000000..7da1da656
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ab/40af3cb8a3ed2e2843e96d9aa7871336b94573
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/ab/6c44a2e84492ad4b41bb6bac87353e9d02ac8b b/tests/resources/merge-resolve/.gitted/objects/ab/6c44a2e84492ad4b41bb6bac87353e9d02ac8b
new file mode 100644
index 000000000..d840c1a57
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ab/6c44a2e84492ad4b41bb6bac87353e9d02ac8b
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/ab/929391ac42572f92110f3deeb4f0844a951e22 b/tests/resources/merge-resolve/.gitted/objects/ab/929391ac42572f92110f3deeb4f0844a951e22
new file mode 100644
index 000000000..8840d00c5
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ab/929391ac42572f92110f3deeb4f0844a951e22
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/ac/4045f965119e6998f4340ed0f411decfb3ec05 b/tests/resources/merge-resolve/.gitted/objects/ac/4045f965119e6998f4340ed0f411decfb3ec05
new file mode 100644
index 000000000..4c32d63f8
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ac/4045f965119e6998f4340ed0f411decfb3ec05
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/ad/01aebfdf2ac13145efafe3f9fcf798882f1730 b/tests/resources/merge-resolve/.gitted/objects/ad/01aebfdf2ac13145efafe3f9fcf798882f1730
new file mode 100644
index 000000000..ae3ef8ce3
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ad/01aebfdf2ac13145efafe3f9fcf798882f1730
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/ad/26b598134264fd284292cb233fc0b2f25851da b/tests/resources/merge-resolve/.gitted/objects/ad/26b598134264fd284292cb233fc0b2f25851da
new file mode 100644
index 000000000..5819a2e25
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ad/26b598134264fd284292cb233fc0b2f25851da
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/ad/a14492498136771f69dd451866cabcb0e9ef9a b/tests/resources/merge-resolve/.gitted/objects/ad/a14492498136771f69dd451866cabcb0e9ef9a
new file mode 100644
index 000000000..71023de39
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ad/a14492498136771f69dd451866cabcb0e9ef9a
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/ad/a55a45d14527dc3dfc714ea1c65d2e1e6fbe87 b/tests/resources/merge-resolve/.gitted/objects/ad/a55a45d14527dc3dfc714ea1c65d2e1e6fbe87
new file mode 100644
index 000000000..3091b8f3d
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ad/a55a45d14527dc3dfc714ea1c65d2e1e6fbe87
@@ -0,0 +1 @@
+x+)JMU067d040031QH,-ÉÏM-JOMLÊIÕ+©(aH:,»:ÎCÉýúô:ÞË ²o>ZC'g$楧¦èfæé&%æ%g€5¬ÎqYôÂeÒZoÇÝÙkÚMíæ2­éÆÔ›X\’ZDPC~^ZNfrIf^:Xéõ›ZHÙŠž1O(_œ,'º× jvn~JfZ&Ä5†Æ`ÕÆnלU7Ï V.6™t6ôÇL/•R¨ê¢ÔÜü2 §ßüI{•|þ¹÷ 2m»gÜ˾‹©Éé1ÖËåüŠ5Ó¿Ü,\“µ})TC)0<ÀÁvʉùzÖ›¦9–¤×Mü°úÕ…'6ób±N’* \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/b2/d399ae15224e1d58066e3c8df70ce37de7a656 b/tests/resources/merge-resolve/.gitted/objects/b2/d399ae15224e1d58066e3c8df70ce37de7a656
new file mode 100644
index 000000000..20fa838f2
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/b2/d399ae15224e1d58066e3c8df70ce37de7a656
@@ -0,0 +1,2 @@
+xíQA1ôÌ+x€ÙÄ‹‰Ï¡-kI*5”f¿/»zõîÁ af˜!¤Ö^/·“W¸Jcܤ5LŒÆ›‰;+ŠBŸ6ÎHZP|`îóh>\(óÙ$“sà´î•íX·@š¢75€}57¹K
+¯µ+= ;g—® @нÒ!¬4Úè!Œ,\$\ \Âb/±ÉHsø©#þa¾¼÷QÄß \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/b4/2712cfe99a1a500b2a51fe984e0b8a7702ba11 b/tests/resources/merge-resolve/.gitted/objects/b4/2712cfe99a1a500b2a51fe984e0b8a7702ba11
new file mode 100644
index 000000000..2820b46cc
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/b4/2712cfe99a1a500b2a51fe984e0b8a7702ba11
@@ -0,0 +1,5 @@
+xíA
+À {öû†Bé{M1  ¨¥ß¯>£PØËË–‹3ŽýÜFÖŽ7Á¥E02 <Z
+XÐĨJ
+ÃAˆ^«Ú
+ündim=p#‹Yz¿÷“?¼ \»š \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/b6/9fe837e4cecfd4c9a40cdca7c138468687df07 b/tests/resources/merge-resolve/.gitted/objects/b6/9fe837e4cecfd4c9a40cdca7c138468687df07
new file mode 100644
index 000000000..6dbcb05ea
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/b6/9fe837e4cecfd4c9a40cdca7c138468687df07
@@ -0,0 +1,2 @@
+xí’A
+1E»Î)þf7í}ÌŒ!‚QHzýZºí\|üOùU½âµ?Ñe!‹pJk<ÙM”oQ-¤ËQéÑàáI>†Ûý¶‹XŽ0üš¹Ì’ê,)ë$;:¯‚­Ü·îþÍÆ(óä: \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/b6/f610aef53bd343e6c96227de874c66f00ee8e8 b/tests/resources/merge-resolve/.gitted/objects/b6/f610aef53bd343e6c96227de874c66f00ee8e8
new file mode 100644
index 000000000..fb102f15d
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/b6/f610aef53bd343e6c96227de874c66f00ee8e8
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/b7/a2576f9fc20024ac9ef17cb134acbd1ac73127 b/tests/resources/merge-resolve/.gitted/objects/b7/a2576f9fc20024ac9ef17cb134acbd1ac73127
new file mode 100644
index 000000000..22f2d137d
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/b7/a2576f9fc20024ac9ef17cb134acbd1ac73127
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/b8/a3a806d3950e8c0a03a34f234a92eff0e2c68d b/tests/resources/merge-resolve/.gitted/objects/b8/a3a806d3950e8c0a03a34f234a92eff0e2c68d
new file mode 100644
index 000000000..24f029900
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/b8/a3a806d3950e8c0a03a34f234a92eff0e2c68d
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/ba/cac9b3493509aa15e1730e1545fc0919d1dae0 b/tests/resources/merge-resolve/.gitted/objects/ba/cac9b3493509aa15e1730e1545fc0919d1dae0
new file mode 100644
index 000000000..f35586f7f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ba/cac9b3493509aa15e1730e1545fc0919d1dae0
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/bc/744705e1d8a019993cf88f62bc4020f1b80919 b/tests/resources/merge-resolve/.gitted/objects/bc/744705e1d8a019993cf88f62bc4020f1b80919
new file mode 100644
index 000000000..0d4bdb323
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/bc/744705e1d8a019993cf88f62bc4020f1b80919
@@ -0,0 +1,2 @@
+x¥ŽK
+1D]ç}%ÿˆ¸ñî%t˜YÄH&âõâ ÜU½¢ŠJ­Öu€Ôj7:P(Ú#¥”¬ÌF§Ì„Ä£1”°Ù+kÙ#vºˆÎš‚ÞÊ⢒£S8«‘W|ñÚÙ¨%²øKëpɯØ3\—V·v‡#MúQgú?wH­ž@(c„ ž ØsË9›tžôç Ë嶭™{ŸkOó \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/bc/95c75d59386147d1e79a87c33068d8dbfd71f2 b/tests/resources/merge-resolve/.gitted/objects/bc/95c75d59386147d1e79a87c33068d8dbfd71f2
new file mode 100644
index 000000000..436d5a076
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/bc/95c75d59386147d1e79a87c33068d8dbfd71f2
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/bd/593285fc7fe4ca18ccdbabf027f5d689101452 b/tests/resources/merge-resolve/.gitted/objects/bd/593285fc7fe4ca18ccdbabf027f5d689101452
new file mode 100644
index 000000000..75ab1f0f3
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/bd/593285fc7fe4ca18ccdbabf027f5d689101452
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/bd/867fbae2faa80b920b002b80b1c91bcade7784 b/tests/resources/merge-resolve/.gitted/objects/bd/867fbae2faa80b920b002b80b1c91bcade7784
new file mode 100644
index 000000000..0f7421963
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/bd/867fbae2faa80b920b002b80b1c91bcade7784
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/bd/9cb4cd0a770cb9adcb5fce212142ef40ea1c35 b/tests/resources/merge-resolve/.gitted/objects/bd/9cb4cd0a770cb9adcb5fce212142ef40ea1c35
new file mode 100644
index 000000000..2aafdc64f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/bd/9cb4cd0a770cb9adcb5fce212142ef40ea1c35
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/be/f6e37b3ee632ba74159168836f382fed21d77d b/tests/resources/merge-resolve/.gitted/objects/be/f6e37b3ee632ba74159168836f382fed21d77d
new file mode 100644
index 000000000..6c243150d
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/be/f6e37b3ee632ba74159168836f382fed21d77d
@@ -0,0 +1,2 @@
+x¥K
+1D]ç}¥»3?AÄ7p?ô$=Œ`Ò‰x}GñîªÞƒª`)Ý+0Ñ®Uhú.N“WÆÞ“!ÆÐIdl¦™øØjë<ëb®ñ%%Âm±´Z†“nô“.ú¿v–Î@¾Cdjy€=ˆn£ÛyÕ?g\Ñ,IÇ`y~ÜC%]«÷½¿H& \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/c0/6a9be584ac49aa02c5551312d9e2982c91df10 b/tests/resources/merge-resolve/.gitted/objects/c0/6a9be584ac49aa02c5551312d9e2982c91df10
new file mode 100644
index 000000000..963ef23ac
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/c0/6a9be584ac49aa02c5551312d9e2982c91df10
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/c1/b17981db0840109a820dae8674ee29684134ff b/tests/resources/merge-resolve/.gitted/objects/c1/b17981db0840109a820dae8674ee29684134ff
new file mode 100644
index 000000000..fdcf28cb0
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/c1/b17981db0840109a820dae8674ee29684134ff
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/c1/b6a51bbb87c2f82b161412c3d20b59fc69b090 b/tests/resources/merge-resolve/.gitted/objects/c1/b6a51bbb87c2f82b161412c3d20b59fc69b090
new file mode 100644
index 000000000..3b369f8fe
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/c1/b6a51bbb87c2f82b161412c3d20b59fc69b090
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/c3/5dee9bcc0e989f3b0c40f68372a9a51b6c4e6a b/tests/resources/merge-resolve/.gitted/objects/c3/5dee9bcc0e989f3b0c40f68372a9a51b6c4e6a
new file mode 100644
index 000000000..d22b3b23c
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/c3/5dee9bcc0e989f3b0c40f68372a9a51b6c4e6a
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/c3/d02eeef75183df7584d8d13ac03053910c1301 b/tests/resources/merge-resolve/.gitted/objects/c3/d02eeef75183df7584d8d13ac03053910c1301
new file mode 100644
index 000000000..2294f018d
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/c3/d02eeef75183df7584d8d13ac03053910c1301
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/c4/efe31e9decccc8b2b4d3df9aac2cdfe2995618 b/tests/resources/merge-resolve/.gitted/objects/c4/efe31e9decccc8b2b4d3df9aac2cdfe2995618
new file mode 100644
index 000000000..c7572d5bc
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/c4/efe31e9decccc8b2b4d3df9aac2cdfe2995618
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/c5/0d0f1cb60b8b0fe1615ad20ace557e9d68d7bd b/tests/resources/merge-resolve/.gitted/objects/c5/0d0f1cb60b8b0fe1615ad20ace557e9d68d7bd
new file mode 100644
index 000000000..a1d5321e8
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/c5/0d0f1cb60b8b0fe1615ad20ace557e9d68d7bd
@@ -0,0 +1 @@
+x¥ŽKj1D³Ö)t˜–4ê–À˜l|ï>-f ‚¯9äÞU½‚â¥Vë6¤¶ô1:³Ì¸èè‰ !ÆüÀå>Z.¼P…0“x„Îû‘ ²¡h˜ÑèhQÖ+tÎ`1NÎZe¢,ÂÏX[—×ü =ËÛÚêÑvyæI_é‹ÿ†ÿvJ­^¤2 í$?ÁˆI§ìà7oÄ4•ï©íå{Kã>VÞú!~|”U= \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/c5/bbe550b9f09444bdddd3ecf3d97c0b42aa786c b/tests/resources/merge-resolve/.gitted/objects/c5/bbe550b9f09444bdddd3ecf3d97c0b42aa786c
new file mode 100644
index 000000000..2f2ada732
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/c5/bbe550b9f09444bdddd3ecf3d97c0b42aa786c
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/c6/07fc30883e335def28cd686b51f6cfa02b06ec b/tests/resources/merge-resolve/.gitted/objects/c6/07fc30883e335def28cd686b51f6cfa02b06ec
new file mode 100644
index 000000000..475b87ef9
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/c6/07fc30883e335def28cd686b51f6cfa02b06ec
@@ -0,0 +1,2 @@
+x¥Q
+1 Dýî)r%±›n "þx/m[°[¨¯ooàßÌ{0k)¹…i×›*`â£ZavJ>,‚aò¢ìf<EZÈÍÉȳ¯µÁ5½¤%¸­µ<ê'ô“.ú¿vˆµœ,;ë]€=2¢tœwýsÆä-÷,wóÊ8@° \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/c6/92ecf62007c0ac9fb26e2aa884de2933de15ed b/tests/resources/merge-resolve/.gitted/objects/c6/92ecf62007c0ac9fb26e2aa884de2933de15ed
new file mode 100644
index 000000000..ae430bd4a
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/c6/92ecf62007c0ac9fb26e2aa884de2933de15ed
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/c8/f06f2e3bb2964174677e91f0abead0e43c9e5d b/tests/resources/merge-resolve/.gitted/objects/c8/f06f2e3bb2964174677e91f0abead0e43c9e5d
new file mode 100644
index 000000000..5dae4c3ac
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/c8/f06f2e3bb2964174677e91f0abead0e43c9e5d
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/c9/174cef549ec94ecbc43ef03cdc775b4950becb b/tests/resources/merge-resolve/.gitted/objects/c9/174cef549ec94ecbc43ef03cdc775b4950becb
new file mode 100644
index 000000000..da8dba244
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/c9/174cef549ec94ecbc43ef03cdc775b4950becb
@@ -0,0 +1,2 @@
+x¥ŽQ
+Â0DýÎ)rÊf“nSñÇxífCÛHŒx}«xÿfÞÀc¤,˵Y ´kUÕbïò8‚pÔu`%—|@rä3GtBÀ™;W]›‚!‹‡½zß'Í%Q¤iÓdœ€T ?Û\ª=§×d/sYeµÝè'ô;üÚ^Êr´Î÷#l`6ºmú§Æ ßZ7U^e6oVòO´ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/c9/4b27e41064c521120627e07e2035cca1d24ffa b/tests/resources/merge-resolve/.gitted/objects/c9/4b27e41064c521120627e07e2035cca1d24ffa
new file mode 100644
index 000000000..fd1ec9fab
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/c9/4b27e41064c521120627e07e2035cca1d24ffa
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/ca/b2cf23998b40f1af2d9d9a756dc9e285a8df4b b/tests/resources/merge-resolve/.gitted/objects/ca/b2cf23998b40f1af2d9d9a756dc9e285a8df4b
new file mode 100644
index 000000000..32ba2aa53
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ca/b2cf23998b40f1af2d9d9a756dc9e285a8df4b
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/ca/ff6b7d44973f53e3e0cf31d0d695188b19aec6 b/tests/resources/merge-resolve/.gitted/objects/ca/ff6b7d44973f53e3e0cf31d0d695188b19aec6
new file mode 100644
index 000000000..6d0f60077
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ca/ff6b7d44973f53e3e0cf31d0d695188b19aec6
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/cb/491780d82e46dc88a065b965ab307a038f2bc2 b/tests/resources/merge-resolve/.gitted/objects/cb/491780d82e46dc88a065b965ab307a038f2bc2
new file mode 100644
index 000000000..cf9cd7d39
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/cb/491780d82e46dc88a065b965ab307a038f2bc2
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/cb/6693a788715b82440a54e0eacd19ba9f6ec559 b/tests/resources/merge-resolve/.gitted/objects/cb/6693a788715b82440a54e0eacd19ba9f6ec559
new file mode 100644
index 000000000..e11181a96
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/cb/6693a788715b82440a54e0eacd19ba9f6ec559
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/cc/338e4710c9b257106b8d16d82f86458d5beaf1 b/tests/resources/merge-resolve/.gitted/objects/cc/338e4710c9b257106b8d16d82f86458d5beaf1
new file mode 100644
index 000000000..85b3b8112
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/cc/338e4710c9b257106b8d16d82f86458d5beaf1
@@ -0,0 +1,2 @@
+x¥ŽK!]sŠ¾€†æ3`bŒoà »ÉÌ1 Æë‹gpW©E½G­Öm€±x]“ˆ˜Èù6d
+ûƒçeaŽÎ‰ç¢µz¥.ϬŽDv Ù[êhŽ¥äD³[´Jﱶwþ¤ÎðX[ÝÛ.2ínu£ÞöVƉZ½Ú³F´!x8ê8÷¦G‡ü‘PÂÛP_’?KN \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/cc/3e3009134cb88014129fc8858d1101359e5e2f b/tests/resources/merge-resolve/.gitted/objects/cc/3e3009134cb88014129fc8858d1101359e5e2f
new file mode 100644
index 000000000..9a0cb7a0c
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/cc/3e3009134cb88014129fc8858d1101359e5e2f
@@ -0,0 +1,2 @@
+x¥Ž]
+Â0„}Î)ö•Íß&_¼H“ -ØFbÔë[Åø6ó}0LªË2wPÆìzc†’­Ë*“sXbö‚ Rt”®#Gë$‰[l¼vH„®$ÞkÖÚf.ʧLžF+ ¥QHœD|ô©68çWl.S]îu…oô“Nü¿¶Ou9‚ÔVa0^ZÅF·³ÿœ½ÍÏ9^#Þ ØOd \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/ce/8860d49e3bea6fd745874a01b7c3e46da8cbc3 b/tests/resources/merge-resolve/.gitted/objects/ce/8860d49e3bea6fd745874a01b7c3e46da8cbc3
new file mode 100644
index 000000000..860f9952f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ce/8860d49e3bea6fd745874a01b7c3e46da8cbc3
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/ce/e656c392ad0557b3aae0fb411475c206e2926f b/tests/resources/merge-resolve/.gitted/objects/ce/e656c392ad0557b3aae0fb411475c206e2926f
new file mode 100644
index 000000000..ff0624ccb
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ce/e656c392ad0557b3aae0fb411475c206e2926f
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/cf/8c5cc8a85a1ff5a4ba51e0bc7cf5665669924d b/tests/resources/merge-resolve/.gitted/objects/cf/8c5cc8a85a1ff5a4ba51e0bc7cf5665669924d
new file mode 100644
index 000000000..36b0289e6
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/cf/8c5cc8a85a1ff5a4ba51e0bc7cf5665669924d
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/d0/7ec190c306ec690bac349e87d01c4358e49bb2 b/tests/resources/merge-resolve/.gitted/objects/d0/7ec190c306ec690bac349e87d01c4358e49bb2
new file mode 100644
index 000000000..d52a56ffe
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/d0/7ec190c306ec690bac349e87d01c4358e49bb2
@@ -0,0 +1,2 @@
+xÕ± €0 ©™Â3¤ažObˆ%Ç‘S°=HTŒ@u:]uYG¦´¥%šLÚE™–;¸’uÌ`_?gŒÎ~0²òßÒ”.
+׋Pëû£ÓœñxvXië Ó­f! \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/d0/d4594e16f2e19107e3fa7ea63e7aaaff305ffb b/tests/resources/merge-resolve/.gitted/objects/d0/d4594e16f2e19107e3fa7ea63e7aaaff305ffb
new file mode 100644
index 000000000..5f7e286ff
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/d0/d4594e16f2e19107e3fa7ea63e7aaaff305ffb
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/d2/f8637f2eab2507a1e13cbc9df4729ec386627e b/tests/resources/merge-resolve/.gitted/objects/d2/f8637f2eab2507a1e13cbc9df4729ec386627e
new file mode 100644
index 000000000..558a8513f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/d2/f8637f2eab2507a1e13cbc9df4729ec386627e
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/d3/3cedf513c059e0515653fa2c2e386631387a05 b/tests/resources/merge-resolve/.gitted/objects/d3/3cedf513c059e0515653fa2c2e386631387a05
new file mode 100644
index 000000000..d6d4c2b45
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/d3/3cedf513c059e0515653fa2c2e386631387a05
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/d3/719a5ae8e4d92276b5313ce976f6ee5af2b436 b/tests/resources/merge-resolve/.gitted/objects/d3/719a5ae8e4d92276b5313ce976f6ee5af2b436
new file mode 100644
index 000000000..930bf5a5e
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/d3/719a5ae8e4d92276b5313ce976f6ee5af2b436
@@ -0,0 +1,2 @@
+x=1Â0 E™}Š¿uAH…Ø¨Ä z‡¸ÔRHPÔŸ´T –¿÷ìBrh/ç]?ªaÐ ˜48¡,‘_â¡MúdköÄуTPF!«÷›TïZµQ?
+RÖ§F«Në™ÕÊÒ_J͆ÃÔ Øh’ª{Ÿßò(kÒËLKV1p–+Q‹êÑÕÝÑ® A× \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/d3/7aa3bbfe1c0c49b909781251b956dbabe85f96 b/tests/resources/merge-resolve/.gitted/objects/d3/7aa3bbfe1c0c49b909781251b956dbabe85f96
new file mode 100644
index 000000000..5902e0f32
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/d3/7aa3bbfe1c0c49b909781251b956dbabe85f96
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/d3/7ad72a2052685fc6201c2af90103ad42d2079b b/tests/resources/merge-resolve/.gitted/objects/d3/7ad72a2052685fc6201c2af90103ad42d2079b
new file mode 100644
index 000000000..b2f39bff4
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/d3/7ad72a2052685fc6201c2af90103ad42d2079b
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/d4/207f77243500bec335ab477f9227fcdb1e271a b/tests/resources/merge-resolve/.gitted/objects/d4/207f77243500bec335ab477f9227fcdb1e271a
new file mode 100644
index 000000000..862e4e5bc
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/d4/207f77243500bec335ab477f9227fcdb1e271a
@@ -0,0 +1,2 @@
+x¥ŽK
+1D]ç¹€’O'™7ÞÀ½t:ÝŒ‹™H&âõŸ«zŠ¢:Ï÷®Ä]oÌZÀBñBXlô(ƒƒ“昭+d<"«6^ºÎ%ŒÞ A( ¡ˆJÆ,Æ% %£5‚SøìSmúR^ØŠ¾Nu^뢼ÑO:óWüÚê|ÒÖ ÆèôÞDcÔF·³ÿœQEn¸¯½6õÔ#Qç \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/d4/27e0b2e138501a3d15cc376077a3631e15bd46 b/tests/resources/merge-resolve/.gitted/objects/d4/27e0b2e138501a3d15cc376077a3631e15bd46
new file mode 100644
index 000000000..0b3611ae4
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/d4/27e0b2e138501a3d15cc376077a3631e15bd46
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/d5/093787ef302b941b6aab081b99fb4880038bd8 b/tests/resources/merge-resolve/.gitted/objects/d5/093787ef302b941b6aab081b99fb4880038bd8
new file mode 100644
index 000000000..7d73449eb
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/d5/093787ef302b941b6aab081b99fb4880038bd8
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/d5/a61b0b4992a4f0caa887fa08b52431e727bb6f b/tests/resources/merge-resolve/.gitted/objects/d5/a61b0b4992a4f0caa887fa08b52431e727bb6f
new file mode 100644
index 000000000..a7921de43
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/d5/a61b0b4992a4f0caa887fa08b52431e727bb6f
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/d5/b6fc965c926a1bfc9ee456042b94088b5c5d21 b/tests/resources/merge-resolve/.gitted/objects/d5/b6fc965c926a1bfc9ee456042b94088b5c5d21
new file mode 100644
index 000000000..924bdbbb5
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/d5/b6fc965c926a1bfc9ee456042b94088b5c5d21
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/d5/ec1152fe25e9fec00189eb00b3db71db24c218 b/tests/resources/merge-resolve/.gitted/objects/d5/ec1152fe25e9fec00189eb00b3db71db24c218
new file mode 100644
index 000000000..0d2534bc9
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/d5/ec1152fe25e9fec00189eb00b3db71db24c218
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/d6/42b9770c66bba94a08df09b5efb095001f76d7 b/tests/resources/merge-resolve/.gitted/objects/d6/42b9770c66bba94a08df09b5efb095001f76d7
new file mode 100644
index 000000000..1671f9f2c
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/d6/42b9770c66bba94a08df09b5efb095001f76d7
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/d6/462fa3f5292857db599c54aea2bf91616230c5 b/tests/resources/merge-resolve/.gitted/objects/d6/462fa3f5292857db599c54aea2bf91616230c5
new file mode 100644
index 000000000..baae3f0e0
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/d6/462fa3f5292857db599c54aea2bf91616230c5
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/d6/cf6c7741b3316826af1314042550c97ded1d50 b/tests/resources/merge-resolve/.gitted/objects/d6/cf6c7741b3316826af1314042550c97ded1d50
new file mode 100644
index 000000000..8f9ae1fc6
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/d6/cf6c7741b3316826af1314042550c97ded1d50
@@ -0,0 +1,2 @@
+x¥Q
+1 Dýî)r%µ¦i@Äoàj›e»…ÚÅë[Åø7ó̤Zʽƒ¿éMSLB‘­Nl°ìm öÑBºÉ~×>×—üŠ-Ãu®åY8ê ŸtÖ¯øµ]ªåÖQaǶèÍ ã¼ëŸ3f]š>b×lÞ(„A] \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/d7/308cc367b2cc23f710834ec1fd8ffbacf1b460 b/tests/resources/merge-resolve/.gitted/objects/d7/308cc367b2cc23f710834ec1fd8ffbacf1b460
new file mode 100644
index 000000000..b02cda4fa
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/d7/308cc367b2cc23f710834ec1fd8ffbacf1b460
@@ -0,0 +1 @@
+x¥K @]sŠ¹€†)ÐÄ7ÞÀ ð™¦]ÐI(Æx{ñ î^Þâ½,µnлSoÌ`¬/¹Xä)Ù™B@GžÃ”¸œaòD¼ «øê«4x”wlž«ÔCv¸ò°?º×-79dé—,õh‚F4Žœµ×Z ;ÆÿH¨´í±}Ô·š=  \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/d8/74671ef5b20184836cb983bb273e5280384d0b b/tests/resources/merge-resolve/.gitted/objects/d8/74671ef5b20184836cb983bb273e5280384d0b
new file mode 100644
index 000000000..1d8037895
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/d8/74671ef5b20184836cb983bb273e5280384d0b
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/d8/dec75ff2f8b41d1c5bfef0cd57b7300c834f66 b/tests/resources/merge-resolve/.gitted/objects/d8/dec75ff2f8b41d1c5bfef0cd57b7300c834f66
new file mode 100644
index 000000000..74f807e68
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/d8/dec75ff2f8b41d1c5bfef0cd57b7300c834f66
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/d8/fa77b6833082c1ea36b7828a582d4c43882450 b/tests/resources/merge-resolve/.gitted/objects/d8/fa77b6833082c1ea36b7828a582d4c43882450
new file mode 100644
index 000000000..988145322
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/d8/fa77b6833082c1ea36b7828a582d4c43882450
@@ -0,0 +1 @@
+xíÐ1€ DQkN1¥–&6‡•%l²BkŒ·naa1Åk¦ø¤…°íëdI¢(ãU£rö'7‰‘»LŸ’AÅ,±+Wmð9 ŒI'UŸÄ͹ÿñ£_ÛÜ°N \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/d9/63979c237d08b6ba39062ee7bf64c7d34a27f8 b/tests/resources/merge-resolve/.gitted/objects/d9/63979c237d08b6ba39062ee7bf64c7d34a27f8
new file mode 100644
index 000000000..5fa10405c
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/d9/63979c237d08b6ba39062ee7bf64c7d34a27f8
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/da/178208145ef585a1bd5ca5f4c9785d738df2cf b/tests/resources/merge-resolve/.gitted/objects/da/178208145ef585a1bd5ca5f4c9785d738df2cf
new file mode 100644
index 000000000..6292118e0
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/da/178208145ef585a1bd5ca5f4c9785d738df2cf
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/db/6261a7c65c7fd678520c9bb6f2c47582ab9ed5 b/tests/resources/merge-resolve/.gitted/objects/db/6261a7c65c7fd678520c9bb6f2c47582ab9ed5
new file mode 100644
index 000000000..b82e7fcaf
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/db/6261a7c65c7fd678520c9bb6f2c47582ab9ed5
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/dd/2ae5ab264e5592aa754235d5ad5eac8f0ecdfd b/tests/resources/merge-resolve/.gitted/objects/dd/2ae5ab264e5592aa754235d5ad5eac8f0ecdfd
new file mode 100644
index 000000000..55626a57b
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/dd/2ae5ab264e5592aa754235d5ad5eac8f0ecdfd
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/dd/9a570c3400e6e07bc4d7651d6e20b08926b3d9 b/tests/resources/merge-resolve/.gitted/objects/dd/9a570c3400e6e07bc4d7651d6e20b08926b3d9
new file mode 100644
index 000000000..8fd60cbe8
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/dd/9a570c3400e6e07bc4d7651d6e20b08926b3d9
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/de/872ee3618b894992e9d1e18ba2ebe256a112f9 b/tests/resources/merge-resolve/.gitted/objects/de/872ee3618b894992e9d1e18ba2ebe256a112f9
new file mode 100644
index 000000000..04dda4a75
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/de/872ee3618b894992e9d1e18ba2ebe256a112f9
@@ -0,0 +1 @@
+xí± À S3Å‚…ŒlK–A†ˆõƒ²BÚ4W®¸b­ Ÿù˜—T5Á¢:§8ÔS»c€œ±Ô Ô»P`KäI¥Ë†O3Z½•”þàç‡&ØÝ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/df/e3f22baa1f6fce5447901c3086bae368de6bdd b/tests/resources/merge-resolve/.gitted/objects/df/e3f22baa1f6fce5447901c3086bae368de6bdd
new file mode 100644
index 000000000..e13569440
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/df/e3f22baa1f6fce5447901c3086bae368de6bdd
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/e0/67f9361140f19391472df8a82d6610813c73b7 b/tests/resources/merge-resolve/.gitted/objects/e0/67f9361140f19391472df8a82d6610813c73b7
new file mode 100644
index 000000000..955431dd7
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/e0/67f9361140f19391472df8a82d6610813c73b7
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/e1/129b3cfb5898e0fbd606e0cb80b2755e50d161 b/tests/resources/merge-resolve/.gitted/objects/e1/129b3cfb5898e0fbd606e0cb80b2755e50d161
new file mode 100644
index 000000000..751f1dd33
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/e1/129b3cfb5898e0fbd606e0cb80b2755e50d161
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/e1/7ace1492648c9dc5701bad5c47af9d1b60c4e9 b/tests/resources/merge-resolve/.gitted/objects/e1/7ace1492648c9dc5701bad5c47af9d1b60c4e9
new file mode 100644
index 000000000..4a812e5df
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/e1/7ace1492648c9dc5701bad5c47af9d1b60c4e9
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/e2/c6abbd55fed5ac71a5f2751e29b4a34726a595 b/tests/resources/merge-resolve/.gitted/objects/e2/c6abbd55fed5ac71a5f2751e29b4a34726a595
new file mode 100644
index 000000000..7b84ce966
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/e2/c6abbd55fed5ac71a5f2751e29b4a34726a595
@@ -0,0 +1 @@
+x+)JMU067f040031QH,-ÉÏM-JOMLÊIÕ+©(aH:,»:ÎCÉýúô:ÞË ²o>ZC'g$楧¦èfæé&%æ%g€5¬ÎqYôÂeÒZoÇÝÙkÚMíæ2­éÆÔ›X\’ZDPC~^ZNfrIf^:Xéõ›ZHÙŠž1O(_œ,'º× jvn~JfZ&Ô5ù%·˜º³\N´º¢ÔçÞö§,5[ðe“Ù¨ú¢ÔÜü2 ÇßüI{•|þ¹÷ 2m»gÜ˾‹©Éñ1ÖËåüŠ5Ó¿Ü,\“µ})TC)0DÀvú‰ùzÖ›¦9–¤×Mü°úÕ…'6ób‹” \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/e3/1e7ad3ed298f24e383c4950f4671993ec078e4 b/tests/resources/merge-resolve/.gitted/objects/e3/1e7ad3ed298f24e383c4950f4671993ec078e4
new file mode 100644
index 000000000..a28ded3fb
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/e3/1e7ad3ed298f24e383c4950f4671993ec078e4
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/e3/76fbdd06ebf021c92724da9f26f44212734e3e b/tests/resources/merge-resolve/.gitted/objects/e3/76fbdd06ebf021c92724da9f26f44212734e3e
new file mode 100644
index 000000000..8da234114
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/e3/76fbdd06ebf021c92724da9f26f44212734e3e
@@ -0,0 +1,3 @@
+xíAÂ@E]s
+`ö@Ì uHš)M=¾Scô®Üþü’zÂé:¢ÊŠ³(ãN+6Þ›D°¡Feð­­˜Y®g$+GˆÞä&F
+Ÿ½ì‹p‡þâG —4”mQÉ\±á85Æ#FìCð¥ï~QEóÀÄÀÚR—š½u)£;cáàâ6ü­öë'ÍjÄÇ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/e4/9f917b448d1340b31d76e54ba388268fd4c922 b/tests/resources/merge-resolve/.gitted/objects/e4/9f917b448d1340b31d76e54ba388268fd4c922
new file mode 100644
index 000000000..870c3e732
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/e4/9f917b448d1340b31d76e54ba388268fd4c922
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/e4/f618a2c3ed0669308735727df5ebf2447f022f b/tests/resources/merge-resolve/.gitted/objects/e4/f618a2c3ed0669308735727df5ebf2447f022f
new file mode 100644
index 000000000..c7e1ee9d7
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/e4/f618a2c3ed0669308735727df5ebf2447f022f
@@ -0,0 +1,2 @@
+x¥ŽK!D]s
+. išObŒoà i2. b¼¾h¼»ª÷’JQ«õ6äb`7:³DN.%•±œ4¹’u¦‹ÏÄIQñYÜcçmÈàÙ`Q¨ aQYa¶@>äÄÉ—Ecñ9ÖÖå%¿bÏòº¶úh›<ò¤Ÿtæ¯øµµz’J£òÁ(¹“γƒÿœFCñ_‹NŒ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/e5/060729746ca9888239cba08fdcf4bee907b406 b/tests/resources/merge-resolve/.gitted/objects/e5/060729746ca9888239cba08fdcf4bee907b406
new file mode 100644
index 000000000..33299c2b0
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/e5/060729746ca9888239cba08fdcf4bee907b406
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/e6/5a9bb2af9f4c2d1c375dd0f8f8a46cf9c68812 b/tests/resources/merge-resolve/.gitted/objects/e6/5a9bb2af9f4c2d1c375dd0f8f8a46cf9c68812
new file mode 100644
index 000000000..72f1cbcd9
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/e6/5a9bb2af9f4c2d1c375dd0f8f8a46cf9c68812
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/e8/107f24196736b870a318a0e28f048e29f6feff b/tests/resources/merge-resolve/.gitted/objects/e8/107f24196736b870a318a0e28f048e29f6feff
new file mode 100644
index 000000000..ffcf843c2
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/e8/107f24196736b870a318a0e28f048e29f6feff
@@ -0,0 +1,3 @@
+x¥ŽK!D]s
+. i~=LbŒoàÞ@ÓdfãõEã ÜU½—TŠj)k—Úš]oÌÒ¯mð„èü„ž5(3ƒÓj&Ô™4Íâß»póp™iRI;‘³·¨“Â9Ò‚²Q„g_j“—ô
+-ÉëRËVïòȃ~Ò™¿â×TËI*ãÜtr g;ÿ9#R¾mkb%ÞrLNÁ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/e9/2cdb7017dc6c5aed25cb4202c5b0104b872246 b/tests/resources/merge-resolve/.gitted/objects/e9/2cdb7017dc6c5aed25cb4202c5b0104b872246
new file mode 100644
index 000000000..cb1260eb8
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/e9/2cdb7017dc6c5aed25cb4202c5b0104b872246
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/e9/ad6ec3e38364a3d07feda7c4197d4d845c53b5 b/tests/resources/merge-resolve/.gitted/objects/e9/ad6ec3e38364a3d07feda7c4197d4d845c53b5
new file mode 100644
index 000000000..da4a5edd1
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/e9/ad6ec3e38364a3d07feda7c4197d4d845c53b5
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/e9/f48beccc62d535739bfbdebe0a55ed716d8366 b/tests/resources/merge-resolve/.gitted/objects/e9/f48beccc62d535739bfbdebe0a55ed716d8366
new file mode 100644
index 000000000..23c59e4c9
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/e9/f48beccc62d535739bfbdebe0a55ed716d8366
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/eb/c09d0137cfb0c26697aed0109fb943ad906f3f b/tests/resources/merge-resolve/.gitted/objects/eb/c09d0137cfb0c26697aed0109fb943ad906f3f
new file mode 100644
index 000000000..83b489d3a
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/eb/c09d0137cfb0c26697aed0109fb943ad906f3f
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/ec/67e5a86adff465359f1c8f995e12dbdfa08d8a b/tests/resources/merge-resolve/.gitted/objects/ec/67e5a86adff465359f1c8f995e12dbdfa08d8a
new file mode 100644
index 000000000..8490346e1
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ec/67e5a86adff465359f1c8f995e12dbdfa08d8a
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/ed/9523e62e453e50dd9be1606af19399b96e397a b/tests/resources/merge-resolve/.gitted/objects/ed/9523e62e453e50dd9be1606af19399b96e397a
new file mode 100644
index 000000000..7853e235c
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ed/9523e62e453e50dd9be1606af19399b96e397a
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/ee/1d6f164893c1866a323f072eeed36b855656be b/tests/resources/merge-resolve/.gitted/objects/ee/1d6f164893c1866a323f072eeed36b855656be
new file mode 100644
index 000000000..87d808007
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ee/1d6f164893c1866a323f072eeed36b855656be
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/ee/3fa1b8c00aff7fe02065fdb50864bb0d932ccf b/tests/resources/merge-resolve/.gitted/objects/ee/3fa1b8c00aff7fe02065fdb50864bb0d932ccf
new file mode 100644
index 000000000..974b72dfd
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ee/3fa1b8c00aff7fe02065fdb50864bb0d932ccf
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/ee/a9286df54245fea72c5b557291470eb825f38f b/tests/resources/merge-resolve/.gitted/objects/ee/a9286df54245fea72c5b557291470eb825f38f
new file mode 100644
index 000000000..ead0b2cda
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ee/a9286df54245fea72c5b557291470eb825f38f
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/ef/58fdd8086c243bdc81f99e379acacfd21d32d6 b/tests/resources/merge-resolve/.gitted/objects/ef/58fdd8086c243bdc81f99e379acacfd21d32d6
new file mode 100644
index 000000000..55f79e066
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ef/58fdd8086c243bdc81f99e379acacfd21d32d6
@@ -0,0 +1,2 @@
+x ÉÁ À0Оâï:JŠB¢ÝŸæôOV
+Þñ´yáó5éê†5jã†q!’4÷Î{¡³:ýp;¼ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/ef/c499524cf105d5264ac7fc54e07e95764e8075 b/tests/resources/merge-resolve/.gitted/objects/ef/c499524cf105d5264ac7fc54e07e95764e8075
new file mode 100644
index 000000000..bc9350bc0
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ef/c499524cf105d5264ac7fc54e07e95764e8075
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/ef/c9121fdedaf08ba180b53ebfbcf71bd488ed09 b/tests/resources/merge-resolve/.gitted/objects/ef/c9121fdedaf08ba180b53ebfbcf71bd488ed09
new file mode 100644
index 000000000..5f9cd3012
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ef/c9121fdedaf08ba180b53ebfbcf71bd488ed09
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/f0/053b8060bb3f0be5cbcc3147a07ece26bf097e b/tests/resources/merge-resolve/.gitted/objects/f0/053b8060bb3f0be5cbcc3147a07ece26bf097e
new file mode 100644
index 000000000..c63d37fb0
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/f0/053b8060bb3f0be5cbcc3147a07ece26bf097e
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/f0/ce2b8e4986084d9b308fb72709e414c23eb5e6 b/tests/resources/merge-resolve/.gitted/objects/f0/ce2b8e4986084d9b308fb72709e414c23eb5e6
new file mode 100644
index 000000000..e78c19f1a
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/f0/ce2b8e4986084d9b308fb72709e414c23eb5e6
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/f2/0c9063fa0bda9a397c96947a7b687305c49753 b/tests/resources/merge-resolve/.gitted/objects/f2/0c9063fa0bda9a397c96947a7b687305c49753
new file mode 100644
index 000000000..34d9aed20
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/f2/0c9063fa0bda9a397c96947a7b687305c49753
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/f2/9e7fb590551095230c6149cbe72f2e9104a796 b/tests/resources/merge-resolve/.gitted/objects/f2/9e7fb590551095230c6149cbe72f2e9104a796
new file mode 100644
index 000000000..663f6aeb7
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/f2/9e7fb590551095230c6149cbe72f2e9104a796
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/f2/e1550a0c9e53d5811175864a29536642ae3821 b/tests/resources/merge-resolve/.gitted/objects/f2/e1550a0c9e53d5811175864a29536642ae3821
new file mode 100644
index 000000000..1fdcbe22a
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/f2/e1550a0c9e53d5811175864a29536642ae3821
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/f3/293571dcd708b6a3faf03818cd2844d000e198 b/tests/resources/merge-resolve/.gitted/objects/f3/293571dcd708b6a3faf03818cd2844d000e198
new file mode 100644
index 000000000..f748743b8
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/f3/293571dcd708b6a3faf03818cd2844d000e198
@@ -0,0 +1 @@
+x¥ŽKjÄ0³Ö)údµZ!›¹Á\@nIعƒ,3×Ͳ{¼¢XZÛë?F/\Eã12zcó’©"#êX1§]µÚ[Ô_êå˜GŒ&™œ˜c©+9³‡”ùXWKÁÓžiUé›t¸çgê›´Sø*“¾×OÛ¹Ë)u|²´oXp*" pÓ¤µšt†Žò…šÅ©`9êïÎäê§zÅKO\ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/f3/f1164b68b57b1995b658a828320e6df3081fae b/tests/resources/merge-resolve/.gitted/objects/f3/f1164b68b57b1995b658a828320e6df3081fae
new file mode 100644
index 000000000..5f0b4e424
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/f3/f1164b68b57b1995b658a828320e6df3081fae
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/f4/15caf3fcad16304cb424b67f0ee6b12dc03aae b/tests/resources/merge-resolve/.gitted/objects/f4/15caf3fcad16304cb424b67f0ee6b12dc03aae
new file mode 100644
index 000000000..21ce1a0fc
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/f4/15caf3fcad16304cb424b67f0ee6b12dc03aae
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/f4/8097eb340dc5a7cae55aabcf1faf4548aa821f b/tests/resources/merge-resolve/.gitted/objects/f4/8097eb340dc5a7cae55aabcf1faf4548aa821f
new file mode 100644
index 000000000..5a4a9a54f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/f4/8097eb340dc5a7cae55aabcf1faf4548aa821f
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/f5/504f36e6f4eb797a56fc5bac6c6c7f32969bf2 b/tests/resources/merge-resolve/.gitted/objects/f5/504f36e6f4eb797a56fc5bac6c6c7f32969bf2
new file mode 100644
index 000000000..2aa0c3b9a
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/f5/504f36e6f4eb797a56fc5bac6c6c7f32969bf2
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/f5/b50c85a87cac64d7eb3254cdd1aec9564c0293 b/tests/resources/merge-resolve/.gitted/objects/f5/b50c85a87cac64d7eb3254cdd1aec9564c0293
new file mode 100644
index 000000000..c1885cbe7
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/f5/b50c85a87cac64d7eb3254cdd1aec9564c0293
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/f5/f9dd5886a6ee20272be0aafc790cba43b31931 b/tests/resources/merge-resolve/.gitted/objects/f5/f9dd5886a6ee20272be0aafc790cba43b31931
new file mode 100644
index 000000000..17ad5063d
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/f5/f9dd5886a6ee20272be0aafc790cba43b31931
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/f6/65b45cde9b568009c6e6b7b568e89cfe717df8 b/tests/resources/merge-resolve/.gitted/objects/f6/65b45cde9b568009c6e6b7b568e89cfe717df8
new file mode 100644
index 000000000..7af50d7df
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/f6/65b45cde9b568009c6e6b7b568e89cfe717df8
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/f6/be049e284c0f9dcbbc745543885be3502ea521 b/tests/resources/merge-resolve/.gitted/objects/f6/be049e284c0f9dcbbc745543885be3502ea521
new file mode 100644
index 000000000..12d3c25c2
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/f6/be049e284c0f9dcbbc745543885be3502ea521
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/f7/c332bd4d4d4b777366cae4d24d1687477576bf b/tests/resources/merge-resolve/.gitted/objects/f7/c332bd4d4d4b777366cae4d24d1687477576bf
new file mode 100644
index 000000000..b36bceabf
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/f7/c332bd4d4d4b777366cae4d24d1687477576bf
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/f8/958bdf4d365a84a9a178b1f5f35ff1dacbd884 b/tests/resources/merge-resolve/.gitted/objects/f8/958bdf4d365a84a9a178b1f5f35ff1dacbd884
new file mode 100644
index 000000000..5dbbef276
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/f8/958bdf4d365a84a9a178b1f5f35ff1dacbd884
@@ -0,0 +1,2 @@
+x¥ŽK
+1D]ç}¥ÓI¦qã ÜK>f1’‰x}£xwU¯àQ±–²v kv½‰@`OŽ§<çHˆd}œ%kŽA›‘CÒ>²ÑÄêá›Ü;$KÈ™ybhŒóÁ2癈sLA ±öÊ?ûR\ÒË·×¥–­Þá(ƒ~ÒY¾Ã¯b-'ÐÆYÇŽp‚=NˆjÐq¶ËŸ•òm[“zí O+ \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/fa/c03f2c5139618d87d53614c153823bf1f31396 b/tests/resources/merge-resolve/.gitted/objects/fa/c03f2c5139618d87d53614c153823bf1f31396
new file mode 100644
index 000000000..30e07e5b7
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/fa/c03f2c5139618d87d53614c153823bf1f31396
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/fa/da9356aa3f74622327a3038ae9c6f92e1c5c1d b/tests/resources/merge-resolve/.gitted/objects/fa/da9356aa3f74622327a3038ae9c6f92e1c5c1d
new file mode 100644
index 000000000..16ce49a1b
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/fa/da9356aa3f74622327a3038ae9c6f92e1c5c1d
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/fb/738a106cfd097a4acb96ce132ecb1ad6c46b03 b/tests/resources/merge-resolve/.gitted/objects/fb/738a106cfd097a4acb96ce132ecb1ad6c46b03
new file mode 100644
index 000000000..4f1e72688
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/fb/738a106cfd097a4acb96ce132ecb1ad6c46b03
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/fc/4c636d6515e9e261f9260dbcf3cc6eca97ea08 b/tests/resources/merge-resolve/.gitted/objects/fc/4c636d6515e9e261f9260dbcf3cc6eca97ea08
new file mode 100644
index 000000000..be8a810cd
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/fc/4c636d6515e9e261f9260dbcf3cc6eca97ea08
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/fc/7d7b805f7a9428574f4f802b2e34cd20ab9d99 b/tests/resources/merge-resolve/.gitted/objects/fc/7d7b805f7a9428574f4f802b2e34cd20ab9d99
new file mode 100644
index 000000000..20493e68c
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/fc/7d7b805f7a9428574f4f802b2e34cd20ab9d99
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/fc/90237dc4891fa6c69827fc465632225e391618 b/tests/resources/merge-resolve/.gitted/objects/fc/90237dc4891fa6c69827fc465632225e391618
new file mode 100644
index 000000000..961814bae
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/fc/90237dc4891fa6c69827fc465632225e391618
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/fd/57d2d6770fad8e9959124793a17f441b571e66 b/tests/resources/merge-resolve/.gitted/objects/fd/57d2d6770fad8e9959124793a17f441b571e66
new file mode 100644
index 000000000..21e6b2c55
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/fd/57d2d6770fad8e9959124793a17f441b571e66
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/fd/89f8cffb663ac89095a0f9764902e93ceaca6a b/tests/resources/merge-resolve/.gitted/objects/fd/89f8cffb663ac89095a0f9764902e93ceaca6a
new file mode 100644
index 000000000..2f9d83b26
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/fd/89f8cffb663ac89095a0f9764902e93ceaca6a
@@ -0,0 +1,2 @@
+x¥ŽK!D]s
+. ¡{` cÜx/À§É¸`0 ÆëËoà®êURy±–òèõ|èY`ÀdPÌA!±íÜäŒå4C2d=x#ž¾ñÚe`Bgr´™uôàbLÁ‡¬Ðf“fG @þÕ—Úä-½}Kò¾Ô²ÕUžyÐ=]ù;üÚ)Ör‘0 Rˆ$Ê(%²ÿ¼Ùo=׶›ˆ‡OPw \ No newline at end of file
diff --git a/tests/resources/merge-resolve/.gitted/objects/fe/5407fc50a53aecb41d1a6e9ea7b612e581af87 b/tests/resources/merge-resolve/.gitted/objects/fe/5407fc50a53aecb41d1a6e9ea7b612e581af87
new file mode 100644
index 000000000..4ce7d2297
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/fe/5407fc50a53aecb41d1a6e9ea7b612e581af87
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/ff/49d07869831ad761bbdaea026086f8789bcb00 b/tests/resources/merge-resolve/.gitted/objects/ff/49d07869831ad761bbdaea026086f8789bcb00
new file mode 100644
index 000000000..eada39b77
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ff/49d07869831ad761bbdaea026086f8789bcb00
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/objects/ff/b312248d607284c290023f9502eea010d34efd b/tests/resources/merge-resolve/.gitted/objects/ff/b312248d607284c290023f9502eea010d34efd
new file mode 100644
index 000000000..7e46c4fe3
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/objects/ff/b312248d607284c290023f9502eea010d34efd
Binary files differ
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/branch b/tests/resources/merge-resolve/.gitted/refs/heads/branch
new file mode 100644
index 000000000..03f79a3dc
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/branch
@@ -0,0 +1 @@
+7cb63eed597130ba4abb87b3e544b85021905520
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/df_ancestor b/tests/resources/merge-resolve/.gitted/refs/heads/df_ancestor
new file mode 100644
index 000000000..4bc37ac60
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/df_ancestor
@@ -0,0 +1 @@
+2da538570bc1e5b2c3e855bf702f35248ad0735f
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/df_side1 b/tests/resources/merge-resolve/.gitted/refs/heads/df_side1
new file mode 100644
index 000000000..ca6dd679d
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/df_side1
@@ -0,0 +1 @@
+a7dbfcbfc1a60709cb80b5ca24539008456531d0
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/df_side2 b/tests/resources/merge-resolve/.gitted/refs/heads/df_side2
new file mode 100644
index 000000000..b8160f80e
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/df_side2
@@ -0,0 +1 @@
+fc90237dc4891fa6c69827fc465632225e391618
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/ff_branch b/tests/resources/merge-resolve/.gitted/refs/heads/ff_branch
new file mode 100644
index 000000000..e9e90512f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/ff_branch
@@ -0,0 +1 @@
+fd89f8cffb663ac89095a0f9764902e93ceaca6a
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/master b/tests/resources/merge-resolve/.gitted/refs/heads/master
new file mode 100644
index 000000000..8a329ae5f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/master
@@ -0,0 +1 @@
+bd593285fc7fe4ca18ccdbabf027f5d689101452
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/octo1 b/tests/resources/merge-resolve/.gitted/refs/heads/octo1
new file mode 100644
index 000000000..4d2c66902
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/octo1
@@ -0,0 +1 @@
+16f825815cfd20a07a75c71554e82d8eede0b061
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/octo2 b/tests/resources/merge-resolve/.gitted/refs/heads/octo2
new file mode 100644
index 000000000..f503977a7
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/octo2
@@ -0,0 +1 @@
+158dc7bedb202f5b26502bf3574faa7f4238d56c
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/octo3 b/tests/resources/merge-resolve/.gitted/refs/heads/octo3
new file mode 100644
index 000000000..b92994f10
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/octo3
@@ -0,0 +1 @@
+50ce7d7d01217679e26c55939eef119e0c93e272
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/octo4 b/tests/resources/merge-resolve/.gitted/refs/heads/octo4
new file mode 100644
index 000000000..f33d57cbc
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/octo4
@@ -0,0 +1 @@
+54269b3f6ec3d7d4ede24dd350dd5d605495c3ae
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/octo5 b/tests/resources/merge-resolve/.gitted/refs/heads/octo5
new file mode 100644
index 000000000..e9f943385
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/octo5
@@ -0,0 +1 @@
+e4f618a2c3ed0669308735727df5ebf2447f022f
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/octo6 b/tests/resources/merge-resolve/.gitted/refs/heads/octo6
new file mode 100644
index 000000000..4c5a98ad9
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/octo6
@@ -0,0 +1 @@
+b6f610aef53bd343e6c96227de874c66f00ee8e8
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/previous b/tests/resources/merge-resolve/.gitted/refs/heads/previous
new file mode 100644
index 000000000..7bc1a8d15
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/previous
@@ -0,0 +1 @@
+c607fc30883e335def28cd686b51f6cfa02b06ec
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/rename_conflict_ancestor b/tests/resources/merge-resolve/.gitted/refs/heads/rename_conflict_ancestor
new file mode 100644
index 000000000..4092d428f
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/rename_conflict_ancestor
@@ -0,0 +1 @@
+2392a2dacc9efb562b8635d6579fb458751c7c5b
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/rename_conflict_ours b/tests/resources/merge-resolve/.gitted/refs/heads/rename_conflict_ours
new file mode 100644
index 000000000..a1c50dce8
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/rename_conflict_ours
@@ -0,0 +1 @@
+34bfafff88eaf118402b44e6f3e2dbbf1a582b05
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/rename_conflict_theirs b/tests/resources/merge-resolve/.gitted/refs/heads/rename_conflict_theirs
new file mode 100644
index 000000000..130989399
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/rename_conflict_theirs
@@ -0,0 +1 @@
+a802e06f1782a9645b9851bc7202cee74a8a4972
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/renames1 b/tests/resources/merge-resolve/.gitted/refs/heads/renames1
new file mode 100644
index 000000000..3d248102c
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/renames1
@@ -0,0 +1 @@
+412b32fb66137366147f1801ecc962452757d48a
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/renames2 b/tests/resources/merge-resolve/.gitted/refs/heads/renames2
new file mode 100644
index 000000000..d22621561
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/renames2
@@ -0,0 +1 @@
+ab40af3cb8a3ed2e2843e96d9aa7871336b94573
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/submodules b/tests/resources/merge-resolve/.gitted/refs/heads/submodules
new file mode 100644
index 000000000..e5511eca9
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/submodules
@@ -0,0 +1 @@
+d8dec75ff2f8b41d1c5bfef0cd57b7300c834f66
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/submodules-branch b/tests/resources/merge-resolve/.gitted/refs/heads/submodules-branch
new file mode 100644
index 000000000..7d47e07b8
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/submodules-branch
@@ -0,0 +1 @@
+811c70fcb6d5bbd022d04cc31836d30b436f9551
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/submodules-branch2 b/tests/resources/merge-resolve/.gitted/refs/heads/submodules-branch2
new file mode 100644
index 000000000..ced60d813
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/submodules-branch2
@@ -0,0 +1 @@
+7c04ca611203ed320c5f495b9813054dd23be3be
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-10 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-10
new file mode 100644
index 000000000..5b378cd88
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-10
@@ -0,0 +1 @@
+0ec5f433959cd46177f745903353efb5be08d151
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-10-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-10-branch
new file mode 100644
index 000000000..b3db6c892
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-10-branch
@@ -0,0 +1 @@
+11f4f3c08b737f5fd896cbefa1425ee63b21b2fa
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-11 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-11
new file mode 100644
index 000000000..154de9a64
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-11
@@ -0,0 +1 @@
+3168dca1a561889b045a6441909f4c56145e666d
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-11-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-11-branch
new file mode 100644
index 000000000..2e4118029
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-11-branch
@@ -0,0 +1 @@
+6718a45909532d1fcf5600d0877f7fe7e78f0b86
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-13 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-13
new file mode 100644
index 000000000..297573a57
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-13
@@ -0,0 +1 @@
+a3fabece9eb8748da810e1e08266fef9b7136ad4
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-13-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-13-branch
new file mode 100644
index 000000000..22e429a61
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-13-branch
@@ -0,0 +1 @@
+05f3c1a2a56ca95c3d2ef28dc9ddf32b5cd6c91c
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-14 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-14
new file mode 100644
index 000000000..89051853a
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-14
@@ -0,0 +1 @@
+7e2d058d5fedf8329db44db4fac610d6b1a89159
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-14-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-14-branch
new file mode 100644
index 000000000..0158f950c
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-14-branch
@@ -0,0 +1 @@
+8187117062b750eed4f93fd7e899f17b52ce554d
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-2alt b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-2alt
new file mode 100644
index 000000000..474074120
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-2alt
@@ -0,0 +1 @@
+566ab53c220a2eafc1212af1a024513230280ab9
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-2alt-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-2alt-branch
new file mode 100644
index 000000000..2f5f1a4af
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-2alt-branch
@@ -0,0 +1 @@
+c9174cef549ec94ecbc43ef03cdc775b4950becb
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-3alt b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-3alt
new file mode 100644
index 000000000..18e50ae12
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-3alt
@@ -0,0 +1 @@
+4c9fac0707f8d4195037ae5a681aa48626491541
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-3alt-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-3alt-branch
new file mode 100644
index 000000000..7bc1a8d15
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-3alt-branch
@@ -0,0 +1 @@
+c607fc30883e335def28cd686b51f6cfa02b06ec
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-4 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-4
new file mode 100644
index 000000000..f49bbf956
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-4
@@ -0,0 +1 @@
+cc3e3009134cb88014129fc8858d1101359e5e2f
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-4-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-4-branch
new file mode 100644
index 000000000..bff519ef1
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-4-branch
@@ -0,0 +1 @@
+183310e30fb1499af8c619108ffea4d300b5e778
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-1 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-1
new file mode 100644
index 000000000..963a7b336
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-1
@@ -0,0 +1 @@
+4fe93c0ec83eb6305cbace3dace88ecee1b63cb6
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-1-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-1-branch
new file mode 100644
index 000000000..4a22138e7
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-1-branch
@@ -0,0 +1 @@
+478172cb2f5ff9b514bc9d04d3bd5ef5840cb3b2
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-2 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-2
new file mode 100644
index 000000000..aa4ada17e
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-2
@@ -0,0 +1 @@
+3b47b031b3e55ae11e14a05260b1c3ffd6838d55
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-2-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-2-branch
new file mode 100644
index 000000000..5553cdba1
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-5alt-2-branch
@@ -0,0 +1 @@
+f48097eb340dc5a7cae55aabcf1faf4548aa821f
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-6 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-6
new file mode 100644
index 000000000..fb685bb63
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-6
@@ -0,0 +1 @@
+99b4f7e4f24470fa06b980bc21f1095c2a9425c0
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-6-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-6-branch
new file mode 100644
index 000000000..efc4c55ac
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-6-branch
@@ -0,0 +1 @@
+a43150a738849c59376cf30bb2a68348a83c8f48
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-7 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-7
new file mode 100644
index 000000000..9c9424346
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-7
@@ -0,0 +1 @@
+d874671ef5b20184836cb983bb273e5280384d0b
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-7-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-7-branch
new file mode 100644
index 000000000..1762bb5db
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-7-branch
@@ -0,0 +1 @@
+5195a1b480f66691b667f10a9e41e70115a78351
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-8 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-8
new file mode 100644
index 000000000..837c4915a
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-8
@@ -0,0 +1 @@
+3575826c96a975031d2c14368529cc5c4353a8fd
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-8-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-8-branch
new file mode 100644
index 000000000..874230eff
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-8-branch
@@ -0,0 +1 @@
+52d8bc572af2b6d4ee0d5e62ed5d1fbad92210a9
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-9 b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-9
new file mode 100644
index 000000000..b968a3efb
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-9
@@ -0,0 +1 @@
+c35dee9bcc0e989f3b0c40f68372a9a51b6c4e6a
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/trivial-9-branch b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-9-branch
new file mode 100644
index 000000000..7f3097b69
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/trivial-9-branch
@@ -0,0 +1 @@
+13d1be4ea52a6ced1d7a1d832f0ee3c399348e5e
diff --git a/tests/resources/merge-resolve/.gitted/refs/heads/unrelated b/tests/resources/merge-resolve/.gitted/refs/heads/unrelated
new file mode 100644
index 000000000..bb877be2e
--- /dev/null
+++ b/tests/resources/merge-resolve/.gitted/refs/heads/unrelated
@@ -0,0 +1 @@
+55b4e4687e7a0d9ca367016ed930f385d4022e6f
diff --git a/tests/resources/merge-resolve/added-in-master.txt b/tests/resources/merge-resolve/added-in-master.txt
new file mode 100644
index 000000000..233c0919c
--- /dev/null
+++ b/tests/resources/merge-resolve/added-in-master.txt
@@ -0,0 +1 @@
+this file is added in master
diff --git a/tests/resources/merge-resolve/automergeable.txt b/tests/resources/merge-resolve/automergeable.txt
new file mode 100644
index 000000000..ee3fa1b8c
--- /dev/null
+++ b/tests/resources/merge-resolve/automergeable.txt
@@ -0,0 +1,9 @@
+this file is changed in master
+this file is automergeable
+this file is automergeable
+this file is automergeable
+this file is automergeable
+this file is automergeable
+this file is automergeable
+this file is automergeable
+this file is automergeable
diff --git a/tests/resources/merge-resolve/changed-in-branch.txt b/tests/resources/merge-resolve/changed-in-branch.txt
new file mode 100644
index 000000000..ab6c44a2e
--- /dev/null
+++ b/tests/resources/merge-resolve/changed-in-branch.txt
@@ -0,0 +1 @@
+initial revision
diff --git a/tests/resources/merge-resolve/changed-in-master.txt b/tests/resources/merge-resolve/changed-in-master.txt
new file mode 100644
index 000000000..11deab00b
--- /dev/null
+++ b/tests/resources/merge-resolve/changed-in-master.txt
@@ -0,0 +1 @@
+changed in master
diff --git a/tests/resources/merge-resolve/conflicting.txt b/tests/resources/merge-resolve/conflicting.txt
new file mode 100644
index 000000000..4e886e602
--- /dev/null
+++ b/tests/resources/merge-resolve/conflicting.txt
@@ -0,0 +1 @@
+this file is changed in master and branch
diff --git a/tests/resources/merge-resolve/removed-in-branch.txt b/tests/resources/merge-resolve/removed-in-branch.txt
new file mode 100644
index 000000000..dfe3f22ba
--- /dev/null
+++ b/tests/resources/merge-resolve/removed-in-branch.txt
@@ -0,0 +1 @@
+this is removed in branch
diff --git a/tests/resources/merge-resolve/unchanged.txt b/tests/resources/merge-resolve/unchanged.txt
new file mode 100644
index 000000000..c8f06f2e3
--- /dev/null
+++ b/tests/resources/merge-resolve/unchanged.txt
@@ -0,0 +1 @@
+this file is unchanged in both
diff --git a/tests/resources/mergedrepo/.gitted/COMMIT_EDITMSG b/tests/resources/mergedrepo/.gitted/COMMIT_EDITMSG
new file mode 100644
index 000000000..1f7391f92
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/COMMIT_EDITMSG
@@ -0,0 +1 @@
+master
diff --git a/tests/resources/mergedrepo/.gitted/HEAD b/tests/resources/mergedrepo/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/mergedrepo/.gitted/MERGE_HEAD b/tests/resources/mergedrepo/.gitted/MERGE_HEAD
new file mode 100644
index 000000000..a5bdf6e40
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/MERGE_HEAD
@@ -0,0 +1 @@
+e2809157a7766f272e4cfe26e61ef2678a5357ff
diff --git a/tests/resources/mergedrepo/.gitted/MERGE_MODE b/tests/resources/mergedrepo/.gitted/MERGE_MODE
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/MERGE_MODE
diff --git a/tests/resources/mergedrepo/.gitted/MERGE_MSG b/tests/resources/mergedrepo/.gitted/MERGE_MSG
new file mode 100644
index 000000000..7c4d1f5a9
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/MERGE_MSG
@@ -0,0 +1,5 @@
+Merge branch 'branch'
+
+Conflicts:
+ conflicts-one.txt
+ conflicts-two.txt
diff --git a/tests/resources/mergedrepo/.gitted/ORIG_HEAD b/tests/resources/mergedrepo/.gitted/ORIG_HEAD
new file mode 100644
index 000000000..13d4d6721
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/ORIG_HEAD
@@ -0,0 +1 @@
+3a34580a35add43a4cf361e8e9a30060a905c876
diff --git a/tests/resources/mergedrepo/.gitted/config b/tests/resources/mergedrepo/.gitted/config
new file mode 100644
index 000000000..af107929f
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/config
@@ -0,0 +1,6 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
diff --git a/tests/resources/mergedrepo/.gitted/description b/tests/resources/mergedrepo/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/mergedrepo/.gitted/index b/tests/resources/mergedrepo/.gitted/index
new file mode 100644
index 000000000..3d29f78e7
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/index
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/info/exclude b/tests/resources/mergedrepo/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/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/resources/mergedrepo/.gitted/logs/HEAD b/tests/resources/mergedrepo/.gitted/logs/HEAD
new file mode 100644
index 000000000..a385da67b
--- /dev/null
+++ b/tests/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/resources/mergedrepo/.gitted/logs/refs/heads/branch b/tests/resources/mergedrepo/.gitted/logs/refs/heads/branch
new file mode 100644
index 000000000..26a5e8dc5
--- /dev/null
+++ b/tests/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/resources/mergedrepo/.gitted/logs/refs/heads/master b/tests/resources/mergedrepo/.gitted/logs/refs/heads/master
new file mode 100644
index 000000000..425f7bd89
--- /dev/null
+++ b/tests/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/resources/mergedrepo/.gitted/objects/03/db1d37504ca0c4f7c26d7776b0e28bdea08712 b/tests/resources/mergedrepo/.gitted/objects/03/db1d37504ca0c4f7c26d7776b0e28bdea08712
new file mode 100644
index 000000000..9232f79d9
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/03/db1d37504ca0c4f7c26d7776b0e28bdea08712
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/17/0efc1023e0ed2390150bb4469c8456b63e8f91 b/tests/resources/mergedrepo/.gitted/objects/17/0efc1023e0ed2390150bb4469c8456b63e8f91
new file mode 100644
index 000000000..3e124d9a4
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/17/0efc1023e0ed2390150bb4469c8456b63e8f91
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/1f/85ca51b8e0aac893a621b61a9c2661d6aa6d81 b/tests/resources/mergedrepo/.gitted/objects/1f/85ca51b8e0aac893a621b61a9c2661d6aa6d81
new file mode 100644
index 000000000..7bb19c873
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/1f/85ca51b8e0aac893a621b61a9c2661d6aa6d81
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/22/0bd62631c8cf7a83ef39c6b94595f00517211e b/tests/resources/mergedrepo/.gitted/objects/22/0bd62631c8cf7a83ef39c6b94595f00517211e
new file mode 100644
index 000000000..487bcffb1
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/22/0bd62631c8cf7a83ef39c6b94595f00517211e
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/32/d55d59265db86dd690f0a7fc563db43e2bc6a6 b/tests/resources/mergedrepo/.gitted/objects/32/d55d59265db86dd690f0a7fc563db43e2bc6a6
new file mode 100644
index 000000000..2eb3954fc
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/32/d55d59265db86dd690f0a7fc563db43e2bc6a6
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/38/e2d82b9065a237904af4b780b4d68da6950534 b/tests/resources/mergedrepo/.gitted/objects/38/e2d82b9065a237904af4b780b4d68da6950534
new file mode 100644
index 000000000..ebe83ccb2
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/38/e2d82b9065a237904af4b780b4d68da6950534
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/3a/34580a35add43a4cf361e8e9a30060a905c876 b/tests/resources/mergedrepo/.gitted/objects/3a/34580a35add43a4cf361e8e9a30060a905c876
new file mode 100644
index 000000000..0d4095ffc
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/3a/34580a35add43a4cf361e8e9a30060a905c876
@@ -0,0 +1,2 @@
+x¥ŽK
+1D]ç}¥óíDÜx/Oã"F2¯ooà®ê<*·Zo”‘»Ñ™uI²h²hrÄlÊÊ"r YùT8¢'©Ä#v¾mÎÉ0.Áø¨¥òŒÁ.:”È.#+³ñ9ÖÖáR^±¸®­níGžô“Îü~í[=ÔVjRìÑ"ŠIçÙÁjDÛ”ˆ7|N` \ No newline at end of file
diff --git a/tests/resources/mergedrepo/.gitted/objects/44/58b8bc9e72b6c8755ae456f60e9844d0538d8c b/tests/resources/mergedrepo/.gitted/objects/44/58b8bc9e72b6c8755ae456f60e9844d0538d8c
new file mode 100644
index 000000000..33389c302
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/44/58b8bc9e72b6c8755ae456f60e9844d0538d8c
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/47/8871385b9cd03908c5383acfd568bef023c6b3 b/tests/resources/mergedrepo/.gitted/objects/47/8871385b9cd03908c5383acfd568bef023c6b3
new file mode 100644
index 000000000..5361ea685
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/47/8871385b9cd03908c5383acfd568bef023c6b3
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/51/6bd85f78061e09ccc714561d7b504672cb52da b/tests/resources/mergedrepo/.gitted/objects/51/6bd85f78061e09ccc714561d7b504672cb52da
new file mode 100644
index 000000000..a60da877c
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/51/6bd85f78061e09ccc714561d7b504672cb52da
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/53/c1d95a01f4514b162066fc98564500c96c46ad b/tests/resources/mergedrepo/.gitted/objects/53/c1d95a01f4514b162066fc98564500c96c46ad
new file mode 100644
index 000000000..85e84d71e
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/53/c1d95a01f4514b162066fc98564500c96c46ad
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/6a/ea5f295304c36144ad6e9247a291b7f8112399 b/tests/resources/mergedrepo/.gitted/objects/6a/ea5f295304c36144ad6e9247a291b7f8112399
new file mode 100644
index 000000000..b16b521e6
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/6a/ea5f295304c36144ad6e9247a291b7f8112399
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/70/68e30a7f0090ae32db35dfa1e4189d8780fcb8 b/tests/resources/mergedrepo/.gitted/objects/70/68e30a7f0090ae32db35dfa1e4189d8780fcb8
new file mode 100644
index 000000000..7c4e85ffb
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/70/68e30a7f0090ae32db35dfa1e4189d8780fcb8
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/75/938de1e367098b3e9a7b1ec3c4ac4548afffe4 b/tests/resources/mergedrepo/.gitted/objects/75/938de1e367098b3e9a7b1ec3c4ac4548afffe4
new file mode 100644
index 000000000..65173fc4d
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/75/938de1e367098b3e9a7b1ec3c4ac4548afffe4
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/7b/26923aaf452b1977eb08617c59475fb3f74b71 b/tests/resources/mergedrepo/.gitted/objects/7b/26923aaf452b1977eb08617c59475fb3f74b71
new file mode 100644
index 000000000..162fa4455
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/7b/26923aaf452b1977eb08617c59475fb3f74b71
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/84/af62840be1b1c47b778a8a249f3ff45155038c b/tests/resources/mergedrepo/.gitted/objects/84/af62840be1b1c47b778a8a249f3ff45155038c
new file mode 100644
index 000000000..77a519f55
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/84/af62840be1b1c47b778a8a249f3ff45155038c
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/88/71f7a2ee3addfc4ba39fbd0783c8e738d04cda b/tests/resources/mergedrepo/.gitted/objects/88/71f7a2ee3addfc4ba39fbd0783c8e738d04cda
new file mode 100644
index 000000000..f624cd4f1
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/88/71f7a2ee3addfc4ba39fbd0783c8e738d04cda
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/88/7b153b165d32409c70163e0f734c090f12f673 b/tests/resources/mergedrepo/.gitted/objects/88/7b153b165d32409c70163e0f734c090f12f673
new file mode 100644
index 000000000..096474c03
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/88/7b153b165d32409c70163e0f734c090f12f673
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/8a/ad34cc83733590e74b93d0f7cf00375e2a735a b/tests/resources/mergedrepo/.gitted/objects/8a/ad34cc83733590e74b93d0f7cf00375e2a735a
new file mode 100644
index 000000000..a413bc6b0
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/8a/ad34cc83733590e74b93d0f7cf00375e2a735a
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/8b/3f43d2402825c200f835ca1762413e386fd0b2 b/tests/resources/mergedrepo/.gitted/objects/8b/3f43d2402825c200f835ca1762413e386fd0b2
new file mode 100644
index 000000000..3ac8f6018
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/8b/3f43d2402825c200f835ca1762413e386fd0b2
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/8b/72416545c7e761b64cecad4f1686eae4078aa8 b/tests/resources/mergedrepo/.gitted/objects/8b/72416545c7e761b64cecad4f1686eae4078aa8
new file mode 100644
index 000000000..589a5ae9b
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/8b/72416545c7e761b64cecad4f1686eae4078aa8
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/8f/3c06cff9a83757cec40c80bc9bf31a2582bde9 b/tests/resources/mergedrepo/.gitted/objects/8f/3c06cff9a83757cec40c80bc9bf31a2582bde9
new file mode 100644
index 000000000..6503985e3
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/8f/3c06cff9a83757cec40c80bc9bf31a2582bde9
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/8f/fcc405925511824a2240a6d3686aa7f8c7ac50 b/tests/resources/mergedrepo/.gitted/objects/8f/fcc405925511824a2240a6d3686aa7f8c7ac50
new file mode 100644
index 000000000..2eaa80838
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/8f/fcc405925511824a2240a6d3686aa7f8c7ac50
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/9a/05ccb4e0f948de03128e095f39dae6976751c5 b/tests/resources/mergedrepo/.gitted/objects/9a/05ccb4e0f948de03128e095f39dae6976751c5
new file mode 100644
index 000000000..7373a80d8
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/9a/05ccb4e0f948de03128e095f39dae6976751c5
@@ -0,0 +1 @@
+x¥Ñ !Dý¦Šm@³Ë ÉÅøc6Àq#‘#AŒí‹Æü›y/™ µ”Üœ:ô#$–l•tH:é—„*D³XÖh¬VœáŸ}« ®ëË·n[-ºÃý¤KüŠ_;…ZÎ@“¦‰ÉJ GÔˆbÐqÞãŸ3"ï¹goŒ«@I \ No newline at end of file
diff --git a/tests/resources/mergedrepo/.gitted/objects/9d/81f82fccc7dcd7de7a1ffead1815294c2e092c b/tests/resources/mergedrepo/.gitted/objects/9d/81f82fccc7dcd7de7a1ffead1815294c2e092c
new file mode 100644
index 000000000..c5a651f97
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/9d/81f82fccc7dcd7de7a1ffead1815294c2e092c
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/b7/cedb8ad4cbb22b6363f9578cbd749797f7ef0d b/tests/resources/mergedrepo/.gitted/objects/b7/cedb8ad4cbb22b6363f9578cbd749797f7ef0d
new file mode 100644
index 000000000..3e14b5dc8
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/b7/cedb8ad4cbb22b6363f9578cbd749797f7ef0d
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/d0/1885ea594926eae9ba5b54ad76692af5969f51 b/tests/resources/mergedrepo/.gitted/objects/d0/1885ea594926eae9ba5b54ad76692af5969f51
new file mode 100644
index 000000000..a641adc2e
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/d0/1885ea594926eae9ba5b54ad76692af5969f51
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/e2/809157a7766f272e4cfe26e61ef2678a5357ff b/tests/resources/mergedrepo/.gitted/objects/e2/809157a7766f272e4cfe26e61ef2678a5357ff
new file mode 100644
index 000000000..fa86662e0
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/e2/809157a7766f272e4cfe26e61ef2678a5357ff
@@ -0,0 +1,3 @@
+x¥ŽK
+1D]ç¹€Òùt> âÆxžN‡q1‰¯ï(ÞÀ]Õ{P·e¹ m½Ù.¢S­Ì0[Dc’õd­
+Å…ˆbM‰Ôº¬Cgdž¼@Í>glÈX].$!ÇÑ0*zŽ¹u})/êE_ç¶<Úª²ÑO:ËWüÚÛrÒÆ¡qѤhõ@mt;;äÏ5uZyVoÓ\Mÿ \ No newline at end of file
diff --git a/tests/resources/mergedrepo/.gitted/objects/e6/2cac5c88b9928f2695b934c70efa4285324478 b/tests/resources/mergedrepo/.gitted/objects/e6/2cac5c88b9928f2695b934c70efa4285324478
new file mode 100644
index 000000000..c9841c698
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/e6/2cac5c88b9928f2695b934c70efa4285324478
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/objects/f7/2784290c151092abf04ce6b875068547f70406 b/tests/resources/mergedrepo/.gitted/objects/f7/2784290c151092abf04ce6b875068547f70406
new file mode 100644
index 000000000..cd587dbec
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/objects/f7/2784290c151092abf04ce6b875068547f70406
Binary files differ
diff --git a/tests/resources/mergedrepo/.gitted/refs/heads/branch b/tests/resources/mergedrepo/.gitted/refs/heads/branch
new file mode 100644
index 000000000..a5bdf6e40
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/refs/heads/branch
@@ -0,0 +1 @@
+e2809157a7766f272e4cfe26e61ef2678a5357ff
diff --git a/tests/resources/mergedrepo/.gitted/refs/heads/master b/tests/resources/mergedrepo/.gitted/refs/heads/master
new file mode 100644
index 000000000..13d4d6721
--- /dev/null
+++ b/tests/resources/mergedrepo/.gitted/refs/heads/master
@@ -0,0 +1 @@
+3a34580a35add43a4cf361e8e9a30060a905c876
diff --git a/tests/resources/mergedrepo/conflicts-one.txt b/tests/resources/mergedrepo/conflicts-one.txt
new file mode 100644
index 000000000..8aad34cc8
--- /dev/null
+++ b/tests/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/resources/mergedrepo/conflicts-two.txt b/tests/resources/mergedrepo/conflicts-two.txt
new file mode 100644
index 000000000..e62cac5c8
--- /dev/null
+++ b/tests/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/resources/mergedrepo/one.txt b/tests/resources/mergedrepo/one.txt
new file mode 100644
index 000000000..75938de1e
--- /dev/null
+++ b/tests/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/resources/mergedrepo/two.txt b/tests/resources/mergedrepo/two.txt
new file mode 100644
index 000000000..7b26923aa
--- /dev/null
+++ b/tests/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/resources/partial-testrepo/.gitted/HEAD b/tests/resources/partial-testrepo/.gitted/HEAD
new file mode 100644
index 000000000..4bfb9c93f
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/dir
diff --git a/tests/resources/partial-testrepo/.gitted/config b/tests/resources/partial-testrepo/.gitted/config
new file mode 100644
index 000000000..99abaab97
--- /dev/null
+++ b/tests/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/resources/partial-testrepo/.gitted/index b/tests/resources/partial-testrepo/.gitted/index
new file mode 100644
index 000000000..4f241f914
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/index
Binary files differ
diff --git a/tests/resources/partial-testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 b/tests/resources/partial-testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08
new file mode 100644
index 000000000..cedb2a22e
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08
Binary files differ
diff --git a/tests/resources/partial-testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e b/tests/resources/partial-testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e
new file mode 100644
index 000000000..b7d944fa1
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e
Binary files differ
diff --git a/tests/resources/partial-testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925 b/tests/resources/partial-testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925
new file mode 100644
index 000000000..d37b93e4f
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925
Binary files differ
diff --git a/tests/resources/partial-testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 b/tests/resources/partial-testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7
new file mode 100644
index 000000000..93a16f146
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7
Binary files differ
diff --git a/tests/resources/partial-testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd b/tests/resources/partial-testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd
new file mode 100644
index 000000000..ba0bfb30c
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd
Binary files differ
diff --git a/tests/resources/partial-testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 b/tests/resources/partial-testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057
new file mode 100644
index 000000000..7ca4ceed5
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057
Binary files differ
diff --git a/tests/resources/partial-testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 b/tests/resources/partial-testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045
new file mode 100644
index 000000000..8953b6cef
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045
@@ -0,0 +1,2 @@
+xŽQ
+Â0DýÎ)öÊ6›Í¦ "xO°‰‰-ØFb¼¿EoàÏ0 ¼Ç¤º,ske×[ÎPn8R,EpD?±gŸ}Ê^3² âÙ<µåµGŽhYKÄèÒ8ЖDAÉ)¿ÉÈ;gôݧÚàšjïp™4ÕŽ¯ô-çû¢óãêr‚ÁŠ;°s°GA4Ûº=ìùÖ(ôin7øIÌKÍFE \ No newline at end of file
diff --git a/tests/resources/partial-testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63 b/tests/resources/partial-testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63
new file mode 100644
index 000000000..e9150214b
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63
Binary files differ
diff --git a/tests/resources/partial-testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 b/tests/resources/partial-testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644
new file mode 100644
index 000000000..c1f22c54f
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644
@@ -0,0 +1,2 @@
+xŽÛ 1EýNi@™Ék2 "X‚$ÙYW0YcÿíÀ¿Ã…s¸¥ÕzïÚÚõMDÏ€0æœ8!¶†ÉÌÞs‰ XŠªgÚdí::@X0»P¢wÙ"F/‰‰œÍRàˆUz÷¥múZZïú²¤ÒV}|•/œo5݇ÒêI£!¬1z Æ:vùÇUim}ê/¢>
+öF- \ No newline at end of file
diff --git a/tests/resources/partial-testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc b/tests/resources/partial-testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc
new file mode 100644
index 000000000..b669961d8
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc
Binary files differ
diff --git a/tests/resources/partial-testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735 b/tests/resources/partial-testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735
new file mode 100644
index 000000000..9ff5eb2b5
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735
Binary files differ
diff --git a/tests/resources/partial-testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a b/tests/resources/partial-testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a
new file mode 100644
index 000000000..2ef4faa0f
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a
Binary files differ
diff --git a/tests/resources/partial-testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d b/tests/resources/partial-testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d
new file mode 100644
index 000000000..2f9b6b6e3
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d
Binary files differ
diff --git a/tests/resources/partial-testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 b/tests/resources/partial-testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479
new file mode 100644
index 000000000..5df58dda5
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479
Binary files differ
diff --git a/tests/resources/partial-testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a b/tests/resources/partial-testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a
new file mode 100644
index 000000000..a79612435
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a
@@ -0,0 +1,3 @@
+xŽ[
+Â0EýÎ*fÊäÕ¤ "¸W0“‡-ØFâtÿÝ—çpS[–YÀ˜x^
+Díb CLhutɉ}¥8X*4Zí¬sY½¨—UÀ‘AÃÖ ÌX3‡R«Mµ¶) s6è¼¢M¦ÖážšÜ&Jm…ó;}Çõ±Ðü<¥¶\@›à‚ÑÞpÄ€¨vº?”ò«jÛºLð«¨Ø?Hå \ No newline at end of file
diff --git a/tests/resources/partial-testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f b/tests/resources/partial-testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f
new file mode 100644
index 000000000..f8588696b
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f
@@ -0,0 +1,2 @@
+x;j1DëmdÓú·À˜ÇŽ|M«µ3`ŒV{ >€³âQ¯ ¸·vL0I?Í!š4–Z=Ê! ×¦8²F¢Ã’!rÖsQßyÈ9]$DŽ&„l6AÇ>jFWüÒµ IKNiûë§Z¢%¡SˆŒ‘
+‹Ò ­ÅʉøU~̽øä>'¼ï™û ¯wþ ×[ËÇ× ÷öÚDGÚ¡±ðŒQ-ºMù«>dܶ‘OÞáÒò}í\à8g_ШÂoYr \ No newline at end of file
diff --git a/tests/resources/partial-testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd b/tests/resources/partial-testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd
new file mode 100644
index 000000000..d0d7e736e
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd
Binary files differ
diff --git a/tests/resources/partial-testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 b/tests/resources/partial-testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6
new file mode 100644
index 000000000..18a7f61c2
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6
Binary files differ
diff --git a/tests/resources/partial-testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd b/tests/resources/partial-testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd
new file mode 100644
index 000000000..75f541f10
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd
@@ -0,0 +1,3 @@
+xŽQ
+Â0DýÎ)öʦ»I<‚'ØlR+˜Fj¼¿EoàÏ0<xÃh«õÞa Üõµ]È™­åXUlÞPF)Åz‘4yó”µ,\r 'SÂÄ-mI4
+‘Xhô”&òÌFÞ}n+\µõ—Y´-p|é·œoUz;-‘aÑlt{ØË?®I«,:ÃoÚR̳cHK \ No newline at end of file
diff --git a/tests/resources/partial-testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9 b/tests/resources/partial-testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9
new file mode 100644
index 000000000..7620c514f
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9
Binary files differ
diff --git a/tests/resources/partial-testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83 b/tests/resources/partial-testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83
new file mode 100644
index 000000000..00940f0f2
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83
Binary files differ
diff --git a/tests/resources/partial-testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 b/tests/resources/partial-testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1
new file mode 100644
index 000000000..03770969a
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1
Binary files differ
diff --git a/tests/resources/partial-testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 b/tests/resources/partial-testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92
new file mode 100644
index 000000000..112998d42
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92
Binary files differ
diff --git a/tests/resources/partial-testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 b/tests/resources/partial-testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765
new file mode 100644
index 000000000..12bf5f3e3
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765
Binary files differ
diff --git a/tests/resources/partial-testrepo/.gitted/objects/pack/.gitkeep b/tests/resources/partial-testrepo/.gitted/objects/pack/.gitkeep
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/objects/pack/.gitkeep
diff --git a/tests/resources/partial-testrepo/.gitted/refs/heads/dir b/tests/resources/partial-testrepo/.gitted/refs/heads/dir
new file mode 100644
index 000000000..4567d37fa
--- /dev/null
+++ b/tests/resources/partial-testrepo/.gitted/refs/heads/dir
@@ -0,0 +1 @@
+144344043ba4d4a405da03de3844aa829ae8be0e
diff --git a/tests/resources/peeled.git/HEAD b/tests/resources/peeled.git/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/peeled.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/peeled.git/config b/tests/resources/peeled.git/config
new file mode 100644
index 000000000..88300524a
--- /dev/null
+++ b/tests/resources/peeled.git/config
@@ -0,0 +1,8 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = true
+[remote "origin"]
+ url = /home/peff/compile/libgit2/tests-clar/resources/peeled
+ fetch = +refs/*:refs/*
+ mirror = true
diff --git a/tests/resources/peeled.git/objects/info/packs b/tests/resources/peeled.git/objects/info/packs
new file mode 100644
index 000000000..0d88b32e5
--- /dev/null
+++ b/tests/resources/peeled.git/objects/info/packs
@@ -0,0 +1,2 @@
+P pack-e84773eaf3fce1774755580e3dbb8d9f3a1adc45.pack
+
diff --git a/tests/resources/peeled.git/objects/pack/pack-e84773eaf3fce1774755580e3dbb8d9f3a1adc45.idx b/tests/resources/peeled.git/objects/pack/pack-e84773eaf3fce1774755580e3dbb8d9f3a1adc45.idx
new file mode 100644
index 000000000..9b79e9b85
--- /dev/null
+++ b/tests/resources/peeled.git/objects/pack/pack-e84773eaf3fce1774755580e3dbb8d9f3a1adc45.idx
Binary files differ
diff --git a/tests/resources/peeled.git/objects/pack/pack-e84773eaf3fce1774755580e3dbb8d9f3a1adc45.pack b/tests/resources/peeled.git/objects/pack/pack-e84773eaf3fce1774755580e3dbb8d9f3a1adc45.pack
new file mode 100644
index 000000000..2459ca287
--- /dev/null
+++ b/tests/resources/peeled.git/objects/pack/pack-e84773eaf3fce1774755580e3dbb8d9f3a1adc45.pack
Binary files differ
diff --git a/tests/resources/peeled.git/packed-refs b/tests/resources/peeled.git/packed-refs
new file mode 100644
index 000000000..ad053d550
--- /dev/null
+++ b/tests/resources/peeled.git/packed-refs
@@ -0,0 +1,6 @@
+# pack-refs with: peeled fully-peeled
+c2596aa0151888587ec5c0187f261e63412d9e11 refs/foo/tag-outside-tags
+^0df1a5865c8abfc09f1f2182e6a31be550e99f07
+0df1a5865c8abfc09f1f2182e6a31be550e99f07 refs/heads/master
+c2596aa0151888587ec5c0187f261e63412d9e11 refs/tags/tag-inside-tags
+^0df1a5865c8abfc09f1f2182e6a31be550e99f07
diff --git a/tests/resources/peeled.git/refs/heads/master b/tests/resources/peeled.git/refs/heads/master
new file mode 100644
index 000000000..76c15e203
--- /dev/null
+++ b/tests/resources/peeled.git/refs/heads/master
@@ -0,0 +1 @@
+0df1a5865c8abfc09f1f2182e6a31be550e99f07
diff --git a/tests/resources/push.sh b/tests/resources/push.sh
new file mode 100644
index 000000000..607117675
--- /dev/null
+++ b/tests/resources/push.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+#creates push_src repo for libgit2 push tests.
+set -eu
+
+#Create src repo for push
+mkdir push_src
+pushd push_src
+ git init
+
+ echo a > a.txt
+ git add .
+ git commit -m 'added a.txt'
+
+ mkdir fold
+ echo b > fold/b.txt
+ git add .
+ git commit -m 'added fold and fold/b.txt'
+
+ git branch b1 #b1 and b2 are the same
+ git branch b2
+
+ git checkout -b b3
+ echo edit >> a.txt
+ git add .
+ git commit -m 'edited a.txt'
+
+ git checkout -b b4 master
+ echo edit >> fold\b.txt
+ git add .
+ git commit -m 'edited fold\b.txt'
+
+ git checkout -b b5 master
+ git submodule add ../testrepo.git submodule
+ git commit -m "added submodule named 'submodule' pointing to '../testrepo.git'"
+
+ git checkout master
+ git merge -m "merge b3, b4, and b5 to master" b3 b4 b5
+
+ #Log commits to include in testcase
+ git log --format=oneline --decorate --graph
+ #*-. 951bbbb90e2259a4c8950db78946784fb53fcbce (HEAD, master) merge b3, b4, and b5 to master
+ #|\ \
+ #| | * fa38b91f199934685819bea316186d8b008c52a2 (b5) added submodule named 'submodule' pointing to '../testrepo.git'
+ #| * | 27b7ce66243eb1403862d05f958c002312df173d (b4) edited fold\b.txt
+ #| |/
+ #* | d9b63a88223d8367516f50bd131a5f7349b7f3e4 (b3) edited a.txt
+ #|/
+ #* a78705c3b2725f931d3ee05348d83cc26700f247 (b2, b1) added fold and fold/b.txt
+ #* 5c0bb3d1b9449d1cc69d7519fd05166f01840915 added a.txt
+
+ #fix paths so that we can add repo folders under libgit2 repo
+ #rename .git to .gitted
+ find . -name .git -exec mv -i '{}' '{}ted' \;
+ mv -i .gitmodules gitmodules
+popd
diff --git a/tests/resources/push_src/.gitted/COMMIT_EDITMSG b/tests/resources/push_src/.gitted/COMMIT_EDITMSG
new file mode 100644
index 000000000..b1295084c
--- /dev/null
+++ b/tests/resources/push_src/.gitted/COMMIT_EDITMSG
@@ -0,0 +1 @@
+added submodule named 'submodule' pointing to '../testrepo.git'
diff --git a/tests/resources/push_src/.gitted/HEAD b/tests/resources/push_src/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/push_src/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/push_src/.gitted/ORIG_HEAD b/tests/resources/push_src/.gitted/ORIG_HEAD
new file mode 100644
index 000000000..afadf9d26
--- /dev/null
+++ b/tests/resources/push_src/.gitted/ORIG_HEAD
@@ -0,0 +1 @@
+a78705c3b2725f931d3ee05348d83cc26700f247
diff --git a/tests/resources/push_src/.gitted/config b/tests/resources/push_src/.gitted/config
new file mode 100644
index 000000000..51de0311b
--- /dev/null
+++ b/tests/resources/push_src/.gitted/config
@@ -0,0 +1,10 @@
+[core]
+ repositoryformatversion = 0
+ filemode = false
+ bare = false
+ logallrefupdates = true
+ symlinks = false
+ ignorecase = true
+ hideDotFiles = dotGitOnly
+[submodule "submodule"]
+ url = m:/dd/libgit2/tests-clar/resources/testrepo.git
diff --git a/tests/resources/push_src/.gitted/description b/tests/resources/push_src/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/push_src/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/push_src/.gitted/index b/tests/resources/push_src/.gitted/index
new file mode 100644
index 000000000..0ef6594b3
--- /dev/null
+++ b/tests/resources/push_src/.gitted/index
Binary files differ
diff --git a/tests/resources/push_src/.gitted/info/exclude b/tests/resources/push_src/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/push_src/.gitted/info/exclude
@@ -0,0 +1,6 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
diff --git a/tests/resources/push_src/.gitted/logs/HEAD b/tests/resources/push_src/.gitted/logs/HEAD
new file mode 100644
index 000000000..4ef336f84
--- /dev/null
+++ b/tests/resources/push_src/.gitted/logs/HEAD
@@ -0,0 +1,10 @@
+0000000000000000000000000000000000000000 5c0bb3d1b9449d1cc69d7519fd05166f01840915 Congyi Wu <congyiwu@gmail.com> 1352923200 -0500 commit (initial): added a.txt
+5c0bb3d1b9449d1cc69d7519fd05166f01840915 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923200 -0500 commit: added fold and fold/b.txt
+a78705c3b2725f931d3ee05348d83cc26700f247 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 checkout: moving from master to b3
+a78705c3b2725f931d3ee05348d83cc26700f247 d9b63a88223d8367516f50bd131a5f7349b7f3e4 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 commit: edited a.txt
+d9b63a88223d8367516f50bd131a5f7349b7f3e4 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 checkout: moving from b3 to b4
+a78705c3b2725f931d3ee05348d83cc26700f247 27b7ce66243eb1403862d05f958c002312df173d Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 commit: edited fold\b.txt
+27b7ce66243eb1403862d05f958c002312df173d a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 checkout: moving from b4 to b5
+a78705c3b2725f931d3ee05348d83cc26700f247 fa38b91f199934685819bea316186d8b008c52a2 Congyi Wu <congyiwu@gmail.com> 1352923206 -0500 commit: added submodule named 'submodule' pointing to '../testrepo.git'
+fa38b91f199934685819bea316186d8b008c52a2 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923207 -0500 checkout: moving from b5 to master
+a78705c3b2725f931d3ee05348d83cc26700f247 951bbbb90e2259a4c8950db78946784fb53fcbce Congyi Wu <congyiwu@gmail.com> 1352923207 -0500 merge b3 b4 b5: Merge made by the 'octopus' strategy.
diff --git a/tests/resources/push_src/.gitted/logs/refs/heads/b1 b/tests/resources/push_src/.gitted/logs/refs/heads/b1
new file mode 100644
index 000000000..390a03d5c
--- /dev/null
+++ b/tests/resources/push_src/.gitted/logs/refs/heads/b1
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923200 -0500 branch: Created from master
diff --git a/tests/resources/push_src/.gitted/logs/refs/heads/b2 b/tests/resources/push_src/.gitted/logs/refs/heads/b2
new file mode 100644
index 000000000..390a03d5c
--- /dev/null
+++ b/tests/resources/push_src/.gitted/logs/refs/heads/b2
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923200 -0500 branch: Created from master
diff --git a/tests/resources/push_src/.gitted/logs/refs/heads/b3 b/tests/resources/push_src/.gitted/logs/refs/heads/b3
new file mode 100644
index 000000000..01e302c44
--- /dev/null
+++ b/tests/resources/push_src/.gitted/logs/refs/heads/b3
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 branch: Created from HEAD
+a78705c3b2725f931d3ee05348d83cc26700f247 d9b63a88223d8367516f50bd131a5f7349b7f3e4 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 commit: edited a.txt
diff --git a/tests/resources/push_src/.gitted/logs/refs/heads/b4 b/tests/resources/push_src/.gitted/logs/refs/heads/b4
new file mode 100644
index 000000000..7afddc54e
--- /dev/null
+++ b/tests/resources/push_src/.gitted/logs/refs/heads/b4
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 branch: Created from master
+a78705c3b2725f931d3ee05348d83cc26700f247 27b7ce66243eb1403862d05f958c002312df173d Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 commit: edited fold\b.txt
diff --git a/tests/resources/push_src/.gitted/logs/refs/heads/b5 b/tests/resources/push_src/.gitted/logs/refs/heads/b5
new file mode 100644
index 000000000..bc22567f7
--- /dev/null
+++ b/tests/resources/push_src/.gitted/logs/refs/heads/b5
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 branch: Created from master
+a78705c3b2725f931d3ee05348d83cc26700f247 fa38b91f199934685819bea316186d8b008c52a2 Congyi Wu <congyiwu@gmail.com> 1352923206 -0500 commit: added submodule named 'submodule' pointing to '../testrepo.git'
diff --git a/tests/resources/push_src/.gitted/logs/refs/heads/master b/tests/resources/push_src/.gitted/logs/refs/heads/master
new file mode 100644
index 000000000..8aafa9ca4
--- /dev/null
+++ b/tests/resources/push_src/.gitted/logs/refs/heads/master
@@ -0,0 +1,3 @@
+0000000000000000000000000000000000000000 5c0bb3d1b9449d1cc69d7519fd05166f01840915 Congyi Wu <congyiwu@gmail.com> 1352923200 -0500 commit (initial): added a.txt
+5c0bb3d1b9449d1cc69d7519fd05166f01840915 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923200 -0500 commit: added fold and fold/b.txt
+a78705c3b2725f931d3ee05348d83cc26700f247 951bbbb90e2259a4c8950db78946784fb53fcbce Congyi Wu <congyiwu@gmail.com> 1352923207 -0500 merge b3 b4 b5: Merge made by the 'octopus' strategy.
diff --git a/tests/resources/push_src/.gitted/modules/submodule/HEAD b/tests/resources/push_src/.gitted/modules/submodule/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/push_src/.gitted/modules/submodule/config b/tests/resources/push_src/.gitted/modules/submodule/config
new file mode 100644
index 000000000..59810077d
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/config
@@ -0,0 +1,15 @@
+[core]
+ repositoryformatversion = 0
+ filemode = false
+ bare = false
+ logallrefupdates = true
+ worktree = ../../../submodule
+ symlinks = false
+ ignorecase = true
+ hideDotFiles = dotGitOnly
+[remote "origin"]
+ fetch = +refs/heads/*:refs/remotes/origin/*
+ url = m:/dd/libgit2/tests-clar/resources/testrepo.git
+[branch "master"]
+ remote = origin
+ merge = refs/heads/master
diff --git a/tests/resources/push_src/.gitted/modules/submodule/description b/tests/resources/push_src/.gitted/modules/submodule/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/push_src/.gitted/modules/submodule/index b/tests/resources/push_src/.gitted/modules/submodule/index
new file mode 100644
index 000000000..8e44080f3
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/index
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/info/exclude b/tests/resources/push_src/.gitted/modules/submodule/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/info/exclude
@@ -0,0 +1,6 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
diff --git a/tests/resources/push_src/.gitted/modules/submodule/logs/HEAD b/tests/resources/push_src/.gitted/modules/submodule/logs/HEAD
new file mode 100644
index 000000000..aedcdf295
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/logs/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Congyi Wu <congyiwu@gmail.com> 1352923205 -0500 clone: from m:/dd/libgit2/tests-clar/resources/testrepo.git
diff --git a/tests/resources/push_src/.gitted/modules/submodule/logs/refs/heads/master b/tests/resources/push_src/.gitted/modules/submodule/logs/refs/heads/master
new file mode 100644
index 000000000..aedcdf295
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/logs/refs/heads/master
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Congyi Wu <congyiwu@gmail.com> 1352923205 -0500 clone: from m:/dd/libgit2/tests-clar/resources/testrepo.git
diff --git a/tests/resources/push_src/.gitted/modules/submodule/logs/refs/remotes/origin/HEAD b/tests/resources/push_src/.gitted/modules/submodule/logs/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..aedcdf295
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/logs/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Congyi Wu <congyiwu@gmail.com> 1352923205 -0500 clone: from m:/dd/libgit2/tests-clar/resources/testrepo.git
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/08/b041783f40edfe12bb406c9c9a8a040177c125 b/tests/resources/push_src/.gitted/modules/submodule/objects/08/b041783f40edfe12bb406c9c9a8a040177c125
new file mode 100644
index 000000000..d1c032fce
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/08/b041783f40edfe12bb406c9c9a8a040177c125
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 b/tests/resources/push_src/.gitted/modules/submodule/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08
new file mode 100644
index 000000000..cedb2a22e
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 b/tests/resources/push_src/.gitted/modules/submodule/objects/18/1037049a54a1eb5fab404658a3a250b44335d7
new file mode 100644
index 000000000..93a16f146
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/18/1037049a54a1eb5fab404658a3a250b44335d7
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/18/10dff58d8a660512d4832e740f692884338ccd b/tests/resources/push_src/.gitted/modules/submodule/objects/18/10dff58d8a660512d4832e740f692884338ccd
new file mode 100644
index 000000000..ba0bfb30c
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/18/10dff58d8a660512d4832e740f692884338ccd
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fd b/tests/resources/push_src/.gitted/modules/submodule/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fd
new file mode 100644
index 000000000..3ec541288
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fd
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/1b/8cbad43e867676df601306689fe7c3def5e689 b/tests/resources/push_src/.gitted/modules/submodule/objects/1b/8cbad43e867676df601306689fe7c3def5e689
new file mode 100644
index 000000000..6048d4bad
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/1b/8cbad43e867676df601306689fe7c3def5e689
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b b/tests/resources/push_src/.gitted/modules/submodule/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b
new file mode 100644
index 000000000..225c45734
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10 b/tests/resources/push_src/.gitted/modules/submodule/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10
new file mode 100644
index 000000000..cb1ed5712
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d b/tests/resources/push_src/.gitted/modules/submodule/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d
new file mode 100644
index 000000000..df40d99af
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7 b/tests/resources/push_src/.gitted/modules/submodule/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7
new file mode 100644
index 000000000..0a1500a6f
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 b/tests/resources/push_src/.gitted/modules/submodule/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54
new file mode 100644
index 000000000..321eaa867
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc b/tests/resources/push_src/.gitted/modules/submodule/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc
new file mode 100644
index 000000000..9bb5b623b
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 b/tests/resources/push_src/.gitted/modules/submodule/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057
new file mode 100644
index 000000000..7ca4ceed5
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 b/tests/resources/push_src/.gitted/modules/submodule/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045
new file mode 100644
index 000000000..8953b6cef
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045
@@ -0,0 +1,2 @@
+xŽQ
+Â0DýÎ)öÊ6›Í¦ "xO°‰‰-ØFb¼¿EoàÏ0 ¼Ç¤º,ske×[ÎPn8R,EpD?±gŸ}Ê^3² âÙ<µåµGŽhYKÄèÒ8ЖDAÉ)¿ÉÈ;gôݧÚàšjïp™4ÕŽ¯ô-çû¢óãêr‚ÁŠ;°s°GA4Ûº=ìùÖ(ôin7øIÌKÍFE \ No newline at end of file
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/4b/22b35d44b5a4f589edf3dc89196399771796ea b/tests/resources/push_src/.gitted/modules/submodule/objects/4b/22b35d44b5a4f589edf3dc89196399771796ea
new file mode 100644
index 000000000..b4e5aa186
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/4b/22b35d44b5a4f589edf3dc89196399771796ea
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91 b/tests/resources/push_src/.gitted/modules/submodule/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91
new file mode 100644
index 000000000..351cff823
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 b/tests/resources/push_src/.gitted/modules/submodule/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644
new file mode 100644
index 000000000..c1f22c54f
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644
@@ -0,0 +1,2 @@
+xŽÛ 1EýNi@™Ék2 "X‚$ÙYW0YcÿíÀ¿Ã…s¸¥ÕzïÚÚõMDÏ€0æœ8!¶†ÉÌÞs‰ XŠªgÚdí::@X0»P¢wÙ"F/‰‰œÍRàˆUz÷¥múZZïú²¤ÒV}|•/œo5݇ÒêI£!¬1z Æ:vùÇUim}ê/¢>
+öF- \ No newline at end of file
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a b/tests/resources/push_src/.gitted/modules/submodule/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a
new file mode 100644
index 000000000..2ef4faa0f
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af b/tests/resources/push_src/.gitted/modules/submodule/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af
new file mode 100644
index 000000000..716b0c64b
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af
@@ -0,0 +1 @@
+xŽAj!³ö?0¨£ßÂ09Êo}HÚ6¨}ÿôjUPP©ÕZ&Yÿø˜ AÔ›±€pŒÁFdë¼÷pz[fŽYŒ½PÒqLJ.,Z§`™Å®Ð.ù`’vÙ ³q $Æ5+9çOëtœû>Û/úDE/龡W¯ï*e¿§VŸdf1>ð覭Öê²×äÄ›¹úÊ™F« ­ìTŽÙhœk.i¶^0Ô?P¼R, \ No newline at end of file
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/7b/4384978d2493e851f9cca7858815fac9b10980 b/tests/resources/push_src/.gitted/modules/submodule/objects/7b/4384978d2493e851f9cca7858815fac9b10980
new file mode 100644
index 000000000..23c462f34
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/7b/4384978d2493e851f9cca7858815fac9b10980
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/81/4889a078c031f61ed08ab5fa863aea9314344d b/tests/resources/push_src/.gitted/modules/submodule/objects/81/4889a078c031f61ed08ab5fa863aea9314344d
new file mode 100644
index 000000000..2f9b6b6e3
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/81/4889a078c031f61ed08ab5fa863aea9314344d
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/84/96071c1b46c854b31185ea97743be6a8774479 b/tests/resources/push_src/.gitted/modules/submodule/objects/84/96071c1b46c854b31185ea97743be6a8774479
new file mode 100644
index 000000000..5df58dda5
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/84/96071c1b46c854b31185ea97743be6a8774479
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe b/tests/resources/push_src/.gitted/modules/submodule/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe
new file mode 100644
index 000000000..71019a636
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe
@@ -0,0 +1,2 @@
+xŒM F]sŠ¹€†Ÿ41ÆxÝ(­I‹ÁéÂÛKݽ/_ÞãP@¡ÚÕø¢!8›)es
+” ¥N&FGSÆ„¹hÑ{+ßCç‰÷ÆZzvØF¡7ZàÎ-¬Îñó‡k™x\ã¡[PÆ8ï´ôGØK/¥^© lÊ>.4 \ No newline at end of file
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 b/tests/resources/push_src/.gitted/modules/submodule/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162
new file mode 100644
index 000000000..4cc3f4dff
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162
@@ -0,0 +1 @@
+x+)JMU044b040031QrutñueX¡l¨ðmmA‹m›Ì£íJ}Gß;U‘T”˜—œŸ–™“ªWRQÂ`6ýš÷KÇ¥¶^/¾-*|òøWØ¥3P¥y©å`%ËEÛÞ±\&gŽÐ|Ÿ0§ÿ†{Ó1X \ No newline at end of file
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 b/tests/resources/push_src/.gitted/modules/submodule/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4
new file mode 100644
index 000000000..bf7b2bb68
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d2 b/tests/resources/push_src/.gitted/modules/submodule/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d2
new file mode 100644
index 000000000..7f1cfb23c
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d2
@@ -0,0 +1,2 @@
+xŽM
+Â0F]ç³d2¤ñ®<A~&´`­ÄôþVàæãmïËë²ÌÈÒ¡7Uà$äJöL9yM!¢GuœªH¤&UÈæ›>;ÔÂÁ…¬£³X†ÂEÈŽ5R±£ ÛAÑE &n}ZÜæ<E}À=O[ÒÖáüÞéúÓ¼^À,ã^†#¢É¿ƒ]ÿPÍ`>™A¹ \ No newline at end of file
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a b/tests/resources/push_src/.gitted/modules/submodule/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a
new file mode 100644
index 000000000..a79612435
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a
@@ -0,0 +1,3 @@
+xŽ[
+Â0EýÎ*fÊäÕ¤ "¸W0“‡-ØFâtÿÝ—çpS[–YÀ˜x^
+Díb CLhutɉ}¥8X*4Zí¬sY½¨—UÀ‘AÃÖ ÌX3‡R«Mµ¶) s6è¼¢M¦ÖážšÜ&Jm…ó;}Çõ±Ðü<¥¶\@›à‚ÑÞpÄ€¨vº?”ò«jÛºLð«¨Ø?Hå \ No newline at end of file
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f b/tests/resources/push_src/.gitted/modules/submodule/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f
new file mode 100644
index 000000000..f8588696b
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f
@@ -0,0 +1,2 @@
+x;j1DëmdÓú·À˜ÇŽ|M«µ3`ŒV{ >€³âQ¯ ¸·vL0I?Í!š4–Z=Ê! ×¦8²F¢Ã’!rÖsQßyÈ9]$DŽ&„l6AÇ>jFWüÒµ IKNiûë§Z¢%¡SˆŒ‘
+‹Ò ­ÅʉøU~̽øä>'¼ï™û ¯wþ ×[ËÇ× ÷öÚDGÚ¡±ðŒQ-ºMù«>dܶ‘OÞáÒò}í\à8g_ШÂoYr \ No newline at end of file
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 b/tests/resources/push_src/.gitted/modules/submodule/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750
new file mode 100644
index 000000000..29c8e824d
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750
@@ -0,0 +1,3 @@
+xŽQ
+!@ûösBQ"‚ŽÐ ÆÙ± rÍîßÒú{<xð¤·öàîƪ
+™HlJSer!ZPTe*Žj°UÝÑEo^¼ê2 (†ˆ¬XSÅ€ED‘ƒO<Y¦šj$2üs_á&} ¸Î,}Ó[~p¹7~<ÒÛ:Ÿ £°·ÉZ³Ùípè?­1_ûåC0 \ No newline at end of file
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd b/tests/resources/push_src/.gitted/modules/submodule/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd
new file mode 100644
index 000000000..d0d7e736e
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 b/tests/resources/push_src/.gitted/modules/submodule/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6
new file mode 100644
index 000000000..18a7f61c2
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/ae/90f12eea699729ed24555e40b9fd669da12a12 b/tests/resources/push_src/.gitted/modules/submodule/objects/ae/90f12eea699729ed24555e40b9fd669da12a12
new file mode 100644
index 000000000..d95254674
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/ae/90f12eea699729ed24555e40b9fd669da12a12
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 b/tests/resources/push_src/.gitted/modules/submodule/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1
new file mode 100644
index 000000000..f460f2547
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1
@@ -0,0 +1,2 @@
+xÌA
+Â0…a×9ÅìIÒ ™€ˆp'î§1Ѷ‘v\x{cáýðVŸpƒvWûgŠ¾ÇŽ0xº[ ]"g†#{rDÆ Cot ­äûN œU $­ò?9-p+1Í^¤ÀQx®¯Ï9O\ÆC¬Ó Œm'D {mµVêú(+´ñælè¶,Þ \ No newline at end of file
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593 b/tests/resources/push_src/.gitted/modules/submodule/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593
new file mode 100644
index 000000000..f613670e2
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 b/tests/resources/push_src/.gitted/modules/submodule/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644
new file mode 100644
index 000000000..0817229bc
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644
@@ -0,0 +1,3 @@
+xKj1D³Ö)zçUBëÛ-0ÁuV9¦Õò<#£È÷ÏȲ+ŠW<Jú¶Ý&8Ê/s¨‚e‹µµÈ•KJ­«½S
+ØRvÌÁ{©æQ†îr«äY¹QN$H\Eµ²Íè=6áX5¦òÇK Fr)·(‰dC‡Î†”­–œ—jÊs®}À—ô9ác-Òw8Ëo¸\·r»¿IßÞÁ:
+l}F‚W$Ds´Ç£©ÿÙšOW…e”]V8-ÃÌÈ"U \ No newline at end of file
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd b/tests/resources/push_src/.gitted/modules/submodule/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd
new file mode 100644
index 000000000..75f541f10
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd
@@ -0,0 +1,3 @@
+xŽQ
+Â0DýÎ)öʦ»I<‚'ØlR+˜Fj¼¿EoàÏ0<xÃh«õÞa Üõµ]È™­åXUlÞPF)Åz‘4yó”µ,\r 'SÂÄ-mI4
+‘Xhô”&òÌFÞ}n+\µõ—Y´-p|é·œoUz;-‘aÑlt{ØË?®I«,:ÃoÚR̳cHK \ No newline at end of file
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/d0/7b0f9a8c89f1d9e74dc4fce6421dec5ef8a659 b/tests/resources/push_src/.gitted/modules/submodule/objects/d0/7b0f9a8c89f1d9e74dc4fce6421dec5ef8a659
new file mode 100644
index 000000000..f3b46b3ca
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/d0/7b0f9a8c89f1d9e74dc4fce6421dec5ef8a659
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f b/tests/resources/push_src/.gitted/modules/submodule/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f
new file mode 100644
index 000000000..a67d6e647
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/d7/1aab4f9b04b45ce09bcaa636a9be6231474759 b/tests/resources/push_src/.gitted/modules/submodule/objects/d7/1aab4f9b04b45ce09bcaa636a9be6231474759
new file mode 100644
index 000000000..2d47e6faf
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/d7/1aab4f9b04b45ce09bcaa636a9be6231474759
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/tests/resources/push_src/.gitted/modules/submodule/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
new file mode 100644
index 000000000..711223894
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0 b/tests/resources/push_src/.gitted/modules/submodule/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0
new file mode 100644
index 000000000..b135eccda
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3 b/tests/resources/push_src/.gitted/modules/submodule/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3
new file mode 100644
index 000000000..82e2790e8
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 b/tests/resources/push_src/.gitted/modules/submodule/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1
new file mode 100644
index 000000000..697c94c92
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/fa/49b077972391ad58037050f2a75f74e3671e92 b/tests/resources/push_src/.gitted/modules/submodule/objects/fa/49b077972391ad58037050f2a75f74e3671e92
new file mode 100644
index 000000000..112998d42
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/fa/49b077972391ad58037050f2a75f74e3671e92
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/fd/093bff70906175335656e6ce6ae05783708765 b/tests/resources/push_src/.gitted/modules/submodule/objects/fd/093bff70906175335656e6ce6ae05783708765
new file mode 100644
index 000000000..12bf5f3e3
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/fd/093bff70906175335656e6ce6ae05783708765
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/fd/4959ce7510db09d4d8217fa2d1780413e05a09 b/tests/resources/push_src/.gitted/modules/submodule/objects/fd/4959ce7510db09d4d8217fa2d1780413e05a09
new file mode 100644
index 000000000..158aef21f
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/fd/4959ce7510db09d4d8217fa2d1780413e05a09
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx b/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx
new file mode 100644
index 000000000..5068f2818
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack b/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack
new file mode 100644
index 000000000..a6a1f3020
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx b/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx
new file mode 100644
index 000000000..94c3c71da
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack b/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack
new file mode 100644
index 000000000..74c7fe4f3
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx b/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx
new file mode 100644
index 000000000..555cfa977
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack b/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack
new file mode 100644
index 000000000..4d539ed0a
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack
Binary files differ
diff --git a/tests/resources/push_src/.gitted/modules/submodule/packed-refs b/tests/resources/push_src/.gitted/modules/submodule/packed-refs
new file mode 100644
index 000000000..506a8607c
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/packed-refs
@@ -0,0 +1,24 @@
+# pack-refs with: peeled
+a4a7dce85cf63874e984719f4fdd239f5145052f refs/remotes/origin/br2
+a4a7dce85cf63874e984719f4fdd239f5145052f refs/remotes/origin/cannot-fetch
+e90810b8df3e80c413d903f631643c716887138d refs/remotes/origin/chomped
+258f0e2a959a364e40ed6603d5d44fbb24765b10 refs/remotes/origin/haacked
+a65fedf39aefe402d3bb6e24df4d4f5fe4547750 refs/remotes/origin/master
+a65fedf39aefe402d3bb6e24df4d4f5fe4547750 refs/remotes/origin/not-good
+41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9 refs/remotes/origin/packed
+4a202b346bb0fb0db7eff3cffeb3c70babbd2045 refs/remotes/origin/packed-test
+763d71aadf09a7951596c9746c024e7eece7c7af refs/remotes/origin/subtrees
+e90810b8df3e80c413d903f631643c716887138d refs/remotes/origin/test
+9fd738e8f7967c078dceed8190330fc8648ee56a refs/remotes/origin/track-local
+e90810b8df3e80c413d903f631643c716887138d refs/remotes/origin/trailing
+521d87c1ec3aef9824daf6d96cc0ae3710766d91 refs/tags/annotated_tag_to_blob
+^1385f264afb75a56a5bec74243be9b367ba4ca08
+7b4384978d2493e851f9cca7858815fac9b10980 refs/tags/e90810b
+^e90810b8df3e80c413d903f631643c716887138d
+849a5e34a26815e821f865b8479f5815a47af0fe refs/tags/hard_tag
+^a65fedf39aefe402d3bb6e24df4d4f5fe4547750
+1385f264afb75a56a5bec74243be9b367ba4ca08 refs/tags/point_to_blob
+b25fa35b38051e4ae45d4222e795f9df2e43f1d1 refs/tags/test
+^e90810b8df3e80c413d903f631643c716887138d
+849a5e34a26815e821f865b8479f5815a47af0fe refs/tags/wrapped_tag
+^a65fedf39aefe402d3bb6e24df4d4f5fe4547750
diff --git a/tests/resources/push_src/.gitted/modules/submodule/refs/heads/master b/tests/resources/push_src/.gitted/modules/submodule/refs/heads/master
new file mode 100644
index 000000000..3d8f0a402
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/refs/heads/master
@@ -0,0 +1 @@
+a65fedf39aefe402d3bb6e24df4d4f5fe4547750
diff --git a/tests/resources/push_src/.gitted/modules/submodule/refs/remotes/origin/HEAD b/tests/resources/push_src/.gitted/modules/submodule/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..6efe28fff
--- /dev/null
+++ b/tests/resources/push_src/.gitted/modules/submodule/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+ref: refs/remotes/origin/master
diff --git a/tests/resources/push_src/.gitted/objects/08/585692ce06452da6f82ae66b90d98b55536fca b/tests/resources/push_src/.gitted/objects/08/585692ce06452da6f82ae66b90d98b55536fca
new file mode 100644
index 000000000..39d126b2b
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/08/585692ce06452da6f82ae66b90d98b55536fca
@@ -0,0 +1 @@
+x+)JMU06f040031QHÔ+©(a¨˜!©”h­õ;A•EÿÛÞö®ƒ3ýZüÝ* \ No newline at end of file
diff --git a/tests/resources/push_src/.gitted/objects/27/b7ce66243eb1403862d05f958c002312df173d b/tests/resources/push_src/.gitted/objects/27/b7ce66243eb1403862d05f958c002312df173d
new file mode 100644
index 000000000..01d63b5c4
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/27/b7ce66243eb1403862d05f958c002312df173d
@@ -0,0 +1,4 @@
+x•ŽM
+Â0F]çsËt¦i<„7ù™Ñ‚m¤¤¨··xw÷àKešÆ
+Dý®." ªâmrš1°Ó@’’ê9Ú>RëûÈž¬y†Eæ
+Á mâHŽì&µ™EÐr7äS¢Þ!*u΄µÞËç2ß>#\V8¤ß|­§ÛÆG“Êt„–-ybÂöhÍF·Uþ/ä±J-|M}Wóã+GK \ No newline at end of file
diff --git a/tests/resources/push_src/.gitted/objects/28/905c54ea45a4bed8d7b90f51bd8bd81eec8840 b/tests/resources/push_src/.gitted/objects/28/905c54ea45a4bed8d7b90f51bd8bd81eec8840
new file mode 100644
index 000000000..dc10f6831
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/28/905c54ea45a4bed8d7b90f51bd8bd81eec8840
Binary files differ
diff --git a/tests/resources/push_src/.gitted/objects/36/6226fb970ac0caa9d3f55967ab01334a548f60 b/tests/resources/push_src/.gitted/objects/36/6226fb970ac0caa9d3f55967ab01334a548f60
new file mode 100644
index 000000000..45c4d9208
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/36/6226fb970ac0caa9d3f55967ab01334a548f60
Binary files differ
diff --git a/tests/resources/push_src/.gitted/objects/36/f79b2846017d3761e0a02d0bccd573e0f90c57 b/tests/resources/push_src/.gitted/objects/36/f79b2846017d3761e0a02d0bccd573e0f90c57
new file mode 100644
index 000000000..0bc57f266
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/36/f79b2846017d3761e0a02d0bccd573e0f90c57
@@ -0,0 +1,2 @@
+x%Œ]
+ƒ0„ûœSì”ÄÍB)}é ¼@bµnÑéíf`f`>ö3(§nÞibˆC°èû¾ë0öhQ6¢BåMv¨‡à2&-ø÷M0Q)+ ®Œêæª tNsÚà¿E*;}àžøJϲN픹­§(th­ÔÖ@#”BŒËºC•?ÉÀTÃ…oÅyk7ÿ \ No newline at end of file
diff --git a/tests/resources/push_src/.gitted/objects/5c/0bb3d1b9449d1cc69d7519fd05166f01840915 b/tests/resources/push_src/.gitted/objects/5c/0bb3d1b9449d1cc69d7519fd05166f01840915
new file mode 100644
index 000000000..883182138
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/5c/0bb3d1b9449d1cc69d7519fd05166f01840915
Binary files differ
diff --git a/tests/resources/push_src/.gitted/objects/61/780798228d17af2d34fce4cfbdf35556832472 b/tests/resources/push_src/.gitted/objects/61/780798228d17af2d34fce4cfbdf35556832472
new file mode 100644
index 000000000..586bf17a4
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/61/780798228d17af2d34fce4cfbdf35556832472
Binary files differ
diff --git a/tests/resources/push_src/.gitted/objects/64/fd55f9b6390202db5e5666fd1fb339089fba4d b/tests/resources/push_src/.gitted/objects/64/fd55f9b6390202db5e5666fd1fb339089fba4d
new file mode 100644
index 000000000..bcaaa91c2
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/64/fd55f9b6390202db5e5666fd1fb339089fba4d
Binary files differ
diff --git a/tests/resources/push_src/.gitted/objects/78/981922613b2afb6025042ff6bd878ac1994e85 b/tests/resources/push_src/.gitted/objects/78/981922613b2afb6025042ff6bd878ac1994e85
new file mode 100644
index 000000000..e814d07b0
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/78/981922613b2afb6025042ff6bd878ac1994e85
Binary files differ
diff --git a/tests/resources/push_src/.gitted/objects/80/5c54522e614f29f70d2413a0470247d8b424ac b/tests/resources/push_src/.gitted/objects/80/5c54522e614f29f70d2413a0470247d8b424ac
new file mode 100644
index 000000000..552670c06
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/80/5c54522e614f29f70d2413a0470247d8b424ac
Binary files differ
diff --git a/tests/resources/push_src/.gitted/objects/95/1bbbb90e2259a4c8950db78946784fb53fcbce b/tests/resources/push_src/.gitted/objects/95/1bbbb90e2259a4c8950db78946784fb53fcbce
new file mode 100644
index 000000000..596cd43de
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/95/1bbbb90e2259a4c8950db78946784fb53fcbce
@@ -0,0 +1,2 @@
+x•[JÆ0…}î*f¿2—Ì$ÁEøœi’úƒm¥¶ˆ»·
+úîÛáã\8ã:Ï×DôfßZ ½ªöì&¹º65³^©»œ,åî%Ôá­lmÙ¡~;KJÌR“XT²®è•„Šö(!{ìÒ¯Ÿ£Ç±™qæP’qÅsPÓˆÈB\;EùëïE’gê”s–`IeoEÈ(YMŽ˜FåÂC9ö—uƒ§u™>¯ð|Àýø#?ŽÇi.××»q€D9³0F¸EENzþßÛÿ“Ãܶ©Ë<\ ,\a_a.ïgßðëd \ No newline at end of file
diff --git a/tests/resources/push_src/.gitted/objects/a7/8705c3b2725f931d3ee05348d83cc26700f247 b/tests/resources/push_src/.gitted/objects/a7/8705c3b2725f931d3ee05348d83cc26700f247
new file mode 100644
index 000000000..6ad835e86
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/a7/8705c3b2725f931d3ee05348d83cc26700f247
Binary files differ
diff --git a/tests/resources/push_src/.gitted/objects/b4/83ae7ba66decee9aee971f501221dea84b1498 b/tests/resources/push_src/.gitted/objects/b4/83ae7ba66decee9aee971f501221dea84b1498
new file mode 100644
index 000000000..1e0bd3b05
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/b4/83ae7ba66decee9aee971f501221dea84b1498
@@ -0,0 +1,3 @@
+x5K
+1D]ç½¥;1i"^À•'èÎô|d`dŒ ooqQE½Í«*PàÝ¢+
+á 3…$, }ì%¢Rßw¬É+sç9»úy輨«ÍÐrøÃ`+ܦ2ŠÍp/ã[m­p~µuÝê8-—öSˆ™r„=¢Û,?ÃZ+g \ No newline at end of file
diff --git a/tests/resources/push_src/.gitted/objects/b4/e1f2b375a64c1ccd40c5ff6aa8bc96839ba4fd b/tests/resources/push_src/.gitted/objects/b4/e1f2b375a64c1ccd40c5ff6aa8bc96839ba4fd
new file mode 100644
index 000000000..4e650aaa1
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/b4/e1f2b375a64c1ccd40c5ff6aa8bc96839ba4fd
Binary files differ
diff --git a/tests/resources/push_src/.gitted/objects/c1/0409136a7a75e025fa502a1b2fd7b62b77d279 b/tests/resources/push_src/.gitted/objects/c1/0409136a7a75e025fa502a1b2fd7b62b77d279
new file mode 100644
index 000000000..fcb2b32f3
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/c1/0409136a7a75e025fa502a1b2fd7b62b77d279
Binary files differ
diff --git a/tests/resources/push_src/.gitted/objects/cd/881f90f2933db2e4cc26b8c71fe6037ac7fe4c b/tests/resources/push_src/.gitted/objects/cd/881f90f2933db2e4cc26b8c71fe6037ac7fe4c
new file mode 100644
index 000000000..ad4272638
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/cd/881f90f2933db2e4cc26b8c71fe6037ac7fe4c
Binary files differ
diff --git a/tests/resources/push_src/.gitted/objects/d9/b63a88223d8367516f50bd131a5f7349b7f3e4 b/tests/resources/push_src/.gitted/objects/d9/b63a88223d8367516f50bd131a5f7349b7f3e4
new file mode 100644
index 000000000..b471e2155
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/d9/b63a88223d8367516f50bd131a5f7349b7f3e4
@@ -0,0 +1,2 @@
+x•ŽA
+Â0E]çsËd¦Ó$ "x×i2Õ‚m¤¤¨··zwŸ÷S™¦±‘ÝÕErŠ½gjÃÐ ![ÍŽ%wbY(z˜C/ÁšG\t®w(‰{r$C`›Y…[Ÿ=§DC¨u&®õV8—ùúá²Â!ýæs=]§8Þ›T¦#|;˜ÐÂÑltûWõÓh«fˆM}Uó¼QDM \ No newline at end of file
diff --git a/tests/resources/push_src/.gitted/objects/dc/ab83249f6f9d1ed735d651352a80519339b591 b/tests/resources/push_src/.gitted/objects/dc/ab83249f6f9d1ed735d651352a80519339b591
new file mode 100644
index 000000000..9f6b1502f
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/dc/ab83249f6f9d1ed735d651352a80519339b591
Binary files differ
diff --git a/tests/resources/push_src/.gitted/objects/ee/a4f2705eeec2db3813f2430829afce99cd00b5 b/tests/resources/push_src/.gitted/objects/ee/a4f2705eeec2db3813f2430829afce99cd00b5
new file mode 100644
index 000000000..b7b81d5e3
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/ee/a4f2705eeec2db3813f2430829afce99cd00b5
Binary files differ
diff --git a/tests/resources/push_src/.gitted/objects/f7/8a3106c85fb549c65198b2a2086276c6174928 b/tests/resources/push_src/.gitted/objects/f7/8a3106c85fb549c65198b2a2086276c6174928
new file mode 100644
index 000000000..b9813576d
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/f7/8a3106c85fb549c65198b2a2086276c6174928
Binary files differ
diff --git a/tests/resources/push_src/.gitted/objects/f8/f7aefc2900a3d737cea9eee45729fd55761e1a b/tests/resources/push_src/.gitted/objects/f8/f7aefc2900a3d737cea9eee45729fd55761e1a
new file mode 100644
index 000000000..888354fc2
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/f8/f7aefc2900a3d737cea9eee45729fd55761e1a
Binary files differ
diff --git a/tests/resources/push_src/.gitted/objects/fa/38b91f199934685819bea316186d8b008c52a2 b/tests/resources/push_src/.gitted/objects/fa/38b91f199934685819bea316186d8b008c52a2
new file mode 100644
index 000000000..13d9bca20
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/fa/38b91f199934685819bea316186d8b008c52a2
@@ -0,0 +1,2 @@
+x•½J1„­ó§ÛÊ5›ÿ…‹>„urr²n’eo‚øö {»™f[)¹ƒ°â©_DmIiµ7
+7Ĩ8ꔌ÷.ànœÜƒW)²Ó_T;xë,×(ÃlÐi—[”D\K墓ˆÂXΓP–ùÑ?Ûï­ß>ÜðW~·£ø|_±•Wؤ»‚xæšs6éÜ×éÿIæc¤J‹ãNP}™~ù œ-מë½Á²®/ó„³­Gî û§X \ No newline at end of file
diff --git a/tests/resources/push_src/.gitted/objects/ff/83aa4c5e5d28e3bcba2f5c6e2adc61286a4e5e b/tests/resources/push_src/.gitted/objects/ff/83aa4c5e5d28e3bcba2f5c6e2adc61286a4e5e
new file mode 100644
index 000000000..10f25eb7c
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/ff/83aa4c5e5d28e3bcba2f5c6e2adc61286a4e5e
@@ -0,0 +1,4 @@
+x5ÍM
+1 `×=Eö¢4ÓNAÄ ¸òýÉüÈÀH ooq‘Ç{›/G@ò»5=$+”SOÝ) n¥xâ≻Ø[Æ@4úy
+h1Ú„v‡ÿ¥ÂmÎS”îyz'©
+çWk×-ŽóziÙQc<ÃÞ¢µfS~Âpv+… \ No newline at end of file
diff --git a/tests/resources/push_src/.gitted/objects/ff/fe95c7fd0a37fa2ed702f8f93b56b2196b3925 b/tests/resources/push_src/.gitted/objects/ff/fe95c7fd0a37fa2ed702f8f93b56b2196b3925
new file mode 100644
index 000000000..1cdc048c0
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/ff/fe95c7fd0a37fa2ed702f8f93b56b2196b3925
Binary files differ
diff --git a/tests/resources/push_src/.gitted/objects/pack/dummy b/tests/resources/push_src/.gitted/objects/pack/dummy
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/resources/push_src/.gitted/objects/pack/dummy
diff --git a/tests/resources/push_src/.gitted/refs/heads/b1 b/tests/resources/push_src/.gitted/refs/heads/b1
new file mode 100644
index 000000000..afadf9d26
--- /dev/null
+++ b/tests/resources/push_src/.gitted/refs/heads/b1
@@ -0,0 +1 @@
+a78705c3b2725f931d3ee05348d83cc26700f247
diff --git a/tests/resources/push_src/.gitted/refs/heads/b2 b/tests/resources/push_src/.gitted/refs/heads/b2
new file mode 100644
index 000000000..afadf9d26
--- /dev/null
+++ b/tests/resources/push_src/.gitted/refs/heads/b2
@@ -0,0 +1 @@
+a78705c3b2725f931d3ee05348d83cc26700f247
diff --git a/tests/resources/push_src/.gitted/refs/heads/b3 b/tests/resources/push_src/.gitted/refs/heads/b3
new file mode 100644
index 000000000..3056bb436
--- /dev/null
+++ b/tests/resources/push_src/.gitted/refs/heads/b3
@@ -0,0 +1 @@
+d9b63a88223d8367516f50bd131a5f7349b7f3e4
diff --git a/tests/resources/push_src/.gitted/refs/heads/b4 b/tests/resources/push_src/.gitted/refs/heads/b4
new file mode 100644
index 000000000..efed6f064
--- /dev/null
+++ b/tests/resources/push_src/.gitted/refs/heads/b4
@@ -0,0 +1 @@
+27b7ce66243eb1403862d05f958c002312df173d
diff --git a/tests/resources/push_src/.gitted/refs/heads/b5 b/tests/resources/push_src/.gitted/refs/heads/b5
new file mode 100644
index 000000000..cf313ad05
--- /dev/null
+++ b/tests/resources/push_src/.gitted/refs/heads/b5
@@ -0,0 +1 @@
+fa38b91f199934685819bea316186d8b008c52a2
diff --git a/tests/resources/push_src/.gitted/refs/heads/b6 b/tests/resources/push_src/.gitted/refs/heads/b6
new file mode 100644
index 000000000..711e466ae
--- /dev/null
+++ b/tests/resources/push_src/.gitted/refs/heads/b6
@@ -0,0 +1 @@
+951bbbb90e2259a4c8950db78946784fb53fcbce
diff --git a/tests/resources/push_src/.gitted/refs/tags/tag-blob b/tests/resources/push_src/.gitted/refs/tags/tag-blob
new file mode 100644
index 000000000..abfebf22e
--- /dev/null
+++ b/tests/resources/push_src/.gitted/refs/tags/tag-blob
@@ -0,0 +1 @@
+b483ae7ba66decee9aee971f501221dea84b1498
diff --git a/tests/resources/push_src/.gitted/refs/tags/tag-commit b/tests/resources/push_src/.gitted/refs/tags/tag-commit
new file mode 100644
index 000000000..c023b8452
--- /dev/null
+++ b/tests/resources/push_src/.gitted/refs/tags/tag-commit
@@ -0,0 +1 @@
+805c54522e614f29f70d2413a0470247d8b424ac
diff --git a/tests/resources/push_src/.gitted/refs/tags/tag-commit-two b/tests/resources/push_src/.gitted/refs/tags/tag-commit-two
new file mode 100644
index 000000000..abb365080
--- /dev/null
+++ b/tests/resources/push_src/.gitted/refs/tags/tag-commit-two
@@ -0,0 +1 @@
+36f79b2846017d3761e0a02d0bccd573e0f90c57
diff --git a/tests/resources/push_src/.gitted/refs/tags/tag-lightweight b/tests/resources/push_src/.gitted/refs/tags/tag-lightweight
new file mode 100644
index 000000000..711e466ae
--- /dev/null
+++ b/tests/resources/push_src/.gitted/refs/tags/tag-lightweight
@@ -0,0 +1 @@
+951bbbb90e2259a4c8950db78946784fb53fcbce
diff --git a/tests/resources/push_src/.gitted/refs/tags/tag-tag b/tests/resources/push_src/.gitted/refs/tags/tag-tag
new file mode 100644
index 000000000..d6cd74804
--- /dev/null
+++ b/tests/resources/push_src/.gitted/refs/tags/tag-tag
@@ -0,0 +1 @@
+eea4f2705eeec2db3813f2430829afce99cd00b5
diff --git a/tests/resources/push_src/.gitted/refs/tags/tag-tree b/tests/resources/push_src/.gitted/refs/tags/tag-tree
new file mode 100644
index 000000000..7a530d381
--- /dev/null
+++ b/tests/resources/push_src/.gitted/refs/tags/tag-tree
@@ -0,0 +1 @@
+ff83aa4c5e5d28e3bcba2f5c6e2adc61286a4e5e
diff --git a/tests/resources/push_src/a.txt b/tests/resources/push_src/a.txt
new file mode 100644
index 000000000..f7eac1c51
--- /dev/null
+++ b/tests/resources/push_src/a.txt
@@ -0,0 +1,2 @@
+a
+edit
diff --git a/tests/resources/push_src/fold/b.txt b/tests/resources/push_src/fold/b.txt
new file mode 100644
index 000000000..617807982
--- /dev/null
+++ b/tests/resources/push_src/fold/b.txt
@@ -0,0 +1 @@
+b
diff --git a/tests/resources/push_src/foldb.txt b/tests/resources/push_src/foldb.txt
new file mode 100644
index 000000000..5b38718be
--- /dev/null
+++ b/tests/resources/push_src/foldb.txt
@@ -0,0 +1 @@
+edit
diff --git a/tests/resources/push_src/gitmodules b/tests/resources/push_src/gitmodules
new file mode 100644
index 000000000..f1734dfc1
--- /dev/null
+++ b/tests/resources/push_src/gitmodules
@@ -0,0 +1,3 @@
+[submodule "submodule"]
+ path = submodule
+ url = ../testrepo.git
diff --git a/tests/resources/push_src/submodule/.gitted b/tests/resources/push_src/submodule/.gitted
new file mode 100644
index 000000000..3ffcf960a
--- /dev/null
+++ b/tests/resources/push_src/submodule/.gitted
@@ -0,0 +1 @@
+gitdir: ../.git/modules/submodule
diff --git a/tests/resources/push_src/submodule/README b/tests/resources/push_src/submodule/README
new file mode 100644
index 000000000..ca8c64728
--- /dev/null
+++ b/tests/resources/push_src/submodule/README
@@ -0,0 +1 @@
+hey there
diff --git a/tests/resources/push_src/submodule/branch_file.txt b/tests/resources/push_src/submodule/branch_file.txt
new file mode 100644
index 000000000..a26902575
--- /dev/null
+++ b/tests/resources/push_src/submodule/branch_file.txt
@@ -0,0 +1,2 @@
+hi
+bye!
diff --git a/tests/resources/push_src/submodule/new.txt b/tests/resources/push_src/submodule/new.txt
new file mode 100644
index 000000000..8e0884e36
--- /dev/null
+++ b/tests/resources/push_src/submodule/new.txt
@@ -0,0 +1 @@
+my new file
diff --git a/tests/resources/renames/.gitted/HEAD b/tests/resources/renames/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/renames/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/renames/.gitted/config b/tests/resources/renames/.gitted/config
new file mode 100644
index 000000000..bb4d11c1f
--- /dev/null
+++ b/tests/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/resources/renames/.gitted/description b/tests/resources/renames/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/renames/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/renames/.gitted/index b/tests/resources/renames/.gitted/index
new file mode 100644
index 000000000..72363c0f5
--- /dev/null
+++ b/tests/resources/renames/.gitted/index
Binary files differ
diff --git a/tests/resources/renames/.gitted/info/exclude b/tests/resources/renames/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/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/resources/renames/.gitted/logs/HEAD b/tests/resources/renames/.gitted/logs/HEAD
new file mode 100644
index 000000000..e69792263
--- /dev/null
+++ b/tests/resources/renames/.gitted/logs/HEAD
@@ -0,0 +1,4 @@
+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
+2bc7f351d20b53f1c72c16c4b036e491c478c49a 1c068dee5790ef1580cfc4cd670915b48d790084 Russell Belfer <rb@github.com> 1361485758 -0800 commit: rewrites, copies with changes, etc.
+1c068dee5790ef1580cfc4cd670915b48d790084 19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13 Russell Belfer <rb@github.com> 1361486360 -0800 commit: more renames and smallish modifications
diff --git a/tests/resources/renames/.gitted/logs/refs/heads/master b/tests/resources/renames/.gitted/logs/refs/heads/master
new file mode 100644
index 000000000..e69792263
--- /dev/null
+++ b/tests/resources/renames/.gitted/logs/refs/heads/master
@@ -0,0 +1,4 @@
+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
+2bc7f351d20b53f1c72c16c4b036e491c478c49a 1c068dee5790ef1580cfc4cd670915b48d790084 Russell Belfer <rb@github.com> 1361485758 -0800 commit: rewrites, copies with changes, etc.
+1c068dee5790ef1580cfc4cd670915b48d790084 19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13 Russell Belfer <rb@github.com> 1361486360 -0800 commit: more renames and smallish modifications
diff --git a/tests/resources/renames/.gitted/objects/03/da7ad872536bd448da8d88eb7165338bf923a7 b/tests/resources/renames/.gitted/objects/03/da7ad872536bd448da8d88eb7165338bf923a7
new file mode 100644
index 000000000..2ee86444d
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/03/da7ad872536bd448da8d88eb7165338bf923a7
Binary files differ
diff --git a/tests/resources/renames/.gitted/objects/17/58bdd7c16a72ff7c17d8de0c957ced3ccad645 b/tests/resources/renames/.gitted/objects/17/58bdd7c16a72ff7c17d8de0c957ced3ccad645
new file mode 100644
index 000000000..01801ed11
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/17/58bdd7c16a72ff7c17d8de0c957ced3ccad645
@@ -0,0 +1,5 @@
+xEͱ Â@QbWq ÿ®—ÃÅHôŸ¡_ž‰&{ëó]ãðy›û¸•¶Y¬X³û`Ÿì‹=¯Ý'í¶=Zo´Þh½Ñz£õFëÖí¿­­Ð
+­Ð
+­Ð
+­Ð
+MhBšÐ„&4¡ MhB3šÑŒf4£ÍhF3šÑŽKûxŒŒ \ No newline at end of file
diff --git a/tests/resources/renames/.gitted/objects/19/dd32dfb1520a64e5bbaae8dce6ef423dfa2f13 b/tests/resources/renames/.gitted/objects/19/dd32dfb1520a64e5bbaae8dce6ef423dfa2f13
new file mode 100644
index 000000000..4be4c6952
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/19/dd32dfb1520a64e5bbaae8dce6ef423dfa2f13
@@ -0,0 +1 @@
+x•ŽÑM!Eý¦Ši@3¼Þc,Á\X °ýK þÞ{Nrbo­,xzYC¬<‡h[&“È?=fcvÎyƒèC£W¿<äZ #:J"vs’µ%Œ9š˜Ü½¶ÁPÚ’Q|¯³ø¾ç”ZáKj–ï#|þ”uÞá-ööúpÚ;Â+¢Úëî[ý¯©Z;’›Là+Ál\k™'´žJ.‘Wé×T¯;O
diff --git a/tests/resources/renames/.gitted/objects/1c/068dee5790ef1580cfc4cd670915b48d790084 b/tests/resources/renames/.gitted/objects/1c/068dee5790ef1580cfc4cd670915b48d790084
new file mode 100644
index 000000000..d65ab0a9b
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/1c/068dee5790ef1580cfc4cd670915b48d790084
Binary files differ
diff --git a/tests/resources/renames/.gitted/objects/2b/c7f351d20b53f1c72c16c4b036e491c478c49a b/tests/resources/renames/.gitted/objects/2b/c7f351d20b53f1c72c16c4b036e491c478c49a
new file mode 100644
index 000000000..93f1ccb3f
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/2b/c7f351d20b53f1c72c16c4b036e491c478c49a
Binary files differ
diff --git a/tests/resources/renames/.gitted/objects/31/e47d8c1fa36d7f8d537b96158e3f024de0a9f2 b/tests/resources/renames/.gitted/objects/31/e47d8c1fa36d7f8d537b96158e3f024de0a9f2
new file mode 100644
index 000000000..00ce53212
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/31/e47d8c1fa36d7f8d537b96158e3f024de0a9f2
Binary files differ
diff --git a/tests/resources/renames/.gitted/objects/35/92953ff3ea5e8ba700c429f3aefe33c8806754 b/tests/resources/renames/.gitted/objects/35/92953ff3ea5e8ba700c429f3aefe33c8806754
new file mode 100644
index 000000000..886271d32
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/35/92953ff3ea5e8ba700c429f3aefe33c8806754
Binary files differ
diff --git a/tests/resources/renames/.gitted/objects/36/020db6cdacaa93497f31edcd8f242ff9bc366d b/tests/resources/renames/.gitted/objects/36/020db6cdacaa93497f31edcd8f242ff9bc366d
new file mode 100644
index 000000000..f4f9303ed
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/36/020db6cdacaa93497f31edcd8f242ff9bc366d
Binary files differ
diff --git a/tests/resources/renames/.gitted/objects/3c/04741dd4b96c4ae4b00ec0f6e10c816a30aad2 b/tests/resources/renames/.gitted/objects/3c/04741dd4b96c4ae4b00ec0f6e10c816a30aad2
new file mode 100644
index 000000000..c23602262
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/3c/04741dd4b96c4ae4b00ec0f6e10c816a30aad2
Binary files differ
diff --git a/tests/resources/renames/.gitted/objects/42/10ffd5c390b21dd5483375e75288dea9ede512 b/tests/resources/renames/.gitted/objects/42/10ffd5c390b21dd5483375e75288dea9ede512
new file mode 100644
index 000000000..d351a6d13
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/42/10ffd5c390b21dd5483375e75288dea9ede512
Binary files differ
diff --git a/tests/resources/renames/.gitted/objects/44/4a76ed3e45b183753f49376af30da8c3fe276a b/tests/resources/renames/.gitted/objects/44/4a76ed3e45b183753f49376af30da8c3fe276a
new file mode 100644
index 000000000..5ce12a3c5
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/44/4a76ed3e45b183753f49376af30da8c3fe276a
Binary files differ
diff --git a/tests/resources/renames/.gitted/objects/47/184c1e7eb22abcbed2bf4ee87d4e38096f7951 b/tests/resources/renames/.gitted/objects/47/184c1e7eb22abcbed2bf4ee87d4e38096f7951
new file mode 100644
index 000000000..aa9192699
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/47/184c1e7eb22abcbed2bf4ee87d4e38096f7951
Binary files differ
diff --git a/tests/resources/renames/.gitted/objects/4e/4cae3e7dd56ed74bff39526d0469e554432953 b/tests/resources/renames/.gitted/objects/4e/4cae3e7dd56ed74bff39526d0469e554432953
new file mode 100644
index 000000000..5e6ebd5e0
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/4e/4cae3e7dd56ed74bff39526d0469e554432953
Binary files differ
diff --git a/tests/resources/renames/.gitted/objects/50/e90273af7d826ff0a95865bcd3ba8412c447d9 b/tests/resources/renames/.gitted/objects/50/e90273af7d826ff0a95865bcd3ba8412c447d9
new file mode 100644
index 000000000..a98d14ee7
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/50/e90273af7d826ff0a95865bcd3ba8412c447d9
@@ -0,0 +1,3 @@
+xmÍM
+Â0`×9Å\@ÉOÛd ˆîE¼@š&4hLI"âíî]Ícà}Ïå”bÉÕ®ïaBÙORvΡ5çÂ"¢
+“óbÀ0[kLo³ï¶ä·èpÍ­U˺ÝSŠ®äšC;¸œŽ ”æ8 ÜhØóŽsF_šl¾ÀeþØ2Ã}ɩ挞È-ý!Dg4*IDO;Ûê!Ð~)>m‰í ä®”¨Ï~*ùD‰ \ No newline at end of file
diff --git a/tests/resources/renames/.gitted/objects/5e/26abc56a5a84d89790f45416648899cbe13109 b/tests/resources/renames/.gitted/objects/5e/26abc56a5a84d89790f45416648899cbe13109
new file mode 100644
index 000000000..2acd3d583
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/5e/26abc56a5a84d89790f45416648899cbe13109
Binary files differ
diff --git a/tests/resources/renames/.gitted/objects/61/8c6f2f8740bd6049b2fb9eb93fc15726462745 b/tests/resources/renames/.gitted/objects/61/8c6f2f8740bd6049b2fb9eb93fc15726462745
new file mode 100644
index 000000000..24eac54c5
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/61/8c6f2f8740bd6049b2fb9eb93fc15726462745
Binary files differ
diff --git a/tests/resources/renames/.gitted/objects/66/311f5cfbe7836c27510a3ba2f43e282e2c8bba b/tests/resources/renames/.gitted/objects/66/311f5cfbe7836c27510a3ba2f43e282e2c8bba
new file mode 100644
index 000000000..5ee28a76a
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/66/311f5cfbe7836c27510a3ba2f43e282e2c8bba
Binary files differ
diff --git a/tests/resources/renames/.gitted/objects/93/f538c45a57a87eb4c1e86f91c6ee41d66c7ba7 b/tests/resources/renames/.gitted/objects/93/f538c45a57a87eb4c1e86f91c6ee41d66c7ba7
new file mode 100644
index 000000000..39105f94d
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/93/f538c45a57a87eb4c1e86f91c6ee41d66c7ba7
Binary files differ
diff --git a/tests/resources/renames/.gitted/objects/9a/69d960ae94b060f56c2a8702545e2bb1abb935 b/tests/resources/renames/.gitted/objects/9a/69d960ae94b060f56c2a8702545e2bb1abb935
new file mode 100644
index 000000000..f75178c59
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/9a/69d960ae94b060f56c2a8702545e2bb1abb935
Binary files differ
diff --git a/tests/resources/renames/.gitted/objects/ad/0a8e55a104ac54a8a29ed4b84b49e76837a113 b/tests/resources/renames/.gitted/objects/ad/0a8e55a104ac54a8a29ed4b84b49e76837a113
new file mode 100644
index 000000000..440b7bec3
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/ad/0a8e55a104ac54a8a29ed4b84b49e76837a113
Binary files differ
diff --git a/tests/resources/renames/.gitted/objects/b9/25b224cc91f897001a9993fbce169fdaa8858f b/tests/resources/renames/.gitted/objects/b9/25b224cc91f897001a9993fbce169fdaa8858f
new file mode 100644
index 000000000..90e107fa2
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/b9/25b224cc91f897001a9993fbce169fdaa8858f
Binary files differ
diff --git a/tests/resources/renames/.gitted/objects/d7/9b202de198fa61b02424b9e25e840dc75e1323 b/tests/resources/renames/.gitted/objects/d7/9b202de198fa61b02424b9e25e840dc75e1323
new file mode 100644
index 000000000..daa2b3997
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/d7/9b202de198fa61b02424b9e25e840dc75e1323
Binary files differ
diff --git a/tests/resources/renames/.gitted/objects/ea/c43f5195a2cee53b7458d8dad16aedde10711b b/tests/resources/renames/.gitted/objects/ea/c43f5195a2cee53b7458d8dad16aedde10711b
new file mode 100644
index 000000000..2fb025080
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/ea/c43f5195a2cee53b7458d8dad16aedde10711b
Binary files differ
diff --git a/tests/resources/renames/.gitted/objects/ea/f4a3e3bfe68585e90cada20736ace491cd100b b/tests/resources/renames/.gitted/objects/ea/f4a3e3bfe68585e90cada20736ace491cd100b
new file mode 100644
index 000000000..f72df8d82
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/ea/f4a3e3bfe68585e90cada20736ace491cd100b
@@ -0,0 +1,5 @@
+x}RÁªÔ@ô<_QžTÈ.¼“ÂâAáÉ[–‡â
+{ž¼t2C’žez’˜›áú%öLžº'!aº»ª«jê!Ôx{÷îÅ'¢+Îþ;“$œ)Ξ»Ý#±yÿ¿Ç˜³#q#¯h„­c° ÃQDX¶m­R|ŠaD*ÝOˆþ†+±”^ZI~ýøi>3aôÃàã!,R!-áïÉEaIÁ>äyš‰o*«¼4æˆRf¡ m&e¯ IAÑú™ò*!â;¢ždÍݬ‚…´Å
+êH¶o­¤
+ÃÄO®‚U¾DöyTVØHpwqÅH¼7§„Æ·­.ÈʆÎÎts6{Zä +öX\)Šª”ÑC–žì5QªÂä9
+%©ÌÅt*Ã&Ï&è°êv;|šÕÆ'4½ìÆéþþ Dƒu[°7h¯¿e!ÉNK*"C©-=Óòæ`´æ#ØŽ$EëÅe2õáâT|ùêå@NBsús¢¦lµ°Wö|/¶0¬÷aÈ¥üJ±ò¶Nêv)-šÚ¡˜iÛ¤3ÅëbäbO:uWMâˆNÓÜàóæ¶X²7¿Tóº \ No newline at end of file
diff --git a/tests/resources/renames/.gitted/objects/f9/0d4fc20ecddf21eebe6a37e9225d244339d2b5 b/tests/resources/renames/.gitted/objects/f9/0d4fc20ecddf21eebe6a37e9225d244339d2b5
new file mode 100644
index 000000000..f6d933be9
--- /dev/null
+++ b/tests/resources/renames/.gitted/objects/f9/0d4fc20ecddf21eebe6a37e9225d244339d2b5
Binary files differ
diff --git a/tests/resources/renames/.gitted/refs/heads/master b/tests/resources/renames/.gitted/refs/heads/master
new file mode 100644
index 000000000..642c3198d
--- /dev/null
+++ b/tests/resources/renames/.gitted/refs/heads/master
@@ -0,0 +1 @@
+19dd32dfb1520a64e5bbaae8dce6ef423dfa2f13
diff --git a/tests/resources/renames/.gitted/refs/heads/renames_similar b/tests/resources/renames/.gitted/refs/heads/renames_similar
new file mode 100644
index 000000000..8ffb6d94b
--- /dev/null
+++ b/tests/resources/renames/.gitted/refs/heads/renames_similar
@@ -0,0 +1 @@
+444a76ed3e45b183753f49376af30da8c3fe276a
diff --git a/tests/resources/renames/.gitted/refs/heads/renames_similar_two b/tests/resources/renames/.gitted/refs/heads/renames_similar_two
new file mode 100644
index 000000000..4ee5d04e1
--- /dev/null
+++ b/tests/resources/renames/.gitted/refs/heads/renames_similar_two
@@ -0,0 +1 @@
+50e90273af7d826ff0a95865bcd3ba8412c447d9
diff --git a/tests/resources/renames/ikeepsix.txt b/tests/resources/renames/ikeepsix.txt
new file mode 100644
index 000000000..eaf4a3e3b
--- /dev/null
+++ b/tests/resources/renames/ikeepsix.txt
@@ -0,0 +1,27 @@
+I Keep Six Honest Serving-Men
+=============================
+
+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!
+
+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!
+
+ -- Rudyard Kipling
+
+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.
diff --git a/tests/resources/renames/sixserving.txt b/tests/resources/renames/sixserving.txt
new file mode 100644
index 000000000..f90d4fc20
--- /dev/null
+++ b/tests/resources/renames/sixserving.txt
@@ -0,0 +1,25 @@
+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/resources/renames/songof7cities.txt b/tests/resources/renames/songof7cities.txt
new file mode 100644
index 000000000..4210ffd5c
--- /dev/null
+++ b/tests/resources/renames/songof7cities.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/resources/renames/untimely.txt b/tests/resources/renames/untimely.txt
new file mode 100644
index 000000000..9a69d960a
--- /dev/null
+++ b/tests/resources/renames/untimely.txt
@@ -0,0 +1,24 @@
+Untimely
+========
+
+Nothing in life has been made by man for man's using
+But it was shown long since to man in ages
+Lost as the name of the maker of it,
+Who received oppression and shame for his wages--
+Hate, avoidance, and scorn in his daily dealings--
+Until he perished, wholly confounded
+
+More to be pitied than he are the wise
+Souls which foresaw the evil of loosing
+Knowledge or Art before time, and aborted
+Noble devices and deep-wrought healings,
+Lest offense should arise.
+
+Heaven delivers on earth the Hour that cannot be
+ thwarted,
+Neither advanced, at the price of a world nor a soul,
+ and its Prophet
+Comes through the blood of the vanguards who
+ dreamed--too soon--it had sounded.
+
+ -- Rudyard Kipling
diff --git a/tests/resources/revert/.gitted/HEAD b/tests/resources/revert/.gitted/HEAD
new file mode 100644
index 000000000..cb4380516
--- /dev/null
+++ b/tests/resources/revert/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/revert/.gitted/config b/tests/resources/revert/.gitted/config
new file mode 100644
index 000000000..454e576b9
--- /dev/null
+++ b/tests/resources/revert/.gitted/config
@@ -0,0 +1,8 @@
+[core]
+ repositoryformatversion = 0
+ filemode = false
+ bare = false
+ logallrefupdates = true
+ symlinks = false
+ ignorecase = true
+ hideDotFiles = dotGitOnly
diff --git a/tests/resources/revert/.gitted/index b/tests/resources/revert/.gitted/index
new file mode 100644
index 000000000..3513c04a6
--- /dev/null
+++ b/tests/resources/revert/.gitted/index
Binary files differ
diff --git a/tests/resources/revert/.gitted/info/exclude b/tests/resources/revert/.gitted/info/exclude
new file mode 100644
index 000000000..f00680973
--- /dev/null
+++ b/tests/resources/revert/.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/resources/revert/.gitted/objects/0a/a8c7e40d342fff78d60b29a4ba8e993ed79c51 b/tests/resources/revert/.gitted/objects/0a/a8c7e40d342fff78d60b29a4ba8e993ed79c51
new file mode 100644
index 000000000..5c811ef89
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/0a/a8c7e40d342fff78d60b29a4ba8e993ed79c51
@@ -0,0 +1,2 @@
+x¥K
+Â0@]çs%Ó4?qã ¼@2Ѐé@ñúÖ3¸{¼Å{$­ÕÜitf@ë¸ÄL9º°1…ÙOI—‰K™cÁäÑdrY¥÷X¥Ãcù¤¾Às•¶ËW>ìî­R—]ʸ´  GଭÖê°Çxð U·:jz©/¤’? \ No newline at end of file
diff --git a/tests/resources/revert/.gitted/objects/0a/b09ea6d4c3634bdf6c221626d8b6f7dd890767 b/tests/resources/revert/.gitted/objects/0a/b09ea6d4c3634bdf6c221626d8b6f7dd890767
new file mode 100644
index 000000000..c050e5a89
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/0a/b09ea6d4c3634bdf6c221626d8b6f7dd890767
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/0a/d19525be6d8cae5e5deb2770fc244b65255057 b/tests/resources/revert/.gitted/objects/0a/d19525be6d8cae5e5deb2770fc244b65255057
new file mode 100644
index 000000000..4aa0459d8
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/0a/d19525be6d8cae5e5deb2770fc244b65255057
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/0c/db66192ee192f70f891f05a47636057420e871 b/tests/resources/revert/.gitted/objects/0c/db66192ee192f70f891f05a47636057420e871
new file mode 100644
index 000000000..31c107fc4
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/0c/db66192ee192f70f891f05a47636057420e871
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/0f/5bfcf58c558d865da6be0281d7795993646cee b/tests/resources/revert/.gitted/objects/0f/5bfcf58c558d865da6be0281d7795993646cee
new file mode 100644
index 000000000..fb4fc763a
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/0f/5bfcf58c558d865da6be0281d7795993646cee
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/13/a6fdfd10bd74b1f258fb58801215985dd2e797 b/tests/resources/revert/.gitted/objects/13/a6fdfd10bd74b1f258fb58801215985dd2e797
new file mode 100644
index 000000000..3c54aab0c
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/13/a6fdfd10bd74b1f258fb58801215985dd2e797
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/13/ee9cd5d8e1023c218e0e1ea684ec0c582b5050 b/tests/resources/revert/.gitted/objects/13/ee9cd5d8e1023c218e0e1ea684ec0c582b5050
new file mode 100644
index 000000000..aed4647a6
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/13/ee9cd5d8e1023c218e0e1ea684ec0c582b5050
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/15/6ef9bcb968dccec8472a0f2eff49f1a713bc6b b/tests/resources/revert/.gitted/objects/15/6ef9bcb968dccec8472a0f2eff49f1a713bc6b
new file mode 100644
index 000000000..3ee2a189b
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/15/6ef9bcb968dccec8472a0f2eff49f1a713bc6b
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/18/1aab27ddb37b40d9a284fb4733497006d57091 b/tests/resources/revert/.gitted/objects/18/1aab27ddb37b40d9a284fb4733497006d57091
new file mode 100644
index 000000000..6b422b808
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/18/1aab27ddb37b40d9a284fb4733497006d57091
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/1b/c915c5cb7185a9438de28a7b1a7dfe8c01ee7f b/tests/resources/revert/.gitted/objects/1b/c915c5cb7185a9438de28a7b1a7dfe8c01ee7f
new file mode 100644
index 000000000..0a6955b5d
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/1b/c915c5cb7185a9438de28a7b1a7dfe8c01ee7f
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/1f/a4e069a641f10f5fb7588138b2d147fcd22c36 b/tests/resources/revert/.gitted/objects/1f/a4e069a641f10f5fb7588138b2d147fcd22c36
new file mode 100644
index 000000000..3df134a8d
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/1f/a4e069a641f10f5fb7588138b2d147fcd22c36
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/1f/f0c423042b46cb1d617b81efb715defbe8054d b/tests/resources/revert/.gitted/objects/1f/f0c423042b46cb1d617b81efb715defbe8054d
new file mode 100644
index 000000000..2ed1a2292
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/1f/f0c423042b46cb1d617b81efb715defbe8054d
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/21/a96a98ed84d45866e1de6e266fd3a61a4ae9dc b/tests/resources/revert/.gitted/objects/21/a96a98ed84d45866e1de6e266fd3a61a4ae9dc
new file mode 100644
index 000000000..95842dbf8
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/21/a96a98ed84d45866e1de6e266fd3a61a4ae9dc
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/29/6a6d3be1dff05c5d1f631d2459389fa7b619eb b/tests/resources/revert/.gitted/objects/29/6a6d3be1dff05c5d1f631d2459389fa7b619eb
new file mode 100644
index 000000000..5555268fd
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/29/6a6d3be1dff05c5d1f631d2459389fa7b619eb
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/2d/440f2b3147d3dc7ad1085813478d6d869d5a4d b/tests/resources/revert/.gitted/objects/2d/440f2b3147d3dc7ad1085813478d6d869d5a4d
new file mode 100644
index 000000000..c06cc9472
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/2d/440f2b3147d3dc7ad1085813478d6d869d5a4d
@@ -0,0 +1,2 @@
+x¥ŽK
+1D]ç}%Iç3"n¼Èt:΀I$ñúÆ3¸)ŠWð(ª9o´q‡Þ˜aaQ9ËRyífž"É]’JÍv Ym¯Ð¸tð“ñÑð„N9‡2%\F°!™/DHÆŠðîkmp‹ŸÐ"Üך÷Zà̃þÚ5oÔê^S?QÍP8Y…~ÖŽÒJ)G;ÿ¡Ž’žõ­<€ÖP¼‹/ælPÞ \ No newline at end of file
diff --git a/tests/resources/revert/.gitted/objects/33/c6fd981c49a2abf2971482089350bfc5cda8ea b/tests/resources/revert/.gitted/objects/33/c6fd981c49a2abf2971482089350bfc5cda8ea
new file mode 100644
index 000000000..683f27f0e
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/33/c6fd981c49a2abf2971482089350bfc5cda8ea
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/39/467716290f6df775a91cdb9a4eb39295018145 b/tests/resources/revert/.gitted/objects/39/467716290f6df775a91cdb9a4eb39295018145
new file mode 100644
index 000000000..7c1e01d67
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/39/467716290f6df775a91cdb9a4eb39295018145
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/39/9fb3aba3d9d13f7d40a9254ce4402067ef3149 b/tests/resources/revert/.gitted/objects/39/9fb3aba3d9d13f7d40a9254ce4402067ef3149
new file mode 100644
index 000000000..6cb6839d6
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/39/9fb3aba3d9d13f7d40a9254ce4402067ef3149
@@ -0,0 +1,2 @@
+x¥ŽK!]s
+. á;@bŒoàzºA\0mŒ×Ïàª<äÖžC§£ç,3Ød@û ¢_ÑDò @§K6Å—5Tâ=oS$çT1«Õ.% @zªQ["-—D xÊ]Þèä½rÛy“ç<éo]Û;ï\Æ ¹]¤¶1YüåQE¥Ä¤óèÈ$l<ê,`…í‘ÅaGNÅ \ No newline at end of file
diff --git a/tests/resources/revert/.gitted/objects/3a/3ef367eaf3fe79effbfb0a56b269c04c2b59fe b/tests/resources/revert/.gitted/objects/3a/3ef367eaf3fe79effbfb0a56b269c04c2b59fe
new file mode 100644
index 000000000..b83806e68
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/3a/3ef367eaf3fe79effbfb0a56b269c04c2b59fe
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/46/ff0854663aeb2182b9838c8da68e33ac23bc1e b/tests/resources/revert/.gitted/objects/46/ff0854663aeb2182b9838c8da68e33ac23bc1e
new file mode 100644
index 000000000..7064dab52
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/46/ff0854663aeb2182b9838c8da68e33ac23bc1e
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/4b/8fcff56437e60f58e9a6bc630dd242ebf6ea2c b/tests/resources/revert/.gitted/objects/4b/8fcff56437e60f58e9a6bc630dd242ebf6ea2c
new file mode 100644
index 000000000..c0bd3dbf9
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/4b/8fcff56437e60f58e9a6bc630dd242ebf6ea2c
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/52/c95c4264245469a0617e289a7d737f156826b4 b/tests/resources/revert/.gitted/objects/52/c95c4264245469a0617e289a7d737f156826b4
new file mode 100644
index 000000000..7cc9e9662
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/52/c95c4264245469a0617e289a7d737f156826b4
@@ -0,0 +1,2 @@
+x¥=N1 …©s
+k›mØU~Ç !J´È8ÎÎIP&ÀõÉ2@¢°ä÷¤Ïï™jÎk+ñ¡7fð˜ŒCË^'…FF3¹4{–’”dCN")JìÅGh\:¸@‘І¤Q¡f²^E­ ³™ˆò`“vèEøìKmð¿C‹p]jÞj'î}{É+µºÕÔÏTó3(sqcŒ“p’NJ1ÜQ´ó?NˆwþâÖáðÆíÆ0·Phc¾«í´Ë#¬¥Wؽƒ×eÝ ýrìþüóãnk¹ ZB)CdÆÛ QMÚË4Å„è‚Wg,ÏÆk鷺(ëÎâçÛä \ No newline at end of file
diff --git a/tests/resources/revert/.gitted/objects/55/568c8de5322ff9a95d72747a239cdb64a19965 b/tests/resources/revert/.gitted/objects/55/568c8de5322ff9a95d72747a239cdb64a19965
new file mode 100644
index 000000000..4dae50b5d
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/55/568c8de5322ff9a95d72747a239cdb64a19965
@@ -0,0 +1 @@
+x¥ŽK!]sŠ¾€¦ù ãÆx莓ˆÄx}ñ î^jñªr«u`œÝÎ ZÈ1.‰§E£xYƒQÛ¸š¢]\ŒÉvQOêü€D1vX¬3"bYp5‰ÜJ‘S²\BÊ^+z[ëp)ê®·V_íGžô·Îu˽½šŒCnõSèµ ìÑ#ªIgèà?.Ô,¦Ê°•Y¾eº«/»ZOH \ No newline at end of file
diff --git a/tests/resources/revert/.gitted/objects/55/acf326a69f0aab7a974ec53ffa55a50bcac14e b/tests/resources/revert/.gitted/objects/55/acf326a69f0aab7a974ec53ffa55a50bcac14e
new file mode 100644
index 000000000..e9cdfc5d5
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/55/acf326a69f0aab7a974ec53ffa55a50bcac14e
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/5a/cdc74af27172ec491d213ee36cea7eb9ef2579 b/tests/resources/revert/.gitted/objects/5a/cdc74af27172ec491d213ee36cea7eb9ef2579
new file mode 100644
index 000000000..61055ea80
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/5a/cdc74af27172ec491d213ee36cea7eb9ef2579
@@ -0,0 +1,3 @@
+x¥=N1 …©s
+w[-Š“8?B4”t\ ?3E&«L×'«' °ä÷é=?9÷Öö éifˆ6Tg¸æDhµIÞ³4=*·<>zRäªAq‹ƒ Æ:‡VYm©ÎQ ˜K
+ÑpÒA’èÑП5sÈ…Šg”Jg…«€‘£õ†³ÌäU"IRÄï¹õïå'ŽŸ[og?à…½oomÏ£Ÿ½ÎçÜÛ+ ö´fõÁuÅ¥Xt=6ù'Ä/†4â‘7¸´»:¯yý˜Lü:øc’ \ No newline at end of file
diff --git a/tests/resources/revert/.gitted/objects/5c/f1d643f100d8112834e540264546ba2c159976 b/tests/resources/revert/.gitted/objects/5c/f1d643f100d8112834e540264546ba2c159976
new file mode 100644
index 000000000..dbbf711b5
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/5c/f1d643f100d8112834e540264546ba2c159976
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/6b/ccd0dc58cea5ccff86014f3d64b31bd8c02a37 b/tests/resources/revert/.gitted/objects/6b/ccd0dc58cea5ccff86014f3d64b31bd8c02a37
new file mode 100644
index 000000000..2664da480
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/6b/ccd0dc58cea5ccff86014f3d64b31bd8c02a37
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/71/eb9c2b53dbbf3c45fb28b27c850db4b7fb8011 b/tests/resources/revert/.gitted/objects/71/eb9c2b53dbbf3c45fb28b27c850db4b7fb8011
new file mode 100644
index 000000000..995a1e626
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/71/eb9c2b53dbbf3c45fb28b27c850db4b7fb8011
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/72/333f47d4e83616630ff3b0ffe4c0faebcc3c45 b/tests/resources/revert/.gitted/objects/72/333f47d4e83616630ff3b0ffe4c0faebcc3c45
new file mode 100644
index 000000000..1f6978703
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/72/333f47d4e83616630ff3b0ffe4c0faebcc3c45
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/73/ec36fa120f8066963a0bc9105bb273dbd903d7 b/tests/resources/revert/.gitted/objects/73/ec36fa120f8066963a0bc9105bb273dbd903d7
new file mode 100644
index 000000000..3c8d2c20a
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/73/ec36fa120f8066963a0bc9105bb273dbd903d7
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/74/7726e021bc5f44b86de60e3032fd6f9f1b8383 b/tests/resources/revert/.gitted/objects/74/7726e021bc5f44b86de60e3032fd6f9f1b8383
new file mode 100644
index 000000000..e4d14776f
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/74/7726e021bc5f44b86de60e3032fd6f9f1b8383
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/77/31926a337c4eaba1e2187d90ebfa0a93659382 b/tests/resources/revert/.gitted/objects/77/31926a337c4eaba1e2187d90ebfa0a93659382
new file mode 100644
index 000000000..b87fa1543
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/77/31926a337c4eaba1e2187d90ebfa0a93659382
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/83/f65df4606c4f8dbf8da43de25de1b7e4c03238 b/tests/resources/revert/.gitted/objects/83/f65df4606c4f8dbf8da43de25de1b7e4c03238
new file mode 100644
index 000000000..152ed5f77
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/83/f65df4606c4f8dbf8da43de25de1b7e4c03238
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/87/59ad453cf01cf7daf14e2a668f8218f9a678eb b/tests/resources/revert/.gitted/objects/87/59ad453cf01cf7daf14e2a668f8218f9a678eb
new file mode 100644
index 000000000..ab19acf83
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/87/59ad453cf01cf7daf14e2a668f8218f9a678eb
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/8f/d40e13fff575b63e86af87175e70fa7fb92f80 b/tests/resources/revert/.gitted/objects/8f/d40e13fff575b63e86af87175e70fa7fb92f80
new file mode 100644
index 000000000..9ff861728
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/8f/d40e13fff575b63e86af87175e70fa7fb92f80
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/97/f3574e92f1730d365fb9e00c10e3c507c1cfe9 b/tests/resources/revert/.gitted/objects/97/f3574e92f1730d365fb9e00c10e3c507c1cfe9
new file mode 100644
index 000000000..12ebc58df
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/97/f3574e92f1730d365fb9e00c10e3c507c1cfe9
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/9a/95fd974e03c5b93828ceedd28755965b5d5c60 b/tests/resources/revert/.gitted/objects/9a/95fd974e03c5b93828ceedd28755965b5d5c60
new file mode 100644
index 000000000..bb93a34bb
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/9a/95fd974e03c5b93828ceedd28755965b5d5c60
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/a6/9f74efcb51634b88e04ea81273158a85257f41 b/tests/resources/revert/.gitted/objects/a6/9f74efcb51634b88e04ea81273158a85257f41
new file mode 100644
index 000000000..b96831f10
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/a6/9f74efcb51634b88e04ea81273158a85257f41
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/a8/c86221b400b836010567cc3593db6e96c1a83a b/tests/resources/revert/.gitted/objects/a8/c86221b400b836010567cc3593db6e96c1a83a
new file mode 100644
index 000000000..29654616e
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/a8/c86221b400b836010567cc3593db6e96c1a83a
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/ac/c4d33902092efeb3b714aa0b1007c329e2f2e6 b/tests/resources/revert/.gitted/objects/ac/c4d33902092efeb3b714aa0b1007c329e2f2e6
new file mode 100644
index 000000000..91bb68b41
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/ac/c4d33902092efeb3b714aa0b1007c329e2f2e6
@@ -0,0 +1,2 @@
+xÍ[
+1 @QßJW‘ étÒ6 F,LâþWàßý8peé½ø1llU.LÊiT B‡<dQ­uȉˆ#MTI"ºò¶ç²Âv·?OçË7ঽ´ù>dN0z¸"!:ùMLÿ䮽š¹õ$Î \ No newline at end of file
diff --git a/tests/resources/revert/.gitted/objects/b6/9d88e177455579896e2be495046e2a51456a9a b/tests/resources/revert/.gitted/objects/b6/9d88e177455579896e2be495046e2a51456a9a
new file mode 100644
index 000000000..a5f69f2c8
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/b6/9d88e177455579896e2be495046e2a51456a9a
@@ -0,0 +1 @@
+x¥]j!…ûì*.yoÐÑ™{…RòÒ „l@½NF¨ZÔ´Ûï$Ù@¡o‡Ãù 5ç4@+z-FP¤œó2{ÞH¶n"³zƒZ‹R.<£´J|¹Ë€‰‘ëäµ2Èš:V’fRÚ ñ´Xžaánc« >øÇ5†ËVs¯ÞâNïê”Shµ×uCÍï 4Yk‹¯’¤;݇ŽøqŽß± 8„ZÖÏF*W›+×ØB\¶Ô¡=,žu¾w¿‡ödÞ \ No newline at end of file
diff --git a/tests/resources/revert/.gitted/objects/b7/a55408832174c54708906a372a9be2ffe3649b b/tests/resources/revert/.gitted/objects/b7/a55408832174c54708906a372a9be2ffe3649b
new file mode 100644
index 000000000..77d4e20f9
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/b7/a55408832174c54708906a372a9be2ffe3649b
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/be/ead165e017269e8dc0dd6f01195726a2e1e01b b/tests/resources/revert/.gitted/objects/be/ead165e017269e8dc0dd6f01195726a2e1e01b
new file mode 100644
index 000000000..57de3f278
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/be/ead165e017269e8dc0dd6f01195726a2e1e01b
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/ca/f99de3a49827117bb66721010eac461b06a80c b/tests/resources/revert/.gitted/objects/ca/f99de3a49827117bb66721010eac461b06a80c
new file mode 100644
index 000000000..7f6f4e0c6
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/ca/f99de3a49827117bb66721010eac461b06a80c
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/ce/f56612d71a6af8d8015691e4865f7fece905b5 b/tests/resources/revert/.gitted/objects/ce/f56612d71a6af8d8015691e4865f7fece905b5
new file mode 100644
index 000000000..564588539
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/ce/f56612d71a6af8d8015691e4865f7fece905b5
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/d1/d403d22cbe24592d725f442835cf46fe60c8ac b/tests/resources/revert/.gitted/objects/d1/d403d22cbe24592d725f442835cf46fe60c8ac
new file mode 100644
index 000000000..2190bb449
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/d1/d403d22cbe24592d725f442835cf46fe60c8ac
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/dd/9a159c89509e73fd37d6af99619994cf7dfc06 b/tests/resources/revert/.gitted/objects/dd/9a159c89509e73fd37d6af99619994cf7dfc06
new file mode 100644
index 000000000..ed80d0aa5
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/dd/9a159c89509e73fd37d6af99619994cf7dfc06
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/de/03538407ed18914ff05657eeff70425c0f304d b/tests/resources/revert/.gitted/objects/de/03538407ed18914ff05657eeff70425c0f304d
new file mode 100644
index 000000000..e651676fd
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/de/03538407ed18914ff05657eeff70425c0f304d
@@ -0,0 +1,2 @@
+x¥Ž]J1„}Î)š}wéIw~D|ñ²H:vÀl–LÔë;ê|)Šú ª¤·¶M æPLeYuY}‰’Ô©+šmXÅ2g0‡.˜{z›,U…5’_¼'¬•ò!Ê‚5i!agÒǼö¯å+—ko{¿Á“é{i›Œ¾÷:ÏÒÛ3,×u H1"š#=ŽNýG…yÓONûÖîï
+Z¶y2ærÝv¿h‡¿(Ka¤b­dµìV[‚u•ÙFrRÙWõ(1ÉÙ|_c& \ No newline at end of file
diff --git a/tests/resources/revert/.gitted/objects/e3/4ef1afe54eb526fd92eec66084125f340f1d65 b/tests/resources/revert/.gitted/objects/e3/4ef1afe54eb526fd92eec66084125f340f1d65
new file mode 100644
index 000000000..fc19ebd3d
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/e3/4ef1afe54eb526fd92eec66084125f340f1d65
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/ea/392a157085bc32daccd59aa1998fe2f5fb9fc0 b/tests/resources/revert/.gitted/objects/ea/392a157085bc32daccd59aa1998fe2f5fb9fc0
new file mode 100644
index 000000000..1451a6ac4
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/ea/392a157085bc32daccd59aa1998fe2f5fb9fc0
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/eb/b03002cee5d66c7732dd06241119fe72ab96a5 b/tests/resources/revert/.gitted/objects/eb/b03002cee5d66c7732dd06241119fe72ab96a5
new file mode 100644
index 000000000..802125ebb
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/eb/b03002cee5d66c7732dd06241119fe72ab96a5
@@ -0,0 +1,2 @@
+x¥Á 1E=§Ši@I6&AÄ‹Ø@6;ÃÌdGlßXƒ‡Ÿwø¯HkUÁa<h'äålÉyf)ÌÑÆ̘\
+”,çÄóeb´&¿u•å“ûÏUÚ.\iØߺ·ZºìÂz*Ònà<†Áä#m°Ö ;ÂJ\˜ºU­ùe¾8[>` \ No newline at end of file
diff --git a/tests/resources/revert/.gitted/objects/ee/c6adcb2f3ceca0cadeccfe01b19382252ece9b b/tests/resources/revert/.gitted/objects/ee/c6adcb2f3ceca0cadeccfe01b19382252ece9b
new file mode 100644
index 000000000..f59f3d48d
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/ee/c6adcb2f3ceca0cadeccfe01b19382252ece9b
Binary files differ
diff --git a/tests/resources/revert/.gitted/objects/f4/e107c230d08a60fb419d19869f1f282b272d9c b/tests/resources/revert/.gitted/objects/f4/e107c230d08a60fb419d19869f1f282b272d9c
new file mode 100644
index 000000000..029da1ba9
--- /dev/null
+++ b/tests/resources/revert/.gitted/objects/f4/e107c230d08a60fb419d19869f1f282b272d9c
Binary files differ
diff --git a/tests/resources/revert/.gitted/refs/heads/master b/tests/resources/revert/.gitted/refs/heads/master
new file mode 100644
index 000000000..180f407e3
--- /dev/null
+++ b/tests/resources/revert/.gitted/refs/heads/master
@@ -0,0 +1 @@
+2d440f2b3147d3dc7ad1085813478d6d869d5a4d
diff --git a/tests/resources/revert/.gitted/refs/heads/merges b/tests/resources/revert/.gitted/refs/heads/merges
new file mode 100644
index 000000000..6533a947b
--- /dev/null
+++ b/tests/resources/revert/.gitted/refs/heads/merges
@@ -0,0 +1 @@
+5acdc74af27172ec491d213ee36cea7eb9ef2579
diff --git a/tests/resources/revert/.gitted/refs/heads/merges-branch b/tests/resources/revert/.gitted/refs/heads/merges-branch
new file mode 100644
index 000000000..febb29c44
--- /dev/null
+++ b/tests/resources/revert/.gitted/refs/heads/merges-branch
@@ -0,0 +1 @@
+13ee9cd5d8e1023c218e0e1ea684ec0c582b5050
diff --git a/tests/resources/revert/.gitted/refs/heads/reverted-branch b/tests/resources/revert/.gitted/refs/heads/reverted-branch
new file mode 100644
index 000000000..16bb7a2d7
--- /dev/null
+++ b/tests/resources/revert/.gitted/refs/heads/reverted-branch
@@ -0,0 +1 @@
+52c95c4264245469a0617e289a7d737f156826b4
diff --git a/tests/resources/revert/.gitted/refs/heads/two b/tests/resources/revert/.gitted/refs/heads/two
new file mode 100644
index 000000000..f31ec00e5
--- /dev/null
+++ b/tests/resources/revert/.gitted/refs/heads/two
@@ -0,0 +1 @@
+e34ef1afe54eb526fd92eec66084125f340f1d65
diff --git a/tests/resources/revert/file1.txt b/tests/resources/revert/file1.txt
new file mode 100644
index 000000000..84b225940
--- /dev/null
+++ b/tests/resources/revert/file1.txt
@@ -0,0 +1,14 @@
+!File one!
+!File one!
+File one!
+File one
+File one
+File one
+File one
+File one
+File one
+File one
+File one!
+!File one!
+!File one!
+!File one!
diff --git a/tests/resources/revert/file2.txt b/tests/resources/revert/file2.txt
new file mode 100644
index 000000000..acb5747b3
--- /dev/null
+++ b/tests/resources/revert/file2.txt
@@ -0,0 +1,16 @@
+File two
+File two
+File two
+File two
+File two
+File two
+File two
+File two
+File two
+File two
+File two
+File two
+File two
+File two
+File two
+File two
diff --git a/tests/resources/revert/file3.txt b/tests/resources/revert/file3.txt
new file mode 100644
index 000000000..b0330597f
--- /dev/null
+++ b/tests/resources/revert/file3.txt
@@ -0,0 +1,16 @@
+File three
+File three
+File three
+File three
+File three
+File three
+File three
+File three
+File three
+File three
+File three
+File three
+File three
+File three
+File three
+File three
diff --git a/tests/resources/revert/file6.txt b/tests/resources/revert/file6.txt
new file mode 100644
index 000000000..5c0cd5d56
--- /dev/null
+++ b/tests/resources/revert/file6.txt
@@ -0,0 +1,14 @@
+File six, actually!
+File four!
+File four!
+File four!
+File four!
+File four!
+File four!
+File four!
+File four!
+File four!
+File four!
+File four!
+File four!
+File four!
diff --git a/tests/resources/shallow.git/HEAD b/tests/resources/shallow.git/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/shallow.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/shallow.git/config b/tests/resources/shallow.git/config
new file mode 100644
index 000000000..a88b74b69
--- /dev/null
+++ b/tests/resources/shallow.git/config
@@ -0,0 +1,8 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = true
+ ignorecase = true
+ precomposeunicode = false
+[remote "origin"]
+ url = file://testrepo.git
diff --git a/tests/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.idx b/tests/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.idx
new file mode 100644
index 000000000..bfc7d24ff
--- /dev/null
+++ b/tests/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.idx
Binary files differ
diff --git a/tests/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.pack b/tests/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.pack
new file mode 100644
index 000000000..ccc6932fc
--- /dev/null
+++ b/tests/resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.pack
Binary files differ
diff --git a/tests/resources/shallow.git/packed-refs b/tests/resources/shallow.git/packed-refs
new file mode 100644
index 000000000..97eed743b
--- /dev/null
+++ b/tests/resources/shallow.git/packed-refs
@@ -0,0 +1,2 @@
+# pack-refs with: peeled
+a65fedf39aefe402d3bb6e24df4d4f5fe4547750 refs/heads/master
diff --git a/tests/resources/shallow.git/refs/.gitkeep b/tests/resources/shallow.git/refs/.gitkeep
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/resources/shallow.git/refs/.gitkeep
diff --git a/tests/resources/shallow.git/shallow b/tests/resources/shallow.git/shallow
new file mode 100644
index 000000000..9536ad89c
--- /dev/null
+++ b/tests/resources/shallow.git/shallow
@@ -0,0 +1 @@
+be3563ae3f795b2b4353bcce3a527ad0a4f7f644
diff --git a/tests/resources/short_tag.git/HEAD b/tests/resources/short_tag.git/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/short_tag.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/short_tag.git/config b/tests/resources/short_tag.git/config
new file mode 100644
index 000000000..a4ef456cb
--- /dev/null
+++ b/tests/resources/short_tag.git/config
@@ -0,0 +1,5 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = true
+ logallrefupdates = true
diff --git a/tests/resources/short_tag.git/index b/tests/resources/short_tag.git/index
new file mode 100644
index 000000000..87fef7847
--- /dev/null
+++ b/tests/resources/short_tag.git/index
Binary files differ
diff --git a/tests/resources/short_tag.git/objects/4a/5ed60bafcf4638b7c8356bd4ce1916bfede93c b/tests/resources/short_tag.git/objects/4a/5ed60bafcf4638b7c8356bd4ce1916bfede93c
new file mode 100644
index 000000000..aeb4e4b0b
--- /dev/null
+++ b/tests/resources/short_tag.git/objects/4a/5ed60bafcf4638b7c8356bd4ce1916bfede93c
Binary files differ
diff --git a/tests/resources/short_tag.git/objects/4d/5fcadc293a348e88f777dc0920f11e7d71441c b/tests/resources/short_tag.git/objects/4d/5fcadc293a348e88f777dc0920f11e7d71441c
new file mode 100644
index 000000000..806ce71a5
--- /dev/null
+++ b/tests/resources/short_tag.git/objects/4d/5fcadc293a348e88f777dc0920f11e7d71441c
Binary files differ
diff --git a/tests/resources/short_tag.git/objects/5d/a7760512a953e3c7c4e47e4392c7a4338fb729 b/tests/resources/short_tag.git/objects/5d/a7760512a953e3c7c4e47e4392c7a4338fb729
new file mode 100644
index 000000000..1192707c9
--- /dev/null
+++ b/tests/resources/short_tag.git/objects/5d/a7760512a953e3c7c4e47e4392c7a4338fb729
@@ -0,0 +1 @@
+xÌM‚0@aלb. ií%1Æ—pcÚé@Š”˜˜èé-Ëï-û¤6§&Bí E+‚pÐV¹Ð¡SƆ¨‘d/m(R¯°áïJ€%çÄ ×ÇR^‘vÜÒÊ©,GiƒÇ–Þðñ <Ó´3\©º­n‡ïcöinëåRé„SögÑ Ñüëu1 \ No newline at end of file
diff --git a/tests/resources/short_tag.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/tests/resources/short_tag.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
new file mode 100644
index 000000000..711223894
--- /dev/null
+++ b/tests/resources/short_tag.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
Binary files differ
diff --git a/tests/resources/short_tag.git/packed-refs b/tests/resources/short_tag.git/packed-refs
new file mode 100644
index 000000000..ca5197e3c
--- /dev/null
+++ b/tests/resources/short_tag.git/packed-refs
@@ -0,0 +1 @@
+5da7760512a953e3c7c4e47e4392c7a4338fb729 refs/tags/no_description
diff --git a/tests/resources/short_tag.git/refs/heads/master b/tests/resources/short_tag.git/refs/heads/master
new file mode 100644
index 000000000..fcefd1ef0
--- /dev/null
+++ b/tests/resources/short_tag.git/refs/heads/master
@@ -0,0 +1 @@
+4a5ed60bafcf4638b7c8356bd4ce1916bfede93c
diff --git a/tests/resources/status/.gitted/COMMIT_EDITMSG b/tests/resources/status/.gitted/COMMIT_EDITMSG
index ff887ba13..1a25cd4a6 100644
--- a/tests/resources/status/.gitted/COMMIT_EDITMSG
+++ b/tests/resources/status/.gitted/COMMIT_EDITMSG
@@ -1 +1 @@
-add subdir
+Add a file which name should appear before the "subdir/" folder while being dealt with by the treewalker
diff --git a/tests/resources/status/.gitted/ORIG_HEAD b/tests/resources/status/.gitted/ORIG_HEAD
index c2805f422..b46871fd6 100644
--- a/tests/resources/status/.gitted/ORIG_HEAD
+++ b/tests/resources/status/.gitted/ORIG_HEAD
@@ -1 +1 @@
-0017bd4ab1ec30440b17bae1680cff124ab5f1f6
+735b6a258cd196a8f7c9428419b02c1dca93fd75
diff --git a/tests/resources/status/.gitted/index b/tests/resources/status/.gitted/index
index 5c4b18841..2af99a183 100644
--- a/tests/resources/status/.gitted/index
+++ b/tests/resources/status/.gitted/index
Binary files differ
diff --git a/tests/resources/status/.gitted/info/exclude b/tests/resources/status/.gitted/info/exclude
index a5196d1be..0c4042a6a 100644
--- a/tests/resources/status/.gitted/info/exclude
+++ b/tests/resources/status/.gitted/info/exclude
@@ -4,3 +4,5 @@
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
+ignored*
+
diff --git a/tests/resources/status/.gitted/logs/HEAD b/tests/resources/status/.gitted/logs/HEAD
index e876bd80b..7b95b3cf1 100644
--- a/tests/resources/status/.gitted/logs/HEAD
+++ b/tests/resources/status/.gitted/logs/HEAD
@@ -1,2 +1,3 @@
0000000000000000000000000000000000000000 0017bd4ab1ec30440b17bae1680cff124ab5f1f6 Jason Penny <jasonpenny4@gmail.com> 1308050070 -0400 commit (initial): initial
0017bd4ab1ec30440b17bae1680cff124ab5f1f6 735b6a258cd196a8f7c9428419b02c1dca93fd75 Jason Penny <jasonpenny4@gmail.com> 1308954538 -0400 commit: add subdir
+735b6a258cd196a8f7c9428419b02c1dca93fd75 26a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f nulltoken <emeric.fermas@gmail.com> 1319911544 +0200 commit: Add a file which name should appear before the "subdir/" folder while being dealt with by the treewalker
diff --git a/tests/resources/status/.gitted/logs/refs/heads/master b/tests/resources/status/.gitted/logs/refs/heads/master
index e876bd80b..7b95b3cf1 100644
--- a/tests/resources/status/.gitted/logs/refs/heads/master
+++ b/tests/resources/status/.gitted/logs/refs/heads/master
@@ -1,2 +1,3 @@
0000000000000000000000000000000000000000 0017bd4ab1ec30440b17bae1680cff124ab5f1f6 Jason Penny <jasonpenny4@gmail.com> 1308050070 -0400 commit (initial): initial
0017bd4ab1ec30440b17bae1680cff124ab5f1f6 735b6a258cd196a8f7c9428419b02c1dca93fd75 Jason Penny <jasonpenny4@gmail.com> 1308954538 -0400 commit: add subdir
+735b6a258cd196a8f7c9428419b02c1dca93fd75 26a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f nulltoken <emeric.fermas@gmail.com> 1319911544 +0200 commit: Add a file which name should appear before the "subdir/" folder while being dealt with by the treewalker
diff --git a/tests/resources/status/.gitted/objects/26/a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f b/tests/resources/status/.gitted/objects/26/a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f
new file mode 100644
index 000000000..f7dddc4ff
--- /dev/null
+++ b/tests/resources/status/.gitted/objects/26/a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f
@@ -0,0 +1,2 @@
+xMnÄ …»Î)¬ÙVšò† ªö(Ì€BDˆ¢Þ¾LÐöûüžž«¥¤RÈ·Þˆ@êà,Î9Ž‹òÜÌœtàìæ•ðNj6f`žM6Z;h©ì …ZÜÐÞp Ú™Y,37¯/Ü;42x­&<z¬ Ö#ç^Ÿ´Â
+µä®ZÁýëQ0å««å¸äÆp®æÞ™`lÛ²Ó?ñéÛ{@)œ1¹+‚=Ö#ö6°j#è‘à²Ö§öqP³®¨¥´>ÀægêìÏŸüÕÛ‰ùImú|½jñ \ No newline at end of file
diff --git a/tests/resources/status/.gitted/objects/37/fcb02ccc1a85d1941e7f106d52dc3702dcf0d0 b/tests/resources/status/.gitted/objects/37/fcb02ccc1a85d1941e7f106d52dc3702dcf0d0
new file mode 100644
index 000000000..b75481b51
--- /dev/null
+++ b/tests/resources/status/.gitted/objects/37/fcb02ccc1a85d1941e7f106d52dc3702dcf0d0
Binary files differ
diff --git a/tests/resources/status/.gitted/objects/e8/ee89e15bbe9b20137715232387b3de5b28972e b/tests/resources/status/.gitted/objects/e8/ee89e15bbe9b20137715232387b3de5b28972e
new file mode 100644
index 000000000..cfc2413d5
--- /dev/null
+++ b/tests/resources/status/.gitted/objects/e8/ee89e15bbe9b20137715232387b3de5b28972e
Binary files differ
diff --git a/tests/resources/status/.gitted/refs/heads/master b/tests/resources/status/.gitted/refs/heads/master
index b46871fd6..3e2e2a07a 100644
--- a/tests/resources/status/.gitted/refs/heads/master
+++ b/tests/resources/status/.gitted/refs/heads/master
@@ -1 +1 @@
-735b6a258cd196a8f7c9428419b02c1dca93fd75
+26a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f
diff --git a/tests/resources/status/ignored_file b/tests/resources/status/ignored_file
new file mode 100644
index 000000000..6a79f808a
--- /dev/null
+++ b/tests/resources/status/ignored_file
@@ -0,0 +1 @@
+ignored_file
diff --git a/tests/resources/status/subdir.txt b/tests/resources/status/subdir.txt
new file mode 100644
index 000000000..e8ee89e15
--- /dev/null
+++ b/tests/resources/status/subdir.txt
@@ -0,0 +1,2 @@
+Is it a bird?
+Is it a plane?
diff --git a/tests/resources/status/è¿™ b/tests/resources/status/è¿™
new file mode 100644
index 000000000..f0ff9a197
--- /dev/null
+++ b/tests/resources/status/è¿™
@@ -0,0 +1 @@
+This
diff --git a/tests/resources/submod2/.gitted/HEAD b/tests/resources/submod2/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/submod2/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/submod2/.gitted/config b/tests/resources/submod2/.gitted/config
new file mode 100644
index 000000000..abc420734
--- /dev/null
+++ b/tests/resources/submod2/.gitted/config
@@ -0,0 +1,20 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
+[submodule "sm_missing_commits"]
+ url = ../submod2_target
+[submodule "sm_unchanged"]
+ url = ../submod2_target
+[submodule "sm_changed_file"]
+ url = ../submod2_target
+[submodule "sm_changed_index"]
+ url = ../submod2_target
+[submodule "sm_changed_head"]
+ url = ../submod2_target
+[submodule "sm_changed_untracked_file"]
+ url = ../submod2_target
+[submodule "sm_added_and_uncommited"]
+ url = ../submod2_target
diff --git a/tests/resources/submod2/.gitted/description b/tests/resources/submod2/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/submod2/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/submod2/.gitted/index b/tests/resources/submod2/.gitted/index
new file mode 100644
index 000000000..0c17e8629
--- /dev/null
+++ b/tests/resources/submod2/.gitted/index
Binary files differ
diff --git a/tests/resources/submod2/.gitted/info/exclude b/tests/resources/submod2/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/submod2/.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/resources/submod2/.gitted/logs/HEAD b/tests/resources/submod2/.gitted/logs/HEAD
new file mode 100644
index 000000000..2cf2ca74d
--- /dev/null
+++ b/tests/resources/submod2/.gitted/logs/HEAD
@@ -0,0 +1,4 @@
+0000000000000000000000000000000000000000 14fe9ccf104058df25e0a08361c4494e167ef243 Russell Belfer <rb@github.com> 1342559771 -0700 commit (initial): Initial commit
+14fe9ccf104058df25e0a08361c4494e167ef243 a9104bf89e911387244ef499413960ba472066d9 Russell Belfer <rb@github.com> 1342559831 -0700 commit: Adding a submodule
+a9104bf89e911387244ef499413960ba472066d9 5901da4f1c67756eeadc5121d206bec2431f253b Russell Belfer <rb@github.com> 1342560036 -0700 commit: Updating submodule
+5901da4f1c67756eeadc5121d206bec2431f253b 7484482eb8db738cafa696993664607500a3f2b9 Russell Belfer <rb@github.com> 1342560288 -0700 commit: Adding a bunch more test content
diff --git a/tests/resources/submod2/.gitted/logs/refs/heads/master b/tests/resources/submod2/.gitted/logs/refs/heads/master
new file mode 100644
index 000000000..2cf2ca74d
--- /dev/null
+++ b/tests/resources/submod2/.gitted/logs/refs/heads/master
@@ -0,0 +1,4 @@
+0000000000000000000000000000000000000000 14fe9ccf104058df25e0a08361c4494e167ef243 Russell Belfer <rb@github.com> 1342559771 -0700 commit (initial): Initial commit
+14fe9ccf104058df25e0a08361c4494e167ef243 a9104bf89e911387244ef499413960ba472066d9 Russell Belfer <rb@github.com> 1342559831 -0700 commit: Adding a submodule
+a9104bf89e911387244ef499413960ba472066d9 5901da4f1c67756eeadc5121d206bec2431f253b Russell Belfer <rb@github.com> 1342560036 -0700 commit: Updating submodule
+5901da4f1c67756eeadc5121d206bec2431f253b 7484482eb8db738cafa696993664607500a3f2b9 Russell Belfer <rb@github.com> 1342560288 -0700 commit: Adding a bunch more test content
diff --git a/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/HEAD b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/config b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/config
new file mode 100644
index 000000000..2d0583e99
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/config
@@ -0,0 +1,13 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ worktree = ../../../sm_added_and_uncommited
+ ignorecase = true
+[remote "origin"]
+ fetch = +refs/heads/*:refs/remotes/origin/*
+ url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target
+[branch "master"]
+ remote = origin
+ merge = refs/heads/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/description b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/index b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/index
new file mode 100644
index 000000000..65140a510
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/index
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/info/exclude b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/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/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/HEAD b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/HEAD
new file mode 100644
index 000000000..53753e7dd
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560316 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
diff --git a/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/refs/heads/master
new file mode 100644
index 000000000..53753e7dd
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/refs/heads/master
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560316 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
diff --git a/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..53753e7dd
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560316 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
diff --git a/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1
new file mode 100644
index 000000000..f4b7094c5
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484
new file mode 100644
index 000000000..56c845e49
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5
new file mode 100644
index 000000000..bd179b5f5
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/41/bd4bc3df978de695f67ace64c560913da11653
new file mode 100644
index 000000000..ccf49bd15
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/41/bd4bc3df978de695f67ace64c560913da11653
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/48/0095882d281ed676fe5b863569520e54a7d5c0
new file mode 100644
index 000000000..53029069a
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/48/0095882d281ed676fe5b863569520e54a7d5c0
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/5e/4963595a9774b90524d35a807169049de8ccad
new file mode 100644
index 000000000..38c791eba
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/5e/4963595a9774b90524d35a807169049de8ccad
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/6b/31c659545507c381e9cd34ec508f16c04e149e
new file mode 100644
index 000000000..a26d29993
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/6b/31c659545507c381e9cd34ec508f16c04e149e
@@ -0,0 +1,2 @@
+x•Q
+!EûvoÅÓy*Ñ_¿í@Çg#h‚£ûOhý^Î9w«¥¤ÒêSoÌ€f1*²ŠÁ[”‰¬§èIc Ô¤ìê¤p£ïµÁkç Α\›¿¿S߇¿lµÜ@.¤´^QpF‹(æ:ÿúDÿ5Åó“zr~ ñen8 \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/73/ba924a80437097795ae839e66e187c55d3babf
new file mode 100644
index 000000000..83d1ba481
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/73/ba924a80437097795ae839e66e187c55d3babf
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a
new file mode 100644
index 000000000..6d27af8a8
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a
@@ -0,0 +1,2 @@
+x-Ë1Â0 FaæžâßØ0pŽÀìÄÐ(N-ÅöÐÛÓ¡Ò“¾é±ãq]>ksÅ*š? |m“‡Õçiª@ÛÖý¶¼m»¨V£…£'©î`)”.Ø-1¨ x
+u„xãòt(+ \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/78/9efbdadaa4a582778d4584385495559ea0994b
new file mode 100644
index 000000000..17458840b
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/78/9efbdadaa4a582778d4584385495559ea0994b
@@ -0,0 +1,2 @@
+x Œ± …0 )ÞŠ?= ¥ÉÄNŠlO¤k®¸‹jÛúÿ¹8&„«¨ ãr ”
+ïqJWñ°7¾B<ÉáöfÙìK8­#Q1C-‘"eª·Ì«£Š°ð>¼'@ \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e
new file mode 100644
index 000000000..83cc29fb1
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5
new file mode 100644
index 000000000..55bda40ef
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/packed-refs b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/packed-refs
new file mode 100644
index 000000000..5a4ebc47c
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/packed-refs
@@ -0,0 +1,2 @@
+# pack-refs with: peeled
+480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/refs/heads/master
new file mode 100644
index 000000000..e12c44d7a
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/refs/heads/master
@@ -0,0 +1 @@
+480095882d281ed676fe5b863569520e54a7d5c0
diff --git a/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..6efe28fff
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_added_and_uncommited/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+ref: refs/remotes/origin/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_file/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_file/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_file/config b/tests/resources/submod2/.gitted/modules/sm_changed_file/config
new file mode 100644
index 000000000..10cc2508e
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/config
@@ -0,0 +1,13 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ worktree = ../../../sm_changed_file
+ ignorecase = true
+[remote "origin"]
+ fetch = +refs/heads/*:refs/remotes/origin/*
+ url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target
+[branch "master"]
+ remote = origin
+ merge = refs/heads/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_file/description b/tests/resources/submod2/.gitted/modules/sm_changed_file/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_file/index b/tests/resources/submod2/.gitted/modules/sm_changed_file/index
new file mode 100644
index 000000000..6914a3b6e
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/index
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_file/info/exclude b/tests/resources/submod2/.gitted/modules/sm_changed_file/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/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/resources/submod2/.gitted/modules/sm_changed_file/logs/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_file/logs/HEAD
new file mode 100644
index 000000000..e5cb63f8d
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/logs/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560173 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_file/logs/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_changed_file/logs/refs/heads/master
new file mode 100644
index 000000000..e5cb63f8d
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/logs/refs/heads/master
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560173 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_file/logs/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_file/logs/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..e5cb63f8d
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/logs/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560173 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1
new file mode 100644
index 000000000..f4b7094c5
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484
new file mode 100644
index 000000000..56c845e49
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5
new file mode 100644
index 000000000..bd179b5f5
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/41/bd4bc3df978de695f67ace64c560913da11653
new file mode 100644
index 000000000..ccf49bd15
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/41/bd4bc3df978de695f67ace64c560913da11653
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0
new file mode 100644
index 000000000..53029069a
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/5e/4963595a9774b90524d35a807169049de8ccad
new file mode 100644
index 000000000..38c791eba
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/5e/4963595a9774b90524d35a807169049de8ccad
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e
new file mode 100644
index 000000000..a26d29993
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e
@@ -0,0 +1,2 @@
+x•Q
+!EûvoÅÓy*Ñ_¿í@Çg#h‚£ûOhý^Î9w«¥¤ÒêSoÌ€f1*²ŠÁ[”‰¬§èIc Ô¤ìê¤p£ïµÁkç Α\›¿¿S߇¿lµÜ@.¤´^QpF‹(æ:ÿúDÿ5Åó“zr~ ñen8 \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/73/ba924a80437097795ae839e66e187c55d3babf
new file mode 100644
index 000000000..83d1ba481
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/73/ba924a80437097795ae839e66e187c55d3babf
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a
new file mode 100644
index 000000000..6d27af8a8
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a
@@ -0,0 +1,2 @@
+x-Ë1Â0 FaæžâßØ0pŽÀìÄÐ(N-ÅöÐÛÓ¡Ò“¾é±ãq]>ksÅ*š? |m“‡Õçiª@ÛÖý¶¼m»¨V£…£'©î`)”.Ø-1¨ x
+u„xãòt(+ \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b
new file mode 100644
index 000000000..17458840b
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b
@@ -0,0 +1,2 @@
+x Œ± …0 )ÞŠ?= ¥ÉÄNŠlO¤k®¸‹jÛúÿ¹8&„«¨ ãr ”
+ïqJWñ°7¾B<ÉáöfÙìK8­#Q1C-‘"eª·Ì«£Š°ð>¼'@ \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e
new file mode 100644
index 000000000..83cc29fb1
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5
new file mode 100644
index 000000000..55bda40ef
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_file/packed-refs b/tests/resources/submod2/.gitted/modules/sm_changed_file/packed-refs
new file mode 100644
index 000000000..5a4ebc47c
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/packed-refs
@@ -0,0 +1,2 @@
+# pack-refs with: peeled
+480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_file/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_changed_file/refs/heads/master
new file mode 100644
index 000000000..e12c44d7a
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/refs/heads/master
@@ -0,0 +1 @@
+480095882d281ed676fe5b863569520e54a7d5c0
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_file/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_file/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..6efe28fff
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_file/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+ref: refs/remotes/origin/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/COMMIT_EDITMSG b/tests/resources/submod2/.gitted/modules/sm_changed_head/COMMIT_EDITMSG
new file mode 100644
index 000000000..6b8d1e3fc
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/COMMIT_EDITMSG
@@ -0,0 +1 @@
+Making a change in a submodule
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_head/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/config b/tests/resources/submod2/.gitted/modules/sm_changed_head/config
new file mode 100644
index 000000000..7d002536a
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/config
@@ -0,0 +1,13 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ worktree = ../../../sm_changed_head
+ ignorecase = true
+[remote "origin"]
+ fetch = +refs/heads/*:refs/remotes/origin/*
+ url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target
+[branch "master"]
+ remote = origin
+ merge = refs/heads/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/description b/tests/resources/submod2/.gitted/modules/sm_changed_head/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/index b/tests/resources/submod2/.gitted/modules/sm_changed_head/index
new file mode 100644
index 000000000..728fa292f
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/index
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/info/exclude b/tests/resources/submod2/.gitted/modules/sm_changed_head/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/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/resources/submod2/.gitted/modules/sm_changed_head/logs/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_head/logs/HEAD
new file mode 100644
index 000000000..cabdeb2b5
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/logs/HEAD
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560179 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
+480095882d281ed676fe5b863569520e54a7d5c0 3d9386c507f6b093471a3e324085657a3c2b4247 Russell Belfer <rb@github.com> 1342560431 -0700 commit: Making a change in a submodule
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/logs/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_changed_head/logs/refs/heads/master
new file mode 100644
index 000000000..cabdeb2b5
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/logs/refs/heads/master
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560179 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
+480095882d281ed676fe5b863569520e54a7d5c0 3d9386c507f6b093471a3e324085657a3c2b4247 Russell Belfer <rb@github.com> 1342560431 -0700 commit: Making a change in a submodule
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/logs/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_head/logs/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..257ca21d1
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/logs/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560179 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1
new file mode 100644
index 000000000..f4b7094c5
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484
new file mode 100644
index 000000000..56c845e49
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5
new file mode 100644
index 000000000..bd179b5f5
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/3d/9386c507f6b093471a3e324085657a3c2b4247 b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/3d/9386c507f6b093471a3e324085657a3c2b4247
new file mode 100644
index 000000000..a2c371642
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/3d/9386c507f6b093471a3e324085657a3c2b4247
@@ -0,0 +1,3 @@
+x•ŽKj!E3vµ„jµüÀ#<Þ<“ì@­êéO°uÿq ™.çÂ)×ql ´‰o­Š€÷sFa#Èv‰ÓÅ )g#{':ªßTål`b¤4ë0 ;ïf¡ár‘4
+Ùä™
+ªÔÛzUøî÷-û/Ùg©ð¨ù¹lmíù£\Ç'LÆjrhÍïèÕXG_êŸê+ýlç ÊšÎE`;ß=÷]ÔÞJç \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/41/bd4bc3df978de695f67ace64c560913da11653
new file mode 100644
index 000000000..ccf49bd15
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/41/bd4bc3df978de695f67ace64c560913da11653
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/48/0095882d281ed676fe5b863569520e54a7d5c0
new file mode 100644
index 000000000..53029069a
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/48/0095882d281ed676fe5b863569520e54a7d5c0
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/5e/4963595a9774b90524d35a807169049de8ccad
new file mode 100644
index 000000000..38c791eba
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/5e/4963595a9774b90524d35a807169049de8ccad
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/6b/31c659545507c381e9cd34ec508f16c04e149e
new file mode 100644
index 000000000..a26d29993
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/6b/31c659545507c381e9cd34ec508f16c04e149e
@@ -0,0 +1,2 @@
+x•Q
+!EûvoÅÓy*Ñ_¿í@Çg#h‚£ûOhý^Î9w«¥¤ÒêSoÌ€f1*²ŠÁ[”‰¬§èIc Ô¤ìê¤p£ïµÁkç Α\›¿¿S߇¿lµÜ@.¤´^QpF‹(æ:ÿúDÿ5Åó“zr~ ñen8 \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/73/ba924a80437097795ae839e66e187c55d3babf
new file mode 100644
index 000000000..83d1ba481
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/73/ba924a80437097795ae839e66e187c55d3babf
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/77/fb0ed3e58568d6ad362c78de08ab8649d76e29 b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/77/fb0ed3e58568d6ad362c78de08ab8649d76e29
new file mode 100644
index 000000000..f8a236f3d
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/77/fb0ed3e58568d6ad362c78de08ab8649d76e29
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a
new file mode 100644
index 000000000..6d27af8a8
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a
@@ -0,0 +1,2 @@
+x-Ë1Â0 FaæžâßØ0pŽÀìÄÐ(N-ÅöÐÛÓ¡Ò“¾é±ãq]>ksÅ*š? |m“‡Õçiª@ÛÖý¶¼m»¨V£…£'©î`)”.Ø-1¨ x
+u„xãòt(+ \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/78/9efbdadaa4a582778d4584385495559ea0994b
new file mode 100644
index 000000000..17458840b
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/78/9efbdadaa4a582778d4584385495559ea0994b
@@ -0,0 +1,2 @@
+x Œ± …0 )ÞŠ?= ¥ÉÄNŠlO¤k®¸‹jÛúÿ¹8&„«¨ ãr ”
+ïqJWñ°7¾B<ÉáöfÙìK8­#Q1C-‘"eª·Ì«£Š°ð>¼'@ \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e
new file mode 100644
index 000000000..83cc29fb1
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/8e/b1e637ed9fc8e5454fa20d38f809091f9395f4 b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/8e/b1e637ed9fc8e5454fa20d38f809091f9395f4
new file mode 100644
index 000000000..8155b3e87
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/8e/b1e637ed9fc8e5454fa20d38f809091f9395f4
@@ -0,0 +1,2 @@
+xMM;
+1µÎ)Þ ÁZPÐÞÆr²3kÉ l²En¿ƒl!¼æýc±ˆóõrz§Üà ,¹º¡çe +ÚlEZxuPY…x QC³*ðf·uLácfR3ŠÍT0'Ò¯øjƒŠ°ð~G¦^s1Šèb2z’ƒÿùVkî]Ü5<·ûv¨'>ã \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5
new file mode 100644
index 000000000..55bda40ef
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/packed-refs b/tests/resources/submod2/.gitted/modules/sm_changed_head/packed-refs
new file mode 100644
index 000000000..5a4ebc47c
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/packed-refs
@@ -0,0 +1,2 @@
+# pack-refs with: peeled
+480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_changed_head/refs/heads/master
new file mode 100644
index 000000000..ae079bd79
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/refs/heads/master
@@ -0,0 +1 @@
+3d9386c507f6b093471a3e324085657a3c2b4247
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_head/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_head/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..6efe28fff
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_head/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+ref: refs/remotes/origin/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_index/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/config b/tests/resources/submod2/.gitted/modules/sm_changed_index/config
new file mode 100644
index 000000000..0274ff7e3
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/config
@@ -0,0 +1,13 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ worktree = ../../../sm_changed_index
+ ignorecase = true
+[remote "origin"]
+ fetch = +refs/heads/*:refs/remotes/origin/*
+ url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target
+[branch "master"]
+ remote = origin
+ merge = refs/heads/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/description b/tests/resources/submod2/.gitted/modules/sm_changed_index/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/index b/tests/resources/submod2/.gitted/modules/sm_changed_index/index
new file mode 100644
index 000000000..6fad3b43e
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/index
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/info/exclude b/tests/resources/submod2/.gitted/modules/sm_changed_index/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/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/resources/submod2/.gitted/modules/sm_changed_index/logs/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_index/logs/HEAD
new file mode 100644
index 000000000..80eb54102
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/logs/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560175 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/logs/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_changed_index/logs/refs/heads/master
new file mode 100644
index 000000000..80eb54102
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/logs/refs/heads/master
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560175 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/logs/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_index/logs/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..80eb54102
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/logs/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560175 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1
new file mode 100644
index 000000000..f4b7094c5
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484
new file mode 100644
index 000000000..56c845e49
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5
new file mode 100644
index 000000000..bd179b5f5
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/41/bd4bc3df978de695f67ace64c560913da11653
new file mode 100644
index 000000000..ccf49bd15
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/41/bd4bc3df978de695f67ace64c560913da11653
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/48/0095882d281ed676fe5b863569520e54a7d5c0
new file mode 100644
index 000000000..53029069a
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/48/0095882d281ed676fe5b863569520e54a7d5c0
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/5e/4963595a9774b90524d35a807169049de8ccad
new file mode 100644
index 000000000..38c791eba
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/5e/4963595a9774b90524d35a807169049de8ccad
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/6b/31c659545507c381e9cd34ec508f16c04e149e
new file mode 100644
index 000000000..a26d29993
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/6b/31c659545507c381e9cd34ec508f16c04e149e
@@ -0,0 +1,2 @@
+x•Q
+!EûvoÅÓy*Ñ_¿í@Çg#h‚£ûOhý^Î9w«¥¤ÒêSoÌ€f1*²ŠÁ[”‰¬§èIc Ô¤ìê¤p£ïµÁkç Α\›¿¿S߇¿lµÜ@.¤´^QpF‹(æ:ÿúDÿ5Åó“zr~ ñen8 \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/73/ba924a80437097795ae839e66e187c55d3babf
new file mode 100644
index 000000000..83d1ba481
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/73/ba924a80437097795ae839e66e187c55d3babf
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a
new file mode 100644
index 000000000..6d27af8a8
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a
@@ -0,0 +1,2 @@
+x-Ë1Â0 FaæžâßØ0pŽÀìÄÐ(N-ÅöÐÛÓ¡Ò“¾é±ãq]>ksÅ*š? |m“‡Õçiª@ÛÖý¶¼m»¨V£…£'©î`)”.Ø-1¨ x
+u„xãòt(+ \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/78/9efbdadaa4a582778d4584385495559ea0994b
new file mode 100644
index 000000000..17458840b
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/78/9efbdadaa4a582778d4584385495559ea0994b
@@ -0,0 +1,2 @@
+x Œ± …0 )ÞŠ?= ¥ÉÄNŠlO¤k®¸‹jÛúÿ¹8&„«¨ ãr ”
+ïqJWñ°7¾B<ÉáöfÙìK8­#Q1C-‘"eª·Ì«£Š°ð>¼'@ \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e
new file mode 100644
index 000000000..83cc29fb1
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/a0/2d31770687965547ab7a04cee199b29ee458d6 b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/a0/2d31770687965547ab7a04cee199b29ee458d6
new file mode 100644
index 000000000..cb3f5a002
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/a0/2d31770687965547ab7a04cee199b29ee458d6
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5
new file mode 100644
index 000000000..55bda40ef
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/packed-refs b/tests/resources/submod2/.gitted/modules/sm_changed_index/packed-refs
new file mode 100644
index 000000000..5a4ebc47c
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/packed-refs
@@ -0,0 +1,2 @@
+# pack-refs with: peeled
+480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_changed_index/refs/heads/master
new file mode 100644
index 000000000..e12c44d7a
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/refs/heads/master
@@ -0,0 +1 @@
+480095882d281ed676fe5b863569520e54a7d5c0
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_index/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_index/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..6efe28fff
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_index/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+ref: refs/remotes/origin/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/config b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/config
new file mode 100644
index 000000000..7f2584476
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/config
@@ -0,0 +1,13 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ worktree = ../../../sm_changed_untracked_file
+ ignorecase = true
+[remote "origin"]
+ fetch = +refs/heads/*:refs/remotes/origin/*
+ url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target
+[branch "master"]
+ remote = origin
+ merge = refs/heads/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/description b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/index b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/index
new file mode 100644
index 000000000..598e30a32
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/index
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/info/exclude b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/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/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/HEAD
new file mode 100644
index 000000000..d1beafbd6
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560186 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/refs/heads/master
new file mode 100644
index 000000000..d1beafbd6
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/refs/heads/master
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560186 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..d1beafbd6
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560186 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1
new file mode 100644
index 000000000..f4b7094c5
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484
new file mode 100644
index 000000000..56c845e49
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5
new file mode 100644
index 000000000..bd179b5f5
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/41/bd4bc3df978de695f67ace64c560913da11653
new file mode 100644
index 000000000..ccf49bd15
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/41/bd4bc3df978de695f67ace64c560913da11653
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0
new file mode 100644
index 000000000..53029069a
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/5e/4963595a9774b90524d35a807169049de8ccad
new file mode 100644
index 000000000..38c791eba
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/5e/4963595a9774b90524d35a807169049de8ccad
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e
new file mode 100644
index 000000000..a26d29993
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e
@@ -0,0 +1,2 @@
+x•Q
+!EûvoÅÓy*Ñ_¿í@Çg#h‚£ûOhý^Î9w«¥¤ÒêSoÌ€f1*²ŠÁ[”‰¬§èIc Ô¤ìê¤p£ïµÁkç Α\›¿¿S߇¿lµÜ@.¤´^QpF‹(æ:ÿúDÿ5Åó“zr~ ñen8 \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/73/ba924a80437097795ae839e66e187c55d3babf
new file mode 100644
index 000000000..83d1ba481
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/73/ba924a80437097795ae839e66e187c55d3babf
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a
new file mode 100644
index 000000000..6d27af8a8
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a
@@ -0,0 +1,2 @@
+x-Ë1Â0 FaæžâßØ0pŽÀìÄÐ(N-ÅöÐÛÓ¡Ò“¾é±ãq]>ksÅ*š? |m“‡Õçiª@ÛÖý¶¼m»¨V£…£'©î`)”.Ø-1¨ x
+u„xãòt(+ \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b
new file mode 100644
index 000000000..17458840b
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b
@@ -0,0 +1,2 @@
+x Œ± …0 )ÞŠ?= ¥ÉÄNŠlO¤k®¸‹jÛúÿ¹8&„«¨ ãr ”
+ïqJWñ°7¾B<ÉáöfÙìK8­#Q1C-‘"eª·Ì«£Š°ð>¼'@ \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e
new file mode 100644
index 000000000..83cc29fb1
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5
new file mode 100644
index 000000000..55bda40ef
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/packed-refs b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/packed-refs
new file mode 100644
index 000000000..5a4ebc47c
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/packed-refs
@@ -0,0 +1,2 @@
+# pack-refs with: peeled
+480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/refs/heads/master
new file mode 100644
index 000000000..e12c44d7a
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/refs/heads/master
@@ -0,0 +1 @@
+480095882d281ed676fe5b863569520e54a7d5c0
diff --git a/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..6efe28fff
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_changed_untracked_file/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+ref: refs/remotes/origin/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_missing_commits/HEAD b/tests/resources/submod2/.gitted/modules/sm_missing_commits/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_missing_commits/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_missing_commits/config b/tests/resources/submod2/.gitted/modules/sm_missing_commits/config
new file mode 100644
index 000000000..45fbb30cf
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_missing_commits/config
@@ -0,0 +1,13 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ worktree = ../../../sm_missing_commits
+ ignorecase = true
+[remote "origin"]
+ fetch = +refs/heads/*:refs/remotes/origin/*
+ url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target
+[branch "master"]
+ remote = origin
+ merge = refs/heads/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_missing_commits/description b/tests/resources/submod2/.gitted/modules/sm_missing_commits/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_missing_commits/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/submod2/.gitted/modules/sm_missing_commits/index b/tests/resources/submod2/.gitted/modules/sm_missing_commits/index
new file mode 100644
index 000000000..490356524
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_missing_commits/index
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_missing_commits/info/exclude b/tests/resources/submod2/.gitted/modules/sm_missing_commits/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_missing_commits/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/resources/submod2/.gitted/modules/sm_missing_commits/logs/HEAD b/tests/resources/submod2/.gitted/modules/sm_missing_commits/logs/HEAD
new file mode 100644
index 000000000..ee08c9706
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_missing_commits/logs/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer <rb@github.com> 1342559796 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
diff --git a/tests/resources/submod2/.gitted/modules/sm_missing_commits/logs/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_missing_commits/logs/refs/heads/master
new file mode 100644
index 000000000..ee08c9706
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_missing_commits/logs/refs/heads/master
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer <rb@github.com> 1342559796 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
diff --git a/tests/resources/submod2/.gitted/modules/sm_missing_commits/logs/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_missing_commits/logs/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..ee08c9706
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_missing_commits/logs/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer <rb@github.com> 1342559796 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
diff --git a/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1
new file mode 100644
index 000000000..f4b7094c5
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484
new file mode 100644
index 000000000..56c845e49
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5
new file mode 100644
index 000000000..bd179b5f5
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/41/bd4bc3df978de695f67ace64c560913da11653
new file mode 100644
index 000000000..ccf49bd15
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/41/bd4bc3df978de695f67ace64c560913da11653
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/5e/4963595a9774b90524d35a807169049de8ccad
new file mode 100644
index 000000000..38c791eba
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/5e/4963595a9774b90524d35a807169049de8ccad
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/6b/31c659545507c381e9cd34ec508f16c04e149e
new file mode 100644
index 000000000..a26d29993
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/6b/31c659545507c381e9cd34ec508f16c04e149e
@@ -0,0 +1,2 @@
+x•Q
+!EûvoÅÓy*Ñ_¿í@Çg#h‚£ûOhý^Î9w«¥¤ÒêSoÌ€f1*²ŠÁ[”‰¬§èIc Ô¤ìê¤p£ïµÁkç Α\›¿¿S߇¿lµÜ@.¤´^QpF‹(æ:ÿúDÿ5Åó“zr~ ñen8 \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a
new file mode 100644
index 000000000..6d27af8a8
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a
@@ -0,0 +1,2 @@
+x-Ë1Â0 FaæžâßØ0pŽÀìÄÐ(N-ÅöÐÛÓ¡Ò“¾é±ãq]>ksÅ*š? |m“‡Õçiª@ÛÖý¶¼m»¨V£…£'©î`)”.Ø-1¨ x
+u„xãòt(+ \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e
new file mode 100644
index 000000000..83cc29fb1
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5
new file mode 100644
index 000000000..55bda40ef
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_missing_commits/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_missing_commits/packed-refs b/tests/resources/submod2/.gitted/modules/sm_missing_commits/packed-refs
new file mode 100644
index 000000000..66fbf5daf
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_missing_commits/packed-refs
@@ -0,0 +1,2 @@
+# pack-refs with: peeled
+5e4963595a9774b90524d35a807169049de8ccad refs/remotes/origin/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_missing_commits/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_missing_commits/refs/heads/master
new file mode 100644
index 000000000..3913aca5d
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_missing_commits/refs/heads/master
@@ -0,0 +1 @@
+5e4963595a9774b90524d35a807169049de8ccad
diff --git a/tests/resources/submod2/.gitted/modules/sm_missing_commits/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_missing_commits/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..6efe28fff
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_missing_commits/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+ref: refs/remotes/origin/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_unchanged/HEAD b/tests/resources/submod2/.gitted/modules/sm_unchanged/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_unchanged/config b/tests/resources/submod2/.gitted/modules/sm_unchanged/config
new file mode 100644
index 000000000..fc706c9dd
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/config
@@ -0,0 +1,13 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ worktree = ../../../sm_unchanged
+ ignorecase = true
+[remote "origin"]
+ fetch = +refs/heads/*:refs/remotes/origin/*
+ url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target
+[branch "master"]
+ remote = origin
+ merge = refs/heads/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_unchanged/description b/tests/resources/submod2/.gitted/modules/sm_unchanged/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/submod2/.gitted/modules/sm_unchanged/index b/tests/resources/submod2/.gitted/modules/sm_unchanged/index
new file mode 100644
index 000000000..629c849ec
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/index
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_unchanged/info/exclude b/tests/resources/submod2/.gitted/modules/sm_unchanged/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/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/resources/submod2/.gitted/modules/sm_unchanged/logs/HEAD b/tests/resources/submod2/.gitted/modules/sm_unchanged/logs/HEAD
new file mode 100644
index 000000000..72653286a
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/logs/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560169 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
diff --git a/tests/resources/submod2/.gitted/modules/sm_unchanged/logs/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_unchanged/logs/refs/heads/master
new file mode 100644
index 000000000..72653286a
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/logs/refs/heads/master
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560169 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
diff --git a/tests/resources/submod2/.gitted/modules/sm_unchanged/logs/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_unchanged/logs/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..72653286a
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/logs/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342560169 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target
diff --git a/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1
new file mode 100644
index 000000000..f4b7094c5
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484
new file mode 100644
index 000000000..56c845e49
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5
new file mode 100644
index 000000000..bd179b5f5
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/41/bd4bc3df978de695f67ace64c560913da11653
new file mode 100644
index 000000000..ccf49bd15
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/41/bd4bc3df978de695f67ace64c560913da11653
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/48/0095882d281ed676fe5b863569520e54a7d5c0
new file mode 100644
index 000000000..53029069a
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/48/0095882d281ed676fe5b863569520e54a7d5c0
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/5e/4963595a9774b90524d35a807169049de8ccad
new file mode 100644
index 000000000..38c791eba
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/5e/4963595a9774b90524d35a807169049de8ccad
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/6b/31c659545507c381e9cd34ec508f16c04e149e
new file mode 100644
index 000000000..a26d29993
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/6b/31c659545507c381e9cd34ec508f16c04e149e
@@ -0,0 +1,2 @@
+x•Q
+!EûvoÅÓy*Ñ_¿í@Çg#h‚£ûOhý^Î9w«¥¤ÒêSoÌ€f1*²ŠÁ[”‰¬§èIc Ô¤ìê¤p£ïµÁkç Α\›¿¿S߇¿lµÜ@.¤´^QpF‹(æ:ÿúDÿ5Åó“zr~ ñen8 \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/73/ba924a80437097795ae839e66e187c55d3babf
new file mode 100644
index 000000000..83d1ba481
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/73/ba924a80437097795ae839e66e187c55d3babf
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a
new file mode 100644
index 000000000..6d27af8a8
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a
@@ -0,0 +1,2 @@
+x-Ë1Â0 FaæžâßØ0pŽÀìÄÐ(N-ÅöÐÛÓ¡Ò“¾é±ãq]>ksÅ*š? |m“‡Õçiª@ÛÖý¶¼m»¨V£…£'©î`)”.Ø-1¨ x
+u„xãòt(+ \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/78/9efbdadaa4a582778d4584385495559ea0994b
new file mode 100644
index 000000000..17458840b
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/78/9efbdadaa4a582778d4584385495559ea0994b
@@ -0,0 +1,2 @@
+x Œ± …0 )ÞŠ?= ¥ÉÄNŠlO¤k®¸‹jÛúÿ¹8&„«¨ ãr ”
+ïqJWñ°7¾B<ÉáöfÙìK8­#Q1C-‘"eª·Ì«£Š°ð>¼'@ \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e
new file mode 100644
index 000000000..83cc29fb1
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5
new file mode 100644
index 000000000..55bda40ef
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5
Binary files differ
diff --git a/tests/resources/submod2/.gitted/modules/sm_unchanged/packed-refs b/tests/resources/submod2/.gitted/modules/sm_unchanged/packed-refs
new file mode 100644
index 000000000..5a4ebc47c
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/packed-refs
@@ -0,0 +1,2 @@
+# pack-refs with: peeled
+480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master
diff --git a/tests/resources/submod2/.gitted/modules/sm_unchanged/refs/heads/master b/tests/resources/submod2/.gitted/modules/sm_unchanged/refs/heads/master
new file mode 100644
index 000000000..e12c44d7a
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/refs/heads/master
@@ -0,0 +1 @@
+480095882d281ed676fe5b863569520e54a7d5c0
diff --git a/tests/resources/submod2/.gitted/modules/sm_unchanged/refs/remotes/origin/HEAD b/tests/resources/submod2/.gitted/modules/sm_unchanged/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..6efe28fff
--- /dev/null
+++ b/tests/resources/submod2/.gitted/modules/sm_unchanged/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+ref: refs/remotes/origin/master
diff --git a/tests/resources/submod2/.gitted/objects/09/460e5b6cbcb05a3e404593c32a3aa7221eca0e b/tests/resources/submod2/.gitted/objects/09/460e5b6cbcb05a3e404593c32a3aa7221eca0e
new file mode 100644
index 000000000..f1ea5f4c8
--- /dev/null
+++ b/tests/resources/submod2/.gitted/objects/09/460e5b6cbcb05a3e404593c32a3aa7221eca0e
Binary files differ
diff --git a/tests/resources/submod2/.gitted/objects/14/fe9ccf104058df25e0a08361c4494e167ef243 b/tests/resources/submod2/.gitted/objects/14/fe9ccf104058df25e0a08361c4494e167ef243
new file mode 100644
index 000000000..d3c8582e3
--- /dev/null
+++ b/tests/resources/submod2/.gitted/objects/14/fe9ccf104058df25e0a08361c4494e167ef243
@@ -0,0 +1 @@
+x•M F]sŠ¹€fh¡ccŒ;·Þ€ŸÁ’@I(Ü_OàöË{ïs%çØ@’>µÊ ^!¹²F'½‘!諲l£_¼q4Íä´ÇE˜Þ¶Rá݃S‚'§ÀnÕ>>±mÝ^\Éw³š´^‰$œ‘ÅXÇ_迦xí±E“à—_.à9} \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/objects/22/ce3e0311dda73a5992d54a4a595518d3876ea7 b/tests/resources/submod2/.gitted/objects/22/ce3e0311dda73a5992d54a4a595518d3876ea7
new file mode 100644
index 000000000..fce6a94b5
--- /dev/null
+++ b/tests/resources/submod2/.gitted/objects/22/ce3e0311dda73a5992d54a4a595518d3876ea7
@@ -0,0 +1,4 @@
+xµË
+Â0Eݶ_Qº·.
+.ü W"!1 æ!3 øù>+¶Š.¤Û9Ã=3Wº(«nÕ-¶”¥:;¨jòÜ[" WÑ{›¨Þ•ÅQ¤¾ZWï°,2º iviyh •“ÐT/‚=Ž{Ž‡ ¶!@b(¡bÎJcSËP¢¥rÅŒ
+è‡ð¡ã{ë`ì|%³imÐpú콡ÙÄ=ˆIÇÿW2›6‡„B@)|¼óÿ)g£ý™ \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/objects/25/5546424b0efb847b1bfc91dbf7348b277f8970 b/tests/resources/submod2/.gitted/objects/25/5546424b0efb847b1bfc91dbf7348b277f8970
new file mode 100644
index 000000000..2965becf6
--- /dev/null
+++ b/tests/resources/submod2/.gitted/objects/25/5546424b0efb847b1bfc91dbf7348b277f8970
Binary files differ
diff --git a/tests/resources/submod2/.gitted/objects/2a/30f1e6f94b20917005a21273f65b406d0f8bad b/tests/resources/submod2/.gitted/objects/2a/30f1e6f94b20917005a21273f65b406d0f8bad
new file mode 100644
index 000000000..08faf0fa8
--- /dev/null
+++ b/tests/resources/submod2/.gitted/objects/2a/30f1e6f94b20917005a21273f65b406d0f8bad
Binary files differ
diff --git a/tests/resources/submod2/.gitted/objects/42/cfb95cd01bf9225b659b5ee3edcc78e8eeb478 b/tests/resources/submod2/.gitted/objects/42/cfb95cd01bf9225b659b5ee3edcc78e8eeb478
new file mode 100644
index 000000000..ee7848ae6
--- /dev/null
+++ b/tests/resources/submod2/.gitted/objects/42/cfb95cd01bf9225b659b5ee3edcc78e8eeb478
Binary files differ
diff --git a/tests/resources/submod2/.gitted/objects/57/958699c2dc394f81cfc76950e9c3ac3025c398 b/tests/resources/submod2/.gitted/objects/57/958699c2dc394f81cfc76950e9c3ac3025c398
new file mode 100644
index 000000000..ca9203a6e
--- /dev/null
+++ b/tests/resources/submod2/.gitted/objects/57/958699c2dc394f81cfc76950e9c3ac3025c398
Binary files differ
diff --git a/tests/resources/submod2/.gitted/objects/59/01da4f1c67756eeadc5121d206bec2431f253b b/tests/resources/submod2/.gitted/objects/59/01da4f1c67756eeadc5121d206bec2431f253b
new file mode 100644
index 000000000..9f88f6bdf
--- /dev/null
+++ b/tests/resources/submod2/.gitted/objects/59/01da4f1c67756eeadc5121d206bec2431f253b
@@ -0,0 +1,2 @@
+x•ŽÛ 1EýNÓ€2yg@D,A°€$;YöE6éßmÁß{Λ·e™(å/­2ƒõdƒ#ÊjÈšL 2—ìYdÊ:fÊž ˆ=V^D’hR Ä$¥^ÃÅ©ÉaŠÆ+tn {ûnÞý8xžáÅsá
+÷šžãÔ¾=Ýò¶<@j£¬CÔ®èŹžÿÚ©þ[ŠÏ>Ä6­#=-ÛÐg?,¯FŒ \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/objects/60/7d96653d4d0a4f733107f7890c2e67b55b620d b/tests/resources/submod2/.gitted/objects/60/7d96653d4d0a4f733107f7890c2e67b55b620d
new file mode 100644
index 000000000..30bee40e9
--- /dev/null
+++ b/tests/resources/submod2/.gitted/objects/60/7d96653d4d0a4f733107f7890c2e67b55b620d
Binary files differ
diff --git a/tests/resources/submod2/.gitted/objects/74/84482eb8db738cafa696993664607500a3f2b9 b/tests/resources/submod2/.gitted/objects/74/84482eb8db738cafa696993664607500a3f2b9
new file mode 100644
index 000000000..79018042d
--- /dev/null
+++ b/tests/resources/submod2/.gitted/objects/74/84482eb8db738cafa696993664607500a3f2b9
Binary files differ
diff --git a/tests/resources/submod2/.gitted/objects/7b/a4c5c3561daa5ab1a86215cfb0587e96d404d6 b/tests/resources/submod2/.gitted/objects/7b/a4c5c3561daa5ab1a86215cfb0587e96d404d6
new file mode 100644
index 000000000..cde89e5bb
--- /dev/null
+++ b/tests/resources/submod2/.gitted/objects/7b/a4c5c3561daa5ab1a86215cfb0587e96d404d6
Binary files differ
diff --git a/tests/resources/submod2/.gitted/objects/87/3585b94bdeabccea991ea5e3ec1a277895b698 b/tests/resources/submod2/.gitted/objects/87/3585b94bdeabccea991ea5e3ec1a277895b698
new file mode 100644
index 000000000..41af98aa9
--- /dev/null
+++ b/tests/resources/submod2/.gitted/objects/87/3585b94bdeabccea991ea5e3ec1a277895b698
Binary files differ
diff --git a/tests/resources/submod2/.gitted/objects/97/4cf7c73de336b0c4e019f918f3cee367d72e84 b/tests/resources/submod2/.gitted/objects/97/4cf7c73de336b0c4e019f918f3cee367d72e84
new file mode 100644
index 000000000..160f1caf4
--- /dev/null
+++ b/tests/resources/submod2/.gitted/objects/97/4cf7c73de336b0c4e019f918f3cee367d72e84
@@ -0,0 +1,2 @@
+xµË
+Â0Eݶ_º·Bqåg¸ yŒi ™IÀÏ÷Y±Up!ÝÎs¸£|R¬ï7«=’)XCAGä¢:…à25‡º:É<°-û„uUÐ_IÛò‡¤Y¢…\Ϥ%êAF fª{Gß qTœPsï”u¹ã(ÓZ{‰RA ô#ø̉£ó0m¾“Ų.8ïÞÑbáäìÇãÞù?{vÊŒ \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/objects/9d/bc299bc013ea253583b40bf327b5a6e4037b89 b/tests/resources/submod2/.gitted/objects/9d/bc299bc013ea253583b40bf327b5a6e4037b89
new file mode 100644
index 000000000..1ee52218d
--- /dev/null
+++ b/tests/resources/submod2/.gitted/objects/9d/bc299bc013ea253583b40bf327b5a6e4037b89
Binary files differ
diff --git a/tests/resources/submod2/.gitted/objects/a9/104bf89e911387244ef499413960ba472066d9 b/tests/resources/submod2/.gitted/objects/a9/104bf89e911387244ef499413960ba472066d9
new file mode 100644
index 000000000..2239e14a8
--- /dev/null
+++ b/tests/resources/submod2/.gitted/objects/a9/104bf89e911387244ef499413960ba472066d9
Binary files differ
diff --git a/tests/resources/submod2/.gitted/objects/b6/14088620bbdc1d29549d223ceba0f4419fd4cb b/tests/resources/submod2/.gitted/objects/b6/14088620bbdc1d29549d223ceba0f4419fd4cb
new file mode 100644
index 000000000..a03ea66e4
--- /dev/null
+++ b/tests/resources/submod2/.gitted/objects/b6/14088620bbdc1d29549d223ceba0f4419fd4cb
Binary files differ
diff --git a/tests/resources/submod2/.gitted/objects/d4/07f19e50c1da1ff584beafe0d6dac7237c5d06 b/tests/resources/submod2/.gitted/objects/d4/07f19e50c1da1ff584beafe0d6dac7237c5d06
new file mode 100644
index 000000000..292303eb9
--- /dev/null
+++ b/tests/resources/submod2/.gitted/objects/d4/07f19e50c1da1ff584beafe0d6dac7237c5d06
Binary files differ
diff --git a/tests/resources/submod2/.gitted/objects/d9/3e95571d92cceb5de28c205f1d5f3cc8b88bc8 b/tests/resources/submod2/.gitted/objects/d9/3e95571d92cceb5de28c205f1d5f3cc8b88bc8
new file mode 100644
index 000000000..b92c7eebd
--- /dev/null
+++ b/tests/resources/submod2/.gitted/objects/d9/3e95571d92cceb5de28c205f1d5f3cc8b88bc8
@@ -0,0 +1,2 @@
+x•ÏÛ
+!€ánק}€ "‚.z’uRÉCx€}üΑۼøt¸ œ.׫Ù6î‚,iŸs&%ãÁ9“S¿#ݲ¦úIW¢=—a˜ßËf2A‹¼BYsÏñßÐa{c±¶^K3g¼Äñ³wMÍ F˜Üúøߥ4sÅçâ€òÇáõÎ÷'Nê°I \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/objects/e3/b83bf274ee065eee48734cf8c6dfaf5e81471c b/tests/resources/submod2/.gitted/objects/e3/b83bf274ee065eee48734cf8c6dfaf5e81471c
new file mode 100644
index 000000000..3c7750b12
--- /dev/null
+++ b/tests/resources/submod2/.gitted/objects/e3/b83bf274ee065eee48734cf8c6dfaf5e81471c
Binary files differ
diff --git a/tests/resources/submod2/.gitted/objects/f5/4414c25e6d24fe39f5c3f128d7c8a17bc23833 b/tests/resources/submod2/.gitted/objects/f5/4414c25e6d24fe39f5c3f128d7c8a17bc23833
new file mode 100644
index 000000000..219620b25
--- /dev/null
+++ b/tests/resources/submod2/.gitted/objects/f5/4414c25e6d24fe39f5c3f128d7c8a17bc23833
@@ -0,0 +1,2 @@
+xeÍÁ
+Â0„a¯íS„ÞíbOzð1<I Iº¤¤‘Íû+ˆ‚õ:?|ãsõæt9îh¾Ô¥e6Š- H[´¡–’ÃÜw§«¹šÿØwMò«Œ#½‘ɪ“ÈÚïж…Õm‘—_î; º$ž rò1éDÊPCvB¨Mcø‡ýI^ \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/objects/f9/90a25a74d1a8281ce2ab018ea8df66795cd60b b/tests/resources/submod2/.gitted/objects/f9/90a25a74d1a8281ce2ab018ea8df66795cd60b
new file mode 100644
index 000000000..883a40bfc
--- /dev/null
+++ b/tests/resources/submod2/.gitted/objects/f9/90a25a74d1a8281ce2ab018ea8df66795cd60b
@@ -0,0 +1 @@
+xEŒA€ =óŠý„ÿáZ)¤RE¿/ñb2·É «1¶uÙsé˜xôÁ§Å¡—îˆä>ßä2<E™nG=2,ýÉœTÄ’’4©Žî4!¼N¬$` \ No newline at end of file
diff --git a/tests/resources/submod2/.gitted/refs/heads/master b/tests/resources/submod2/.gitted/refs/heads/master
new file mode 100644
index 000000000..d1d38aa49
--- /dev/null
+++ b/tests/resources/submod2/.gitted/refs/heads/master
@@ -0,0 +1 @@
+7484482eb8db738cafa696993664607500a3f2b9
diff --git a/tests/resources/submod2/README.txt b/tests/resources/submod2/README.txt
new file mode 100644
index 000000000..f990a25a7
--- /dev/null
+++ b/tests/resources/submod2/README.txt
@@ -0,0 +1,3 @@
+This is the submodule test data
+This repo will have a bunch of submodules in different states
+
diff --git a/tests/resources/submod2/gitmodules b/tests/resources/submod2/gitmodules
new file mode 100644
index 000000000..4c31108ed
--- /dev/null
+++ b/tests/resources/submod2/gitmodules
@@ -0,0 +1,24 @@
+[submodule "sm_missing_commits"]
+ path = sm_missing_commits
+ url = ../submod2_target
+[submodule "sm_unchanged"]
+ path = sm_unchanged
+ url = ../submod2_target
+[submodule "sm_changed_file"]
+ path = sm_changed_file
+ url = ../submod2_target
+[submodule "sm_changed_index"]
+ path = sm_changed_index
+ url = ../submod2_target
+[submodule "sm_changed_head"]
+ path = sm_changed_head
+ url = ../submod2_target
+[submodule "sm_changed_untracked_file"]
+ path = sm_changed_untracked_file
+ url = ../submod2_target
+[submodule "sm_added_and_uncommited"]
+ path = sm_added_and_uncommited
+ url = ../submod2_target
+[submodule "sm_gitmodules_only"]
+ path = sm_gitmodules_only
+ url = ../submod2_target
diff --git a/tests/resources/submod2/just_a_dir/contents b/tests/resources/submod2/just_a_dir/contents
new file mode 100644
index 000000000..7ba4c5c35
--- /dev/null
+++ b/tests/resources/submod2/just_a_dir/contents
@@ -0,0 +1 @@
+This is a file in a plain directory
diff --git a/tests/resources/submod2/just_a_file b/tests/resources/submod2/just_a_file
new file mode 100644
index 000000000..42cfb95cd
--- /dev/null
+++ b/tests/resources/submod2/just_a_file
@@ -0,0 +1 @@
+This is just a plain file
diff --git a/tests/resources/submod2/not-submodule/.gitted/HEAD b/tests/resources/submod2/not-submodule/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/submod2/not-submodule/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/submod2/not-submodule/.gitted/config b/tests/resources/submod2/not-submodule/.gitted/config
new file mode 100644
index 000000000..af107929f
--- /dev/null
+++ b/tests/resources/submod2/not-submodule/.gitted/config
@@ -0,0 +1,6 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
diff --git a/tests/resources/submod2/not-submodule/.gitted/description b/tests/resources/submod2/not-submodule/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/submod2/not-submodule/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/submod2/not-submodule/.gitted/index b/tests/resources/submod2/not-submodule/.gitted/index
new file mode 100644
index 000000000..f3fafa536
--- /dev/null
+++ b/tests/resources/submod2/not-submodule/.gitted/index
Binary files differ
diff --git a/tests/resources/submod2/not-submodule/.gitted/info/exclude b/tests/resources/submod2/not-submodule/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/submod2/not-submodule/.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/resources/submod2/not-submodule/.gitted/logs/HEAD b/tests/resources/submod2/not-submodule/.gitted/logs/HEAD
new file mode 100644
index 000000000..1749e7dff
--- /dev/null
+++ b/tests/resources/submod2/not-submodule/.gitted/logs/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 68e92c611b80ee1ed8f38314ff9577f0d15b2444 Russell Belfer <rb@github.com> 1342560358 -0700 commit (initial): Initial commit
diff --git a/tests/resources/submod2/not-submodule/.gitted/logs/refs/heads/master b/tests/resources/submod2/not-submodule/.gitted/logs/refs/heads/master
new file mode 100644
index 000000000..1749e7dff
--- /dev/null
+++ b/tests/resources/submod2/not-submodule/.gitted/logs/refs/heads/master
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 68e92c611b80ee1ed8f38314ff9577f0d15b2444 Russell Belfer <rb@github.com> 1342560358 -0700 commit (initial): Initial commit
diff --git a/tests/resources/submod2/not-submodule/.gitted/objects/68/e92c611b80ee1ed8f38314ff9577f0d15b2444 b/tests/resources/submod2/not-submodule/.gitted/objects/68/e92c611b80ee1ed8f38314ff9577f0d15b2444
new file mode 100644
index 000000000..8892531a7
--- /dev/null
+++ b/tests/resources/submod2/not-submodule/.gitted/objects/68/e92c611b80ee1ed8f38314ff9577f0d15b2444
Binary files differ
diff --git a/tests/resources/submod2/not-submodule/.gitted/objects/71/ff9927d7c8a5639e062c38a7d35c433c424627 b/tests/resources/submod2/not-submodule/.gitted/objects/71/ff9927d7c8a5639e062c38a7d35c433c424627
new file mode 100644
index 000000000..c4e1a77d7
--- /dev/null
+++ b/tests/resources/submod2/not-submodule/.gitted/objects/71/ff9927d7c8a5639e062c38a7d35c433c424627
Binary files differ
diff --git a/tests/resources/submod2/not-submodule/.gitted/objects/f0/1d56b18efd353ef2bb93a4585d590a0847195e b/tests/resources/submod2/not-submodule/.gitted/objects/f0/1d56b18efd353ef2bb93a4585d590a0847195e
new file mode 100644
index 000000000..e9f1942a9
--- /dev/null
+++ b/tests/resources/submod2/not-submodule/.gitted/objects/f0/1d56b18efd353ef2bb93a4585d590a0847195e
Binary files differ
diff --git a/tests/resources/submod2/not-submodule/.gitted/refs/heads/master b/tests/resources/submod2/not-submodule/.gitted/refs/heads/master
new file mode 100644
index 000000000..0bd8514bd
--- /dev/null
+++ b/tests/resources/submod2/not-submodule/.gitted/refs/heads/master
@@ -0,0 +1 @@
+68e92c611b80ee1ed8f38314ff9577f0d15b2444
diff --git a/tests/resources/submod2/not-submodule/README.txt b/tests/resources/submod2/not-submodule/README.txt
new file mode 100644
index 000000000..71ff9927d
--- /dev/null
+++ b/tests/resources/submod2/not-submodule/README.txt
@@ -0,0 +1 @@
+This is a git repo but not a submodule
diff --git a/tests/resources/submod2/not/.gitted/notempty b/tests/resources/submod2/not/.gitted/notempty
new file mode 100644
index 000000000..9b33ac4e4
--- /dev/null
+++ b/tests/resources/submod2/not/.gitted/notempty
@@ -0,0 +1 @@
+fooled you
diff --git a/tests/resources/submod2/not/README.txt b/tests/resources/submod2/not/README.txt
new file mode 100644
index 000000000..4f6935b98
--- /dev/null
+++ b/tests/resources/submod2/not/README.txt
@@ -0,0 +1 @@
+what am I really
diff --git a/tests/resources/submod2/sm_added_and_uncommited/.gitted b/tests/resources/submod2/sm_added_and_uncommited/.gitted
new file mode 100644
index 000000000..2b2a4cf90
--- /dev/null
+++ b/tests/resources/submod2/sm_added_and_uncommited/.gitted
@@ -0,0 +1 @@
+gitdir: ../.git/modules/sm_added_and_uncommited
diff --git a/tests/resources/submod2/sm_added_and_uncommited/README.txt b/tests/resources/submod2/sm_added_and_uncommited/README.txt
new file mode 100644
index 000000000..780d7397f
--- /dev/null
+++ b/tests/resources/submod2/sm_added_and_uncommited/README.txt
@@ -0,0 +1,3 @@
+This is the target for submod2 submodule links.
+Don't add commits casually because you make break tests.
+
diff --git a/tests/resources/submod2/sm_added_and_uncommited/file_to_modify b/tests/resources/submod2/sm_added_and_uncommited/file_to_modify
new file mode 100644
index 000000000..789efbdad
--- /dev/null
+++ b/tests/resources/submod2/sm_added_and_uncommited/file_to_modify
@@ -0,0 +1,3 @@
+This is a file to modify in submodules
+It already has some history.
+You can add local changes as needed.
diff --git a/tests/resources/submod2/sm_changed_file/.gitted b/tests/resources/submod2/sm_changed_file/.gitted
new file mode 100644
index 000000000..dc98b1674
--- /dev/null
+++ b/tests/resources/submod2/sm_changed_file/.gitted
@@ -0,0 +1 @@
+gitdir: ../.git/modules/sm_changed_file
diff --git a/tests/resources/submod2/sm_changed_file/README.txt b/tests/resources/submod2/sm_changed_file/README.txt
new file mode 100644
index 000000000..780d7397f
--- /dev/null
+++ b/tests/resources/submod2/sm_changed_file/README.txt
@@ -0,0 +1,3 @@
+This is the target for submod2 submodule links.
+Don't add commits casually because you make break tests.
+
diff --git a/tests/resources/submod2/sm_changed_file/file_to_modify b/tests/resources/submod2/sm_changed_file/file_to_modify
new file mode 100644
index 000000000..e5ba67168
--- /dev/null
+++ b/tests/resources/submod2/sm_changed_file/file_to_modify
@@ -0,0 +1,4 @@
+This is a file to modify in submodules
+It already has some history.
+You can add local changes as needed.
+In this case, the file is changed in the workdir
diff --git a/tests/resources/submod2/sm_changed_head/.gitted b/tests/resources/submod2/sm_changed_head/.gitted
new file mode 100644
index 000000000..d5419b62d
--- /dev/null
+++ b/tests/resources/submod2/sm_changed_head/.gitted
@@ -0,0 +1 @@
+gitdir: ../.git/modules/sm_changed_head
diff --git a/tests/resources/submod2/sm_changed_head/README.txt b/tests/resources/submod2/sm_changed_head/README.txt
new file mode 100644
index 000000000..780d7397f
--- /dev/null
+++ b/tests/resources/submod2/sm_changed_head/README.txt
@@ -0,0 +1,3 @@
+This is the target for submod2 submodule links.
+Don't add commits casually because you make break tests.
+
diff --git a/tests/resources/submod2/sm_changed_head/file_to_modify b/tests/resources/submod2/sm_changed_head/file_to_modify
new file mode 100644
index 000000000..8eb1e637e
--- /dev/null
+++ b/tests/resources/submod2/sm_changed_head/file_to_modify
@@ -0,0 +1,4 @@
+This is a file to modify in submodules
+It already has some history.
+You can add local changes as needed.
+This one has been changed and the change has been committed to HEAD.
diff --git a/tests/resources/submod2/sm_changed_index/.gitted b/tests/resources/submod2/sm_changed_index/.gitted
new file mode 100644
index 000000000..2c7a5b271
--- /dev/null
+++ b/tests/resources/submod2/sm_changed_index/.gitted
@@ -0,0 +1 @@
+gitdir: ../.git/modules/sm_changed_index
diff --git a/tests/resources/submod2/sm_changed_index/README.txt b/tests/resources/submod2/sm_changed_index/README.txt
new file mode 100644
index 000000000..780d7397f
--- /dev/null
+++ b/tests/resources/submod2/sm_changed_index/README.txt
@@ -0,0 +1,3 @@
+This is the target for submod2 submodule links.
+Don't add commits casually because you make break tests.
+
diff --git a/tests/resources/submod2/sm_changed_index/file_to_modify b/tests/resources/submod2/sm_changed_index/file_to_modify
new file mode 100644
index 000000000..a02d31770
--- /dev/null
+++ b/tests/resources/submod2/sm_changed_index/file_to_modify
@@ -0,0 +1,4 @@
+This is a file to modify in submodules
+It already has some history.
+You can add local changes as needed.
+Here the file is changed in the index and the workdir
diff --git a/tests/resources/submod2/sm_changed_untracked_file/.gitted b/tests/resources/submod2/sm_changed_untracked_file/.gitted
new file mode 100644
index 000000000..9a1070647
--- /dev/null
+++ b/tests/resources/submod2/sm_changed_untracked_file/.gitted
@@ -0,0 +1 @@
+gitdir: ../.git/modules/sm_changed_untracked_file
diff --git a/tests/resources/submod2/sm_changed_untracked_file/README.txt b/tests/resources/submod2/sm_changed_untracked_file/README.txt
new file mode 100644
index 000000000..780d7397f
--- /dev/null
+++ b/tests/resources/submod2/sm_changed_untracked_file/README.txt
@@ -0,0 +1,3 @@
+This is the target for submod2 submodule links.
+Don't add commits casually because you make break tests.
+
diff --git a/tests/resources/submod2/sm_changed_untracked_file/file_to_modify b/tests/resources/submod2/sm_changed_untracked_file/file_to_modify
new file mode 100644
index 000000000..789efbdad
--- /dev/null
+++ b/tests/resources/submod2/sm_changed_untracked_file/file_to_modify
@@ -0,0 +1,3 @@
+This is a file to modify in submodules
+It already has some history.
+You can add local changes as needed.
diff --git a/tests/resources/submod2/sm_changed_untracked_file/i_am_untracked b/tests/resources/submod2/sm_changed_untracked_file/i_am_untracked
new file mode 100644
index 000000000..d2bae6167
--- /dev/null
+++ b/tests/resources/submod2/sm_changed_untracked_file/i_am_untracked
@@ -0,0 +1 @@
+This file is untracked, but in a submodule
diff --git a/tests/resources/submod2/sm_missing_commits/.gitted b/tests/resources/submod2/sm_missing_commits/.gitted
new file mode 100644
index 000000000..70193be84
--- /dev/null
+++ b/tests/resources/submod2/sm_missing_commits/.gitted
@@ -0,0 +1 @@
+gitdir: ../.git/modules/sm_missing_commits
diff --git a/tests/resources/submod2/sm_missing_commits/README.txt b/tests/resources/submod2/sm_missing_commits/README.txt
new file mode 100644
index 000000000..780d7397f
--- /dev/null
+++ b/tests/resources/submod2/sm_missing_commits/README.txt
@@ -0,0 +1,3 @@
+This is the target for submod2 submodule links.
+Don't add commits casually because you make break tests.
+
diff --git a/tests/resources/submod2/sm_missing_commits/file_to_modify b/tests/resources/submod2/sm_missing_commits/file_to_modify
new file mode 100644
index 000000000..8834b635d
--- /dev/null
+++ b/tests/resources/submod2/sm_missing_commits/file_to_modify
@@ -0,0 +1,3 @@
+This is a file to modify in submodules
+It already has some history.
+
diff --git a/tests/resources/submod2/sm_unchanged/.gitted b/tests/resources/submod2/sm_unchanged/.gitted
new file mode 100644
index 000000000..51a679c80
--- /dev/null
+++ b/tests/resources/submod2/sm_unchanged/.gitted
@@ -0,0 +1 @@
+gitdir: ../.git/modules/sm_unchanged
diff --git a/tests/resources/submod2/sm_unchanged/README.txt b/tests/resources/submod2/sm_unchanged/README.txt
new file mode 100644
index 000000000..780d7397f
--- /dev/null
+++ b/tests/resources/submod2/sm_unchanged/README.txt
@@ -0,0 +1,3 @@
+This is the target for submod2 submodule links.
+Don't add commits casually because you make break tests.
+
diff --git a/tests/resources/submod2/sm_unchanged/file_to_modify b/tests/resources/submod2/sm_unchanged/file_to_modify
new file mode 100644
index 000000000..789efbdad
--- /dev/null
+++ b/tests/resources/submod2/sm_unchanged/file_to_modify
@@ -0,0 +1,3 @@
+This is a file to modify in submodules
+It already has some history.
+You can add local changes as needed.
diff --git a/tests/resources/submod2_target/.gitted/HEAD b/tests/resources/submod2_target/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/submod2_target/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/submod2_target/.gitted/config b/tests/resources/submod2_target/.gitted/config
new file mode 100644
index 000000000..af107929f
--- /dev/null
+++ b/tests/resources/submod2_target/.gitted/config
@@ -0,0 +1,6 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
diff --git a/tests/resources/submod2_target/.gitted/description b/tests/resources/submod2_target/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/submod2_target/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/submod2_target/.gitted/index b/tests/resources/submod2_target/.gitted/index
new file mode 100644
index 000000000..eb3ff8c10
--- /dev/null
+++ b/tests/resources/submod2_target/.gitted/index
Binary files differ
diff --git a/tests/resources/submod2_target/.gitted/info/exclude b/tests/resources/submod2_target/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/submod2_target/.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/resources/submod2_target/.gitted/logs/HEAD b/tests/resources/submod2_target/.gitted/logs/HEAD
new file mode 100644
index 000000000..0ecd1113f
--- /dev/null
+++ b/tests/resources/submod2_target/.gitted/logs/HEAD
@@ -0,0 +1,4 @@
+0000000000000000000000000000000000000000 6b31c659545507c381e9cd34ec508f16c04e149e Russell Belfer <rb@github.com> 1342559662 -0700 commit (initial): Initial commit
+6b31c659545507c381e9cd34ec508f16c04e149e 41bd4bc3df978de695f67ace64c560913da11653 Russell Belfer <rb@github.com> 1342559709 -0700 commit: Adding test file
+41bd4bc3df978de695f67ace64c560913da11653 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer <rb@github.com> 1342559726 -0700 commit: Updating test file
+5e4963595a9774b90524d35a807169049de8ccad 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342559925 -0700 commit: One more update
diff --git a/tests/resources/submod2_target/.gitted/logs/refs/heads/master b/tests/resources/submod2_target/.gitted/logs/refs/heads/master
new file mode 100644
index 000000000..0ecd1113f
--- /dev/null
+++ b/tests/resources/submod2_target/.gitted/logs/refs/heads/master
@@ -0,0 +1,4 @@
+0000000000000000000000000000000000000000 6b31c659545507c381e9cd34ec508f16c04e149e Russell Belfer <rb@github.com> 1342559662 -0700 commit (initial): Initial commit
+6b31c659545507c381e9cd34ec508f16c04e149e 41bd4bc3df978de695f67ace64c560913da11653 Russell Belfer <rb@github.com> 1342559709 -0700 commit: Adding test file
+41bd4bc3df978de695f67ace64c560913da11653 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer <rb@github.com> 1342559726 -0700 commit: Updating test file
+5e4963595a9774b90524d35a807169049de8ccad 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer <rb@github.com> 1342559925 -0700 commit: One more update
diff --git a/tests/resources/submod2_target/.gitted/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod2_target/.gitted/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1
new file mode 100644
index 000000000..f4b7094c5
--- /dev/null
+++ b/tests/resources/submod2_target/.gitted/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1
Binary files differ
diff --git a/tests/resources/submod2_target/.gitted/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod2_target/.gitted/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484
new file mode 100644
index 000000000..56c845e49
--- /dev/null
+++ b/tests/resources/submod2_target/.gitted/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484
Binary files differ
diff --git a/tests/resources/submod2_target/.gitted/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod2_target/.gitted/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5
new file mode 100644
index 000000000..bd179b5f5
--- /dev/null
+++ b/tests/resources/submod2_target/.gitted/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5
Binary files differ
diff --git a/tests/resources/submod2_target/.gitted/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod2_target/.gitted/objects/41/bd4bc3df978de695f67ace64c560913da11653
new file mode 100644
index 000000000..ccf49bd15
--- /dev/null
+++ b/tests/resources/submod2_target/.gitted/objects/41/bd4bc3df978de695f67ace64c560913da11653
Binary files differ
diff --git a/tests/resources/submod2_target/.gitted/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod2_target/.gitted/objects/48/0095882d281ed676fe5b863569520e54a7d5c0
new file mode 100644
index 000000000..53029069a
--- /dev/null
+++ b/tests/resources/submod2_target/.gitted/objects/48/0095882d281ed676fe5b863569520e54a7d5c0
Binary files differ
diff --git a/tests/resources/submod2_target/.gitted/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod2_target/.gitted/objects/5e/4963595a9774b90524d35a807169049de8ccad
new file mode 100644
index 000000000..38c791eba
--- /dev/null
+++ b/tests/resources/submod2_target/.gitted/objects/5e/4963595a9774b90524d35a807169049de8ccad
Binary files differ
diff --git a/tests/resources/submod2_target/.gitted/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod2_target/.gitted/objects/6b/31c659545507c381e9cd34ec508f16c04e149e
new file mode 100644
index 000000000..a26d29993
--- /dev/null
+++ b/tests/resources/submod2_target/.gitted/objects/6b/31c659545507c381e9cd34ec508f16c04e149e
@@ -0,0 +1,2 @@
+x•Q
+!EûvoÅÓy*Ñ_¿í@Çg#h‚£ûOhý^Î9w«¥¤ÒêSoÌ€f1*²ŠÁ[”‰¬§èIc Ô¤ìê¤p£ïµÁkç Α\›¿¿S߇¿lµÜ@.¤´^QpF‹(æ:ÿúDÿ5Åó“zr~ ñen8 \ No newline at end of file
diff --git a/tests/resources/submod2_target/.gitted/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod2_target/.gitted/objects/73/ba924a80437097795ae839e66e187c55d3babf
new file mode 100644
index 000000000..83d1ba481
--- /dev/null
+++ b/tests/resources/submod2_target/.gitted/objects/73/ba924a80437097795ae839e66e187c55d3babf
Binary files differ
diff --git a/tests/resources/submod2_target/.gitted/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod2_target/.gitted/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a
new file mode 100644
index 000000000..6d27af8a8
--- /dev/null
+++ b/tests/resources/submod2_target/.gitted/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a
@@ -0,0 +1,2 @@
+x-Ë1Â0 FaæžâßØ0pŽÀìÄÐ(N-ÅöÐÛÓ¡Ò“¾é±ãq]>ksÅ*š? |m“‡Õçiª@ÛÖý¶¼m»¨V£…£'©î`)”.Ø-1¨ x
+u„xãòt(+ \ No newline at end of file
diff --git a/tests/resources/submod2_target/.gitted/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod2_target/.gitted/objects/78/9efbdadaa4a582778d4584385495559ea0994b
new file mode 100644
index 000000000..17458840b
--- /dev/null
+++ b/tests/resources/submod2_target/.gitted/objects/78/9efbdadaa4a582778d4584385495559ea0994b
@@ -0,0 +1,2 @@
+x Œ± …0 )ÞŠ?= ¥ÉÄNŠlO¤k®¸‹jÛúÿ¹8&„«¨ ãr ”
+ïqJWñ°7¾B<ÉáöfÙìK8­#Q1C-‘"eª·Ì«£Š°ð>¼'@ \ No newline at end of file
diff --git a/tests/resources/submod2_target/.gitted/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod2_target/.gitted/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e
new file mode 100644
index 000000000..83cc29fb1
--- /dev/null
+++ b/tests/resources/submod2_target/.gitted/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e
Binary files differ
diff --git a/tests/resources/submod2_target/.gitted/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod2_target/.gitted/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5
new file mode 100644
index 000000000..55bda40ef
--- /dev/null
+++ b/tests/resources/submod2_target/.gitted/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5
Binary files differ
diff --git a/tests/resources/submod2_target/.gitted/refs/heads/master b/tests/resources/submod2_target/.gitted/refs/heads/master
new file mode 100644
index 000000000..e12c44d7a
--- /dev/null
+++ b/tests/resources/submod2_target/.gitted/refs/heads/master
@@ -0,0 +1 @@
+480095882d281ed676fe5b863569520e54a7d5c0
diff --git a/tests/resources/submod2_target/README.txt b/tests/resources/submod2_target/README.txt
new file mode 100644
index 000000000..780d7397f
--- /dev/null
+++ b/tests/resources/submod2_target/README.txt
@@ -0,0 +1,3 @@
+This is the target for submod2 submodule links.
+Don't add commits casually because you make break tests.
+
diff --git a/tests/resources/submod2_target/file_to_modify b/tests/resources/submod2_target/file_to_modify
new file mode 100644
index 000000000..789efbdad
--- /dev/null
+++ b/tests/resources/submod2_target/file_to_modify
@@ -0,0 +1,3 @@
+This is a file to modify in submodules
+It already has some history.
+You can add local changes as needed.
diff --git a/tests/resources/submodules/.gitted/HEAD b/tests/resources/submodules/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/submodules/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/submodules/.gitted/config b/tests/resources/submodules/.gitted/config
new file mode 100644
index 000000000..af107929f
--- /dev/null
+++ b/tests/resources/submodules/.gitted/config
@@ -0,0 +1,6 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
diff --git a/tests/resources/submodules/.gitted/description b/tests/resources/submodules/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/submodules/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/submodules/.gitted/index b/tests/resources/submodules/.gitted/index
new file mode 100644
index 000000000..97bf8ef51
--- /dev/null
+++ b/tests/resources/submodules/.gitted/index
Binary files differ
diff --git a/tests/resources/submodules/.gitted/info/exclude b/tests/resources/submodules/.gitted/info/exclude
new file mode 100644
index 000000000..dfc411579
--- /dev/null
+++ b/tests/resources/submodules/.gitted/info/exclude
@@ -0,0 +1,8 @@
+# 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]
+# *~
+ignored
+
diff --git a/tests/resources/submodules/.gitted/info/refs b/tests/resources/submodules/.gitted/info/refs
new file mode 100644
index 000000000..ba17abdde
--- /dev/null
+++ b/tests/resources/submodules/.gitted/info/refs
@@ -0,0 +1 @@
+09176a980273d801a3e37cc45c84af1366501ed9 refs/heads/master
diff --git a/tests/resources/submodules/.gitted/logs/HEAD b/tests/resources/submodules/.gitted/logs/HEAD
new file mode 100644
index 000000000..87a7bdafc
--- /dev/null
+++ b/tests/resources/submodules/.gitted/logs/HEAD
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 09176a980273d801a3e37cc45c84af1366501ed9 Russell Belfer <arrbee@arrbee.com> 1332365253 -0700 commit (initial): initial commit
+09176a980273d801a3e37cc45c84af1366501ed9 97896810b3210244a62a82458b8e0819ecfc6850 Russell Belfer <arrbee@arrbee.com> 1332780781 -0700 commit: Setting up gitmodules
diff --git a/tests/resources/submodules/.gitted/logs/refs/heads/master b/tests/resources/submodules/.gitted/logs/refs/heads/master
new file mode 100644
index 000000000..87a7bdafc
--- /dev/null
+++ b/tests/resources/submodules/.gitted/logs/refs/heads/master
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 09176a980273d801a3e37cc45c84af1366501ed9 Russell Belfer <arrbee@arrbee.com> 1332365253 -0700 commit (initial): initial commit
+09176a980273d801a3e37cc45c84af1366501ed9 97896810b3210244a62a82458b8e0819ecfc6850 Russell Belfer <arrbee@arrbee.com> 1332780781 -0700 commit: Setting up gitmodules
diff --git a/tests/resources/submodules/.gitted/objects/26/a3b32a9b7d97486c5557f5902e8ac94638145e b/tests/resources/submodules/.gitted/objects/26/a3b32a9b7d97486c5557f5902e8ac94638145e
new file mode 100644
index 000000000..2c3c2cb61
--- /dev/null
+++ b/tests/resources/submodules/.gitted/objects/26/a3b32a9b7d97486c5557f5902e8ac94638145e
@@ -0,0 +1,2 @@
+x%‰=
+€0 F]í)Š0à"ÃIŒ*•|Éý-t{?œ2ÇilV8¿ùô$±«Øm¡ýv»ãk­k*F DAÊ=(=|=6 ¬DAv=ÛÍA}™&'…Oò$= \ No newline at end of file
diff --git a/tests/resources/submodules/.gitted/objects/78/308c9251cf4eee8b25a76c7d2790c73d797357 b/tests/resources/submodules/.gitted/objects/78/308c9251cf4eee8b25a76c7d2790c73d797357
new file mode 100644
index 000000000..c85fb5512
--- /dev/null
+++ b/tests/resources/submodules/.gitted/objects/78/308c9251cf4eee8b25a76c7d2790c73d797357
Binary files differ
diff --git a/tests/resources/submodules/.gitted/objects/97/896810b3210244a62a82458b8e0819ecfc6850 b/tests/resources/submodules/.gitted/objects/97/896810b3210244a62a82458b8e0819ecfc6850
new file mode 100644
index 000000000..1c8dbdf9f
--- /dev/null
+++ b/tests/resources/submodules/.gitted/objects/97/896810b3210244a62a82458b8e0819ecfc6850
@@ -0,0 +1,3 @@
+xŽ[
+Â0EýÎ*fʤS“ ˆˆKФéú4Ý¿wà×…Ã9pÓ2MC¥FôP @ãÜu.„.¶pÚ!²OYáƒdiYUÍ'Ì•8XïbPn¼ôÊ6
+ħԞ“¶1[qîÌ}0q«ï¥Ðc[WŒ#Ý1fºÄR:àö›SZ¦+Y‘Æ+{µtdÏlvº¬»þOmž¨u˜_´}è5Ôié·«ù` Kæ \ No newline at end of file
diff --git a/tests/resources/submodules/.gitted/objects/b6/0fd986699ba4e9e68bea07cf8e793f323ef888 b/tests/resources/submodules/.gitted/objects/b6/0fd986699ba4e9e68bea07cf8e793f323ef888
new file mode 100644
index 000000000..3d78bd6be
--- /dev/null
+++ b/tests/resources/submodules/.gitted/objects/b6/0fd986699ba4e9e68bea07cf8e793f323ef888
Binary files differ
diff --git a/tests/resources/submodules/.gitted/objects/d5/f7fc3f74f7dec08280f370a975b112e8f60818 b/tests/resources/submodules/.gitted/objects/d5/f7fc3f74f7dec08280f370a975b112e8f60818
new file mode 100644
index 000000000..6e0b49e86
--- /dev/null
+++ b/tests/resources/submodules/.gitted/objects/d5/f7fc3f74f7dec08280f370a975b112e8f60818
Binary files differ
diff --git a/tests/resources/submodules/.gitted/objects/e3/50052cc767cd1fcb37e84e9a89e701925be4ae b/tests/resources/submodules/.gitted/objects/e3/50052cc767cd1fcb37e84e9a89e701925be4ae
new file mode 100644
index 000000000..082a58941
--- /dev/null
+++ b/tests/resources/submodules/.gitted/objects/e3/50052cc767cd1fcb37e84e9a89e701925be4ae
Binary files differ
diff --git a/tests/resources/submodules/.gitted/objects/info/packs b/tests/resources/submodules/.gitted/objects/info/packs
new file mode 100644
index 000000000..0785ef698
--- /dev/null
+++ b/tests/resources/submodules/.gitted/objects/info/packs
@@ -0,0 +1,2 @@
+P pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.pack
+
diff --git a/tests/resources/submodules/.gitted/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.idx b/tests/resources/submodules/.gitted/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.idx
new file mode 100644
index 000000000..810fc3181
--- /dev/null
+++ b/tests/resources/submodules/.gitted/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.idx
Binary files differ
diff --git a/tests/resources/submodules/.gitted/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.pack b/tests/resources/submodules/.gitted/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.pack
new file mode 100644
index 000000000..c25c4a73f
--- /dev/null
+++ b/tests/resources/submodules/.gitted/objects/pack/pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.pack
Binary files differ
diff --git a/tests/resources/submodules/.gitted/packed-refs b/tests/resources/submodules/.gitted/packed-refs
new file mode 100644
index 000000000..a6450691e
--- /dev/null
+++ b/tests/resources/submodules/.gitted/packed-refs
@@ -0,0 +1,2 @@
+# pack-refs with: peeled
+09176a980273d801a3e37cc45c84af1366501ed9 refs/heads/master
diff --git a/tests/resources/submodules/.gitted/refs/heads/master b/tests/resources/submodules/.gitted/refs/heads/master
new file mode 100644
index 000000000..32b935853
--- /dev/null
+++ b/tests/resources/submodules/.gitted/refs/heads/master
@@ -0,0 +1 @@
+97896810b3210244a62a82458b8e0819ecfc6850
diff --git a/tests/resources/submodules/added b/tests/resources/submodules/added
new file mode 100644
index 000000000..d5f7fc3f7
--- /dev/null
+++ b/tests/resources/submodules/added
@@ -0,0 +1 @@
+added
diff --git a/tests/resources/submodules/gitmodules b/tests/resources/submodules/gitmodules
new file mode 100644
index 000000000..2798b696c
--- /dev/null
+++ b/tests/resources/submodules/gitmodules
@@ -0,0 +1,6 @@
+[submodule "testrepo"]
+ path = testrepo
+ url =
+[submodule ""]
+ path = testrepo
+ url = \ No newline at end of file
diff --git a/tests/resources/submodules/ignored b/tests/resources/submodules/ignored
new file mode 100644
index 000000000..092bfb9bd
--- /dev/null
+++ b/tests/resources/submodules/ignored
@@ -0,0 +1 @@
+yo
diff --git a/tests/resources/submodules/modified b/tests/resources/submodules/modified
new file mode 100644
index 000000000..452216e1d
--- /dev/null
+++ b/tests/resources/submodules/modified
@@ -0,0 +1,2 @@
+changed
+
diff --git a/tests/resources/submodules/testrepo/.gitted/HEAD b/tests/resources/submodules/testrepo/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/submodules/testrepo/.gitted/config b/tests/resources/submodules/testrepo/.gitted/config
new file mode 100644
index 000000000..d6dcad12b
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/config
@@ -0,0 +1,12 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
+[remote "origin"]
+ fetch = +refs/heads/*:refs/remotes/origin/*
+ url = /Users/rb/src/libgit2/tests/resources/testrepo.git
+[branch "master"]
+ remote = origin
+ merge = refs/heads/master
diff --git a/tests/resources/submodules/testrepo/.gitted/description b/tests/resources/submodules/testrepo/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/submodules/testrepo/.gitted/index b/tests/resources/submodules/testrepo/.gitted/index
new file mode 100644
index 000000000..3eb8d84fe
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/index
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/info/exclude b/tests/resources/submodules/testrepo/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.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/resources/submodules/testrepo/.gitted/logs/HEAD b/tests/resources/submodules/testrepo/.gitted/logs/HEAD
new file mode 100644
index 000000000..147643a30
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/logs/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Russell Belfer <arrbee@arrbee.com> 1332366307 -0700 clone: from /Users/rb/src/libgit2/tests/resources/testrepo.git
diff --git a/tests/resources/submodules/testrepo/.gitted/logs/refs/heads/master b/tests/resources/submodules/testrepo/.gitted/logs/refs/heads/master
new file mode 100644
index 000000000..147643a30
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/logs/refs/heads/master
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Russell Belfer <arrbee@arrbee.com> 1332366307 -0700 clone: from /Users/rb/src/libgit2/tests/resources/testrepo.git
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 b/tests/resources/submodules/testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08
new file mode 100644
index 000000000..cedb2a22e
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 b/tests/resources/submodules/testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7
new file mode 100644
index 000000000..93a16f146
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd b/tests/resources/submodules/testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd
new file mode 100644
index 000000000..ba0bfb30c
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b b/tests/resources/submodules/testrepo/.gitted/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b
new file mode 100644
index 000000000..225c45734
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d b/tests/resources/submodules/testrepo/.gitted/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d
new file mode 100644
index 000000000..df40d99af
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 b/tests/resources/submodules/testrepo/.gitted/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54
new file mode 100644
index 000000000..321eaa867
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc b/tests/resources/submodules/testrepo/.gitted/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc
new file mode 100644
index 000000000..9bb5b623b
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 b/tests/resources/submodules/testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057
new file mode 100644
index 000000000..7ca4ceed5
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 b/tests/resources/submodules/testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045
new file mode 100644
index 000000000..8953b6cef
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045
@@ -0,0 +1,2 @@
+xŽQ
+Â0DýÎ)öÊ6›Í¦ "xO°‰‰-ØFb¼¿EoàÏ0 ¼Ç¤º,ske×[ÎPn8R,EpD?±gŸ}Ê^3² âÙ<µåµGŽhYKÄèÒ8ЖDAÉ)¿ÉÈ;gôݧÚàšjïp™4ÕŽ¯ô-çû¢óãêr‚ÁŠ;°s°GA4Ûº=ìùÖ(ôin7øIÌKÍFE \ No newline at end of file
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 b/tests/resources/submodules/testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644
new file mode 100644
index 000000000..c1f22c54f
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644
@@ -0,0 +1,2 @@
+xŽÛ 1EýNi@™Ék2 "X‚$ÙYW0YcÿíÀ¿Ã…s¸¥ÕzïÚÚõMDÏ€0æœ8!¶†ÉÌÞs‰ XŠªgÚdí::@X0»P¢wÙ"F/‰‰œÍRàˆUz÷¥múZZïú²¤ÒV}|•/œo5݇ÒêI£!¬1z Æ:vùÇUim}ê/¢>
+öF- \ No newline at end of file
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a b/tests/resources/submodules/testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a
new file mode 100644
index 000000000..2ef4faa0f
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af b/tests/resources/submodules/testrepo/.gitted/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af
new file mode 100644
index 000000000..716b0c64b
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af
@@ -0,0 +1 @@
+xŽAj!³ö?0¨£ßÂ09Êo}HÚ6¨}ÿôjUPP©ÕZ&Yÿø˜ AÔ›±€pŒÁFdë¼÷pz[fŽYŒ½PÒqLJ.,Z§`™Å®Ð.ù`’vÙ ³q $Æ5+9çOëtœû>Û/úDE/龡W¯ï*e¿§VŸdf1>ð覭Öê²×äÄ›¹úÊ™F« ­ìTŽÙhœk.i¶^0Ô?P¼R, \ No newline at end of file
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/7b/4384978d2493e851f9cca7858815fac9b10980 b/tests/resources/submodules/testrepo/.gitted/objects/7b/4384978d2493e851f9cca7858815fac9b10980
new file mode 100644
index 000000000..23c462f34
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/7b/4384978d2493e851f9cca7858815fac9b10980
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d b/tests/resources/submodules/testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d
new file mode 100644
index 000000000..2f9b6b6e3
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 b/tests/resources/submodules/testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479
new file mode 100644
index 000000000..5df58dda5
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 b/tests/resources/submodules/testrepo/.gitted/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162
new file mode 100644
index 000000000..4cc3f4dff
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162
@@ -0,0 +1 @@
+x+)JMU044b040031QrutñueX¡l¨ðmmA‹m›Ì£íJ}Gß;U‘T”˜—œŸ–™“ªWRQÂ`6ýš÷KÇ¥¶^/¾-*|òøWØ¥3P¥y©å`%ËEÛÞ±\&gŽÐ|Ÿ0§ÿ†{Ó1X \ No newline at end of file
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 b/tests/resources/submodules/testrepo/.gitted/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4
new file mode 100644
index 000000000..bf7b2bb68
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a b/tests/resources/submodules/testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a
new file mode 100644
index 000000000..a79612435
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a
@@ -0,0 +1,3 @@
+xŽ[
+Â0EýÎ*fÊäÕ¤ "¸W0“‡-ØFâtÿÝ—çpS[–YÀ˜x^
+Díb CLhutɉ}¥8X*4Zí¬sY½¨—UÀ‘AÃÖ ÌX3‡R«Mµ¶) s6è¼¢M¦ÖážšÜ&Jm…ó;}Çõ±Ðü<¥¶\@›à‚ÑÞpÄ€¨vº?”ò«jÛºLð«¨Ø?Hå \ No newline at end of file
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f b/tests/resources/submodules/testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f
new file mode 100644
index 000000000..f8588696b
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f
@@ -0,0 +1,2 @@
+x;j1DëmdÓú·À˜ÇŽ|M«µ3`ŒV{ >€³âQ¯ ¸·vL0I?Í!š4–Z=Ê! ×¦8²F¢Ã’!rÖsQßyÈ9]$DŽ&„l6AÇ>jFWüÒµ IKNiûë§Z¢%¡SˆŒ‘
+‹Ò ­ÅʉøU~̽øä>'¼ï™û ¯wþ ×[ËÇ× ÷öÚDGÚ¡±ðŒQ-ºMù«>dܶ‘OÞáÒò}í\à8g_ШÂoYr \ No newline at end of file
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 b/tests/resources/submodules/testrepo/.gitted/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750
new file mode 100644
index 000000000..29c8e824d
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750
@@ -0,0 +1,3 @@
+xŽQ
+!@ûösBQ"‚ŽÐ ÆÙ± rÍîßÒú{<xð¤·öàîƪ
+™HlJSer!ZPTe*Žj°UÝÑEo^¼ê2 (†ˆ¬XSÅ€ED‘ƒO<Y¦šj$2üs_á&} ¸Î,}Ó[~p¹7~<ÒÛ:Ÿ £°·ÉZ³Ùípè?­1_ûåC0 \ No newline at end of file
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd b/tests/resources/submodules/testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd
new file mode 100644
index 000000000..d0d7e736e
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 b/tests/resources/submodules/testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6
new file mode 100644
index 000000000..18a7f61c2
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/ae/90f12eea699729ed24555e40b9fd669da12a12 b/tests/resources/submodules/testrepo/.gitted/objects/ae/90f12eea699729ed24555e40b9fd669da12a12
new file mode 100644
index 000000000..d95254674
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/ae/90f12eea699729ed24555e40b9fd669da12a12
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 b/tests/resources/submodules/testrepo/.gitted/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1
new file mode 100644
index 000000000..f460f2547
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1
@@ -0,0 +1,2 @@
+xÌA
+Â0…a×9ÅìIÒ ™€ˆp'î§1Ѷ‘v\x{cáýðVŸpƒvWûgŠ¾ÇŽ0xº[ ]"g†#{rDÆ Cot ­äûN œU $­ò?9-p+1Í^¤ÀQx®¯Ï9O\ÆC¬Ó Œm'D {mµVêú(+´ñælè¶,Þ \ No newline at end of file
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593 b/tests/resources/submodules/testrepo/.gitted/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593
new file mode 100644
index 000000000..f613670e2
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 b/tests/resources/submodules/testrepo/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644
new file mode 100644
index 000000000..0817229bc
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644
@@ -0,0 +1,3 @@
+xKj1D³Ö)zçUBëÛ-0ÁuV9¦Õò<#£È÷ÏȲ+ŠW<Jú¶Ý&8Ê/s¨‚e‹µµÈ•KJ­«½S
+ØRvÌÁ{©æQ†îr«äY¹QN$H\Eµ²Íè=6áX5¦òÇK Fr)·(‰dC‡Î†”­–œ—jÊs®}À—ô9ác-Òw8Ëo¸\·r»¿IßÞÁ:
+l}F‚W$Ds´Ç£©ÿÙšOW…e”]V8-ÃÌÈ"U \ No newline at end of file
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd b/tests/resources/submodules/testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd
new file mode 100644
index 000000000..75f541f10
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd
@@ -0,0 +1,3 @@
+xŽQ
+Â0DýÎ)öʦ»I<‚'ØlR+˜Fj¼¿EoàÏ0<xÃh«õÞa Üõµ]È™­åXUlÞPF)Åz‘4yó”µ,\r 'SÂÄ-mI4
+‘Xhô”&òÌFÞ}n+\µõ—Y´-p|é·œoUz;-‘aÑlt{ØË?®I«,:ÃoÚR̳cHK \ No newline at end of file
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f b/tests/resources/submodules/testrepo/.gitted/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f
new file mode 100644
index 000000000..a67d6e647
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/tests/resources/submodules/testrepo/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
new file mode 100644
index 000000000..711223894
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0 b/tests/resources/submodules/testrepo/.gitted/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0
new file mode 100644
index 000000000..b135eccda
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3 b/tests/resources/submodules/testrepo/.gitted/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3
new file mode 100644
index 000000000..82e2790e8
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 b/tests/resources/submodules/testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1
new file mode 100644
index 000000000..697c94c92
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 b/tests/resources/submodules/testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92
new file mode 100644
index 000000000..112998d42
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 b/tests/resources/submodules/testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765
new file mode 100644
index 000000000..12bf5f3e3
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx b/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx
new file mode 100644
index 000000000..5068f2818
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack b/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack
new file mode 100644
index 000000000..a6a1f3020
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx b/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx
new file mode 100644
index 000000000..94c3c71da
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack b/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack
new file mode 100644
index 000000000..74c7fe4f3
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx b/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx
new file mode 100644
index 000000000..555cfa977
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack b/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack
new file mode 100644
index 000000000..4d539ed0a
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack
Binary files differ
diff --git a/tests/resources/submodules/testrepo/.gitted/packed-refs b/tests/resources/submodules/testrepo/.gitted/packed-refs
new file mode 100644
index 000000000..9c0433e1c
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/packed-refs
@@ -0,0 +1,12 @@
+# pack-refs with: peeled
+b25fa35b38051e4ae45d4222e795f9df2e43f1d1 refs/tags/test
+^e90810b8df3e80c413d903f631643c716887138d
+1385f264afb75a56a5bec74243be9b367ba4ca08 refs/tags/point_to_blob
+7b4384978d2493e851f9cca7858815fac9b10980 refs/tags/e90810b
+^e90810b8df3e80c413d903f631643c716887138d
+e90810b8df3e80c413d903f631643c716887138d refs/remotes/origin/test
+763d71aadf09a7951596c9746c024e7eece7c7af refs/remotes/origin/subtrees
+4a202b346bb0fb0db7eff3cffeb3c70babbd2045 refs/remotes/origin/packed-test
+41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9 refs/remotes/origin/packed
+a65fedf39aefe402d3bb6e24df4d4f5fe4547750 refs/remotes/origin/master
+a4a7dce85cf63874e984719f4fdd239f5145052f refs/remotes/origin/br2
diff --git a/tests/resources/submodules/testrepo/.gitted/refs/heads/master b/tests/resources/submodules/testrepo/.gitted/refs/heads/master
new file mode 100644
index 000000000..3d8f0a402
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/refs/heads/master
@@ -0,0 +1 @@
+a65fedf39aefe402d3bb6e24df4d4f5fe4547750
diff --git a/tests/resources/submodules/testrepo/.gitted/refs/remotes/origin/HEAD b/tests/resources/submodules/testrepo/.gitted/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..6efe28fff
--- /dev/null
+++ b/tests/resources/submodules/testrepo/.gitted/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+ref: refs/remotes/origin/master
diff --git a/tests/resources/submodules/testrepo/README b/tests/resources/submodules/testrepo/README
new file mode 100644
index 000000000..a8233120f
--- /dev/null
+++ b/tests/resources/submodules/testrepo/README
@@ -0,0 +1 @@
+hey there
diff --git a/tests/resources/submodules/testrepo/branch_file.txt b/tests/resources/submodules/testrepo/branch_file.txt
new file mode 100644
index 000000000..3697d64be
--- /dev/null
+++ b/tests/resources/submodules/testrepo/branch_file.txt
@@ -0,0 +1,2 @@
+hi
+bye!
diff --git a/tests/resources/submodules/testrepo/new.txt b/tests/resources/submodules/testrepo/new.txt
new file mode 100644
index 000000000..a71586c1d
--- /dev/null
+++ b/tests/resources/submodules/testrepo/new.txt
@@ -0,0 +1 @@
+my new file
diff --git a/tests/resources/submodules/unmodified b/tests/resources/submodules/unmodified
new file mode 100644
index 000000000..092bfb9bd
--- /dev/null
+++ b/tests/resources/submodules/unmodified
@@ -0,0 +1 @@
+yo
diff --git a/tests/resources/submodules/untracked b/tests/resources/submodules/untracked
new file mode 100644
index 000000000..092bfb9bd
--- /dev/null
+++ b/tests/resources/submodules/untracked
@@ -0,0 +1 @@
+yo
diff --git a/tests/resources/template/branches/.gitignore b/tests/resources/template/branches/.gitignore
new file mode 100644
index 000000000..16868cedb
--- /dev/null
+++ b/tests/resources/template/branches/.gitignore
@@ -0,0 +1,2 @@
+# This file should not be copied, nor should the
+# containing directory, since it is effectively "empty"
diff --git a/tests/resources/template/description b/tests/resources/template/description
new file mode 100644
index 000000000..ff04c4c13
--- /dev/null
+++ b/tests/resources/template/description
@@ -0,0 +1 @@
+Edit this file 'description' to name the repository.
diff --git a/tests/resources/template/hooks/link.sample b/tests/resources/template/hooks/link.sample
new file mode 120000
index 000000000..771acc43b
--- /dev/null
+++ b/tests/resources/template/hooks/link.sample
@@ -0,0 +1 @@
+update.sample \ No newline at end of file
diff --git a/tests/resources/template/hooks/update.sample b/tests/resources/template/hooks/update.sample
new file mode 100755
index 000000000..3b5f41202
--- /dev/null
+++ b/tests/resources/template/hooks/update.sample
@@ -0,0 +1,9 @@
+#!/bin/sh
+#
+# A sample hook to make sure that the `git_repository_init_ext()` function
+# can correctly copy a hook over and set it up with the correct permissions.
+#
+# To enable a hook, you copy the file and remove the ".sample" suffix, but
+# in this case, we're just making sure it gets copied correctly.
+
+echo "$GIT_DIR"
diff --git a/tests/resources/template/info/exclude b/tests/resources/template/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/template/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/resources/testrepo.git/FETCH_HEAD b/tests/resources/testrepo.git/FETCH_HEAD
new file mode 100644
index 000000000..48446265c
--- /dev/null
+++ b/tests/resources/testrepo.git/FETCH_HEAD
@@ -0,0 +1,2 @@
+a65fedf39aefe402d3bb6e24df4d4f5fe4547750 branch 'master' of git://example.com/git/testrepo.git
+258f0e2a959a364e40ed6603d5d44fbb24765b10 not-for-merge branch 'haacked' of git://example.com/git/testrepo.git
diff --git a/tests/resources/testrepo.git/head-tracker b/tests/resources/testrepo.git/HEAD_TRACKER
index 40d876b4c..40d876b4c 100644
--- a/tests/resources/testrepo.git/head-tracker
+++ b/tests/resources/testrepo.git/HEAD_TRACKER
diff --git a/tests/resources/testrepo.git/config b/tests/resources/testrepo.git/config
index 1a5aacdfa..dfab4eeb4 100644
--- a/tests/resources/testrepo.git/config
+++ b/tests/resources/testrepo.git/config
@@ -6,3 +6,35 @@
[remote "test"]
url = git://github.com/libgit2/libgit2
fetch = +refs/heads/*:refs/remotes/test/*
+[remote "joshaber"]
+ url = git://github.com/libgit2/libgit2
+[remote "empty-remote-url"]
+ url =
+ pushurl =
+[remote "empty-remote-pushurl"]
+ pushurl =
+[remote "no-remote-url"]
+ fetch =
+[remote "test_with_pushurl"]
+ url = git://github.com/libgit2/fetchlibgit2
+ pushurl = git://github.com/libgit2/pushlibgit2
+ fetch = +refs/heads/*:refs/remotes/test_with_pushurl/*
+
+[branch "master"]
+ remote = test
+ merge = refs/heads/master
+[branch "track-local"]
+ remote = .
+ merge = refs/heads/master
+[branch "cannot-fetch"]
+ remote = joshaber
+ merge = refs/heads/cannot-fetch
+[branch "remoteless"]
+ remote =
+ merge = refs/heads/master
+[branch "mergeless"]
+ remote = test
+ merge =
+[branch "mergeandremoteless"]
+ remote =
+ merge =
diff --git a/tests/resources/testrepo.git/logs/HEAD b/tests/resources/testrepo.git/logs/HEAD
new file mode 100644
index 000000000..9413b72cc
--- /dev/null
+++ b/tests/resources/testrepo.git/logs/HEAD
@@ -0,0 +1,7 @@
+0000000000000000000000000000000000000000 be3563ae3f795b2b4353bcce3a527ad0a4f7f644 Ben Straub <bstraub@github.com> 1335806563 -0700 clone: from /Users/ben/src/libgit2/tests/resources/testrepo.git
+be3563ae3f795b2b4353bcce3a527ad0a4f7f644 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub <bstraub@github.com> 1335806603 -0900 commit:
+a65fedf39aefe402d3bb6e24df4d4f5fe4547750 5b5b025afb0b4c913b4c338a42934a3863bf3644 Ben Straub <bstraub@github.com> 1335806604 -0900 checkout: moving from master to 5b5b025
+5b5b025afb0b4c913b4c338a42934a3863bf3644 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub <bstraub@github.com> 1335806605 -0900 checkout: moving from 5b5b025 to master
+a65fedf39aefe402d3bb6e24df4d4f5fe4547750 c47800c7266a2be04c571c04d5a6614691ea99bd Ben Straub <bstraub@github.com> 1335806608 -0900 checkout: moving from master to br2
+c47800c7266a2be04c571c04d5a6614691ea99bd a4a7dce85cf63874e984719f4fdd239f5145052f Ben Straub <bstraub@github.com> 1335806617 -0900 commit: checking in
+a4a7dce85cf63874e984719f4fdd239f5145052f a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub <bstraub@github.com> 1335806621 -0900 checkout: moving from br2 to master
diff --git a/tests/resources/testrepo.git/logs/refs/heads/br2 b/tests/resources/testrepo.git/logs/refs/heads/br2
new file mode 100644
index 000000000..4e27f6b8d
--- /dev/null
+++ b/tests/resources/testrepo.git/logs/refs/heads/br2
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 c47800c7266a2be04c571c04d5a6614691ea99bd Ben Straub <bstraub@github.com> 1335806608 -0700 branch: Created from refs/remotes/origin/br2
+a4a7dce85cf63874e984719f4fdd239f5145052f a4a7dce85cf63874e984719f4fdd239f5145052f Ben Straub <bstraub@github.com> 1335806617 -0700 commit: checking in
diff --git a/tests/resources/testrepo.git/logs/refs/heads/master b/tests/resources/testrepo.git/logs/refs/heads/master
new file mode 100644
index 000000000..e1c729a45
--- /dev/null
+++ b/tests/resources/testrepo.git/logs/refs/heads/master
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 be3563ae3f795b2b4353bcce3a527ad0a4f7f644 Ben Straub <bstraub@github.com> 1335806563 -0800 clone: from /Users/ben/src/libgit2/tests/resources/testrepo.git
+be3563ae3f795b2b4353bcce3a527ad0a4f7f644 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub <bstraub@github.com> 1335806603 -0800 commit: checking in
diff --git a/tests/resources/testrepo.git/logs/refs/heads/not-good b/tests/resources/testrepo.git/logs/refs/heads/not-good
new file mode 100644
index 000000000..bfbeacb8a
--- /dev/null
+++ b/tests/resources/testrepo.git/logs/refs/heads/not-good
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub <bstraub@github.com> 1336761944 -0700 branch: Created from master
diff --git a/tests/resources/testrepo.git/logs/refs/remotes/origin/HEAD b/tests/resources/testrepo.git/logs/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..f1aac6d0f
--- /dev/null
+++ b/tests/resources/testrepo.git/logs/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 be3563ae3f795b2b4353bcce3a527ad0a4f7f644 Ben Straub <bstraub@github.com> 1335806563 -0700 clone: from /Users/ben/src/libgit2/tests/resources/testrepo.git
diff --git a/tests/resources/testrepo.git/logs/refs/remotes/test/master b/tests/resources/testrepo.git/logs/refs/remotes/test/master
new file mode 100644
index 000000000..8d49ba3e0
--- /dev/null
+++ b/tests/resources/testrepo.git/logs/refs/remotes/test/master
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub <bstraub@github.com> 1335806565 -0800 update by push
+a65fedf39aefe402d3bb6e24df4d4f5fe4547750 be3563ae3f795b2b4353bcce3a527ad0a4f7f644 Ben Straub <bstraub@github.com> 1335806688 -0800 update by push
diff --git a/tests/resources/testrepo.git/objects/08/b041783f40edfe12bb406c9c9a8a040177c125 b/tests/resources/testrepo.git/objects/08/b041783f40edfe12bb406c9c9a8a040177c125
new file mode 100644
index 000000000..d1c032fce
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/08/b041783f40edfe12bb406c9c9a8a040177c125
Binary files differ
diff --git a/tests/resources/testrepo.git/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fd b/tests/resources/testrepo.git/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fd
new file mode 100644
index 000000000..3ec541288
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fd
Binary files differ
diff --git a/tests/resources/testrepo.git/objects/1b/8cbad43e867676df601306689fe7c3def5e689 b/tests/resources/testrepo.git/objects/1b/8cbad43e867676df601306689fe7c3def5e689
new file mode 100644
index 000000000..6048d4bad
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/1b/8cbad43e867676df601306689fe7c3def5e689
Binary files differ
diff --git a/tests/resources/testrepo.git/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b b/tests/resources/testrepo.git/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b
new file mode 100644
index 000000000..225c45734
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b
Binary files differ
diff --git a/tests/resources/testrepo.git/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10 b/tests/resources/testrepo.git/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10
new file mode 100644
index 000000000..cb1ed5712
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10
Binary files differ
diff --git a/tests/resources/testrepo.git/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d b/tests/resources/testrepo.git/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d
new file mode 100644
index 000000000..df40d99af
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d
Binary files differ
diff --git a/tests/resources/testrepo.git/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7 b/tests/resources/testrepo.git/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7
new file mode 100644
index 000000000..0a1500a6f
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7
Binary files differ
diff --git a/tests/resources/testrepo.git/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 b/tests/resources/testrepo.git/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54
new file mode 100644
index 000000000..321eaa867
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54
Binary files differ
diff --git a/tests/resources/testrepo.git/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc b/tests/resources/testrepo.git/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc
new file mode 100644
index 000000000..9bb5b623b
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc
Binary files differ
diff --git a/tests/resources/testrepo.git/objects/4a/23e2e65ad4e31c4c9db7dc746650bfad082679 b/tests/resources/testrepo.git/objects/4a/23e2e65ad4e31c4c9db7dc746650bfad082679
new file mode 100644
index 000000000..18e3964b3
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/4a/23e2e65ad4e31c4c9db7dc746650bfad082679
Binary files differ
diff --git a/tests/resources/testrepo.git/objects/4b/22b35d44b5a4f589edf3dc89196399771796ea b/tests/resources/testrepo.git/objects/4b/22b35d44b5a4f589edf3dc89196399771796ea
new file mode 100644
index 000000000..b4e5aa186
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/4b/22b35d44b5a4f589edf3dc89196399771796ea
Binary files differ
diff --git a/tests/resources/testrepo.git/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91 b/tests/resources/testrepo.git/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91
new file mode 100644
index 000000000..351cff823
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91
Binary files differ
diff --git a/tests/resources/testrepo.git/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af b/tests/resources/testrepo.git/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af
new file mode 100644
index 000000000..716b0c64b
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af
@@ -0,0 +1 @@
+xŽAj!³ö?0¨£ßÂ09Êo}HÚ6¨}ÿôjUPP©ÕZ&Yÿø˜ AÔ›±€pŒÁFdë¼÷pz[fŽYŒ½PÒqLJ.,Z§`™Å®Ð.ù`’vÙ ³q $Æ5+9çOëtœû>Û/úDE/龡W¯ï*e¿§VŸdf1>ð覭Öê²×äÄ›¹úÊ™F« ­ìTŽÙhœk.i¶^0Ô?P¼R, \ No newline at end of file
diff --git a/tests/resources/testrepo.git/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe b/tests/resources/testrepo.git/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe
new file mode 100644
index 000000000..71019a636
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe
@@ -0,0 +1,2 @@
+xŒM F]sŠ¹€†Ÿ41ÆxÝ(­I‹ÁéÂÛKݽ/_ÞãP@¡ÚÕø¢!8›)es
+” ¥N&FGSÆ„¹hÑ{+ßCç‰÷ÆZzvØF¡7ZàÎ-¬Îñó‡k™x\ã¡[PÆ8ï´ôGØK/¥^© lÊ>.4 \ No newline at end of file
diff --git a/tests/resources/testrepo.git/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 b/tests/resources/testrepo.git/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162
new file mode 100644
index 000000000..4cc3f4dff
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162
@@ -0,0 +1 @@
+x+)JMU044b040031QrutñueX¡l¨ðmmA‹m›Ì£íJ}Gß;U‘T”˜—œŸ–™“ªWRQÂ`6ýš÷KÇ¥¶^/¾-*|òøWØ¥3P¥y©å`%ËEÛÞ±\&gŽÐ|Ÿ0§ÿ†{Ó1X \ No newline at end of file
diff --git a/tests/resources/testrepo.git/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 b/tests/resources/testrepo.git/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4
new file mode 100644
index 000000000..bf7b2bb68
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4
Binary files differ
diff --git a/tests/resources/testrepo.git/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d2 b/tests/resources/testrepo.git/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d2
new file mode 100644
index 000000000..7f1cfb23c
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d2
@@ -0,0 +1,2 @@
+xŽM
+Â0F]ç³d2¤ñ®<A~&´`­ÄôþVàæãmïËë²ÌÈÒ¡7Uà$äJöL9yM!¢GuœªH¤&UÈæ›>;ÔÂÁ…¬£³X†ÂEÈŽ5R±£ ÛAÑE &n}ZÜæ<E}À=O[ÒÖáüÞéúÓ¼^À,ã^†#¢É¿ƒ]ÿPÍ`>™A¹ \ No newline at end of file
diff --git a/tests/resources/testrepo.git/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 b/tests/resources/testrepo.git/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750
new file mode 100644
index 000000000..29c8e824d
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750
@@ -0,0 +1,3 @@
+xŽQ
+!@ûösBQ"‚ŽÐ ÆÙ± rÍîßÒú{<xð¤·öàîƪ
+™HlJSer!ZPTe*Žj°UÝÑEo^¼ê2 (†ˆ¬XSÅ€ED‘ƒO<Y¦šj$2üs_á&} ¸Î,}Ó[~p¹7~<ÒÛ:Ÿ £°·ÉZ³Ùípè?­1_ûåC0 \ No newline at end of file
diff --git a/tests/resources/testrepo.git/objects/ae/90f12eea699729ed24555e40b9fd669da12a12 b/tests/resources/testrepo.git/objects/ae/90f12eea699729ed24555e40b9fd669da12a12
new file mode 100644
index 000000000..d95254674
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/ae/90f12eea699729ed24555e40b9fd669da12a12
Binary files differ
diff --git a/tests/resources/testrepo.git/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593 b/tests/resources/testrepo.git/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593
new file mode 100644
index 000000000..f613670e2
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593
Binary files differ
diff --git a/tests/resources/testrepo.git/objects/d0/7b0f9a8c89f1d9e74dc4fce6421dec5ef8a659 b/tests/resources/testrepo.git/objects/d0/7b0f9a8c89f1d9e74dc4fce6421dec5ef8a659
new file mode 100644
index 000000000..f3b46b3ca
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/d0/7b0f9a8c89f1d9e74dc4fce6421dec5ef8a659
Binary files differ
diff --git a/tests/resources/testrepo.git/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f b/tests/resources/testrepo.git/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f
new file mode 100644
index 000000000..a67d6e647
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f
Binary files differ
diff --git a/tests/resources/testrepo.git/objects/d7/1aab4f9b04b45ce09bcaa636a9be6231474759 b/tests/resources/testrepo.git/objects/d7/1aab4f9b04b45ce09bcaa636a9be6231474759
new file mode 100644
index 000000000..2d47e6faf
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/d7/1aab4f9b04b45ce09bcaa636a9be6231474759
Binary files differ
diff --git a/tests/resources/testrepo.git/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0 b/tests/resources/testrepo.git/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0
new file mode 100644
index 000000000..b135eccda
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0
Binary files differ
diff --git a/tests/resources/testrepo.git/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3 b/tests/resources/testrepo.git/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3
new file mode 100644
index 000000000..82e2790e8
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3
Binary files differ
diff --git a/tests/resources/testrepo.git/objects/fd/4959ce7510db09d4d8217fa2d1780413e05a09 b/tests/resources/testrepo.git/objects/fd/4959ce7510db09d4d8217fa2d1780413e05a09
new file mode 100644
index 000000000..158aef21f
--- /dev/null
+++ b/tests/resources/testrepo.git/objects/fd/4959ce7510db09d4d8217fa2d1780413e05a09
Binary files differ
diff --git a/tests/resources/testrepo.git/refs/heads/cannot-fetch b/tests/resources/testrepo.git/refs/heads/cannot-fetch
new file mode 100644
index 000000000..aab87e5e7
--- /dev/null
+++ b/tests/resources/testrepo.git/refs/heads/cannot-fetch
@@ -0,0 +1 @@
+a4a7dce85cf63874e984719f4fdd239f5145052f
diff --git a/tests/resources/testrepo.git/refs/heads/chomped b/tests/resources/testrepo.git/refs/heads/chomped
new file mode 100644
index 000000000..0166a7f92
--- /dev/null
+++ b/tests/resources/testrepo.git/refs/heads/chomped
@@ -0,0 +1 @@
+e90810b8df3e80c413d903f631643c716887138d \ No newline at end of file
diff --git a/tests/resources/testrepo.git/refs/heads/haacked b/tests/resources/testrepo.git/refs/heads/haacked
new file mode 100644
index 000000000..17f591222
--- /dev/null
+++ b/tests/resources/testrepo.git/refs/heads/haacked
@@ -0,0 +1 @@
+258f0e2a959a364e40ed6603d5d44fbb24765b10
diff --git a/tests/resources/testrepo.git/refs/heads/master b/tests/resources/testrepo.git/refs/heads/master
index 9536ad89c..3d8f0a402 100644
--- a/tests/resources/testrepo.git/refs/heads/master
+++ b/tests/resources/testrepo.git/refs/heads/master
@@ -1 +1 @@
-be3563ae3f795b2b4353bcce3a527ad0a4f7f644
+a65fedf39aefe402d3bb6e24df4d4f5fe4547750
diff --git a/tests/resources/testrepo.git/refs/heads/not-good b/tests/resources/testrepo.git/refs/heads/not-good
new file mode 100644
index 000000000..3d8f0a402
--- /dev/null
+++ b/tests/resources/testrepo.git/refs/heads/not-good
@@ -0,0 +1 @@
+a65fedf39aefe402d3bb6e24df4d4f5fe4547750
diff --git a/tests/resources/testrepo.git/refs/heads/subtrees b/tests/resources/testrepo.git/refs/heads/subtrees
new file mode 100644
index 000000000..ad27e0b13
--- /dev/null
+++ b/tests/resources/testrepo.git/refs/heads/subtrees
@@ -0,0 +1 @@
+763d71aadf09a7951596c9746c024e7eece7c7af
diff --git a/tests/resources/testrepo.git/refs/heads/track-local b/tests/resources/testrepo.git/refs/heads/track-local
new file mode 100644
index 000000000..f37febb2c
--- /dev/null
+++ b/tests/resources/testrepo.git/refs/heads/track-local
@@ -0,0 +1 @@
+9fd738e8f7967c078dceed8190330fc8648ee56a
diff --git a/tests/resources/testrepo.git/refs/heads/trailing b/tests/resources/testrepo.git/refs/heads/trailing
new file mode 100644
index 000000000..2a4a6e62f
--- /dev/null
+++ b/tests/resources/testrepo.git/refs/heads/trailing
@@ -0,0 +1 @@
+e90810b8df3e80c413d903f631643c716887138d
diff --git a/tests/resources/testrepo.git/refs/notes/fanout b/tests/resources/testrepo.git/refs/notes/fanout
new file mode 100644
index 000000000..1f1703631
--- /dev/null
+++ b/tests/resources/testrepo.git/refs/notes/fanout
@@ -0,0 +1 @@
+d07b0f9a8c89f1d9e74dc4fce6421dec5ef8a659
diff --git a/tests/resources/testrepo.git/refs/remotes/test/master b/tests/resources/testrepo.git/refs/remotes/test/master
new file mode 100644
index 000000000..9536ad89c
--- /dev/null
+++ b/tests/resources/testrepo.git/refs/remotes/test/master
@@ -0,0 +1 @@
+be3563ae3f795b2b4353bcce3a527ad0a4f7f644
diff --git a/tests/resources/testrepo.git/refs/tags/annotated_tag_to_blob b/tests/resources/testrepo.git/refs/tags/annotated_tag_to_blob
new file mode 100644
index 000000000..6c146d6e3
--- /dev/null
+++ b/tests/resources/testrepo.git/refs/tags/annotated_tag_to_blob
@@ -0,0 +1 @@
+521d87c1ec3aef9824daf6d96cc0ae3710766d91
diff --git a/tests/resources/testrepo.git/refs/tags/hard_tag b/tests/resources/testrepo.git/refs/tags/hard_tag
new file mode 100644
index 000000000..59ce65649
--- /dev/null
+++ b/tests/resources/testrepo.git/refs/tags/hard_tag
@@ -0,0 +1 @@
+849a5e34a26815e821f865b8479f5815a47af0fe
diff --git a/tests/resources/testrepo.git/refs/tags/taggerless b/tests/resources/testrepo.git/refs/tags/taggerless
new file mode 100644
index 000000000..f960c7d62
--- /dev/null
+++ b/tests/resources/testrepo.git/refs/tags/taggerless
@@ -0,0 +1 @@
+4a23e2e65ad4e31c4c9db7dc746650bfad082679
diff --git a/tests/resources/testrepo.git/refs/tags/wrapped_tag b/tests/resources/testrepo.git/refs/tags/wrapped_tag
new file mode 100644
index 000000000..59ce65649
--- /dev/null
+++ b/tests/resources/testrepo.git/refs/tags/wrapped_tag
@@ -0,0 +1 @@
+849a5e34a26815e821f865b8479f5815a47af0fe
diff --git a/tests/resources/testrepo/.gitted/HEAD b/tests/resources/testrepo/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/testrepo/.gitted/HEAD_TRACKER b/tests/resources/testrepo/.gitted/HEAD_TRACKER
new file mode 100644
index 000000000..40d876b4c
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/HEAD_TRACKER
@@ -0,0 +1 @@
+ref: HEAD
diff --git a/tests/resources/testrepo/.gitted/config b/tests/resources/testrepo/.gitted/config
new file mode 100644
index 000000000..d0114012f
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/config
@@ -0,0 +1,8 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+[remote "test"]
+ url = git://github.com/libgit2/libgit2
+ fetch = +refs/heads/*:refs/remotes/test/*
diff --git a/tests/resources/testrepo/.gitted/index b/tests/resources/testrepo/.gitted/index
new file mode 100644
index 000000000..a27fb9c96
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/index
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/09/9fabac3a9ea935598528c27f866e34089c2eff b/tests/resources/testrepo/.gitted/objects/09/9fabac3a9ea935598528c27f866e34089c2eff
new file mode 100644
index 000000000..c60c78fb5
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/09/9fabac3a9ea935598528c27f866e34089c2eff
@@ -0,0 +1 @@
+xÎQ P¿9Å^@³ÂB!1F½‚'€î¢ÒJ?¼½Õ#ø7™ÉK¦ŸJhM›VE€,³·.3û¼§Þ¦ˆ‚ÔuVsHè-;õŠUÆÑÙ,œMˆ’…P³Iɉ&ÎÄ”×ìסŠK»O.2µո$8¤ùN·¡Ý—´ë§r„½!½l±CTk»lòUgfˆ0¿ËsêÓG( \ No newline at end of file
diff --git a/tests/resources/testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 b/tests/resources/testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08
new file mode 100644
index 000000000..cedb2a22e
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e b/tests/resources/testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e
new file mode 100644
index 000000000..b7d944fa1
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925 b/tests/resources/testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925
new file mode 100644
index 000000000..d37b93e4f
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 b/tests/resources/testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7
new file mode 100644
index 000000000..93a16f146
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd b/tests/resources/testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd
new file mode 100644
index 000000000..ba0bfb30c
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/1d/d0968be3ff95fcaecb6fa4245662db9fdc4568 b/tests/resources/testrepo/.gitted/objects/1d/d0968be3ff95fcaecb6fa4245662db9fdc4568
new file mode 100644
index 000000000..97c6b2cfa
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/1d/d0968be3ff95fcaecb6fa4245662db9fdc4568
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b b/tests/resources/testrepo/.gitted/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b
new file mode 100644
index 000000000..225c45734
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d b/tests/resources/testrepo/.gitted/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d
new file mode 100644
index 000000000..df40d99af
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 b/tests/resources/testrepo/.gitted/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54
new file mode 100644
index 000000000..321eaa867
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc b/tests/resources/testrepo/.gitted/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc
new file mode 100644
index 000000000..9bb5b623b
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 b/tests/resources/testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057
new file mode 100644
index 000000000..7ca4ceed5
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/45/dd856fdd4d89b884c340ba0e047752d9b085d6 b/tests/resources/testrepo/.gitted/objects/45/dd856fdd4d89b884c340ba0e047752d9b085d6
new file mode 100644
index 000000000..a83ed9763
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/45/dd856fdd4d89b884c340ba0e047752d9b085d6
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 b/tests/resources/testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045
new file mode 100644
index 000000000..8953b6cef
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045
@@ -0,0 +1,2 @@
+xŽQ
+Â0DýÎ)öÊ6›Í¦ "xO°‰‰-ØFb¼¿EoàÏ0 ¼Ç¤º,ske×[ÎPn8R,EpD?±gŸ}Ê^3² âÙ<µåµGŽhYKÄèÒ8ЖDAÉ)¿ÉÈ;gôݧÚàšjïp™4ÕŽ¯ô-çû¢óãêr‚ÁŠ;°s°GA4Ûº=ìùÖ(ôin7øIÌKÍFE \ No newline at end of file
diff --git a/tests/resources/testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63 b/tests/resources/testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63
new file mode 100644
index 000000000..e9150214b
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 b/tests/resources/testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644
new file mode 100644
index 000000000..c1f22c54f
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644
@@ -0,0 +1,2 @@
+xŽÛ 1EýNi@™Ék2 "X‚$ÙYW0YcÿíÀ¿Ã…s¸¥ÕzïÚÚõMDÏ€0æœ8!¶†ÉÌÞs‰ XŠªgÚdí::@X0»P¢wÙ"F/‰‰œÍRàˆUz÷¥múZZïú²¤ÒV}|•/œo5݇ÒêI£!¬1z Æ:vùÇUim}ê/¢>
+öF- \ No newline at end of file
diff --git a/tests/resources/testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc b/tests/resources/testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc
new file mode 100644
index 000000000..b669961d8
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735 b/tests/resources/testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735
new file mode 100644
index 000000000..9ff5eb2b5
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/6b/377958d8c6a4906e8573b53672a1a23a4e8ce6 b/tests/resources/testrepo/.gitted/objects/6b/377958d8c6a4906e8573b53672a1a23a4e8ce6
new file mode 100644
index 000000000..ee7c78174
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/6b/377958d8c6a4906e8573b53672a1a23a4e8ce6
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/6b/9b767af9992b4abad5e24ffb1ba2d688ca602e b/tests/resources/testrepo/.gitted/objects/6b/9b767af9992b4abad5e24ffb1ba2d688ca602e
new file mode 100644
index 000000000..197685b86
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/6b/9b767af9992b4abad5e24ffb1ba2d688ca602e
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a b/tests/resources/testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a
new file mode 100644
index 000000000..2ef4faa0f
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af b/tests/resources/testrepo/.gitted/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af
new file mode 100644
index 000000000..716b0c64b
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af
@@ -0,0 +1 @@
+xŽAj!³ö?0¨£ßÂ09Êo}HÚ6¨}ÿôjUPP©ÕZ&Yÿø˜ AÔ›±€pŒÁFdë¼÷pz[fŽYŒ½PÒqLJ.,Z§`™Å®Ð.ù`’vÙ ³q $Æ5+9çOëtœû>Û/úDE/龡W¯ï*e¿§VŸdf1>ð覭Öê²×äÄ›¹úÊ™F« ­ìTŽÙhœk.i¶^0Ô?P¼R, \ No newline at end of file
diff --git a/tests/resources/testrepo/.gitted/objects/7b/2417a23b63e1fdde88c80e14b33247c6e5785a b/tests/resources/testrepo/.gitted/objects/7b/2417a23b63e1fdde88c80e14b33247c6e5785a
new file mode 100644
index 000000000..db778aaae
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/7b/2417a23b63e1fdde88c80e14b33247c6e5785a
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/7b/4384978d2493e851f9cca7858815fac9b10980 b/tests/resources/testrepo/.gitted/objects/7b/4384978d2493e851f9cca7858815fac9b10980
new file mode 100644
index 000000000..23c462f34
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/7b/4384978d2493e851f9cca7858815fac9b10980
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d b/tests/resources/testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d
new file mode 100644
index 000000000..2f9b6b6e3
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 b/tests/resources/testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479
new file mode 100644
index 000000000..5df58dda5
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/87/380ae84009e9c503506c2f6143a4fc6c60bf80 b/tests/resources/testrepo/.gitted/objects/87/380ae84009e9c503506c2f6143a4fc6c60bf80
new file mode 100644
index 000000000..3042f5790
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/87/380ae84009e9c503506c2f6143a4fc6c60bf80
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 b/tests/resources/testrepo/.gitted/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162
new file mode 100644
index 000000000..4cc3f4dff
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162
@@ -0,0 +1 @@
+x+)JMU044b040031QrutñueX¡l¨ðmmA‹m›Ì£íJ}Gß;U‘T”˜—œŸ–™“ªWRQÂ`6ýš÷KÇ¥¶^/¾-*|òøWØ¥3P¥y©å`%ËEÛÞ±\&gŽÐ|Ÿ0§ÿ†{Ó1X \ No newline at end of file
diff --git a/tests/resources/testrepo/.gitted/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 b/tests/resources/testrepo/.gitted/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4
new file mode 100644
index 000000000..bf7b2bb68
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a b/tests/resources/testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a
new file mode 100644
index 000000000..a79612435
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a
@@ -0,0 +1,3 @@
+xŽ[
+Â0EýÎ*fÊäÕ¤ "¸W0“‡-ØFâtÿÝ—çpS[–YÀ˜x^
+Díb CLhutɉ}¥8X*4Zí¬sY½¨—UÀ‘AÃÖ ÌX3‡R«Mµ¶) s6è¼¢M¦ÖážšÜ&Jm…ó;}Çõ±Ðü<¥¶\@›à‚ÑÞpÄ€¨vº?”ò«jÛºLð«¨Ø?Hå \ No newline at end of file
diff --git a/tests/resources/testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f b/tests/resources/testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f
new file mode 100644
index 000000000..f8588696b
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f
@@ -0,0 +1,2 @@
+x;j1DëmdÓú·À˜ÇŽ|M«µ3`ŒV{ >€³âQ¯ ¸·vL0I?Í!š4–Z=Ê! ×¦8²F¢Ã’!rÖsQßyÈ9]$DŽ&„l6AÇ>jFWüÒµ IKNiûë§Z¢%¡SˆŒ‘
+‹Ò ­ÅʉøU~̽øä>'¼ï™û ¯wþ ×[ËÇ× ÷öÚDGÚ¡±ðŒQ-ºMù«>dܶ‘OÞáÒò}í\à8g_ШÂoYr \ No newline at end of file
diff --git a/tests/resources/testrepo/.gitted/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 b/tests/resources/testrepo/.gitted/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750
new file mode 100644
index 000000000..29c8e824d
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750
@@ -0,0 +1,3 @@
+xŽQ
+!@ûösBQ"‚ŽÐ ÆÙ± rÍîßÒú{<xð¤·öàîƪ
+™HlJSer!ZPTe*Žj°UÝÑEo^¼ê2 (†ˆ¬XSÅ€ED‘ƒO<Y¦šj$2üs_á&} ¸Î,}Ó[~p¹7~<ÒÛ:Ÿ £°·ÉZ³Ùípè?­1_ûåC0 \ No newline at end of file
diff --git a/tests/resources/testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd b/tests/resources/testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd
new file mode 100644
index 000000000..d0d7e736e
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 b/tests/resources/testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6
new file mode 100644
index 000000000..18a7f61c2
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/ae/90f12eea699729ed24555e40b9fd669da12a12 b/tests/resources/testrepo/.gitted/objects/ae/90f12eea699729ed24555e40b9fd669da12a12
new file mode 100644
index 000000000..d95254674
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/ae/90f12eea699729ed24555e40b9fd669da12a12
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/af/e4393b2b2a965f06acf2ca9658eaa01e0cd6b6 b/tests/resources/testrepo/.gitted/objects/af/e4393b2b2a965f06acf2ca9658eaa01e0cd6b6
new file mode 100644
index 000000000..6948f1b1c
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/af/e4393b2b2a965f06acf2ca9658eaa01e0cd6b6
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 b/tests/resources/testrepo/.gitted/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1
new file mode 100644
index 000000000..f460f2547
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1
@@ -0,0 +1,2 @@
+xÌA
+Â0…a×9ÅìIÒ ™€ˆp'î§1Ѷ‘v\x{cáýðVŸpƒvWûgŠ¾ÇŽ0xº[ ]"g†#{rDÆ Cot ­äûN œU $­ò?9-p+1Í^¤ÀQx®¯Ï9O\ÆC¬Ó Œm'D {mµVêú(+´ñælè¶,Þ \ No newline at end of file
diff --git a/tests/resources/testrepo/.gitted/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593 b/tests/resources/testrepo/.gitted/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593
new file mode 100644
index 000000000..f613670e2
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 b/tests/resources/testrepo/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644
new file mode 100644
index 000000000..0817229bc
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644
@@ -0,0 +1,3 @@
+xKj1D³Ö)zçUBëÛ-0ÁuV9¦Õò<#£È÷ÏȲ+ŠW<Jú¶Ý&8Ê/s¨‚e‹µµÈ•KJ­«½S
+ØRvÌÁ{©æQ†îr«äY¹QN$H\Eµ²Íè=6áX5¦òÇK Fr)·(‰dC‡Î†”­–œ—jÊs®}À—ô9ác-Òw8Ëo¸\·r»¿IßÞÁ:
+l}F‚W$Ds´Ç£©ÿÙšOW…e”]V8-ÃÌÈ"U \ No newline at end of file
diff --git a/tests/resources/testrepo/.gitted/objects/c0/528fd6cc988c0a40ce0be11bc192fc8dc5346e b/tests/resources/testrepo/.gitted/objects/c0/528fd6cc988c0a40ce0be11bc192fc8dc5346e
new file mode 100644
index 000000000..0401ab489
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/c0/528fd6cc988c0a40ce0be11bc192fc8dc5346e
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd b/tests/resources/testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd
new file mode 100644
index 000000000..75f541f10
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd
@@ -0,0 +1,3 @@
+xŽQ
+Â0DýÎ)öʦ»I<‚'ØlR+˜Fj¼¿EoàÏ0<xÃh«õÞa Üõµ]È™­åXUlÞPF)Åz‘4yó”µ,\r 'SÂÄ-mI4
+‘Xhô”&òÌFÞ}n+\µõ—Y´-p|é·œoUz;-‘aÑlt{ØË?®I«,:ÃoÚR̳cHK \ No newline at end of file
diff --git a/tests/resources/testrepo/.gitted/objects/ce/054d4c5e3c83522aed8bc061987b46b7ede3be b/tests/resources/testrepo/.gitted/objects/ce/054d4c5e3c83522aed8bc061987b46b7ede3be
new file mode 100644
index 000000000..4910e4c0a
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/ce/054d4c5e3c83522aed8bc061987b46b7ede3be
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9 b/tests/resources/testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9
new file mode 100644
index 000000000..7620c514f
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83 b/tests/resources/testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83
new file mode 100644
index 000000000..00940f0f2
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f b/tests/resources/testrepo/.gitted/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f
new file mode 100644
index 000000000..a67d6e647
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/tests/resources/testrepo/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
new file mode 100644
index 000000000..711223894
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0 b/tests/resources/testrepo/.gitted/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0
new file mode 100644
index 000000000..b135eccda
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3 b/tests/resources/testrepo/.gitted/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3
new file mode 100644
index 000000000..82e2790e8
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 b/tests/resources/testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1
new file mode 100644
index 000000000..697c94c92
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 b/tests/resources/testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92
new file mode 100644
index 000000000..112998d42
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 b/tests/resources/testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765
new file mode 100644
index 000000000..12bf5f3e3
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx b/tests/resources/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx
new file mode 100644
index 000000000..5068f2818
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack b/tests/resources/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack
new file mode 100644
index 000000000..a6a1f3020
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx b/tests/resources/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx
new file mode 100644
index 000000000..94c3c71da
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack b/tests/resources/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack
new file mode 100644
index 000000000..74c7fe4f3
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx b/tests/resources/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx
new file mode 100644
index 000000000..555cfa977
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack b/tests/resources/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack
new file mode 100644
index 000000000..4d539ed0a
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack
Binary files differ
diff --git a/tests/resources/testrepo/.gitted/packed-refs b/tests/resources/testrepo/.gitted/packed-refs
new file mode 100644
index 000000000..6018a19d2
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/packed-refs
@@ -0,0 +1,4 @@
+# pack-refs with: peeled
+41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9 refs/heads/packed
+5b5b025afb0b4c913b4c338a42934a3863bf3644 refs/heads/packed-test
+b25fa35b38051e4ae45d4222e795f9df2e43f1d1 refs/tags/packed-tag
diff --git a/tests/resources/testrepo/.gitted/refs/heads/br2 b/tests/resources/testrepo/.gitted/refs/heads/br2
new file mode 100644
index 000000000..aab87e5e7
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/refs/heads/br2
@@ -0,0 +1 @@
+a4a7dce85cf63874e984719f4fdd239f5145052f
diff --git a/tests/resources/testrepo/.gitted/refs/heads/dir b/tests/resources/testrepo/.gitted/refs/heads/dir
new file mode 100644
index 000000000..4567d37fa
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/refs/heads/dir
@@ -0,0 +1 @@
+144344043ba4d4a405da03de3844aa829ae8be0e
diff --git a/tests/resources/testrepo/.gitted/refs/heads/long-file-name b/tests/resources/testrepo/.gitted/refs/heads/long-file-name
new file mode 100644
index 000000000..1f942a746
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/refs/heads/long-file-name
@@ -0,0 +1 @@
+6b377958d8c6a4906e8573b53672a1a23a4e8ce6
diff --git a/tests/resources/testrepo/.gitted/refs/heads/master b/tests/resources/testrepo/.gitted/refs/heads/master
new file mode 100644
index 000000000..f31fe781b
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/refs/heads/master
@@ -0,0 +1 @@
+099fabac3a9ea935598528c27f866e34089c2eff
diff --git a/tests/resources/testrepo/.gitted/refs/heads/packed-test b/tests/resources/testrepo/.gitted/refs/heads/packed-test
new file mode 100644
index 000000000..f2c14ad83
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/refs/heads/packed-test
@@ -0,0 +1 @@
+4a202b346bb0fb0db7eff3cffeb3c70babbd2045
diff --git a/tests/resources/testrepo/.gitted/refs/heads/subtrees b/tests/resources/testrepo/.gitted/refs/heads/subtrees
new file mode 100644
index 000000000..ad27e0b13
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/refs/heads/subtrees
@@ -0,0 +1 @@
+763d71aadf09a7951596c9746c024e7eece7c7af
diff --git a/tests/resources/testrepo/.gitted/refs/heads/test b/tests/resources/testrepo/.gitted/refs/heads/test
new file mode 100644
index 000000000..399c4c73e
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/refs/heads/test
@@ -0,0 +1 @@
+e90810b8df3e80c413d903f631643c716887138d
diff --git a/tests/resources/testrepo/.gitted/refs/tags/e90810b b/tests/resources/testrepo/.gitted/refs/tags/e90810b
new file mode 100644
index 000000000..584495d3c
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/refs/tags/e90810b
@@ -0,0 +1 @@
+7b4384978d2493e851f9cca7858815fac9b10980
diff --git a/tests/resources/testrepo/.gitted/refs/tags/foo/bar b/tests/resources/testrepo/.gitted/refs/tags/foo/bar
new file mode 100644
index 000000000..6ee952a03
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/refs/tags/foo/bar
@@ -0,0 +1 @@
+b25fa35b38051e4ae45d4222e795f9df2e43f1d1
diff --git a/tests/resources/testrepo/.gitted/refs/tags/foo/foo/bar b/tests/resources/testrepo/.gitted/refs/tags/foo/foo/bar
new file mode 100644
index 000000000..6ee952a03
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/refs/tags/foo/foo/bar
@@ -0,0 +1 @@
+b25fa35b38051e4ae45d4222e795f9df2e43f1d1
diff --git a/tests/resources/testrepo/.gitted/refs/tags/point_to_blob b/tests/resources/testrepo/.gitted/refs/tags/point_to_blob
new file mode 100644
index 000000000..f874a3ffc
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/refs/tags/point_to_blob
@@ -0,0 +1 @@
+1385f264afb75a56a5bec74243be9b367ba4ca08
diff --git a/tests/resources/testrepo/.gitted/refs/tags/test b/tests/resources/testrepo/.gitted/refs/tags/test
new file mode 100644
index 000000000..6ee952a03
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/refs/tags/test
@@ -0,0 +1 @@
+b25fa35b38051e4ae45d4222e795f9df2e43f1d1
diff --git a/tests/resources/testrepo2/.gitted/HEAD b/tests/resources/testrepo2/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/testrepo2/.gitted/config b/tests/resources/testrepo2/.gitted/config
new file mode 100644
index 000000000..fc2433caf
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/config
@@ -0,0 +1,14 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
+ precomposeunicode = false
+[remote "origin"]
+ url = https://github.com/libgit2/false.git
+ fetch = +refs/heads/*:refs/remotes/origin/*
+[branch "master"]
+ remote = origin
+ merge = refs/heads/master
+ rebase = true
diff --git a/tests/resources/testrepo2/.gitted/description b/tests/resources/testrepo2/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/testrepo2/.gitted/index b/tests/resources/testrepo2/.gitted/index
new file mode 100644
index 000000000..b614d0727
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/index
Binary files differ
diff --git a/tests/resources/testrepo2/.gitted/info/exclude b/tests/resources/testrepo2/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/testrepo2/.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/resources/testrepo2/.gitted/logs/HEAD b/tests/resources/testrepo2/.gitted/logs/HEAD
new file mode 100644
index 000000000..4e80c69fa
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/logs/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 36060c58702ed4c2a40832c51758d5344201d89a Russell Belfer <rb@github.com> 1368278260 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/../../../rugged/test/fixtures/testrepo.git
diff --git a/tests/resources/testrepo2/.gitted/logs/refs/heads/master b/tests/resources/testrepo2/.gitted/logs/refs/heads/master
new file mode 100644
index 000000000..4e80c69fa
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/logs/refs/heads/master
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 36060c58702ed4c2a40832c51758d5344201d89a Russell Belfer <rb@github.com> 1368278260 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/../../../rugged/test/fixtures/testrepo.git
diff --git a/tests/resources/testrepo2/.gitted/logs/refs/remotes/origin/HEAD b/tests/resources/testrepo2/.gitted/logs/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..4e80c69fa
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/logs/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 36060c58702ed4c2a40832c51758d5344201d89a Russell Belfer <rb@github.com> 1368278260 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/../../../rugged/test/fixtures/testrepo.git
diff --git a/tests/resources/testrepo2/.gitted/objects/0c/37a5391bbff43c37f0d0371823a5509eed5b1d b/tests/resources/testrepo2/.gitted/objects/0c/37a5391bbff43c37f0d0371823a5509eed5b1d
new file mode 100644
index 000000000..bfe146a5a
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/0c/37a5391bbff43c37f0d0371823a5509eed5b1d
Binary files differ
diff --git a/tests/resources/testrepo2/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 b/tests/resources/testrepo2/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08
new file mode 100644
index 000000000..cedb2a22e
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08
Binary files differ
diff --git a/tests/resources/testrepo2/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 b/tests/resources/testrepo2/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7
new file mode 100644
index 000000000..93a16f146
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7
Binary files differ
diff --git a/tests/resources/testrepo2/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd b/tests/resources/testrepo2/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd
new file mode 100644
index 000000000..ba0bfb30c
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd
Binary files differ
diff --git a/tests/resources/testrepo2/.gitted/objects/2d/2eff63372b08adf0a9eb84109ccf7d19e2f3a2 b/tests/resources/testrepo2/.gitted/objects/2d/2eff63372b08adf0a9eb84109ccf7d19e2f3a2
new file mode 100644
index 000000000..3cd240db5
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/2d/2eff63372b08adf0a9eb84109ccf7d19e2f3a2
Binary files differ
diff --git a/tests/resources/testrepo2/.gitted/objects/36/060c58702ed4c2a40832c51758d5344201d89a b/tests/resources/testrepo2/.gitted/objects/36/060c58702ed4c2a40832c51758d5344201d89a
new file mode 100644
index 000000000..0c6246061
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/36/060c58702ed4c2a40832c51758d5344201d89a
@@ -0,0 +1,2 @@
+xŽQ
+Â0ýÎ)re“Ý´ ˆÁ$Û­-˜FÒíý-ÁŸÇ00𸖲¨õ¾?i±L#»‚ÐHSŽŒS×#qÏ2²DÈæ“š¬jC|HS†L‹8$ò)áÐaž°#2i×¹6ûäªjsâºÚëÆ?¸¿JZÞ®åfçtΞÁ˜ÃUþiͶçqiÂZÛ"›ù_/H6 \ No newline at end of file
diff --git a/tests/resources/testrepo2/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 b/tests/resources/testrepo2/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057
new file mode 100644
index 000000000..7ca4ceed5
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057
Binary files differ
diff --git a/tests/resources/testrepo2/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 b/tests/resources/testrepo2/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045
new file mode 100644
index 000000000..8953b6cef
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045
@@ -0,0 +1,2 @@
+xŽQ
+Â0DýÎ)öÊ6›Í¦ "xO°‰‰-ØFb¼¿EoàÏ0 ¼Ç¤º,ske×[ÎPn8R,EpD?±gŸ}Ê^3² âÙ<µåµGŽhYKÄèÒ8ЖDAÉ)¿ÉÈ;gôݧÚàšjïp™4ÕŽ¯ô-çû¢óãêr‚ÁŠ;°s°GA4Ûº=ìùÖ(ôin7øIÌKÍFE \ No newline at end of file
diff --git a/tests/resources/testrepo2/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 b/tests/resources/testrepo2/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644
new file mode 100644
index 000000000..c1f22c54f
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644
@@ -0,0 +1,2 @@
+xŽÛ 1EýNi@™Ék2 "X‚$ÙYW0YcÿíÀ¿Ã…s¸¥ÕzïÚÚõMDÏ€0æœ8!¶†ÉÌÞs‰ XŠªgÚdí::@X0»P¢wÙ"F/‰‰œÍRàˆUz÷¥múZZïú²¤ÒV}|•/œo5݇ÒêI£!¬1z Æ:vùÇUim}ê/¢>
+öF- \ No newline at end of file
diff --git a/tests/resources/testrepo2/.gitted/objects/61/9f9935957e010c419cb9d15621916ddfcc0b96 b/tests/resources/testrepo2/.gitted/objects/61/9f9935957e010c419cb9d15621916ddfcc0b96
new file mode 100644
index 000000000..1fd79b477
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/61/9f9935957e010c419cb9d15621916ddfcc0b96
Binary files differ
diff --git a/tests/resources/testrepo2/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a b/tests/resources/testrepo2/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a
new file mode 100644
index 000000000..2ef4faa0f
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a
Binary files differ
diff --git a/tests/resources/testrepo2/.gitted/objects/7f/043268ea43ce18e3540acaabf9e090c91965b0 b/tests/resources/testrepo2/.gitted/objects/7f/043268ea43ce18e3540acaabf9e090c91965b0
new file mode 100644
index 000000000..3d1016daa
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/7f/043268ea43ce18e3540acaabf9e090c91965b0
Binary files differ
diff --git a/tests/resources/testrepo2/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d b/tests/resources/testrepo2/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d
new file mode 100644
index 000000000..2f9b6b6e3
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d
Binary files differ
diff --git a/tests/resources/testrepo2/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 b/tests/resources/testrepo2/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479
new file mode 100644
index 000000000..5df58dda5
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479
Binary files differ
diff --git a/tests/resources/testrepo2/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a b/tests/resources/testrepo2/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a
new file mode 100644
index 000000000..a79612435
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a
@@ -0,0 +1,3 @@
+xŽ[
+Â0EýÎ*fÊäÕ¤ "¸W0“‡-ØFâtÿÝ—çpS[–YÀ˜x^
+Díb CLhutɉ}¥8X*4Zí¬sY½¨—UÀ‘AÃÖ ÌX3‡R«Mµ¶) s6è¼¢M¦ÖážšÜ&Jm…ó;}Çõ±Ðü<¥¶\@›à‚ÑÞpÄ€¨vº?”ò«jÛºLð«¨Ø?Hå \ No newline at end of file
diff --git a/tests/resources/testrepo2/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f b/tests/resources/testrepo2/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f
new file mode 100644
index 000000000..f8588696b
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f
@@ -0,0 +1,2 @@
+x;j1DëmdÓú·À˜ÇŽ|M«µ3`ŒV{ >€³âQ¯ ¸·vL0I?Í!š4–Z=Ê! ×¦8²F¢Ã’!rÖsQßyÈ9]$DŽ&„l6AÇ>jFWüÒµ IKNiûë§Z¢%¡SˆŒ‘
+‹Ò ­ÅʉøU~̽øä>'¼ï™û ¯wþ ×[ËÇ× ÷öÚDGÚ¡±ðŒQ-ºMù«>dܶ‘OÞáÒò}í\à8g_ШÂoYr \ No newline at end of file
diff --git a/tests/resources/testrepo2/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd b/tests/resources/testrepo2/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd
new file mode 100644
index 000000000..d0d7e736e
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd
Binary files differ
diff --git a/tests/resources/testrepo2/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 b/tests/resources/testrepo2/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6
new file mode 100644
index 000000000..18a7f61c2
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6
Binary files differ
diff --git a/tests/resources/testrepo2/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 b/tests/resources/testrepo2/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644
new file mode 100644
index 000000000..0817229bc
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644
@@ -0,0 +1,3 @@
+xKj1D³Ö)zçUBëÛ-0ÁuV9¦Õò<#£È÷ÏȲ+ŠW<Jú¶Ý&8Ê/s¨‚e‹µµÈ•KJ­«½S
+ØRvÌÁ{©æQ†îr«äY¹QN$H\Eµ²Íè=6áX5¦òÇK Fr)·(‰dC‡Î†”­–œ—jÊs®}À—ô9ác-Òw8Ëo¸\·r»¿IßÞÁ:
+l}F‚W$Ds´Ç£©ÿÙšOW…e”]V8-ÃÌÈ"U \ No newline at end of file
diff --git a/tests/resources/testrepo2/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd b/tests/resources/testrepo2/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd
new file mode 100644
index 000000000..75f541f10
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd
@@ -0,0 +1,3 @@
+xŽQ
+Â0DýÎ)öʦ»I<‚'ØlR+˜Fj¼¿EoàÏ0<xÃh«õÞa Üõµ]È™­åXUlÞPF)Åz‘4yó”µ,\r 'SÂÄ-mI4
+‘Xhô”&òÌFÞ}n+\µõ—Y´-p|é·œoUz;-‘aÑlt{ØË?®I«,:ÃoÚR̳cHK \ No newline at end of file
diff --git a/tests/resources/testrepo2/.gitted/objects/c4/dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b b/tests/resources/testrepo2/.gitted/objects/c4/dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b
new file mode 100644
index 000000000..599e16084
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/c4/dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b
Binary files differ
diff --git a/tests/resources/testrepo2/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/tests/resources/testrepo2/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
new file mode 100644
index 000000000..711223894
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
Binary files differ
diff --git a/tests/resources/testrepo2/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 b/tests/resources/testrepo2/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1
new file mode 100644
index 000000000..03770969a
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1
Binary files differ
diff --git a/tests/resources/testrepo2/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 b/tests/resources/testrepo2/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92
new file mode 100644
index 000000000..112998d42
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92
Binary files differ
diff --git a/tests/resources/testrepo2/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 b/tests/resources/testrepo2/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765
new file mode 100644
index 000000000..12bf5f3e3
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765
Binary files differ
diff --git a/tests/resources/testrepo2/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx b/tests/resources/testrepo2/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx
new file mode 100644
index 000000000..94c3c71da
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx
Binary files differ
diff --git a/tests/resources/testrepo2/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack b/tests/resources/testrepo2/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack
new file mode 100644
index 000000000..74c7fe4f3
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack
Binary files differ
diff --git a/tests/resources/testrepo2/.gitted/packed-refs b/tests/resources/testrepo2/.gitted/packed-refs
new file mode 100644
index 000000000..97ea6a848
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/packed-refs
@@ -0,0 +1,6 @@
+# pack-refs with: peeled fully-peeled
+36060c58702ed4c2a40832c51758d5344201d89a refs/remotes/origin/master
+41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9 refs/remotes/origin/packed
+5b5b025afb0b4c913b4c338a42934a3863bf3644 refs/tags/v0.9
+0c37a5391bbff43c37f0d0371823a5509eed5b1d refs/tags/v1.0
+^5b5b025afb0b4c913b4c338a42934a3863bf3644
diff --git a/tests/resources/testrepo2/.gitted/refs/heads/master b/tests/resources/testrepo2/.gitted/refs/heads/master
new file mode 100644
index 000000000..a7eafce3c
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/refs/heads/master
@@ -0,0 +1 @@
+36060c58702ed4c2a40832c51758d5344201d89a
diff --git a/tests/resources/testrepo2/.gitted/refs/remotes/origin/HEAD b/tests/resources/testrepo2/.gitted/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..6efe28fff
--- /dev/null
+++ b/tests/resources/testrepo2/.gitted/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+ref: refs/remotes/origin/master
diff --git a/tests/resources/testrepo2/README b/tests/resources/testrepo2/README
new file mode 100644
index 000000000..1385f264a
--- /dev/null
+++ b/tests/resources/testrepo2/README
@@ -0,0 +1 @@
+hey
diff --git a/tests/resources/testrepo2/new.txt b/tests/resources/testrepo2/new.txt
new file mode 100644
index 000000000..fa49b0779
--- /dev/null
+++ b/tests/resources/testrepo2/new.txt
@@ -0,0 +1 @@
+new file
diff --git a/tests/resources/testrepo2/subdir/README b/tests/resources/testrepo2/subdir/README
new file mode 100644
index 000000000..1385f264a
--- /dev/null
+++ b/tests/resources/testrepo2/subdir/README
@@ -0,0 +1 @@
+hey
diff --git a/tests/resources/testrepo2/subdir/new.txt b/tests/resources/testrepo2/subdir/new.txt
new file mode 100644
index 000000000..fa49b0779
--- /dev/null
+++ b/tests/resources/testrepo2/subdir/new.txt
@@ -0,0 +1 @@
+new file
diff --git a/tests/resources/testrepo2/subdir/subdir2/README b/tests/resources/testrepo2/subdir/subdir2/README
new file mode 100644
index 000000000..1385f264a
--- /dev/null
+++ b/tests/resources/testrepo2/subdir/subdir2/README
@@ -0,0 +1 @@
+hey
diff --git a/tests/resources/testrepo2/subdir/subdir2/new.txt b/tests/resources/testrepo2/subdir/subdir2/new.txt
new file mode 100644
index 000000000..fa49b0779
--- /dev/null
+++ b/tests/resources/testrepo2/subdir/subdir2/new.txt
@@ -0,0 +1 @@
+new file
diff --git a/tests/resources/twowaymerge.git/HEAD b/tests/resources/twowaymerge.git/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/twowaymerge.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/twowaymerge.git/config b/tests/resources/twowaymerge.git/config
new file mode 100644
index 000000000..c53d818dd
--- /dev/null
+++ b/tests/resources/twowaymerge.git/config
@@ -0,0 +1,5 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = true
+ ignorecase = true
diff --git a/tests/resources/twowaymerge.git/description b/tests/resources/twowaymerge.git/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/twowaymerge.git/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/twowaymerge.git/info/exclude b/tests/resources/twowaymerge.git/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/twowaymerge.git/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/resources/twowaymerge.git/objects/0c/8a3f1f3d5f421cf83048c7c73ee3b55a5e0f29 b/tests/resources/twowaymerge.git/objects/0c/8a3f1f3d5f421cf83048c7c73ee3b55a5e0f29
new file mode 100644
index 000000000..12698affa
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/0c/8a3f1f3d5f421cf83048c7c73ee3b55a5e0f29
Binary files differ
diff --git a/tests/resources/twowaymerge.git/objects/10/2dce8e3081f398e4bdd9fd894dc85ac3ca6a67 b/tests/resources/twowaymerge.git/objects/10/2dce8e3081f398e4bdd9fd894dc85ac3ca6a67
new file mode 100644
index 000000000..3806ee74c
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/10/2dce8e3081f398e4bdd9fd894dc85ac3ca6a67
Binary files differ
diff --git a/tests/resources/twowaymerge.git/objects/17/7d8634a28e26ec7819284752757ebe01a479d5 b/tests/resources/twowaymerge.git/objects/17/7d8634a28e26ec7819284752757ebe01a479d5
new file mode 100644
index 000000000..e91e06db2
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/17/7d8634a28e26ec7819284752757ebe01a479d5
Binary files differ
diff --git a/tests/resources/twowaymerge.git/objects/1c/30b88f5f3ee66d78df6520a7de9e89b890818b b/tests/resources/twowaymerge.git/objects/1c/30b88f5f3ee66d78df6520a7de9e89b890818b
new file mode 100644
index 000000000..57d1a0f1e
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/1c/30b88f5f3ee66d78df6520a7de9e89b890818b
@@ -0,0 +1,3 @@
+xŽM
+Â0F]çsËÌä§ ˆ¸Üz‚Iš¶Ši¤¦÷·^Áå{<>¾TKy4`6‡¶æ š,y’9j§GJì8ÁÇÞb¢‘\Œfõ–5/ Ç^‰8v¹'ö‚ÙËœì`SÆ%[›ë
+÷T[ƒ[×úŠ,púüÌsºL6o±Kµœ´5Ø;ŽèÕn÷›-ÿ= ²úÿDà \ No newline at end of file
diff --git a/tests/resources/twowaymerge.git/objects/1f/4c0311a24b63f6fc209a59a1e404942d4a5006 b/tests/resources/twowaymerge.git/objects/1f/4c0311a24b63f6fc209a59a1e404942d4a5006
new file mode 100644
index 000000000..99288fdd7
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/1f/4c0311a24b63f6fc209a59a1e404942d4a5006
@@ -0,0 +1,2 @@
+xÍ=Â0 @aæœÂ ²Mê6BlH¬œ ¿m!RqïO¹ë7¼[­‹ rÐ5g°N’Xƒ‹Å±)Eg]ÏDY2c R8xã7Û
+ØTáÞÁ­½Rõo8~òœ®Ó¢óºØêèÔ[”™àˆ#¢Ùußjþ;`¼ùÔÙ7ó \ No newline at end of file
diff --git a/tests/resources/twowaymerge.git/objects/22/24e191514cb4bd8c566d80dac22dfcb1e9bb83 b/tests/resources/twowaymerge.git/objects/22/24e191514cb4bd8c566d80dac22dfcb1e9bb83
new file mode 100644
index 000000000..48466ea51
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/22/24e191514cb4bd8c566d80dac22dfcb1e9bb83
@@ -0,0 +1,3 @@
+xŽK
+Â0@]çsK&¤ ˆ¸Üz‚™4éÛHMïo½‚Û÷àñbY–©‚1tª[JàbŽlɈµâ¥í4vÉ¡±Lâ³ ì'—Õ›·´V`B¦û .
+ëÎIöm ï1õZ¨Ç x¯cÙàK­ðhà^^ýÂ+\>?2·aªã.M,Ë°µtTB‹pÖ^kuÐc³¦¿jV_«sFh \ No newline at end of file
diff --git a/tests/resources/twowaymerge.git/objects/29/6e56023cdc034d2735fee8c0d85a659d1b07f4 b/tests/resources/twowaymerge.git/objects/29/6e56023cdc034d2735fee8c0d85a659d1b07f4
new file mode 100644
index 000000000..aa3fccdf0
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/29/6e56023cdc034d2735fee8c0d85a659d1b07f4
Binary files differ
diff --git a/tests/resources/twowaymerge.git/objects/31/51880ae2b363f1c262cf98b750c1f169a0d432 b/tests/resources/twowaymerge.git/objects/31/51880ae2b363f1c262cf98b750c1f169a0d432
new file mode 100644
index 000000000..235d42bff
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/31/51880ae2b363f1c262cf98b750c1f169a0d432
Binary files differ
diff --git a/tests/resources/twowaymerge.git/objects/3b/287f8730c81d0b763c2d294618a5e32b67b4f8 b/tests/resources/twowaymerge.git/objects/3b/287f8730c81d0b763c2d294618a5e32b67b4f8
new file mode 100644
index 000000000..56ddac5ee
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/3b/287f8730c81d0b763c2d294618a5e32b67b4f8
Binary files differ
diff --git a/tests/resources/twowaymerge.git/objects/42/b7311aa626e712891940c1ec5d5cba201946a4 b/tests/resources/twowaymerge.git/objects/42/b7311aa626e712891940c1ec5d5cba201946a4
new file mode 100644
index 000000000..a8e6581f8
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/42/b7311aa626e712891940c1ec5d5cba201946a4
@@ -0,0 +1,3 @@
+xν Â0@ajOq cßù"!D‡DËþKÂ1
+Îþ„h_ñéÅZÊ£AßÛC[s†aÌÈžp´I³±‰#‹„lB…œqÈêí×¼4ðZ"¡Ç(œyGF¢¢ød#y«ò[›ë
+÷X[ƒ[×úJÅ/púüÊsºL6o¡‹µœA²èX„ሂ¨öºo¶ü7 ’úÔ¸Ec \ No newline at end of file
diff --git a/tests/resources/twowaymerge.git/objects/49/6d6428b9cf92981dc9495211e6e1120fb6f2ba b/tests/resources/twowaymerge.git/objects/49/6d6428b9cf92981dc9495211e6e1120fb6f2ba
new file mode 100644
index 000000000..978bc3448
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/49/6d6428b9cf92981dc9495211e6e1120fb6f2ba
Binary files differ
diff --git a/tests/resources/twowaymerge.git/objects/59/b0cf7d74659e1cdb13305319d6d4ce2733c118 b/tests/resources/twowaymerge.git/objects/59/b0cf7d74659e1cdb13305319d6d4ce2733c118
new file mode 100644
index 000000000..30b507c06
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/59/b0cf7d74659e1cdb13305319d6d4ce2733c118
Binary files differ
diff --git a/tests/resources/twowaymerge.git/objects/6a/b5d28acbf3c3bdff276f7ccfdf29c1520e542f b/tests/resources/twowaymerge.git/objects/6a/b5d28acbf3c3bdff276f7ccfdf29c1520e542f
new file mode 100644
index 000000000..ff6a386ac
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/6a/b5d28acbf3c3bdff276f7ccfdf29c1520e542f
@@ -0,0 +1 @@
+xÎM‚0@a×=Å\@2ýcÜ™¸õeÚ†Rƒåþâܾŗǵ”¹RæÔ¶”@¢Šœ(i$™uOÉ 1ö9Ro"“ ¬9¸à¼x‡-­ ü@¬µcc3;ê-KvHÊ+‡9ÙèÁFe¼{›êO®­Á£ƒ{]b +\>¿òoãܦ}踖+Hm zšàŒ„(Žzl¶ô7 ñ•œF- \ No newline at end of file
diff --git a/tests/resources/twowaymerge.git/objects/6c/fca542b55b8b37017e6125a4b8f59a6eae6f11 b/tests/resources/twowaymerge.git/objects/6c/fca542b55b8b37017e6125a4b8f59a6eae6f11
new file mode 100644
index 000000000..9a969a279
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/6c/fca542b55b8b37017e6125a4b8f59a6eae6f11
Binary files differ
diff --git a/tests/resources/twowaymerge.git/objects/76/5b32c65d38f04c4f287abda055818ec0f26912 b/tests/resources/twowaymerge.git/objects/76/5b32c65d38f04c4f287abda055818ec0f26912
new file mode 100644
index 000000000..493bbc076
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/76/5b32c65d38f04c4f287abda055818ec0f26912
Binary files differ
diff --git a/tests/resources/twowaymerge.git/objects/7b/8c336c45fc6895c1c60827260fe5d798e5d247 b/tests/resources/twowaymerge.git/objects/7b/8c336c45fc6895c1c60827260fe5d798e5d247
new file mode 100644
index 000000000..19e7ef463
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/7b/8c336c45fc6895c1c60827260fe5d798e5d247
@@ -0,0 +1,3 @@
+xÎA @Qלb.`3 S(‰1îLÜz‚B[µÅTzëܾÅÏež§
+D|¨kJCß3f‰´íȵœ‚uÙ ›L>YGMÌV½eMK9¢ÑZˆƒ5ÙæHè¥õ¢#{¦ž¥E´J¶:–î±Ô
+·®åÕϲÀéó“Çp¦:n¡‰e>ƒ6-£sH GìÕ®ûfMÔS}ZE² \ No newline at end of file
diff --git a/tests/resources/twowaymerge.git/objects/82/bf9a1a10a4b25c1f14c9607b60970705e92545 b/tests/resources/twowaymerge.git/objects/82/bf9a1a10a4b25c1f14c9607b60970705e92545
new file mode 100644
index 000000000..89b0b9f9b
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/82/bf9a1a10a4b25c1f14c9607b60970705e92545
@@ -0,0 +1 @@
+xŽË Â09»Šm€hã_ÖBܸRן„cœþ1-p§yšPKy4RÚ–D›GŒF»ÀJvÉFE>‡1#q²Ž j§ÅÛoimbvSŽŠYSbEr²Š¸»Q"eÓÑ{+üÞ–ºÁ=ÔÖà6Àµ¾bñ+œ>?òœ/ó£-;¡–3ŒÊhœ¬C‚#¢è´g¶ô÷XÄÌyF¤ \ No newline at end of file
diff --git a/tests/resources/twowaymerge.git/objects/8b/82fb1794cb1c8c7f172ec730a4c2db0ae3e650 b/tests/resources/twowaymerge.git/objects/8b/82fb1794cb1c8c7f172ec730a4c2db0ae3e650
new file mode 100644
index 000000000..8e9b758ea
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/8b/82fb1794cb1c8c7f172ec730a4c2db0ae3e650
@@ -0,0 +1,3 @@
+xν Â0@ajOq ù7BtH´Lp¾ó%A8FÁÙŸ°íW<=ª¥Ì ¬õ‡¶æ x"ÊŽØ$—%1†dÄcÏDNLˆ:Yv=©7®yiÐc
+l¤$Ž\b{‰DÂbOd‚Õ9x+
+·6ÕT[ƒ{·úâ‚ œ??yŽ×qnÓ–:ªåƯcÔÞÁQZ«]÷Í–ÿ¨¢¾7 H† \ No newline at end of file
diff --git a/tests/resources/twowaymerge.git/objects/9a/40a2f11c191f180c47e54b11567cb3c1e89b30 b/tests/resources/twowaymerge.git/objects/9a/40a2f11c191f180c47e54b11567cb3c1e89b30
new file mode 100644
index 000000000..1de1224f7
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/9a/40a2f11c191f180c47e54b11567cb3c1e89b30
Binary files differ
diff --git a/tests/resources/twowaymerge.git/objects/9b/219343610c88a1187c996d0dc58330b55cee28 b/tests/resources/twowaymerge.git/objects/9b/219343610c88a1187c996d0dc58330b55cee28
new file mode 100644
index 000000000..8b64b4381
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/9b/219343610c88a1187c996d0dc58330b55cee28
@@ -0,0 +1,2 @@
+xÏKj1Ьç½óÊFj}Z‚¼3²Ê ¤VÏÇdFA#ß?’\ ËzÅu]—FÓSo" ‰JðÆ& ‚^˜‚Ž,9$G’Eéd)7|¦&[6”(FU"&Žh< ¯FÉc4AÆ¿>"ZÑQ;m9Û\;ïKP%1b9k‰93¤GŸkƒw®½Ãënõ£¬iƒçý[îÓuZúüÈ®ë hã¬"RÞÂY¥†C[]þ=0¼I›rKÏp—¶÷óO:Á²õ
+»pÝʯ _¾(c‡ \ No newline at end of file
diff --git a/tests/resources/twowaymerge.git/objects/9f/e06a50f4d1634d6c6879854d01d80857388706 b/tests/resources/twowaymerge.git/objects/9f/e06a50f4d1634d6c6879854d01d80857388706
new file mode 100644
index 000000000..055de0158
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/9f/e06a50f4d1634d6c6879854d01d80857388706
Binary files differ
diff --git a/tests/resources/twowaymerge.git/objects/a4/1a49f8f5cd9b6cb14a076bf8394881ed0b4d19 b/tests/resources/twowaymerge.git/objects/a4/1a49f8f5cd9b6cb14a076bf8394881ed0b4d19
new file mode 100644
index 000000000..cb4d34e77
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/a4/1a49f8f5cd9b6cb14a076bf8394881ed0b4d19
@@ -0,0 +1,3 @@
+xν Â0@ajOq Ýù7'!D‡DËöÙ A$FÁÙŸ°íW<=©ó<5ÐÚÚZ
+8N(CÈÁzÇ…$'2!Î>[):#D½zǵ, zŽ £MÚ d…=†ä‘t…µ³NÅ­=ê
+w©­Á­ƒk}å9.púüä9^Æ©=¶ÔIÏ@ÆY ž‰áˆ=¢ÚußlåÔ°Dâ \ No newline at end of file
diff --git a/tests/resources/twowaymerge.git/objects/a9/53a018c5b10b20c86e69fef55ebc8ad4c5a417 b/tests/resources/twowaymerge.git/objects/a9/53a018c5b10b20c86e69fef55ebc8ad4c5a417
new file mode 100644
index 000000000..8235f1839
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/a9/53a018c5b10b20c86e69fef55ebc8ad4c5a417
@@ -0,0 +1 @@
+xÍJÄ0…]÷)înV3$¹is"îÁ•Oû“NŶ’ɼ¿ñ\žï|8²¯ëÒ!dzèÍ ªÔdXG/ޫϹp*‰¢C³X³ˆº@ZÂ8|•f[VŸ0HD™H“E]6¯”g¶I#g«*ñÏ­9UEæHÆH!MḦÕñh‚ºR¦¡Üûuoð.{ïðz—ýSײÁãí‡|ÌÏóÒ¯w¾È¾>Ç1º4‘C8;rn8èq«Û¿†7k³·²ÉNui·~þM§áÜ^­ \ No newline at end of file
diff --git a/tests/resources/twowaymerge.git/objects/a9/cce3cd1b3efbda5b1f4a6dcc3f1570b2d3d74c b/tests/resources/twowaymerge.git/objects/a9/cce3cd1b3efbda5b1f4a6dcc3f1570b2d3d74c
new file mode 100644
index 000000000..4da7e826a
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/a9/cce3cd1b3efbda5b1f4a6dcc3f1570b2d3d74c
@@ -0,0 +1 @@
+x+)JMU044c040031QHdx6÷ÑìM¯9{wk®+ºqèIOðD¨d6>É|’¹X%>½9j \ No newline at end of file
diff --git a/tests/resources/twowaymerge.git/objects/bd/1732c43c68d712ad09e1d872b9be6d4b9efdc4 b/tests/resources/twowaymerge.git/objects/bd/1732c43c68d712ad09e1d872b9be6d4b9efdc4
new file mode 100644
index 000000000..b9b60122d
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/bd/1732c43c68d712ad09e1d872b9be6d4b9efdc4
Binary files differ
diff --git a/tests/resources/twowaymerge.git/objects/c3/7a783c20d92ac92362a78a32860f7eebf938ef b/tests/resources/twowaymerge.git/objects/c3/7a783c20d92ac92362a78a32860f7eebf938ef
new file mode 100644
index 000000000..041e890ab
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/c3/7a783c20d92ac92362a78a32860f7eebf938ef
Binary files differ
diff --git a/tests/resources/twowaymerge.git/objects/cb/dd40facab1682754eb67f7a43f29e672903cf6 b/tests/resources/twowaymerge.git/objects/cb/dd40facab1682754eb67f7a43f29e672903cf6
new file mode 100644
index 000000000..ccb156d88
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/cb/dd40facab1682754eb67f7a43f29e672903cf6
Binary files differ
diff --git a/tests/resources/twowaymerge.git/objects/cd/f97fd3bb48eb3827638bb33d208f5fd32d0aa6 b/tests/resources/twowaymerge.git/objects/cd/f97fd3bb48eb3827638bb33d208f5fd32d0aa6
new file mode 100644
index 000000000..0e028dc01
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/cd/f97fd3bb48eb3827638bb33d208f5fd32d0aa6
Binary files differ
diff --git a/tests/resources/twowaymerge.git/objects/d6/f10d549cb335b9e6d38afc1f0088be69b50494 b/tests/resources/twowaymerge.git/objects/d6/f10d549cb335b9e6d38afc1f0088be69b50494
new file mode 100644
index 000000000..b298c520e
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/d6/f10d549cb335b9e6d38afc1f0088be69b50494
Binary files differ
diff --git a/tests/resources/twowaymerge.git/objects/d9/acdc7ae7632adfeec67fa73c1e343cf4d1f47e b/tests/resources/twowaymerge.git/objects/d9/acdc7ae7632adfeec67fa73c1e343cf4d1f47e
new file mode 100644
index 000000000..de94528a4
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/d9/acdc7ae7632adfeec67fa73c1e343cf4d1f47e
@@ -0,0 +1 @@
+x+)JMU044c040031QHdx6÷ÑìM¯9{wk®+ºqèIOðD¨d>É4|’éX%:79U \ No newline at end of file
diff --git a/tests/resources/twowaymerge.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/tests/resources/twowaymerge.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
new file mode 100644
index 000000000..711223894
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
Binary files differ
diff --git a/tests/resources/twowaymerge.git/objects/ef/0488f0b722f0be8bcb90a7730ac7efafd1d694 b/tests/resources/twowaymerge.git/objects/ef/0488f0b722f0be8bcb90a7730ac7efafd1d694
new file mode 100644
index 000000000..00f7d3615
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/ef/0488f0b722f0be8bcb90a7730ac7efafd1d694
@@ -0,0 +1 @@
+xŽM F]sŠ¹€Í0Ã_cÜ™¸õ@¡ÕH1•Þ_¼‚Û—ï}y±–òh@¤mK 8³ÙYÆèä„ÁŽ4ѨŒt^'¦`lPÙ‰·ßÒÚ Ï<g™yÒY‘ŒÙ1*m´œ­»„™Fá÷¶Ô ·®õ5¿Âéó#Ïù2?Ú²‡!ÖrÉZ¡5ÆŽpD‡(:í™-ý} ²ø#E¸ \ No newline at end of file
diff --git a/tests/resources/twowaymerge.git/objects/fc/f7e3f51c11d199ab7a78403ee4f9ccd028da25 b/tests/resources/twowaymerge.git/objects/fc/f7e3f51c11d199ab7a78403ee4f9ccd028da25
new file mode 100644
index 000000000..54989ea87
--- /dev/null
+++ b/tests/resources/twowaymerge.git/objects/fc/f7e3f51c11d199ab7a78403ee4f9ccd028da25
Binary files differ
diff --git a/tests/resources/twowaymerge.git/refs/heads/first-branch b/tests/resources/twowaymerge.git/refs/heads/first-branch
new file mode 100644
index 000000000..ef0dead7f
--- /dev/null
+++ b/tests/resources/twowaymerge.git/refs/heads/first-branch
@@ -0,0 +1 @@
+2224e191514cb4bd8c566d80dac22dfcb1e9bb83
diff --git a/tests/resources/twowaymerge.git/refs/heads/master b/tests/resources/twowaymerge.git/refs/heads/master
new file mode 100644
index 000000000..ebf18f58e
--- /dev/null
+++ b/tests/resources/twowaymerge.git/refs/heads/master
@@ -0,0 +1 @@
+1c30b88f5f3ee66d78df6520a7de9e89b890818b
diff --git a/tests/resources/twowaymerge.git/refs/heads/second-branch b/tests/resources/twowaymerge.git/refs/heads/second-branch
new file mode 100644
index 000000000..586a14a84
--- /dev/null
+++ b/tests/resources/twowaymerge.git/refs/heads/second-branch
@@ -0,0 +1 @@
+9b219343610c88a1187c996d0dc58330b55cee28
diff --git a/tests/resources/typechanges/.gitted/HEAD b/tests/resources/typechanges/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/typechanges/.gitted/config b/tests/resources/typechanges/.gitted/config
new file mode 100644
index 000000000..4cc6e1ddf
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/config
@@ -0,0 +1,12 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
+[submodule "e"]
+ url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target/.git
+[submodule "d"]
+ url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target/.git
+[submodule "b"]
+ url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target/.git
diff --git a/tests/resources/typechanges/.gitted/description b/tests/resources/typechanges/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/typechanges/.gitted/index b/tests/resources/typechanges/.gitted/index
new file mode 100644
index 000000000..4f6d12a3b
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/index
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/info/exclude b/tests/resources/typechanges/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/typechanges/.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/resources/typechanges/.gitted/modules/b/HEAD b/tests/resources/typechanges/.gitted/modules/b/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/b/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/typechanges/.gitted/modules/b/config b/tests/resources/typechanges/.gitted/modules/b/config
new file mode 100644
index 000000000..f57cd4a6f
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/b/config
@@ -0,0 +1,13 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ worktree = ../../../b
+ ignorecase = true
+[remote "origin"]
+ fetch = +refs/heads/*:refs/remotes/origin/*
+ url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target/.git
+[branch "master"]
+ remote = origin
+ merge = refs/heads/master
diff --git a/tests/resources/typechanges/.gitted/modules/b/description b/tests/resources/typechanges/.gitted/modules/b/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/b/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/typechanges/.gitted/modules/b/index b/tests/resources/typechanges/.gitted/modules/b/index
new file mode 100644
index 000000000..c16a026b7
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/b/index
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/b/info/exclude b/tests/resources/typechanges/.gitted/modules/b/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/b/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/resources/typechanges/.gitted/modules/b/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/typechanges/.gitted/modules/b/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1
new file mode 100644
index 000000000..f4b7094c5
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/b/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/b/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/typechanges/.gitted/modules/b/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484
new file mode 100644
index 000000000..56c845e49
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/b/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/b/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/typechanges/.gitted/modules/b/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5
new file mode 100644
index 000000000..bd179b5f5
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/b/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/b/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/typechanges/.gitted/modules/b/objects/41/bd4bc3df978de695f67ace64c560913da11653
new file mode 100644
index 000000000..ccf49bd15
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/b/objects/41/bd4bc3df978de695f67ace64c560913da11653
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/b/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/typechanges/.gitted/modules/b/objects/48/0095882d281ed676fe5b863569520e54a7d5c0
new file mode 100644
index 000000000..53029069a
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/b/objects/48/0095882d281ed676fe5b863569520e54a7d5c0
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/b/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/typechanges/.gitted/modules/b/objects/5e/4963595a9774b90524d35a807169049de8ccad
new file mode 100644
index 000000000..38c791eba
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/b/objects/5e/4963595a9774b90524d35a807169049de8ccad
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/b/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/typechanges/.gitted/modules/b/objects/6b/31c659545507c381e9cd34ec508f16c04e149e
new file mode 100644
index 000000000..a26d29993
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/b/objects/6b/31c659545507c381e9cd34ec508f16c04e149e
@@ -0,0 +1,2 @@
+x•Q
+!EûvoÅÓy*Ñ_¿í@Çg#h‚£ûOhý^Î9w«¥¤ÒêSoÌ€f1*²ŠÁ[”‰¬§èIc Ô¤ìê¤p£ïµÁkç Α\›¿¿S߇¿lµÜ@.¤´^QpF‹(æ:ÿúDÿ5Åó“zr~ ñen8 \ No newline at end of file
diff --git a/tests/resources/typechanges/.gitted/modules/b/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/typechanges/.gitted/modules/b/objects/73/ba924a80437097795ae839e66e187c55d3babf
new file mode 100644
index 000000000..83d1ba481
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/b/objects/73/ba924a80437097795ae839e66e187c55d3babf
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/b/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/typechanges/.gitted/modules/b/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a
new file mode 100644
index 000000000..6d27af8a8
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/b/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a
@@ -0,0 +1,2 @@
+x-Ë1Â0 FaæžâßØ0pŽÀìÄÐ(N-ÅöÐÛÓ¡Ò“¾é±ãq]>ksÅ*š? |m“‡Õçiª@ÛÖý¶¼m»¨V£…£'©î`)”.Ø-1¨ x
+u„xãòt(+ \ No newline at end of file
diff --git a/tests/resources/typechanges/.gitted/modules/b/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/typechanges/.gitted/modules/b/objects/78/9efbdadaa4a582778d4584385495559ea0994b
new file mode 100644
index 000000000..17458840b
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/b/objects/78/9efbdadaa4a582778d4584385495559ea0994b
@@ -0,0 +1,2 @@
+x Œ± …0 )ÞŠ?= ¥ÉÄNŠlO¤k®¸‹jÛúÿ¹8&„«¨ ãr ”
+ïqJWñ°7¾B<ÉáöfÙìK8­#Q1C-‘"eª·Ì«£Š°ð>¼'@ \ No newline at end of file
diff --git a/tests/resources/typechanges/.gitted/modules/b/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/typechanges/.gitted/modules/b/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e
new file mode 100644
index 000000000..83cc29fb1
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/b/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/b/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/typechanges/.gitted/modules/b/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5
new file mode 100644
index 000000000..55bda40ef
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/b/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/b/packed-refs b/tests/resources/typechanges/.gitted/modules/b/packed-refs
new file mode 100644
index 000000000..5a4ebc47c
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/b/packed-refs
@@ -0,0 +1,2 @@
+# pack-refs with: peeled
+480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master
diff --git a/tests/resources/typechanges/.gitted/modules/b/refs/heads/master b/tests/resources/typechanges/.gitted/modules/b/refs/heads/master
new file mode 100644
index 000000000..e12c44d7a
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/b/refs/heads/master
@@ -0,0 +1 @@
+480095882d281ed676fe5b863569520e54a7d5c0
diff --git a/tests/resources/typechanges/.gitted/modules/b/refs/remotes/origin/HEAD b/tests/resources/typechanges/.gitted/modules/b/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..6efe28fff
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/b/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+ref: refs/remotes/origin/master
diff --git a/tests/resources/typechanges/.gitted/modules/d/HEAD b/tests/resources/typechanges/.gitted/modules/d/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/d/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/typechanges/.gitted/modules/d/config b/tests/resources/typechanges/.gitted/modules/d/config
new file mode 100644
index 000000000..42e1bddda
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/d/config
@@ -0,0 +1,13 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ worktree = ../../../d
+ ignorecase = true
+[remote "origin"]
+ fetch = +refs/heads/*:refs/remotes/origin/*
+ url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target/.git
+[branch "master"]
+ remote = origin
+ merge = refs/heads/master
diff --git a/tests/resources/typechanges/.gitted/modules/d/description b/tests/resources/typechanges/.gitted/modules/d/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/d/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/typechanges/.gitted/modules/d/index b/tests/resources/typechanges/.gitted/modules/d/index
new file mode 100644
index 000000000..86d0266e8
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/d/index
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/d/info/exclude b/tests/resources/typechanges/.gitted/modules/d/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/d/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/resources/typechanges/.gitted/modules/d/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/typechanges/.gitted/modules/d/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1
new file mode 100644
index 000000000..f4b7094c5
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/d/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/d/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/typechanges/.gitted/modules/d/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484
new file mode 100644
index 000000000..56c845e49
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/d/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/d/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/typechanges/.gitted/modules/d/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5
new file mode 100644
index 000000000..bd179b5f5
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/d/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/d/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/typechanges/.gitted/modules/d/objects/41/bd4bc3df978de695f67ace64c560913da11653
new file mode 100644
index 000000000..ccf49bd15
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/d/objects/41/bd4bc3df978de695f67ace64c560913da11653
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/d/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/typechanges/.gitted/modules/d/objects/48/0095882d281ed676fe5b863569520e54a7d5c0
new file mode 100644
index 000000000..53029069a
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/d/objects/48/0095882d281ed676fe5b863569520e54a7d5c0
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/d/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/typechanges/.gitted/modules/d/objects/5e/4963595a9774b90524d35a807169049de8ccad
new file mode 100644
index 000000000..38c791eba
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/d/objects/5e/4963595a9774b90524d35a807169049de8ccad
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/d/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/typechanges/.gitted/modules/d/objects/6b/31c659545507c381e9cd34ec508f16c04e149e
new file mode 100644
index 000000000..a26d29993
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/d/objects/6b/31c659545507c381e9cd34ec508f16c04e149e
@@ -0,0 +1,2 @@
+x•Q
+!EûvoÅÓy*Ñ_¿í@Çg#h‚£ûOhý^Î9w«¥¤ÒêSoÌ€f1*²ŠÁ[”‰¬§èIc Ô¤ìê¤p£ïµÁkç Α\›¿¿S߇¿lµÜ@.¤´^QpF‹(æ:ÿúDÿ5Åó“zr~ ñen8 \ No newline at end of file
diff --git a/tests/resources/typechanges/.gitted/modules/d/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/typechanges/.gitted/modules/d/objects/73/ba924a80437097795ae839e66e187c55d3babf
new file mode 100644
index 000000000..83d1ba481
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/d/objects/73/ba924a80437097795ae839e66e187c55d3babf
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/d/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/typechanges/.gitted/modules/d/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a
new file mode 100644
index 000000000..6d27af8a8
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/d/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a
@@ -0,0 +1,2 @@
+x-Ë1Â0 FaæžâßØ0pŽÀìÄÐ(N-ÅöÐÛÓ¡Ò“¾é±ãq]>ksÅ*š? |m“‡Õçiª@ÛÖý¶¼m»¨V£…£'©î`)”.Ø-1¨ x
+u„xãòt(+ \ No newline at end of file
diff --git a/tests/resources/typechanges/.gitted/modules/d/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/typechanges/.gitted/modules/d/objects/78/9efbdadaa4a582778d4584385495559ea0994b
new file mode 100644
index 000000000..17458840b
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/d/objects/78/9efbdadaa4a582778d4584385495559ea0994b
@@ -0,0 +1,2 @@
+x Œ± …0 )ÞŠ?= ¥ÉÄNŠlO¤k®¸‹jÛúÿ¹8&„«¨ ãr ”
+ïqJWñ°7¾B<ÉáöfÙìK8­#Q1C-‘"eª·Ì«£Š°ð>¼'@ \ No newline at end of file
diff --git a/tests/resources/typechanges/.gitted/modules/d/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/typechanges/.gitted/modules/d/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e
new file mode 100644
index 000000000..83cc29fb1
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/d/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/d/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/typechanges/.gitted/modules/d/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5
new file mode 100644
index 000000000..55bda40ef
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/d/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/d/packed-refs b/tests/resources/typechanges/.gitted/modules/d/packed-refs
new file mode 100644
index 000000000..5a4ebc47c
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/d/packed-refs
@@ -0,0 +1,2 @@
+# pack-refs with: peeled
+480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master
diff --git a/tests/resources/typechanges/.gitted/modules/d/refs/heads/master b/tests/resources/typechanges/.gitted/modules/d/refs/heads/master
new file mode 100644
index 000000000..e12c44d7a
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/d/refs/heads/master
@@ -0,0 +1 @@
+480095882d281ed676fe5b863569520e54a7d5c0
diff --git a/tests/resources/typechanges/.gitted/modules/d/refs/remotes/origin/HEAD b/tests/resources/typechanges/.gitted/modules/d/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..6efe28fff
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/d/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+ref: refs/remotes/origin/master
diff --git a/tests/resources/typechanges/.gitted/modules/e/HEAD b/tests/resources/typechanges/.gitted/modules/e/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/e/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/typechanges/.gitted/modules/e/config b/tests/resources/typechanges/.gitted/modules/e/config
new file mode 100644
index 000000000..89b3b9b4f
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/e/config
@@ -0,0 +1,13 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ worktree = ../../../e
+ ignorecase = true
+[remote "origin"]
+ fetch = +refs/heads/*:refs/remotes/origin/*
+ url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target/.git
+[branch "master"]
+ remote = origin
+ merge = refs/heads/master
diff --git a/tests/resources/typechanges/.gitted/modules/e/description b/tests/resources/typechanges/.gitted/modules/e/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/e/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/typechanges/.gitted/modules/e/index b/tests/resources/typechanges/.gitted/modules/e/index
new file mode 100644
index 000000000..cd6e2da6c
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/e/index
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/e/info/exclude b/tests/resources/typechanges/.gitted/modules/e/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/e/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/resources/typechanges/.gitted/modules/e/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/typechanges/.gitted/modules/e/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1
new file mode 100644
index 000000000..f4b7094c5
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/e/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/e/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/typechanges/.gitted/modules/e/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484
new file mode 100644
index 000000000..56c845e49
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/e/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/e/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/typechanges/.gitted/modules/e/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5
new file mode 100644
index 000000000..bd179b5f5
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/e/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/e/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/typechanges/.gitted/modules/e/objects/41/bd4bc3df978de695f67ace64c560913da11653
new file mode 100644
index 000000000..ccf49bd15
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/e/objects/41/bd4bc3df978de695f67ace64c560913da11653
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/e/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/typechanges/.gitted/modules/e/objects/48/0095882d281ed676fe5b863569520e54a7d5c0
new file mode 100644
index 000000000..53029069a
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/e/objects/48/0095882d281ed676fe5b863569520e54a7d5c0
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/e/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/typechanges/.gitted/modules/e/objects/5e/4963595a9774b90524d35a807169049de8ccad
new file mode 100644
index 000000000..38c791eba
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/e/objects/5e/4963595a9774b90524d35a807169049de8ccad
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/e/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/typechanges/.gitted/modules/e/objects/6b/31c659545507c381e9cd34ec508f16c04e149e
new file mode 100644
index 000000000..a26d29993
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/e/objects/6b/31c659545507c381e9cd34ec508f16c04e149e
@@ -0,0 +1,2 @@
+x•Q
+!EûvoÅÓy*Ñ_¿í@Çg#h‚£ûOhý^Î9w«¥¤ÒêSoÌ€f1*²ŠÁ[”‰¬§èIc Ô¤ìê¤p£ïµÁkç Α\›¿¿S߇¿lµÜ@.¤´^QpF‹(æ:ÿúDÿ5Åó“zr~ ñen8 \ No newline at end of file
diff --git a/tests/resources/typechanges/.gitted/modules/e/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/typechanges/.gitted/modules/e/objects/73/ba924a80437097795ae839e66e187c55d3babf
new file mode 100644
index 000000000..83d1ba481
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/e/objects/73/ba924a80437097795ae839e66e187c55d3babf
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/e/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/typechanges/.gitted/modules/e/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a
new file mode 100644
index 000000000..6d27af8a8
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/e/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a
@@ -0,0 +1,2 @@
+x-Ë1Â0 FaæžâßØ0pŽÀìÄÐ(N-ÅöÐÛÓ¡Ò“¾é±ãq]>ksÅ*š? |m“‡Õçiª@ÛÖý¶¼m»¨V£…£'©î`)”.Ø-1¨ x
+u„xãòt(+ \ No newline at end of file
diff --git a/tests/resources/typechanges/.gitted/modules/e/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/typechanges/.gitted/modules/e/objects/78/9efbdadaa4a582778d4584385495559ea0994b
new file mode 100644
index 000000000..17458840b
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/e/objects/78/9efbdadaa4a582778d4584385495559ea0994b
@@ -0,0 +1,2 @@
+x Œ± …0 )ÞŠ?= ¥ÉÄNŠlO¤k®¸‹jÛúÿ¹8&„«¨ ãr ”
+ïqJWñ°7¾B<ÉáöfÙìK8­#Q1C-‘"eª·Ì«£Š°ð>¼'@ \ No newline at end of file
diff --git a/tests/resources/typechanges/.gitted/modules/e/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/typechanges/.gitted/modules/e/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e
new file mode 100644
index 000000000..83cc29fb1
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/e/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/e/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/typechanges/.gitted/modules/e/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5
new file mode 100644
index 000000000..55bda40ef
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/e/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/modules/e/packed-refs b/tests/resources/typechanges/.gitted/modules/e/packed-refs
new file mode 100644
index 000000000..5a4ebc47c
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/e/packed-refs
@@ -0,0 +1,2 @@
+# pack-refs with: peeled
+480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master
diff --git a/tests/resources/typechanges/.gitted/modules/e/refs/heads/master b/tests/resources/typechanges/.gitted/modules/e/refs/heads/master
new file mode 100644
index 000000000..e12c44d7a
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/e/refs/heads/master
@@ -0,0 +1 @@
+480095882d281ed676fe5b863569520e54a7d5c0
diff --git a/tests/resources/typechanges/.gitted/modules/e/refs/remotes/origin/HEAD b/tests/resources/typechanges/.gitted/modules/e/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..6efe28fff
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/modules/e/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+ref: refs/remotes/origin/master
diff --git a/tests/resources/typechanges/.gitted/objects/0d/78578795b7ca49fd8df6c4b6d27c5c02d991d8 b/tests/resources/typechanges/.gitted/objects/0d/78578795b7ca49fd8df6c4b6d27c5c02d991d8
new file mode 100644
index 000000000..f2d02f4f7
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/0d/78578795b7ca49fd8df6c4b6d27c5c02d991d8
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/0e/7ed140b514b8cae23254cb8656fe1674403aff b/tests/resources/typechanges/.gitted/objects/0e/7ed140b514b8cae23254cb8656fe1674403aff
new file mode 100644
index 000000000..527964c92
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/0e/7ed140b514b8cae23254cb8656fe1674403aff
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/0f/f461da9689266f482d8f6654a4400b4e33c586 b/tests/resources/typechanges/.gitted/objects/0f/f461da9689266f482d8f6654a4400b4e33c586
new file mode 100644
index 000000000..2694e4fa0
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/0f/f461da9689266f482d8f6654a4400b4e33c586
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/18/aa7e45bbe4c3cc24a0b079696c59d36675af97 b/tests/resources/typechanges/.gitted/objects/18/aa7e45bbe4c3cc24a0b079696c59d36675af97
new file mode 100644
index 000000000..032a960b4
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/18/aa7e45bbe4c3cc24a0b079696c59d36675af97
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/1b/63caae4a5ca96f78e8dfefc376c6a39a142475 b/tests/resources/typechanges/.gitted/objects/1b/63caae4a5ca96f78e8dfefc376c6a39a142475
new file mode 100644
index 000000000..d32622e67
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/1b/63caae4a5ca96f78e8dfefc376c6a39a142475
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/1e/abe82aa3b2365a394f6108f24435df6e193d02 b/tests/resources/typechanges/.gitted/objects/1e/abe82aa3b2365a394f6108f24435df6e193d02
new file mode 100644
index 000000000..42d5f92f3
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/1e/abe82aa3b2365a394f6108f24435df6e193d02
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/42/061c01a1c70097d1e4579f29a5adf40abdec95 b/tests/resources/typechanges/.gitted/objects/42/061c01a1c70097d1e4579f29a5adf40abdec95
new file mode 100644
index 000000000..0a8f32e15
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/42/061c01a1c70097d1e4579f29a5adf40abdec95
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/46/2838cee476a87e7cff32196b66fa18ed756592 b/tests/resources/typechanges/.gitted/objects/46/2838cee476a87e7cff32196b66fa18ed756592
new file mode 100644
index 000000000..52af51f74
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/46/2838cee476a87e7cff32196b66fa18ed756592
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/63/499e4ea8e096b831515ceb1d5a7593e4d87ae5 b/tests/resources/typechanges/.gitted/objects/63/499e4ea8e096b831515ceb1d5a7593e4d87ae5
new file mode 100644
index 000000000..afafa89f4
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/63/499e4ea8e096b831515ceb1d5a7593e4d87ae5
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/68/1af94e10eaf262f3ab7cb9b8fd5f4158ba4d3e b/tests/resources/typechanges/.gitted/objects/68/1af94e10eaf262f3ab7cb9b8fd5f4158ba4d3e
new file mode 100644
index 000000000..9e518fc28
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/68/1af94e10eaf262f3ab7cb9b8fd5f4158ba4d3e
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/6a/9008602b811e69a9b7a2d83496f39a794fdeeb b/tests/resources/typechanges/.gitted/objects/6a/9008602b811e69a9b7a2d83496f39a794fdeeb
new file mode 100644
index 000000000..a245727a1
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/6a/9008602b811e69a9b7a2d83496f39a794fdeeb
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/6e/ae26c90e8ccc4d16208972119c40635489c6f0 b/tests/resources/typechanges/.gitted/objects/6e/ae26c90e8ccc4d16208972119c40635489c6f0
new file mode 100644
index 000000000..ea35cd311
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/6e/ae26c90e8ccc4d16208972119c40635489c6f0
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/6f/39eabbb8a7541515e0d35971078bccb502e7e0 b/tests/resources/typechanges/.gitted/objects/6f/39eabbb8a7541515e0d35971078bccb502e7e0
new file mode 100644
index 000000000..c54817598
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/6f/39eabbb8a7541515e0d35971078bccb502e7e0
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/71/54d3083461536dfc71ad5542f3e65e723a06c4 b/tests/resources/typechanges/.gitted/objects/71/54d3083461536dfc71ad5542f3e65e723a06c4
new file mode 100644
index 000000000..9fdd8f245
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/71/54d3083461536dfc71ad5542f3e65e723a06c4
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/75/56c1d893a4c0ca85ac8ac51de47ff399758729 b/tests/resources/typechanges/.gitted/objects/75/56c1d893a4c0ca85ac8ac51de47ff399758729
new file mode 100644
index 000000000..d43630f44
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/75/56c1d893a4c0ca85ac8ac51de47ff399758729
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/76/fef844064c26d5e06c2508240dae661e7231b2 b/tests/resources/typechanges/.gitted/objects/76/fef844064c26d5e06c2508240dae661e7231b2
new file mode 100644
index 000000000..355ce4b5b
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/76/fef844064c26d5e06c2508240dae661e7231b2
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/79/b9f23e85f55ea36a472a902e875bc1121a94cb b/tests/resources/typechanges/.gitted/objects/79/b9f23e85f55ea36a472a902e875bc1121a94cb
new file mode 100644
index 000000000..2b07ad256
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/79/b9f23e85f55ea36a472a902e875bc1121a94cb
@@ -0,0 +1,2 @@
+x•A E]sŠ¹€f€JbŒqçÖ í`I@
+÷—ĸýyïýµäH;ŸZeBrž6L˜P«Yº%8½²&v‹4JmÖ¢ÔÛ^*¼úqpJðà¸Âµúû;¶½ûËZò ¤žœ’Æ 3ZD1Öñ×ú¯)žŸØ"%ø%Ä–38_ \ No newline at end of file
diff --git a/tests/resources/typechanges/.gitted/objects/85/28da0ea65eacf1f74f9ed6696adbac547963ad b/tests/resources/typechanges/.gitted/objects/85/28da0ea65eacf1f74f9ed6696adbac547963ad
new file mode 100644
index 000000000..6d2da6c93
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/85/28da0ea65eacf1f74f9ed6696adbac547963ad
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/8b/3726b365824ad5a07c537247f4bc73ed7d37ea b/tests/resources/typechanges/.gitted/objects/8b/3726b365824ad5a07c537247f4bc73ed7d37ea
new file mode 100644
index 000000000..3dc333bc6
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/8b/3726b365824ad5a07c537247f4bc73ed7d37ea
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/93/3e28c1c8a68838a763d250bdf0b2c6068289c3 b/tests/resources/typechanges/.gitted/objects/93/3e28c1c8a68838a763d250bdf0b2c6068289c3
new file mode 100644
index 000000000..02ad0e97a
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/93/3e28c1c8a68838a763d250bdf0b2c6068289c3
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/96/2710fe5b4e453e9e827945b3487c525968ec4a b/tests/resources/typechanges/.gitted/objects/96/2710fe5b4e453e9e827945b3487c525968ec4a
new file mode 100644
index 000000000..d06b06a52
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/96/2710fe5b4e453e9e827945b3487c525968ec4a
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/96/6cf1b3598e195b31b2cde3784f9a19f0728a6f b/tests/resources/typechanges/.gitted/objects/96/6cf1b3598e195b31b2cde3784f9a19f0728a6f
new file mode 100644
index 000000000..5f9ffd4ed
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/96/6cf1b3598e195b31b2cde3784f9a19f0728a6f
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/99/e8bab9ece009f0fba7eb41f850f4c12bedb9b7 b/tests/resources/typechanges/.gitted/objects/99/e8bab9ece009f0fba7eb41f850f4c12bedb9b7
new file mode 100644
index 000000000..ac17defac
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/99/e8bab9ece009f0fba7eb41f850f4c12bedb9b7
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/9b/19edf33a03a0c59cdfc113bfa5c06179bf9b1a b/tests/resources/typechanges/.gitted/objects/9b/19edf33a03a0c59cdfc113bfa5c06179bf9b1a
new file mode 100644
index 000000000..7ab83aefe
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/9b/19edf33a03a0c59cdfc113bfa5c06179bf9b1a
@@ -0,0 +1,5 @@
+x•ŽI
+1E]箕LTˆè ¼A†J·Ðƒ¤Ó÷7WpûyïñÓ¶,ŸZÑ©Uf cXcR ƒC4¼3Y2æ"£NN:ÔHɈo¨¼6 ,µ’žs’ˆòÁjf—#îk½G›¶
+ïcßyžáÉsá
+·ã§MG¼¦m¹ƒ2–´Bò
+.ÒK)úÚÿµŽþkŠ×Ö‘w8ñžCCà \ No newline at end of file
diff --git a/tests/resources/typechanges/.gitted/objects/9b/db75b73836a99e3dbeea640a81de81031fdc29 b/tests/resources/typechanges/.gitted/objects/9b/db75b73836a99e3dbeea640a81de81031fdc29
new file mode 100644
index 000000000..aed4d8165
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/9b/db75b73836a99e3dbeea640a81de81031fdc29
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/9d/0235c7a7edc0889a18f97a42ee6db9fe688447 b/tests/resources/typechanges/.gitted/objects/9d/0235c7a7edc0889a18f97a42ee6db9fe688447
new file mode 100644
index 000000000..3e02a41b2
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/9d/0235c7a7edc0889a18f97a42ee6db9fe688447
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/9e/ffc457877f109b2a4319e14bee613a15f2a00d b/tests/resources/typechanges/.gitted/objects/9e/ffc457877f109b2a4319e14bee613a15f2a00d
new file mode 100644
index 000000000..fb24100fc
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/9e/ffc457877f109b2a4319e14bee613a15f2a00d
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/a0/a9bad6f6f40325198f938a0e3ae981622d7707 b/tests/resources/typechanges/.gitted/objects/a0/a9bad6f6f40325198f938a0e3ae981622d7707
new file mode 100644
index 000000000..b6b7db785
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/a0/a9bad6f6f40325198f938a0e3ae981622d7707
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/b1/977dc4e573b812d4619754c98138c56999dc0d b/tests/resources/typechanges/.gitted/objects/b1/977dc4e573b812d4619754c98138c56999dc0d
new file mode 100644
index 000000000..e1334057c
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/b1/977dc4e573b812d4619754c98138c56999dc0d
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/d7/5992dd02391e128dac332dcc78d649dd9ab095 b/tests/resources/typechanges/.gitted/objects/d7/5992dd02391e128dac332dcc78d649dd9ab095
new file mode 100644
index 000000000..65f1f530f
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/d7/5992dd02391e128dac332dcc78d649dd9ab095
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/da/e2709d638df52212b1f43ff61797ebfedfcc7c b/tests/resources/typechanges/.gitted/objects/da/e2709d638df52212b1f43ff61797ebfedfcc7c
new file mode 100644
index 000000000..355faa61f
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/da/e2709d638df52212b1f43ff61797ebfedfcc7c
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/e1/152adcb9adf37ec551ada9ba377ab53aec3bad b/tests/resources/typechanges/.gitted/objects/e1/152adcb9adf37ec551ada9ba377ab53aec3bad
new file mode 100644
index 000000000..c68fdcfab
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/e1/152adcb9adf37ec551ada9ba377ab53aec3bad
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/e4/ed436a9eb0f198cda722886a5f8d6d6c836b7b b/tests/resources/typechanges/.gitted/objects/e4/ed436a9eb0f198cda722886a5f8d6d6c836b7b
new file mode 100644
index 000000000..c9229ba25
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/e4/ed436a9eb0f198cda722886a5f8d6d6c836b7b
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/tests/resources/typechanges/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
new file mode 100644
index 000000000..711223894
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/f2/0b79342712e0b2315647cd8227a573fd3bc46e b/tests/resources/typechanges/.gitted/objects/f2/0b79342712e0b2315647cd8227a573fd3bc46e
new file mode 100644
index 000000000..3962ba6b4
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/f2/0b79342712e0b2315647cd8227a573fd3bc46e
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/objects/fd/e0147e3b59f381635a3b016e3fe6dacb70779d b/tests/resources/typechanges/.gitted/objects/fd/e0147e3b59f381635a3b016e3fe6dacb70779d
new file mode 100644
index 000000000..e3663da9f
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/objects/fd/e0147e3b59f381635a3b016e3fe6dacb70779d
Binary files differ
diff --git a/tests/resources/typechanges/.gitted/refs/heads/master b/tests/resources/typechanges/.gitted/refs/heads/master
new file mode 100644
index 000000000..546481a33
--- /dev/null
+++ b/tests/resources/typechanges/.gitted/refs/heads/master
@@ -0,0 +1 @@
+6eae26c90e8ccc4d16208972119c40635489c6f0
diff --git a/tests/resources/typechanges/README.md b/tests/resources/typechanges/README.md
new file mode 100644
index 000000000..1f5a95a9f
--- /dev/null
+++ b/tests/resources/typechanges/README.md
@@ -0,0 +1,43 @@
+This is a test repo for libgit2 where tree entries have type changes
+
+Types
+-----
+
+The key types that could be found in tree entries are:
+
+1. GIT_FILEMODE_NEW = 0000000 (i.e. file does not exist)
+2. GIT_FILEMODE_TREE = 0040000
+3. GIT_FILEMODE_BLOB = 0100644
+4. GIT_FILEMODE_BLOB_EXECUTABLE = 0100755
+5. GIT_FILEMODE_LINK = 0120000
+6. GIT_FILEMODE_COMMIT = 0160000
+
+I will try to have every type of transition somewhere in the history
+of this repo.
+
+Commits
+-------
+
+* `a(1--1) b(1--1) c(1--1) d(1--1) e(1--1)`
+ **Initial commit**<br>
+ `79b9f23e85f55ea36a472a902e875bc1121a94cb`
+* `a(1->2) b(1->3) c(1->4) d(1->5) e(1->6)`
+ **Create content**<br>
+ `9bdb75b73836a99e3dbeea640a81de81031fdc29`
+* `a(2->3) b(3->4) c(4->5) d(5->6) e(6->2)`
+ **Changes #1**<br>
+ `0e7ed140b514b8cae23254cb8656fe1674403aff`
+* `a(3->5) b(4->6) c(5->2) d(6->3) e(2->4)`
+ **Changes #2**<br>
+ `9d0235c7a7edc0889a18f97a42ee6db9fe688447`
+* `a(5->3) b(6->4) c(2->5) d(3->6) e(4->2)`
+ **Changes #3**<br>
+ `9b19edf33a03a0c59cdfc113bfa5c06179bf9b1a`
+* `a(3->2) b(4->3) c(5->4) d(6->5) e(2->6)`
+ **Changes #4**<br>
+ `1b63caae4a5ca96f78e8dfefc376c6a39a142475`<br>
+ Matches **Changes #1** except README.md
+* `a(2->1) b(3->1) c(4->1) d(5->1) e(6->1)`
+ **Changes #5**<br>
+ `6eae26c90e8ccc4d16208972119c40635489c6f0`<br>
+ Matches **Initial commit** except README.md and .gitmodules
diff --git a/tests/resources/typechanges/gitmodules b/tests/resources/typechanges/gitmodules
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/resources/typechanges/gitmodules
diff --git a/tests/resources/unsymlinked.git/HEAD b/tests/resources/unsymlinked.git/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/unsymlinked.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/unsymlinked.git/config b/tests/resources/unsymlinked.git/config
new file mode 100644
index 000000000..f57351fd5
--- /dev/null
+++ b/tests/resources/unsymlinked.git/config
@@ -0,0 +1,6 @@
+[core]
+ bare = true
+ repositoryformatversion = 0
+ filemode = false
+ logallrefupdates = true
+ ignorecase = true
diff --git a/tests/resources/unsymlinked.git/description b/tests/resources/unsymlinked.git/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/unsymlinked.git/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/unsymlinked.git/info/exclude b/tests/resources/unsymlinked.git/info/exclude
new file mode 100644
index 000000000..6d05881d3
--- /dev/null
+++ b/tests/resources/unsymlinked.git/info/exclude
@@ -0,0 +1,2 @@
+# File patterns to ignore; see `git help ignore` for more information.
+# Lines that start with '#' are comments.
diff --git a/tests/resources/unsymlinked.git/objects/08/8b64704e0d6b8bd061dea879418cb5442a3fbf b/tests/resources/unsymlinked.git/objects/08/8b64704e0d6b8bd061dea879418cb5442a3fbf
new file mode 100644
index 000000000..953262fa9
--- /dev/null
+++ b/tests/resources/unsymlinked.git/objects/08/8b64704e0d6b8bd061dea879418cb5442a3fbf
Binary files differ
diff --git a/tests/resources/unsymlinked.git/objects/13/a5e939bca25940c069fd2169d993dba328e30b b/tests/resources/unsymlinked.git/objects/13/a5e939bca25940c069fd2169d993dba328e30b
new file mode 100644
index 000000000..91ec8fa5e
--- /dev/null
+++ b/tests/resources/unsymlinked.git/objects/13/a5e939bca25940c069fd2169d993dba328e30b
Binary files differ
diff --git a/tests/resources/unsymlinked.git/objects/19/bf568e59e3a0b363cafb4106226e62d4a4c41c b/tests/resources/unsymlinked.git/objects/19/bf568e59e3a0b363cafb4106226e62d4a4c41c
new file mode 100644
index 000000000..94afd01e8
--- /dev/null
+++ b/tests/resources/unsymlinked.git/objects/19/bf568e59e3a0b363cafb4106226e62d4a4c41c
Binary files differ
diff --git a/tests/resources/unsymlinked.git/objects/58/1fadd35b4cf320d102a152f918729011604773 b/tests/resources/unsymlinked.git/objects/58/1fadd35b4cf320d102a152f918729011604773
new file mode 100644
index 000000000..5b33d027c
--- /dev/null
+++ b/tests/resources/unsymlinked.git/objects/58/1fadd35b4cf320d102a152f918729011604773
Binary files differ
diff --git a/tests/resources/unsymlinked.git/objects/5c/87b6791e8b13da658a14d1ef7e09b5dc3bac8c b/tests/resources/unsymlinked.git/objects/5c/87b6791e8b13da658a14d1ef7e09b5dc3bac8c
new file mode 100644
index 000000000..67eb14930
--- /dev/null
+++ b/tests/resources/unsymlinked.git/objects/5c/87b6791e8b13da658a14d1ef7e09b5dc3bac8c
Binary files differ
diff --git a/tests/resources/unsymlinked.git/objects/6f/e5f5398af85fb3de8a6aba0339b6d3bfa26a27 b/tests/resources/unsymlinked.git/objects/6f/e5f5398af85fb3de8a6aba0339b6d3bfa26a27
new file mode 100644
index 000000000..c1ea0de75
--- /dev/null
+++ b/tests/resources/unsymlinked.git/objects/6f/e5f5398af85fb3de8a6aba0339b6d3bfa26a27
Binary files differ
diff --git a/tests/resources/unsymlinked.git/objects/7f/ccd75616ec188b8f1b23d67506a334cc34a49d b/tests/resources/unsymlinked.git/objects/7f/ccd75616ec188b8f1b23d67506a334cc34a49d
new file mode 100644
index 000000000..028505563
--- /dev/null
+++ b/tests/resources/unsymlinked.git/objects/7f/ccd75616ec188b8f1b23d67506a334cc34a49d
Binary files differ
diff --git a/tests/resources/unsymlinked.git/objects/80/6999882bf91d24241e4077906b9017605eb1f3 b/tests/resources/unsymlinked.git/objects/80/6999882bf91d24241e4077906b9017605eb1f3
new file mode 100644
index 000000000..e866a75a6
--- /dev/null
+++ b/tests/resources/unsymlinked.git/objects/80/6999882bf91d24241e4077906b9017605eb1f3
Binary files differ
diff --git a/tests/resources/unsymlinked.git/objects/83/7d176303c5005505ec1e4a30231c40930c0230 b/tests/resources/unsymlinked.git/objects/83/7d176303c5005505ec1e4a30231c40930c0230
new file mode 100644
index 000000000..189ab044d
--- /dev/null
+++ b/tests/resources/unsymlinked.git/objects/83/7d176303c5005505ec1e4a30231c40930c0230
Binary files differ
diff --git a/tests/resources/unsymlinked.git/objects/a8/595ccca04f40818ae0155c8f9c77a230e597b6 b/tests/resources/unsymlinked.git/objects/a8/595ccca04f40818ae0155c8f9c77a230e597b6
new file mode 100644
index 000000000..a2ef6be45
--- /dev/null
+++ b/tests/resources/unsymlinked.git/objects/a8/595ccca04f40818ae0155c8f9c77a230e597b6
@@ -0,0 +1,2 @@
+x•Ž[
+Â0EýÎ*æ_“¤yˆÝ€{˜4ShZ))¸| ®À¯ ÎáN[ks=èKß™Á¶Åš¨[’ÉÈQ"4&&—M*¤i/Þ´óÚ! ‹1† S‰*Ÿ™Añ€ÞGt)¢ò-'UŒ £×m‡ñ7O cc¸Õ¹=zåå´µ;(ãPY«‡+*DqÒó^ç¿E!¥œ*­/Î0¯}Z?<ÒÂPæ…¥øšÈJp \ No newline at end of file
diff --git a/tests/resources/unsymlinked.git/objects/cf/8f1cf5cce859c438d6cc067284cb5e161206e7 b/tests/resources/unsymlinked.git/objects/cf/8f1cf5cce859c438d6cc067284cb5e161206e7
new file mode 100644
index 000000000..ec274cb1d
--- /dev/null
+++ b/tests/resources/unsymlinked.git/objects/cf/8f1cf5cce859c438d6cc067284cb5e161206e7
Binary files differ
diff --git a/tests/resources/unsymlinked.git/objects/d5/278d05c8607ec420bfee4cf219fbc0eeebfd6a b/tests/resources/unsymlinked.git/objects/d5/278d05c8607ec420bfee4cf219fbc0eeebfd6a
new file mode 100644
index 000000000..c1b6a5101
--- /dev/null
+++ b/tests/resources/unsymlinked.git/objects/d5/278d05c8607ec420bfee4cf219fbc0eeebfd6a
Binary files differ
diff --git a/tests/resources/unsymlinked.git/objects/f4/e16fb76536591a41454194058d048d8e4dd2e9 b/tests/resources/unsymlinked.git/objects/f4/e16fb76536591a41454194058d048d8e4dd2e9
new file mode 100644
index 000000000..ad751adbe
--- /dev/null
+++ b/tests/resources/unsymlinked.git/objects/f4/e16fb76536591a41454194058d048d8e4dd2e9
Binary files differ
diff --git a/tests/resources/unsymlinked.git/objects/f9/e65619d93fdf2673882e0a261c5e93b1a84006 b/tests/resources/unsymlinked.git/objects/f9/e65619d93fdf2673882e0a261c5e93b1a84006
new file mode 100644
index 000000000..f87cd42fb
--- /dev/null
+++ b/tests/resources/unsymlinked.git/objects/f9/e65619d93fdf2673882e0a261c5e93b1a84006
Binary files differ
diff --git a/tests/resources/unsymlinked.git/refs/heads/exe-file b/tests/resources/unsymlinked.git/refs/heads/exe-file
new file mode 100644
index 000000000..b96ef46ca
--- /dev/null
+++ b/tests/resources/unsymlinked.git/refs/heads/exe-file
@@ -0,0 +1 @@
+a8595ccca04f40818ae0155c8f9c77a230e597b6
diff --git a/tests/resources/unsymlinked.git/refs/heads/master b/tests/resources/unsymlinked.git/refs/heads/master
new file mode 100644
index 000000000..96c17ab17
--- /dev/null
+++ b/tests/resources/unsymlinked.git/refs/heads/master
@@ -0,0 +1 @@
+7fccd75616ec188b8f1b23d67506a334cc34a49d
diff --git a/tests/resources/unsymlinked.git/refs/heads/reg-file b/tests/resources/unsymlinked.git/refs/heads/reg-file
new file mode 100644
index 000000000..b428c00d5
--- /dev/null
+++ b/tests/resources/unsymlinked.git/refs/heads/reg-file
@@ -0,0 +1 @@
+806999882bf91d24241e4077906b9017605eb1f3
diff --git a/tests/resources/userdiff/.gitted/HEAD b/tests/resources/userdiff/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/userdiff/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/userdiff/.gitted/config b/tests/resources/userdiff/.gitted/config
new file mode 100644
index 000000000..6c9406b7d
--- /dev/null
+++ b/tests/resources/userdiff/.gitted/config
@@ -0,0 +1,7 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
+ precomposeunicode = true
diff --git a/tests/resources/userdiff/.gitted/description b/tests/resources/userdiff/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/userdiff/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/userdiff/.gitted/index b/tests/resources/userdiff/.gitted/index
new file mode 100644
index 000000000..b69d5cfb1
--- /dev/null
+++ b/tests/resources/userdiff/.gitted/index
Binary files differ
diff --git a/tests/resources/userdiff/.gitted/info/refs b/tests/resources/userdiff/.gitted/info/refs
new file mode 100644
index 000000000..b0743141d
--- /dev/null
+++ b/tests/resources/userdiff/.gitted/info/refs
@@ -0,0 +1 @@
+60e3f7b244a5305e2c9fa4ef0e897f3b14f3b8dd refs/heads/master
diff --git a/tests/resources/userdiff/.gitted/objects/info/packs b/tests/resources/userdiff/.gitted/objects/info/packs
new file mode 100644
index 000000000..0c5fc2a30
--- /dev/null
+++ b/tests/resources/userdiff/.gitted/objects/info/packs
@@ -0,0 +1,2 @@
+P pack-1652578900ac63564f2a24b9714529821276ceb9.pack
+
diff --git a/tests/resources/userdiff/.gitted/objects/pack/pack-1652578900ac63564f2a24b9714529821276ceb9.idx b/tests/resources/userdiff/.gitted/objects/pack/pack-1652578900ac63564f2a24b9714529821276ceb9.idx
new file mode 100644
index 000000000..6f4381cc7
--- /dev/null
+++ b/tests/resources/userdiff/.gitted/objects/pack/pack-1652578900ac63564f2a24b9714529821276ceb9.idx
Binary files differ
diff --git a/tests/resources/userdiff/.gitted/objects/pack/pack-1652578900ac63564f2a24b9714529821276ceb9.pack b/tests/resources/userdiff/.gitted/objects/pack/pack-1652578900ac63564f2a24b9714529821276ceb9.pack
new file mode 100644
index 000000000..39bd1d8f0
--- /dev/null
+++ b/tests/resources/userdiff/.gitted/objects/pack/pack-1652578900ac63564f2a24b9714529821276ceb9.pack
Binary files differ
diff --git a/tests/resources/userdiff/.gitted/packed-refs b/tests/resources/userdiff/.gitted/packed-refs
new file mode 100644
index 000000000..802f67ce0
--- /dev/null
+++ b/tests/resources/userdiff/.gitted/packed-refs
@@ -0,0 +1,2 @@
+# pack-refs with: peeled fully-peeled
+60e3f7b244a5305e2c9fa4ef0e897f3b14f3b8dd refs/heads/master
diff --git a/tests/resources/userdiff/.gitted/refs/dummy-marker.txt b/tests/resources/userdiff/.gitted/refs/dummy-marker.txt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/resources/userdiff/.gitted/refs/dummy-marker.txt
diff --git a/tests/resources/userdiff/after/file.html b/tests/resources/userdiff/after/file.html
new file mode 100644
index 000000000..2320e2f1e
--- /dev/null
+++ b/tests/resources/userdiff/after/file.html
@@ -0,0 +1,41 @@
+<html>
+<body>
+ <h1 id="first section">
+ <ol>
+ <li>item 1.1</li>
+ <li>item 1.2 changed</li>
+ <li>item 1.3 changed</li>
+ <li>item 1.4</li>
+ <li>item 1.5</li>
+ <li>item 1.6</li>
+ <li>item 1.7</li>
+ <li>item 1.8</li>
+ <li>item 1.9</li>
+ <li>item 1.10 added</li>
+ </ol>
+ </h1>
+ <h1 id="second section">
+ <ol>
+ <li>item 2.1</li>
+ <li>item 2.2</li>
+ <li>item 2.3</li>
+ <li>item 2.4</li>
+ <li>item 2.5</li>
+ <li>item 2.6</li>
+ <li>item 2.7 changed</li>
+ <li>item 2.7.1 added</li>
+ <li>item 2.8</li>
+ </ol>
+ </h1>
+ <h1 id="third section">
+ <ol>
+ <li>item 3.1</li>
+ <li>item 3.2</li>
+ <li>item 3.3</li>
+ <li>item 3.4</li>
+ <li>item 3.5</li>
+ <li>item 3.6</li>
+ </ol>
+ </h1>
+</body>
+</html>
diff --git a/tests/resources/userdiff/after/file.javascript b/tests/resources/userdiff/after/file.javascript
new file mode 100644
index 000000000..7cd3c5a8a
--- /dev/null
+++ b/tests/resources/userdiff/after/file.javascript
@@ -0,0 +1,108 @@
+/*
+ Some code extracted from https://github.com/julianlloyd/scrollReveal.js
+ which happens to be a trending Javascript repo with an MIT license at
+ the time I was working on Javascript userdiff support in libgit2
+
+ I extracted just some of the code, so I suspect this is no longer valid
+ Javascript code, but it contains enough example patterns to work.
+*/
+;(function (window) {
+
+ 'use strict';
+
+ var docElem = window.document.documentElement;
+
+ function getViewportH () {
+ var client = docElem['clientHeight'],
+ inner = window['innerHeight'],
+ sample = window['otherProperty'];
+
+ return (client < inner) ? inner : client;
+ }
+
+ function getOffset (el) {
+ var offsetTop = 0,
+ offsetLeft = 0;
+
+ do {
+ if (!isNaN(el.offsetTop)) {
+ offsetTop += el.offsetTop + 1;
+ }
+ if (!isNaN(el.offsetLeft)) {
+ offsetLeft += el.offsetLeft;
+ }
+ } while (el = el.offsetParent)
+
+ return {
+ top: offsetTop,
+ left: offsetLeft
+ }
+ }
+
+ function isElementInViewport (el, h) {
+ var scrolled = window.pageYOffset,
+ viewed = scrolled + getViewportH(),
+ elTop = getOffset(el).top,
+ elBottom = elTop + el.offsetHeight,
+ h = h || 0;
+
+ return (elTop + el.offsetHeight * h) <= viewed && (elBottom) >= scrolled;
+ }
+
+ scrollReveal.prototype = {
+
+ _init: function () {
+
+ var self = this;
+
+ this.elems = Array.prototype.slice.call(docElem.querySelectorAll('[data-scrollReveal]'));
+ this.scrolled = false;
+
+ this.elems.forEach(function (el, i) {
+ self.animate(el);
+ });
+
+ var scrollHandler = function () {
+ if (!self.scrolled) {
+ self.scrolled = true;
+ setTimeout(function () {
+ self._scrollPage();
+ }, 61);
+ }
+ };
+
+ var resizeHandler = function () {
+ function delayed() {
+ self._scrollPage();
+ self.resizeTimeout = null;
+ }
+ if (self.resizeTimeout) {
+ clearTimeout(self.resizeTimeout);
+ }
+ self.resizeTimeout = setTimeout(delayed, 200);
+ };
+
+ window.addEventListener('scroll', scrollHandler, false);
+ window.addEventListener('resize', resizeHandler, false);
+ },
+
+ /*=============================================================================*/
+
+ _scrollPage: function () {
+ var self = this;
+
+ this.elems.forEach(function (el, i) {
+ if (isElementInViewport(el, self.options.viewportFactor)) {
+ self.animate(el);
+ }
+ });
+ this.scrolled = false;
+ this.tested = true;
+ },
+ }; // end scrollReveal.prototype
+
+ document.addEventListener("DOMContentLoaded", function (evt) {
+ window.scrollReveal = new scrollReveal();
+ });
+
+})(window);
diff --git a/tests/resources/userdiff/after/file.php b/tests/resources/userdiff/after/file.php
new file mode 100644
index 000000000..967d6466c
--- /dev/null
+++ b/tests/resources/userdiff/after/file.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Faker;
+
+/**
+ * Proxy for other generators, to return only unique values. Works with
+ * Faker\Generator\Base->unique()
+ */
+class UniqueGenerator
+{
+ protected $generator;
+ protected $maxRetries;
+ protected $moreStuff;
+ protected $uniques = array();
+
+ public function __construct(Generator $generator, $maxRetries)
+ {
+ $this->generator = $generator;
+ $this->maxRetries = $maxRetries + 1;
+ }
+
+ /**
+ * Catch and proxy all generator calls but return only unique values
+ */
+ public function __get($attribute)
+ {
+ return $this->__call($attribute, array());
+ }
+
+ /**
+ * Catch and proxy all generator calls with arguments but return only unique values
+ */
+ public function __call($name, $arguments)
+ {
+ $i = 0;
+ if (!isset($this->uniques[$name])) {
+ $this->uniques[$name] = array();
+ }
+ do {
+ $res = call_user_func_array(array($this->generator, $name), $arguments);
+ $i++;
+ if ($i >= $this->maxRetries) {
+ throw new \OverflowException(sprintf('Maximum retries of %d reached without finding a unique value', $this->maxRetries));
+ }
+ } while (in_array($res, $this->uniques[$name]));
+ $this->uniques[$name][]= $res;
+
+ return $res;
+ }
+}
diff --git a/tests/resources/userdiff/before/file.html b/tests/resources/userdiff/before/file.html
new file mode 100644
index 000000000..872d19663
--- /dev/null
+++ b/tests/resources/userdiff/before/file.html
@@ -0,0 +1,41 @@
+<html>
+<body>
+ <h1 id="first section">
+ <ol>
+ <li>item 1.1</li>
+ <li>item 1.2</li>
+ <li>item 1.3</li>
+ <li>item 1.4</li>
+ <li>item 1.5</li>
+ <li>item 1.6</li>
+ <li>item 1.7</li>
+ <li>item 1.8</li>
+ <li>item 1.9</li>
+ </ol>
+ </h1>
+ <h1 id="second section">
+ <ol>
+ <li>item 2.1</li>
+ <li>item 2.2</li>
+ <li>item 2.3</li>
+ <li>item 2.4</li>
+ <li>item 2.5</li>
+ <li>item 2.6</li>
+ <li>item 2.7</li>
+ <li>item 2.8</li>
+ </ol>
+ </h1>
+ <h1 id="third section">
+ <ol>
+ <li>item 3.1</li>
+ <li>item 3.2</li>
+ <li>item 3.3</li>
+ <li>item 3.4</li>
+ <li>item 3.5</li>
+ <li>item 3.6</li>
+ <li>item 3.7</li>
+ <li>item 3.8</li>
+ </ol>
+ </h1>
+</body>
+</html>
diff --git a/tests/resources/userdiff/before/file.javascript b/tests/resources/userdiff/before/file.javascript
new file mode 100644
index 000000000..b9f1286e5
--- /dev/null
+++ b/tests/resources/userdiff/before/file.javascript
@@ -0,0 +1,109 @@
+/*
+ Some code extracted from https://github.com/julianlloyd/scrollReveal.js
+ which happens to be a trending Javascript repo with an MIT license at
+ the time I was working on Javascript userdiff support in libgit2
+
+ I extracted just some of the code, so I suspect this is no longer valid
+ Javascript code, but it contains enough example patterns to work.
+*/
+;(function (window) {
+
+ 'use strict';
+
+ var docElem = window.document.documentElement;
+
+ function getViewportH () {
+ var client = docElem['clientHeight'],
+ inner = window['innerHeight'];
+
+ return (client < inner) ? inner : client;
+ }
+
+ function getOffset (el) {
+ var offsetTop = 0,
+ offsetLeft = 0;
+
+ do {
+ if (!isNaN(el.offsetTop)) {
+ offsetTop += el.offsetTop;
+ }
+ if (!isNaN(el.offsetLeft)) {
+ offsetLeft += el.offsetLeft;
+ }
+ } while (el = el.offsetParent)
+
+ return {
+ top: offsetTop,
+ left: offsetLeft
+ }
+ }
+
+ function isElementInViewport (el, h) {
+ var scrolled = window.pageYOffset,
+ viewed = scrolled + getViewportH(),
+ elH = el.offsetHeight,
+ elTop = getOffset(el).top,
+ elBottom = elTop + elH,
+ h = h || 0;
+
+ return (elTop + elH * h) <= viewed && (elBottom) >= scrolled;
+ }
+
+ scrollReveal.prototype = {
+
+ _init: function () {
+
+ var self = this;
+
+ this.elems = Array.prototype.slice.call(docElem.querySelectorAll('[data-scrollReveal]'));
+ this.scrolled = false;
+
+ // Initialize all scrollreveals, triggering all
+ // reveals on visible elements.
+ this.elems.forEach(function (el, i) {
+ self.animate(el);
+ });
+
+ var scrollHandler = function () {
+ if (!self.scrolled) {
+ self.scrolled = true;
+ setTimeout(function () {
+ self._scrollPage();
+ }, 60);
+ }
+ };
+
+ var resizeHandler = function () {
+ function delayed() {
+ self._scrollPage();
+ self.resizeTimeout = null;
+ }
+ if (self.resizeTimeout) {
+ clearTimeout(self.resizeTimeout);
+ }
+ self.resizeTimeout = setTimeout(delayed, 200);
+ };
+
+ window.addEventListener('scroll', scrollHandler, false);
+ window.addEventListener('resize', resizeHandler, false);
+ },
+
+ /*=============================================================================*/
+
+ _scrollPage: function () {
+ var self = this;
+
+ this.elems.forEach(function (el, i) {
+ if (isElementInViewport(el, self.options.viewportFactor)) {
+ self.animate(el);
+ }
+ });
+ this.scrolled = false;
+ },
+ }; // end scrollReveal.prototype
+
+ document.addEventListener("DOMContentLoaded", function (evt) {
+ window.scrollReveal = new scrollReveal();
+ });
+
+})(window);
diff --git a/tests/resources/userdiff/before/file.php b/tests/resources/userdiff/before/file.php
new file mode 100644
index 000000000..63250ad01
--- /dev/null
+++ b/tests/resources/userdiff/before/file.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Faker;
+
+/**
+ * Proxy for other generators, to return only unique values. Works with
+ * Faker\Generator\Base->unique()
+ */
+class UniqueGenerator
+{
+ protected $generator;
+ protected $maxRetries;
+ protected $uniques = array();
+
+ public function __construct(Generator $generator, $maxRetries)
+ {
+ $this->generator = $generator;
+ $this->maxRetries = $maxRetries;
+ }
+
+ /**
+ * Catch and proxy all generator calls but return only unique values
+ */
+ public function __get($attribute)
+ {
+ return $this->__call($attribute, array());
+ }
+
+ /**
+ * Catch and proxy all generator calls with arguments but return only unique values
+ */
+ public function __call($name, $arguments)
+ {
+ if (!isset($this->uniques[$name])) {
+ $this->uniques[$name] = array();
+ }
+ $i = 0;
+ do {
+ $res = call_user_func_array(array($this->generator, $name), $arguments);
+ $i++;
+ if ($i > $this->maxRetries) {
+ throw new \OverflowException(sprintf('Maximum retries of %d reached without finding a unique value', $this->maxRetries));
+ }
+ } while (in_array($res, $this->uniques[$name]));
+ $this->uniques[$name][]= $res;
+
+ return $res;
+ }
+}
diff --git a/tests/resources/userdiff/expected/driver/diff.html b/tests/resources/userdiff/expected/driver/diff.html
new file mode 100644
index 000000000..5a428e7dc
--- /dev/null
+++ b/tests/resources/userdiff/expected/driver/diff.html
@@ -0,0 +1,26 @@
+diff --git a/files/file.html b/files/file.html
+index 872d196..2320e2f 100644
+--- a/files/file.html
++++ b/files/file.html
+@@ -5,4 +5,4 @@ <h1 id="first section">
+ <li>item 1.1</li>
+- <li>item 1.2</li>
+- <li>item 1.3</li>
++ <li>item 1.2 changed</li>
++ <li>item 1.3 changed</li>
+ <li>item 1.4</li>
+@@ -13,2 +13,3 @@ <h1 id="first section">
+ <li>item 1.9</li>
++ <li>item 1.10 added</li>
+ </ol>
+@@ -23,3 +24,4 @@ <h1 id="second section">
+ <li>item 2.6</li>
+- <li>item 2.7</li>
++ <li>item 2.7 changed</li>
++ <li>item 2.7.1 added</li>
+ <li>item 2.8</li>
+@@ -35,4 +37,2 @@ <h1 id="third section">
+ <li>item 3.6</li>
+- <li>item 3.7</li>
+- <li>item 3.8</li>
+ </ol>
diff --git a/tests/resources/userdiff/expected/driver/diff.javascript b/tests/resources/userdiff/expected/driver/diff.javascript
new file mode 100644
index 000000000..4e65d0746
--- /dev/null
+++ b/tests/resources/userdiff/expected/driver/diff.javascript
@@ -0,0 +1,40 @@
+diff --git a/files/file.javascript b/files/file.javascript
+index b9f1286..7cd3c5a 100644
+--- a/files/file.javascript
++++ b/files/file.javascript
+@@ -16,3 +16,4 @@ function getViewportH ()
+ var client = docElem['clientHeight'],
+- inner = window['innerHeight'];
++ inner = window['innerHeight'],
++ sample = window['otherProperty'];
+
+@@ -27,3 +28,3 @@ function getOffset (el)
+ if (!isNaN(el.offsetTop)) {
+- offsetTop += el.offsetTop;
++ offsetTop += el.offsetTop + 1;
+ }
+@@ -43,8 +44,7 @@ function isElementInViewport (el, h)
+ viewed = scrolled + getViewportH(),
+- elH = el.offsetHeight,
+ elTop = getOffset(el).top,
+- elBottom = elTop + elH,
++ elBottom = elTop + el.offsetHeight,
+ h = h || 0;
+
+- return (elTop + elH * h) <= viewed && (elBottom) >= scrolled;
++ return (elTop + el.offsetHeight * h) <= viewed && (elBottom) >= scrolled;
+ }
+@@ -60,4 +60,2 @@ _init: function ()
+
+- // Initialize all scrollreveals, triggering all
+- // reveals on visible elements.
+ this.elems.forEach(function (el, i) {
+@@ -71,3 +69,3 @@ var scrollHandler = function ()
+ self._scrollPage();
+- }, 60);
++ }, 61);
+ }
+@@ -101,2 +99,3 @@ _scrollPage: function ()
+ this.scrolled = false;
++ this.tested = true;
+ },
diff --git a/tests/resources/userdiff/expected/driver/diff.php b/tests/resources/userdiff/expected/driver/diff.php
new file mode 100644
index 000000000..9711b5b3e
--- /dev/null
+++ b/tests/resources/userdiff/expected/driver/diff.php
@@ -0,0 +1,26 @@
+diff --git a/files/file.php b/files/file.php
+index 63250ad..967d646 100644
+--- a/files/file.php
++++ b/files/file.php
+@@ -12,2 +12,3 @@ class UniqueGenerator
+ protected $maxRetries;
++ protected $moreStuff;
+ protected $uniques = array();
+@@ -17,3 +18,3 @@ public function __construct(Generator $generator, $maxRetries)
+ $this->generator = $generator;
+- $this->maxRetries = $maxRetries;
++ $this->maxRetries = $maxRetries + 1;
+ }
+@@ -33,10 +34,10 @@ public function __call($name, $arguments)
+ {
++ $i = 0;
+ if (!isset($this->uniques[$name])) {
+ $this->uniques[$name] = array();
+ }
+- $i = 0;
+ do {
+ $res = call_user_func_array(array($this->generator, $name), $arguments);
+ $i++;
+- if ($i > $this->maxRetries) {
++ if ($i >= $this->maxRetries) {
+ throw new \OverflowException(sprintf('Maximum retries of %d reached without finding a unique value', $this->maxRetries));
diff --git a/tests/resources/userdiff/expected/nodriver/diff.html b/tests/resources/userdiff/expected/nodriver/diff.html
new file mode 100644
index 000000000..2ea4b8a16
--- /dev/null
+++ b/tests/resources/userdiff/expected/nodriver/diff.html
@@ -0,0 +1,26 @@
+diff --git a/files/file.html b/files/file.html
+index 872d196..2320e2f 100644
+--- a/files/file.html
++++ b/files/file.html
+@@ -5,4 +5,4 @@
+ <li>item 1.1</li>
+- <li>item 1.2</li>
+- <li>item 1.3</li>
++ <li>item 1.2 changed</li>
++ <li>item 1.3 changed</li>
+ <li>item 1.4</li>
+@@ -13,2 +13,3 @@
+ <li>item 1.9</li>
++ <li>item 1.10 added</li>
+ </ol>
+@@ -23,3 +24,4 @@
+ <li>item 2.6</li>
+- <li>item 2.7</li>
++ <li>item 2.7 changed</li>
++ <li>item 2.7.1 added</li>
+ <li>item 2.8</li>
+@@ -35,4 +37,2 @@
+ <li>item 3.6</li>
+- <li>item 3.7</li>
+- <li>item 3.8</li>
+ </ol>
diff --git a/tests/resources/userdiff/expected/nodriver/diff.javascript b/tests/resources/userdiff/expected/nodriver/diff.javascript
new file mode 100644
index 000000000..69afe4fd8
--- /dev/null
+++ b/tests/resources/userdiff/expected/nodriver/diff.javascript
@@ -0,0 +1,40 @@
+diff --git a/files/file.javascript b/files/file.javascript
+index b9f1286..7cd3c5a 100644
+--- a/files/file.javascript
++++ b/files/file.javascript
+@@ -16,3 +16,4 @@
+ var client = docElem['clientHeight'],
+- inner = window['innerHeight'];
++ inner = window['innerHeight'],
++ sample = window['otherProperty'];
+
+@@ -27,3 +28,3 @@
+ if (!isNaN(el.offsetTop)) {
+- offsetTop += el.offsetTop;
++ offsetTop += el.offsetTop + 1;
+ }
+@@ -43,8 +44,7 @@
+ viewed = scrolled + getViewportH(),
+- elH = el.offsetHeight,
+ elTop = getOffset(el).top,
+- elBottom = elTop + elH,
++ elBottom = elTop + el.offsetHeight,
+ h = h || 0;
+
+- return (elTop + elH * h) <= viewed && (elBottom) >= scrolled;
++ return (elTop + el.offsetHeight * h) <= viewed && (elBottom) >= scrolled;
+ }
+@@ -60,4 +60,2 @@
+
+- // Initialize all scrollreveals, triggering all
+- // reveals on visible elements.
+ this.elems.forEach(function (el, i) {
+@@ -71,3 +69,3 @@
+ self._scrollPage();
+- }, 60);
++ }, 61);
+ }
+@@ -101,2 +99,3 @@
+ this.scrolled = false;
++ this.tested = true;
+ },
diff --git a/tests/resources/userdiff/expected/nodriver/diff.php b/tests/resources/userdiff/expected/nodriver/diff.php
new file mode 100644
index 000000000..e77c094aa
--- /dev/null
+++ b/tests/resources/userdiff/expected/nodriver/diff.php
@@ -0,0 +1,26 @@
+diff --git a/files/file.php b/files/file.php
+index 63250ad..967d646 100644
+--- a/files/file.php
++++ b/files/file.php
+@@ -12,2 +12,3 @@ class UniqueGenerator
+ protected $maxRetries;
++ protected $moreStuff;
+ protected $uniques = array();
+@@ -17,3 +18,3 @@ class UniqueGenerator
+ $this->generator = $generator;
+- $this->maxRetries = $maxRetries;
++ $this->maxRetries = $maxRetries + 1;
+ }
+@@ -33,10 +34,10 @@ class UniqueGenerator
+ {
++ $i = 0;
+ if (!isset($this->uniques[$name])) {
+ $this->uniques[$name] = array();
+ }
+- $i = 0;
+ do {
+ $res = call_user_func_array(array($this->generator, $name), $arguments);
+ $i++;
+- if ($i > $this->maxRetries) {
++ if ($i >= $this->maxRetries) {
+ throw new \OverflowException(sprintf('Maximum retries of %d reached without finding a unique value', $this->maxRetries));
diff --git a/tests/resources/userdiff/files/file.html b/tests/resources/userdiff/files/file.html
new file mode 100644
index 000000000..2320e2f1e
--- /dev/null
+++ b/tests/resources/userdiff/files/file.html
@@ -0,0 +1,41 @@
+<html>
+<body>
+ <h1 id="first section">
+ <ol>
+ <li>item 1.1</li>
+ <li>item 1.2 changed</li>
+ <li>item 1.3 changed</li>
+ <li>item 1.4</li>
+ <li>item 1.5</li>
+ <li>item 1.6</li>
+ <li>item 1.7</li>
+ <li>item 1.8</li>
+ <li>item 1.9</li>
+ <li>item 1.10 added</li>
+ </ol>
+ </h1>
+ <h1 id="second section">
+ <ol>
+ <li>item 2.1</li>
+ <li>item 2.2</li>
+ <li>item 2.3</li>
+ <li>item 2.4</li>
+ <li>item 2.5</li>
+ <li>item 2.6</li>
+ <li>item 2.7 changed</li>
+ <li>item 2.7.1 added</li>
+ <li>item 2.8</li>
+ </ol>
+ </h1>
+ <h1 id="third section">
+ <ol>
+ <li>item 3.1</li>
+ <li>item 3.2</li>
+ <li>item 3.3</li>
+ <li>item 3.4</li>
+ <li>item 3.5</li>
+ <li>item 3.6</li>
+ </ol>
+ </h1>
+</body>
+</html>
diff --git a/tests/resources/userdiff/files/file.javascript b/tests/resources/userdiff/files/file.javascript
new file mode 100644
index 000000000..7cd3c5a8a
--- /dev/null
+++ b/tests/resources/userdiff/files/file.javascript
@@ -0,0 +1,108 @@
+/*
+ Some code extracted from https://github.com/julianlloyd/scrollReveal.js
+ which happens to be a trending Javascript repo with an MIT license at
+ the time I was working on Javascript userdiff support in libgit2
+
+ I extracted just some of the code, so I suspect this is no longer valid
+ Javascript code, but it contains enough example patterns to work.
+*/
+;(function (window) {
+
+ 'use strict';
+
+ var docElem = window.document.documentElement;
+
+ function getViewportH () {
+ var client = docElem['clientHeight'],
+ inner = window['innerHeight'],
+ sample = window['otherProperty'];
+
+ return (client < inner) ? inner : client;
+ }
+
+ function getOffset (el) {
+ var offsetTop = 0,
+ offsetLeft = 0;
+
+ do {
+ if (!isNaN(el.offsetTop)) {
+ offsetTop += el.offsetTop + 1;
+ }
+ if (!isNaN(el.offsetLeft)) {
+ offsetLeft += el.offsetLeft;
+ }
+ } while (el = el.offsetParent)
+
+ return {
+ top: offsetTop,
+ left: offsetLeft
+ }
+ }
+
+ function isElementInViewport (el, h) {
+ var scrolled = window.pageYOffset,
+ viewed = scrolled + getViewportH(),
+ elTop = getOffset(el).top,
+ elBottom = elTop + el.offsetHeight,
+ h = h || 0;
+
+ return (elTop + el.offsetHeight * h) <= viewed && (elBottom) >= scrolled;
+ }
+
+ scrollReveal.prototype = {
+
+ _init: function () {
+
+ var self = this;
+
+ this.elems = Array.prototype.slice.call(docElem.querySelectorAll('[data-scrollReveal]'));
+ this.scrolled = false;
+
+ this.elems.forEach(function (el, i) {
+ self.animate(el);
+ });
+
+ var scrollHandler = function () {
+ if (!self.scrolled) {
+ self.scrolled = true;
+ setTimeout(function () {
+ self._scrollPage();
+ }, 61);
+ }
+ };
+
+ var resizeHandler = function () {
+ function delayed() {
+ self._scrollPage();
+ self.resizeTimeout = null;
+ }
+ if (self.resizeTimeout) {
+ clearTimeout(self.resizeTimeout);
+ }
+ self.resizeTimeout = setTimeout(delayed, 200);
+ };
+
+ window.addEventListener('scroll', scrollHandler, false);
+ window.addEventListener('resize', resizeHandler, false);
+ },
+
+ /*=============================================================================*/
+
+ _scrollPage: function () {
+ var self = this;
+
+ this.elems.forEach(function (el, i) {
+ if (isElementInViewport(el, self.options.viewportFactor)) {
+ self.animate(el);
+ }
+ });
+ this.scrolled = false;
+ this.tested = true;
+ },
+ }; // end scrollReveal.prototype
+
+ document.addEventListener("DOMContentLoaded", function (evt) {
+ window.scrollReveal = new scrollReveal();
+ });
+
+})(window);
diff --git a/tests/resources/userdiff/files/file.php b/tests/resources/userdiff/files/file.php
new file mode 100644
index 000000000..967d6466c
--- /dev/null
+++ b/tests/resources/userdiff/files/file.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Faker;
+
+/**
+ * Proxy for other generators, to return only unique values. Works with
+ * Faker\Generator\Base->unique()
+ */
+class UniqueGenerator
+{
+ protected $generator;
+ protected $maxRetries;
+ protected $moreStuff;
+ protected $uniques = array();
+
+ public function __construct(Generator $generator, $maxRetries)
+ {
+ $this->generator = $generator;
+ $this->maxRetries = $maxRetries + 1;
+ }
+
+ /**
+ * Catch and proxy all generator calls but return only unique values
+ */
+ public function __get($attribute)
+ {
+ return $this->__call($attribute, array());
+ }
+
+ /**
+ * Catch and proxy all generator calls with arguments but return only unique values
+ */
+ public function __call($name, $arguments)
+ {
+ $i = 0;
+ if (!isset($this->uniques[$name])) {
+ $this->uniques[$name] = array();
+ }
+ do {
+ $res = call_user_func_array(array($this->generator, $name), $arguments);
+ $i++;
+ if ($i >= $this->maxRetries) {
+ throw new \OverflowException(sprintf('Maximum retries of %d reached without finding a unique value', $this->maxRetries));
+ }
+ } while (in_array($res, $this->uniques[$name]));
+ $this->uniques[$name][]= $res;
+
+ return $res;
+ }
+}
diff --git a/tests/revert/bare.c b/tests/revert/bare.c
new file mode 100644
index 000000000..206c86d70
--- /dev/null
+++ b/tests/revert/bare.c
@@ -0,0 +1,107 @@
+#include "clar.h"
+#include "clar_libgit2.h"
+
+#include "buffer.h"
+#include "fileops.h"
+#include "git2/revert.h"
+
+#include "../merge/merge_helpers.h"
+
+#define TEST_REPO_PATH "revert"
+
+static git_repository *repo;
+
+// Fixture setup and teardown
+void test_revert_bare__initialize(void)
+{
+ repo = cl_git_sandbox_init(TEST_REPO_PATH);
+}
+
+void test_revert_bare__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_revert_bare__automerge(void)
+{
+ git_commit *head_commit, *revert_commit;
+ git_oid head_oid, revert_oid;
+ git_index *index;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "caf99de3a49827117bb66721010eac461b06a80c", 0, "file1.txt" },
+ { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" },
+ { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" },
+ { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" },
+ };
+
+ git_oid_fromstr(&head_oid, "72333f47d4e83616630ff3b0ffe4c0faebcc3c45");
+ cl_git_pass(git_commit_lookup(&head_commit, repo, &head_oid));
+
+ git_oid_fromstr(&revert_oid, "d1d403d22cbe24592d725f442835cf46fe60c8ac");
+ cl_git_pass(git_commit_lookup(&revert_commit, repo, &revert_oid));
+
+ cl_git_pass(git_revert_commit(&index, repo, revert_commit, head_commit, 0, NULL));
+ cl_assert(merge_test_index(index, merge_index_entries, 4));
+
+ git_commit_free(revert_commit);
+ git_commit_free(head_commit);
+ git_index_free(index);
+}
+
+void test_revert_bare__conflicts(void)
+{
+ git_reference *head_ref;
+ git_commit *head_commit, *revert_commit;
+ git_oid revert_oid;
+ git_index *index;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "7731926a337c4eaba1e2187d90ebfa0a93659382", 1, "file1.txt" },
+ { 0100644, "4b8fcff56437e60f58e9a6bc630dd242ebf6ea2c", 2, "file1.txt" },
+ { 0100644, "3a3ef367eaf3fe79effbfb0a56b269c04c2b59fe", 3, "file1.txt" },
+ { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" },
+ { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" },
+ { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" },
+ };
+
+ git_oid_fromstr(&revert_oid, "72333f47d4e83616630ff3b0ffe4c0faebcc3c45");
+
+ cl_git_pass(git_repository_head(&head_ref, repo));
+ cl_git_pass(git_reference_peel((git_object **)&head_commit, head_ref, GIT_OBJ_COMMIT));
+
+ cl_git_pass(git_commit_lookup(&revert_commit, repo, &revert_oid));
+ cl_git_pass(git_revert_commit(&index, repo, revert_commit, head_commit, 0, NULL));
+
+ cl_assert(git_index_has_conflicts(index));
+ cl_assert(merge_test_index(index, merge_index_entries, 6));
+
+ git_commit_free(revert_commit);
+ git_commit_free(head_commit);
+ git_reference_free(head_ref);
+ git_index_free(index);
+}
+
+void test_revert_bare__orphan(void)
+{
+ git_commit *head_commit, *revert_commit;
+ git_oid head_oid, revert_oid;
+ git_index *index;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "296a6d3be1dff05c5d1f631d2459389fa7b619eb", 0, "file-mainline.txt" },
+ };
+
+ git_oid_fromstr(&head_oid, "39467716290f6df775a91cdb9a4eb39295018145");
+ cl_git_pass(git_commit_lookup(&head_commit, repo, &head_oid));
+
+ git_oid_fromstr(&revert_oid, "ebb03002cee5d66c7732dd06241119fe72ab96a5");
+ cl_git_pass(git_commit_lookup(&revert_commit, repo, &revert_oid));
+
+ cl_git_pass(git_revert_commit(&index, repo, revert_commit, head_commit, 0, NULL));
+ cl_assert(merge_test_index(index, merge_index_entries, 1));
+
+ git_commit_free(revert_commit);
+ git_commit_free(head_commit);
+ git_index_free(index);
+}
diff --git a/tests/revert/workdir.c b/tests/revert/workdir.c
new file mode 100644
index 000000000..e3d7e968a
--- /dev/null
+++ b/tests/revert/workdir.c
@@ -0,0 +1,577 @@
+#include "clar.h"
+#include "clar_libgit2.h"
+
+#include "buffer.h"
+#include "fileops.h"
+#include "git2/revert.h"
+
+#include "../merge/merge_helpers.h"
+
+#define TEST_REPO_PATH "revert"
+
+static git_repository *repo;
+static git_index *repo_index;
+
+// Fixture setup and teardown
+void test_revert_workdir__initialize(void)
+{
+ git_config *cfg;
+
+ repo = cl_git_sandbox_init(TEST_REPO_PATH);
+ git_repository_index(&repo_index, repo);
+
+ /* Ensure that the user's merge.conflictstyle doesn't interfere */
+ cl_git_pass(git_repository_config(&cfg, repo));
+ cl_git_pass(git_config_set_string(cfg, "merge.conflictstyle", "merge"));
+ git_config_free(cfg);
+}
+
+void test_revert_workdir__cleanup(void)
+{
+ git_index_free(repo_index);
+ cl_git_sandbox_cleanup();
+}
+
+/* git reset --hard 72333f47d4e83616630ff3b0ffe4c0faebcc3c45
+ * git revert --no-commit d1d403d22cbe24592d725f442835cf46fe60c8ac */
+void test_revert_workdir__automerge(void)
+{
+ git_commit *head, *commit;
+ git_oid head_oid, revert_oid;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "caf99de3a49827117bb66721010eac461b06a80c", 0, "file1.txt" },
+ { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" },
+ { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" },
+ { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" },
+ };
+
+ git_oid_fromstr(&head_oid, "72333f47d4e83616630ff3b0ffe4c0faebcc3c45");
+ cl_git_pass(git_commit_lookup(&head, repo, &head_oid));
+ cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL, NULL));
+
+ git_oid_fromstr(&revert_oid, "d1d403d22cbe24592d725f442835cf46fe60c8ac");
+ cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid));
+ cl_git_pass(git_revert(repo, commit, NULL));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 4));
+
+ git_commit_free(commit);
+ git_commit_free(head);
+}
+
+/* git revert --no-commit 72333f47d4e83616630ff3b0ffe4c0faebcc3c45 */
+void test_revert_workdir__conflicts(void)
+{
+ git_reference *head_ref;
+ git_commit *head, *commit;
+ git_oid revert_oid;
+ git_buf conflicting_buf = GIT_BUF_INIT, mergemsg_buf = GIT_BUF_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "7731926a337c4eaba1e2187d90ebfa0a93659382", 1, "file1.txt" },
+ { 0100644, "4b8fcff56437e60f58e9a6bc630dd242ebf6ea2c", 2, "file1.txt" },
+ { 0100644, "3a3ef367eaf3fe79effbfb0a56b269c04c2b59fe", 3, "file1.txt" },
+ { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" },
+ { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" },
+ { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" },
+ };
+
+ git_oid_fromstr(&revert_oid, "72333f47d4e83616630ff3b0ffe4c0faebcc3c45");
+
+ cl_git_pass(git_repository_head(&head_ref, repo));
+ cl_git_pass(git_reference_peel((git_object **)&head, head_ref, GIT_OBJ_COMMIT));
+ cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL, NULL));
+
+ cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid));
+ cl_git_pass(git_revert(repo, commit, NULL));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 6));
+
+ cl_git_pass(git_futils_readbuffer(&conflicting_buf,
+ TEST_REPO_PATH "/file1.txt"));
+ cl_assert(strcmp(conflicting_buf.ptr, "!File one!\n" \
+ "!File one!\n" \
+ "File one!\n" \
+ "File one\n" \
+ "File one\n" \
+ "File one\n" \
+ "File one\n" \
+ "File one\n" \
+ "File one\n" \
+ "File one\n" \
+ "<<<<<<< HEAD\n" \
+ "File one!\n" \
+ "!File one!\n" \
+ "!File one!\n" \
+ "!File one!\n" \
+ "=======\n" \
+ "File one\n" \
+ "File one\n" \
+ "File one\n" \
+ "File one\n" \
+ ">>>>>>> parent of 72333f4... automergeable changes\n") == 0);
+
+ cl_assert(git_path_exists(TEST_REPO_PATH "/.git/MERGE_MSG"));
+ cl_git_pass(git_futils_readbuffer(&mergemsg_buf,
+ TEST_REPO_PATH "/.git/MERGE_MSG"));
+ cl_assert(strcmp(mergemsg_buf.ptr,
+ "Revert \"automergeable changes\"\n" \
+ "\n" \
+ "This reverts commit 72333f47d4e83616630ff3b0ffe4c0faebcc3c45.\n"
+ "\n" \
+ "Conflicts:\n" \
+ "\tfile1.txt\n") == 0);
+
+ git_commit_free(commit);
+ git_commit_free(head);
+ git_reference_free(head_ref);
+ git_buf_free(&mergemsg_buf);
+ git_buf_free(&conflicting_buf);
+}
+
+/* git reset --hard 39467716290f6df775a91cdb9a4eb39295018145
+ * git revert --no-commit ebb03002cee5d66c7732dd06241119fe72ab96a5
+*/
+void test_revert_workdir__orphan(void)
+{
+ git_commit *head, *commit;
+ git_oid head_oid, revert_oid;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "296a6d3be1dff05c5d1f631d2459389fa7b619eb", 0, "file-mainline.txt" },
+ };
+
+ git_oid_fromstr(&head_oid, "39467716290f6df775a91cdb9a4eb39295018145");
+ cl_git_pass(git_commit_lookup(&head, repo, &head_oid));
+ cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL, NULL));
+
+ git_oid_fromstr(&revert_oid, "ebb03002cee5d66c7732dd06241119fe72ab96a5");
+ cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid));
+ cl_git_pass(git_revert(repo, commit, NULL));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 1));
+
+ git_commit_free(commit);
+ git_commit_free(head);
+}
+
+/*
+ * revert the same commit twice (when the first reverts cleanly):
+ *
+ * git revert 2d440f2
+ * git revert 2d440f2
+ */
+void test_revert_workdir__again(void)
+{
+ git_reference *head_ref;
+ git_commit *orig_head;
+ git_tree *reverted_tree;
+ git_oid reverted_tree_oid, reverted_commit_oid;
+ git_signature *signature;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "7731926a337c4eaba1e2187d90ebfa0a93659382", 0, "file1.txt" },
+ { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" },
+ { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" },
+ { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" },
+ };
+
+ cl_git_pass(git_repository_head(&head_ref, repo));
+ cl_git_pass(git_reference_peel((git_object **)&orig_head, head_ref, GIT_OBJ_COMMIT));
+ cl_git_pass(git_reset(repo, (git_object *)orig_head, GIT_RESET_HARD, NULL, NULL));
+
+ cl_git_pass(git_revert(repo, orig_head, NULL));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 4));
+
+ cl_git_pass(git_index_write_tree(&reverted_tree_oid, repo_index));
+ cl_git_pass(git_tree_lookup(&reverted_tree, repo, &reverted_tree_oid));
+
+ cl_git_pass(git_signature_new(&signature, "Reverter", "reverter@example.org", time(NULL), 0));
+ cl_git_pass(git_commit_create(&reverted_commit_oid, repo, "HEAD", signature, signature, NULL, "Reverted!", reverted_tree, 1, (const git_commit **)&orig_head));
+
+ cl_git_pass(git_revert(repo, orig_head, NULL));
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 4));
+
+ git_signature_free(signature);
+ git_tree_free(reverted_tree);
+ git_commit_free(orig_head);
+ git_reference_free(head_ref);
+}
+
+/* git reset --hard 72333f47d4e83616630ff3b0ffe4c0faebcc3c45
+ * git revert --no-commit d1d403d22cbe24592d725f442835cf46fe60c8ac */
+void test_revert_workdir__again_after_automerge(void)
+{
+ git_commit *head, *commit;
+ git_tree *reverted_tree;
+ git_oid head_oid, revert_oid, reverted_tree_oid, reverted_commit_oid;
+ git_signature *signature;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "caf99de3a49827117bb66721010eac461b06a80c", 0, "file1.txt" },
+ { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" },
+ { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" },
+ { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" },
+ };
+
+ struct merge_index_entry second_revert_entries[] = {
+ { 0100644, "3a3ef367eaf3fe79effbfb0a56b269c04c2b59fe", 1, "file1.txt" },
+ { 0100644, "caf99de3a49827117bb66721010eac461b06a80c", 2, "file1.txt" },
+ { 0100644, "747726e021bc5f44b86de60e3032fd6f9f1b8383", 3, "file1.txt" },
+ { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" },
+ { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" },
+ { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" },
+ };
+
+ git_oid_fromstr(&head_oid, "72333f47d4e83616630ff3b0ffe4c0faebcc3c45");
+ cl_git_pass(git_commit_lookup(&head, repo, &head_oid));
+ cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL, NULL));
+
+ git_oid_fromstr(&revert_oid, "d1d403d22cbe24592d725f442835cf46fe60c8ac");
+ cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid));
+ cl_git_pass(git_revert(repo, commit, NULL));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 4));
+
+ cl_git_pass(git_index_write_tree(&reverted_tree_oid, repo_index));
+ cl_git_pass(git_tree_lookup(&reverted_tree, repo, &reverted_tree_oid));
+
+ cl_git_pass(git_signature_new(&signature, "Reverter", "reverter@example.org", time(NULL), 0));
+ cl_git_pass(git_commit_create(&reverted_commit_oid, repo, "HEAD", signature, signature, NULL, "Reverted!", reverted_tree, 1, (const git_commit **)&head));
+
+ cl_git_pass(git_revert(repo, commit, NULL));
+ cl_assert(merge_test_index(repo_index, second_revert_entries, 6));
+
+ git_signature_free(signature);
+ git_tree_free(reverted_tree);
+ git_commit_free(commit);
+ git_commit_free(head);
+}
+
+/*
+ * revert the same commit twice (when the first reverts cleanly):
+ *
+ * git revert 2d440f2
+ * git revert 2d440f2
+ */
+void test_revert_workdir__again_after_edit(void)
+{
+ git_reference *head_ref;
+ git_commit *orig_head, *commit;
+ git_tree *reverted_tree;
+ git_oid orig_head_oid, revert_oid, reverted_tree_oid, reverted_commit_oid;
+ git_signature *signature;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "3721552e06c4bdc7d478e0674e6304888545d5fd", 0, "file1.txt" },
+ { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" },
+ { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" },
+ { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" },
+ };
+
+ cl_git_pass(git_repository_head(&head_ref, repo));
+
+ cl_git_pass(git_oid_fromstr(&orig_head_oid, "399fb3aba3d9d13f7d40a9254ce4402067ef3149"));
+ cl_git_pass(git_commit_lookup(&orig_head, repo, &orig_head_oid));
+ cl_git_pass(git_reset(repo, (git_object *)orig_head, GIT_RESET_HARD, NULL, NULL));
+
+ cl_git_pass(git_oid_fromstr(&revert_oid, "2d440f2b3147d3dc7ad1085813478d6d869d5a4d"));
+ cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid));
+
+ cl_git_pass(git_revert(repo, commit, NULL));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 4));
+
+ cl_git_pass(git_index_write_tree(&reverted_tree_oid, repo_index));
+ cl_git_pass(git_tree_lookup(&reverted_tree, repo, &reverted_tree_oid));
+
+ cl_git_pass(git_signature_new(&signature, "Reverter", "reverter@example.org", time(NULL), 0));
+ cl_git_pass(git_commit_create(&reverted_commit_oid, repo, "HEAD", signature, signature, NULL, "Reverted!", reverted_tree, 1, (const git_commit **)&orig_head));
+
+ cl_git_pass(git_revert(repo, commit, NULL));
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 4));
+
+ git_signature_free(signature);
+ git_tree_free(reverted_tree);
+ git_commit_free(commit);
+ git_commit_free(orig_head);
+ git_reference_free(head_ref);
+}
+
+/*
+ * revert the same commit twice (when the first reverts cleanly):
+ *
+ * git reset --hard e34ef1a
+ * git revert 71eb9c2
+ */
+void test_revert_workdir__again_after_edit_two(void)
+{
+ git_buf diff_buf = GIT_BUF_INIT;
+ git_config *config;
+ git_oid head_commit_oid, revert_commit_oid;
+ git_commit *head_commit, *revert_commit;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "1ff0c423042b46cb1d617b81efb715defbe8054d", 0, ".gitattributes" },
+ { 0100644, "1bc915c5cb7185a9438de28a7b1a7dfe8c01ee7f", 0, ".gitignore" },
+ { 0100644, "a8c86221b400b836010567cc3593db6e96c1a83a", 1, "file.txt" },
+ { 0100644, "46ff0854663aeb2182b9838c8da68e33ac23bc1e", 2, "file.txt" },
+ { 0100644, "21a96a98ed84d45866e1de6e266fd3a61a4ae9dc", 3, "file.txt" },
+ };
+
+ cl_git_pass(git_repository_config(&config, repo));
+ cl_git_pass(git_config_set_bool(config, "core.autocrlf", 0));
+
+ cl_git_pass(git_oid_fromstr(&head_commit_oid, "e34ef1afe54eb526fd92eec66084125f340f1d65"));
+ cl_git_pass(git_commit_lookup(&head_commit, repo, &head_commit_oid));
+ cl_git_pass(git_reset(repo, (git_object *)head_commit, GIT_RESET_HARD, NULL, NULL));
+
+ cl_git_pass(git_oid_fromstr(&revert_commit_oid, "71eb9c2b53dbbf3c45fb28b27c850db4b7fb8011"));
+ cl_git_pass(git_commit_lookup(&revert_commit, repo, &revert_commit_oid));
+
+ cl_git_pass(git_revert(repo, revert_commit, NULL));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 5));
+
+ cl_git_pass(git_futils_readbuffer(&diff_buf, "revert/file.txt"));
+ cl_assert(strcmp(diff_buf.ptr, "a\n" \
+ "<<<<<<< HEAD\n" \
+ "=======\n" \
+ "a\n" \
+ ">>>>>>> parent of 71eb9c2... revert me\n" \
+ "a\n" \
+ "a\n" \
+ "a\n" \
+ "a\n" \
+ "ab\n") == 0);
+
+ git_commit_free(revert_commit);
+ git_commit_free(head_commit);
+ git_config_free(config);
+ git_buf_free(&diff_buf);
+}
+
+/* git reset --hard 72333f47d4e83616630ff3b0ffe4c0faebcc3c45
+ * git revert --no-commit d1d403d22cbe24592d725f442835cf46fe60c8ac */
+void test_revert_workdir__conflict_use_ours(void)
+{
+ git_commit *head, *commit;
+ git_oid head_oid, revert_oid;
+ git_revert_options opts = GIT_REVERT_OPTIONS_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "caf99de3a49827117bb66721010eac461b06a80c", 0, "file1.txt" },
+ { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" },
+ { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" },
+ { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" },
+ };
+
+ struct merge_index_entry merge_filesystem_entries[] = {
+ { 0100644, "caf99de3a49827117bb66721010eac461b06a80c", 0, "file1.txt" },
+ { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" },
+ { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" },
+ { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" },
+ };
+
+ opts.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS;
+
+ git_oid_fromstr(&head_oid, "72333f47d4e83616630ff3b0ffe4c0faebcc3c45");
+ cl_git_pass(git_commit_lookup(&head, repo, &head_oid));
+ cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL, NULL));
+
+ git_oid_fromstr(&revert_oid, "d1d403d22cbe24592d725f442835cf46fe60c8ac");
+ cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid));
+ cl_git_pass(git_revert(repo, commit, &opts));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 4));
+ cl_assert(merge_test_workdir(repo, merge_filesystem_entries, 4));
+
+ git_commit_free(commit);
+ git_commit_free(head);
+}
+
+/* git reset --hard cef56612d71a6af8d8015691e4865f7fece905b5
+ * git revert --no-commit 55568c8de5322ff9a95d72747a239cdb64a19965
+ */
+void test_revert_workdir__rename_1_of_2(void)
+{
+ git_commit *head, *commit;
+ git_oid head_oid, revert_oid;
+ git_revert_options opts = GIT_REVERT_OPTIONS_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "747726e021bc5f44b86de60e3032fd6f9f1b8383", 0, "file1.txt" },
+ { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" },
+ { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" },
+ { 0100644, "55acf326a69f0aab7a974ec53ffa55a50bcac14e", 3, "file4.txt" },
+ { 0100644, "55acf326a69f0aab7a974ec53ffa55a50bcac14e", 1, "file5.txt" },
+ { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 2, "file6.txt" },
+ };
+
+ opts.merge_opts.flags |= GIT_MERGE_TREE_FIND_RENAMES;
+ opts.merge_opts.rename_threshold = 50;
+
+ git_oid_fromstr(&head_oid, "cef56612d71a6af8d8015691e4865f7fece905b5");
+ cl_git_pass(git_commit_lookup(&head, repo, &head_oid));
+ cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL, NULL));
+
+ git_oid_fromstr(&revert_oid, "55568c8de5322ff9a95d72747a239cdb64a19965");
+ cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid));
+ cl_git_pass(git_revert(repo, commit, &opts));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 6));
+
+ git_commit_free(commit);
+ git_commit_free(head);
+}
+
+/* git reset --hard 55568c8de5322ff9a95d72747a239cdb64a19965
+ * git revert --no-commit HEAD~1 */
+void test_revert_workdir__rename(void)
+{
+ git_commit *head, *commit;
+ git_oid head_oid, revert_oid;
+ git_revert_options opts = GIT_REVERT_OPTIONS_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "55acf326a69f0aab7a974ec53ffa55a50bcac14e", 1, "file4.txt" },
+ { 0100644, "55acf326a69f0aab7a974ec53ffa55a50bcac14e", 2, "file5.txt" },
+ };
+
+ struct merge_name_entry merge_name_entries[] = {
+ { "file4.txt", "file5.txt", "" },
+ };
+
+ opts.merge_opts.flags |= GIT_MERGE_TREE_FIND_RENAMES;
+ opts.merge_opts.rename_threshold = 50;
+
+ git_oid_fromstr(&head_oid, "55568c8de5322ff9a95d72747a239cdb64a19965");
+ cl_git_pass(git_commit_lookup(&head, repo, &head_oid));
+ cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL, NULL));
+
+ git_oid_fromstr(&revert_oid, "0aa8c7e40d342fff78d60b29a4ba8e993ed79c51");
+ cl_git_pass(git_commit_lookup(&commit, repo, &revert_oid));
+ cl_git_pass(git_revert(repo, commit, &opts));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 2));
+ cl_assert(merge_test_names(repo_index, merge_name_entries, 1));
+
+ git_commit_free(commit);
+ git_commit_free(head);
+}
+
+/* git revert --no-commit HEAD */
+void test_revert_workdir__head(void)
+{
+ git_reference *head;
+ git_commit *commit;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "7731926a337c4eaba1e2187d90ebfa0a93659382", 0, "file1.txt" },
+ { 0100644, "0ab09ea6d4c3634bdf6c221626d8b6f7dd890767", 0, "file2.txt" },
+ { 0100644, "f4e107c230d08a60fb419d19869f1f282b272d9c", 0, "file3.txt" },
+ { 0100644, "0f5bfcf58c558d865da6be0281d7795993646cee", 0, "file6.txt" },
+ };
+
+ /* HEAD is 2d440f2b3147d3dc7ad1085813478d6d869d5a4d */
+ cl_git_pass(git_repository_head(&head, repo));
+ cl_git_pass(git_reference_peel((git_object **)&commit, head, GIT_OBJ_COMMIT));
+ cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL, NULL));
+ cl_git_pass(git_revert(repo, commit, NULL));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 4));
+ cl_assert(merge_test_workdir(repo, merge_index_entries, 4));
+
+ git_reference_free(head);
+ git_commit_free(commit);
+}
+
+void test_revert_workdir__nonmerge_fails_mainline_specified(void)
+{
+ git_reference *head;
+ git_commit *commit;
+ git_revert_options opts = GIT_REVERT_OPTIONS_INIT;
+
+ cl_git_pass(git_repository_head(&head, repo));
+ cl_git_pass(git_reference_peel((git_object **)&commit, head, GIT_OBJ_COMMIT));
+
+ opts.mainline = 1;
+ cl_must_fail(git_revert(repo, commit, &opts));
+ cl_assert(!git_path_exists(TEST_REPO_PATH "/.git/MERGE_MSG"));
+ cl_assert(!git_path_exists(TEST_REPO_PATH "/.git/REVERT_HEAD"));
+
+ git_reference_free(head);
+ git_commit_free(commit);
+}
+
+/* git reset --hard 5acdc74af27172ec491d213ee36cea7eb9ef2579
+ * git revert HEAD */
+void test_revert_workdir__merge_fails_without_mainline_specified(void)
+{
+ git_commit *head;
+ git_oid head_oid;
+
+ git_oid_fromstr(&head_oid, "5acdc74af27172ec491d213ee36cea7eb9ef2579");
+ cl_git_pass(git_commit_lookup(&head, repo, &head_oid));
+ cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL, NULL));
+
+ cl_must_fail(git_revert(repo, head, NULL));
+ cl_assert(!git_path_exists(TEST_REPO_PATH "/.git/MERGE_MSG"));
+ cl_assert(!git_path_exists(TEST_REPO_PATH "/.git/REVERT_HEAD"));
+
+ git_commit_free(head);
+}
+
+/* git reset --hard 5acdc74af27172ec491d213ee36cea7eb9ef2579
+ * git revert HEAD -m1 --no-commit */
+void test_revert_workdir__merge_first_parent(void)
+{
+ git_commit *head;
+ git_oid head_oid;
+ git_revert_options opts = GIT_REVERT_OPTIONS_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "296a6d3be1dff05c5d1f631d2459389fa7b619eb", 0, "file-mainline.txt" },
+ { 0100644, "0cdb66192ee192f70f891f05a47636057420e871", 0, "file1.txt" },
+ { 0100644, "73ec36fa120f8066963a0bc9105bb273dbd903d7", 0, "file2.txt" },
+ };
+
+ opts.mainline = 1;
+
+ git_oid_fromstr(&head_oid, "5acdc74af27172ec491d213ee36cea7eb9ef2579");
+ cl_git_pass(git_commit_lookup(&head, repo, &head_oid));
+ cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL, NULL));
+
+ cl_git_pass(git_revert(repo, head, &opts));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 3));
+
+ git_commit_free(head);
+}
+
+void test_revert_workdir__merge_second_parent(void)
+{
+ git_commit *head;
+ git_oid head_oid;
+ git_revert_options opts = GIT_REVERT_OPTIONS_INIT;
+
+ struct merge_index_entry merge_index_entries[] = {
+ { 0100644, "33c6fd981c49a2abf2971482089350bfc5cda8ea", 0, "file-branch.txt" },
+ { 0100644, "0cdb66192ee192f70f891f05a47636057420e871", 0, "file1.txt" },
+ { 0100644, "73ec36fa120f8066963a0bc9105bb273dbd903d7", 0, "file2.txt" },
+ };
+
+ opts.mainline = 2;
+
+ git_oid_fromstr(&head_oid, "5acdc74af27172ec491d213ee36cea7eb9ef2579");
+ cl_git_pass(git_commit_lookup(&head, repo, &head_oid));
+ cl_git_pass(git_reset(repo, (git_object *)head, GIT_RESET_HARD, NULL, NULL));
+
+ cl_git_pass(git_revert(repo, head, &opts));
+
+ cl_assert(merge_test_index(repo_index, merge_index_entries, 3));
+
+ git_commit_free(head);
+}
diff --git a/tests/revwalk/basic.c b/tests/revwalk/basic.c
new file mode 100644
index 000000000..b015db18b
--- /dev/null
+++ b/tests/revwalk/basic.c
@@ -0,0 +1,422 @@
+#include "clar_libgit2.h"
+
+/*
+ * a4a7dce [0] Merge branch 'master' into br2
+ |\
+ | * 9fd738e [1] a fourth commit
+ | * 4a202b3 [2] a third commit
+ * | c47800c [3] branch commit one
+ |/
+ * 5b5b025 [5] another commit
+ * 8496071 [4] testing
+*/
+static const char *commit_head = "a4a7dce85cf63874e984719f4fdd239f5145052f";
+
+static const char *commit_ids[] = {
+ "a4a7dce85cf63874e984719f4fdd239f5145052f", /* 0 */
+ "9fd738e8f7967c078dceed8190330fc8648ee56a", /* 1 */
+ "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", /* 2 */
+ "c47800c7266a2be04c571c04d5a6614691ea99bd", /* 3 */
+ "8496071c1b46c854b31185ea97743be6a8774479", /* 4 */
+ "5b5b025afb0b4c913b4c338a42934a3863bf3644", /* 5 */
+};
+
+/* Careful: there are two possible topological sorts */
+static const int commit_sorting_topo[][6] = {
+ {0, 1, 2, 3, 5, 4}, {0, 3, 1, 2, 5, 4}
+};
+
+static const int commit_sorting_time[][6] = {
+ {0, 3, 1, 2, 5, 4}
+};
+
+static const int commit_sorting_topo_reverse[][6] = {
+ {4, 5, 3, 2, 1, 0}, {4, 5, 2, 1, 3, 0}
+};
+
+static const int commit_sorting_time_reverse[][6] = {
+ {4, 5, 2, 1, 3, 0}
+};
+
+static const int commit_sorting_segment[][6] = {
+ {1, 2, -1, -1, -1, -1}
+};
+
+#define commit_count 6
+static const int result_bytes = 24;
+
+
+static int get_commit_index(git_oid *raw_oid)
+{
+ int i;
+ char oid[40];
+
+ git_oid_fmt(oid, raw_oid);
+
+ for (i = 0; i < commit_count; ++i)
+ if (memcmp(oid, commit_ids[i], 40) == 0)
+ return i;
+
+ return -1;
+}
+
+static int test_walk_only(git_revwalk *walk,
+ const int possible_results[][commit_count], int results_count)
+{
+ git_oid oid;
+ int i;
+ int result_array[commit_count];
+
+ for (i = 0; i < commit_count; ++i)
+ result_array[i] = -1;
+
+ i = 0;
+ while (git_revwalk_next(&oid, walk) == 0) {
+ result_array[i++] = get_commit_index(&oid);
+ /*{
+ char str[41];
+ git_oid_fmt(str, &oid);
+ str[40] = 0;
+ printf(" %d) %s\n", i, str);
+ }*/
+ }
+
+ for (i = 0; i < results_count; ++i)
+ if (memcmp(possible_results[i],
+ result_array, result_bytes) == 0)
+ return 0;
+
+ return GIT_ERROR;
+}
+
+static int test_walk(git_revwalk *walk, const git_oid *root,
+ int flags, const int possible_results[][6], int results_count)
+{
+ git_revwalk_sorting(walk, flags);
+ git_revwalk_push(walk, root);
+
+ return test_walk_only(walk, possible_results, results_count);
+}
+
+static git_repository *_repo = NULL;
+static git_revwalk *_walk = NULL;
+static const char *_fixture = NULL;
+
+void test_revwalk_basic__initialize(void)
+{
+}
+
+void test_revwalk_basic__cleanup(void)
+{
+ git_revwalk_free(_walk);
+
+ if (_fixture)
+ cl_git_sandbox_cleanup();
+ else
+ git_repository_free(_repo);
+
+ _fixture = NULL;
+ _repo = NULL;
+ _walk = NULL;
+}
+
+static void revwalk_basic_setup_walk(const char *fixture)
+{
+ if (fixture) {
+ _fixture = fixture;
+ _repo = cl_git_sandbox_init(fixture);
+ } else {
+ cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git")));
+ }
+
+ cl_git_pass(git_revwalk_new(&_walk, _repo));
+}
+
+void test_revwalk_basic__sorting_modes(void)
+{
+ git_oid id;
+
+ revwalk_basic_setup_walk(NULL);
+
+ git_oid_fromstr(&id, commit_head);
+
+ cl_git_pass(test_walk(_walk, &id, GIT_SORT_TIME, commit_sorting_time, 1));
+ cl_git_pass(test_walk(_walk, &id, GIT_SORT_TOPOLOGICAL, commit_sorting_topo, 2));
+ cl_git_pass(test_walk(_walk, &id, GIT_SORT_TIME | GIT_SORT_REVERSE, commit_sorting_time_reverse, 1));
+ cl_git_pass(test_walk(_walk, &id, GIT_SORT_TOPOLOGICAL | GIT_SORT_REVERSE, commit_sorting_topo_reverse, 2));
+}
+
+void test_revwalk_basic__glob_heads(void)
+{
+ int i = 0;
+ git_oid oid;
+
+ revwalk_basic_setup_walk(NULL);
+
+ cl_git_pass(git_revwalk_push_glob(_walk, "heads"));
+
+ while (git_revwalk_next(&oid, _walk) == 0) {
+ i++;
+ }
+
+ /* git log --branches --oneline | wc -l => 14 */
+ cl_assert_equal_i(i, 14);
+}
+
+void test_revwalk_basic__glob_heads_with_invalid(void)
+{
+ int i;
+ git_oid oid;
+
+ revwalk_basic_setup_walk("testrepo");
+
+ cl_git_mkfile("testrepo/.git/refs/heads/garbage", "not-a-ref");
+ cl_git_pass(git_revwalk_push_glob(_walk, "heads"));
+
+ for (i = 0; !git_revwalk_next(&oid, _walk); ++i)
+ /* walking */;
+
+ /* git log --branches --oneline | wc -l => 16 */
+ cl_assert_equal_i(17, i);
+}
+
+void test_revwalk_basic__push_head(void)
+{
+ int i = 0;
+ git_oid oid;
+
+ revwalk_basic_setup_walk(NULL);
+
+ cl_git_pass(git_revwalk_push_head(_walk));
+
+ while (git_revwalk_next(&oid, _walk) == 0) {
+ i++;
+ }
+
+ /* git log HEAD --oneline | wc -l => 7 */
+ cl_assert_equal_i(i, 7);
+}
+
+void test_revwalk_basic__push_head_hide_ref(void)
+{
+ int i = 0;
+ git_oid oid;
+
+ revwalk_basic_setup_walk(NULL);
+
+ cl_git_pass(git_revwalk_push_head(_walk));
+ cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/packed-test"));
+
+ while (git_revwalk_next(&oid, _walk) == 0) {
+ i++;
+ }
+
+ /* git log HEAD --oneline --not refs/heads/packed-test | wc -l => 4 */
+ cl_assert_equal_i(i, 4);
+}
+
+void test_revwalk_basic__push_head_hide_ref_nobase(void)
+{
+ int i = 0;
+ git_oid oid;
+
+ revwalk_basic_setup_walk(NULL);
+
+ cl_git_pass(git_revwalk_push_head(_walk));
+ cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/packed"));
+
+ while (git_revwalk_next(&oid, _walk) == 0) {
+ i++;
+ }
+
+ /* git log HEAD --oneline --not refs/heads/packed | wc -l => 7 */
+ cl_assert_equal_i(i, 7);
+}
+
+/*
+* $ git rev-list HEAD 5b5b02 ^refs/heads/packed-test
+* a65fedf39aefe402d3bb6e24df4d4f5fe4547750
+* be3563ae3f795b2b4353bcce3a527ad0a4f7f644
+* c47800c7266a2be04c571c04d5a6614691ea99bd
+* 9fd738e8f7967c078dceed8190330fc8648ee56a
+
+* $ git log HEAD 5b5b02 --oneline --not refs/heads/packed-test | wc -l => 4
+* a65fedf
+* be3563a Merge branch 'br2'
+* c47800c branch commit one
+* 9fd738e a fourth commit
+*/
+void test_revwalk_basic__multiple_push_1(void)
+{
+ int i = 0;
+ git_oid oid;
+
+ revwalk_basic_setup_walk(NULL);
+
+ cl_git_pass(git_revwalk_push_head(_walk));
+
+ cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/packed-test"));
+
+ cl_git_pass(git_oid_fromstr(&oid, "5b5b025afb0b4c913b4c338a42934a3863bf3644"));
+ cl_git_pass(git_revwalk_push(_walk, &oid));
+
+ while (git_revwalk_next(&oid, _walk) == 0)
+ i++;
+
+ cl_assert_equal_i(i, 4);
+}
+
+/*
+* Difference between test_revwalk_basic__multiple_push_1 and
+* test_revwalk_basic__multiple_push_2 is in the order reference
+* refs/heads/packed-test and commit 5b5b02 are pushed.
+* revwalk should return same commits in both the tests.
+
+* $ git rev-list 5b5b02 HEAD ^refs/heads/packed-test
+* a65fedf39aefe402d3bb6e24df4d4f5fe4547750
+* be3563ae3f795b2b4353bcce3a527ad0a4f7f644
+* c47800c7266a2be04c571c04d5a6614691ea99bd
+* 9fd738e8f7967c078dceed8190330fc8648ee56a
+
+* $ git log 5b5b02 HEAD --oneline --not refs/heads/packed-test | wc -l => 4
+* a65fedf
+* be3563a Merge branch 'br2'
+* c47800c branch commit one
+* 9fd738e a fourth commit
+*/
+void test_revwalk_basic__multiple_push_2(void)
+{
+ int i = 0;
+ git_oid oid;
+
+ revwalk_basic_setup_walk(NULL);
+
+ cl_git_pass(git_oid_fromstr(&oid, "5b5b025afb0b4c913b4c338a42934a3863bf3644"));
+ cl_git_pass(git_revwalk_push(_walk, &oid));
+
+ cl_git_pass(git_revwalk_hide_ref(_walk, "refs/heads/packed-test"));
+
+ cl_git_pass(git_revwalk_push_head(_walk));
+
+ while (git_revwalk_next(&oid, _walk) == 0)
+ i++;
+
+ cl_assert_equal_i(i, 4);
+}
+
+void test_revwalk_basic__disallow_non_commit(void)
+{
+ git_oid oid;
+
+ revwalk_basic_setup_walk(NULL);
+
+ cl_git_pass(git_oid_fromstr(&oid, "521d87c1ec3aef9824daf6d96cc0ae3710766d91"));
+ cl_git_fail(git_revwalk_push(_walk, &oid));
+}
+
+void test_revwalk_basic__push_range(void)
+{
+ revwalk_basic_setup_walk(NULL);
+
+ git_revwalk_reset(_walk);
+ git_revwalk_sorting(_walk, 0);
+ cl_git_pass(git_revwalk_push_range(_walk, "9fd738e~2..9fd738e"));
+ cl_git_pass(test_walk_only(_walk, commit_sorting_segment, 1));
+}
+
+void test_revwalk_basic__push_mixed(void)
+{
+ git_oid oid;
+ int i = 0;
+
+ revwalk_basic_setup_walk(NULL);
+
+ git_revwalk_reset(_walk);
+ git_revwalk_sorting(_walk, 0);
+ cl_git_pass(git_revwalk_push_glob(_walk, "tags"));
+
+ while (git_revwalk_next(&oid, _walk) == 0) {
+ i++;
+ }
+
+ /* git rev-list --count --glob=tags #=> 9 */
+ cl_assert_equal_i(9, i);
+}
+
+void test_revwalk_basic__push_all(void)
+{
+ git_oid oid;
+ int i = 0;
+
+ revwalk_basic_setup_walk(NULL);
+
+ git_revwalk_reset(_walk);
+ git_revwalk_sorting(_walk, 0);
+ cl_git_pass(git_revwalk_push_glob(_walk, "*"));
+
+ while (git_revwalk_next(&oid, _walk) == 0) {
+ i++;
+ }
+
+ /* git rev-list --count --all #=> 15 */
+ cl_assert_equal_i(15, i);
+}
+
+/*
+* $ git rev-list br2 master e908
+* a65fedf39aefe402d3bb6e24df4d4f5fe4547750
+* e90810b8df3e80c413d903f631643c716887138d
+* 6dcf9bf7541ee10456529833502442f385010c3d
+* a4a7dce85cf63874e984719f4fdd239f5145052f
+* be3563ae3f795b2b4353bcce3a527ad0a4f7f644
+* c47800c7266a2be04c571c04d5a6614691ea99bd
+* 9fd738e8f7967c078dceed8190330fc8648ee56a
+* 4a202b346bb0fb0db7eff3cffeb3c70babbd2045
+* 5b5b025afb0b4c913b4c338a42934a3863bf3644
+* 8496071c1b46c854b31185ea97743be6a8774479
+*/
+
+void test_revwalk_basic__mimic_git_rev_list(void)
+{
+ git_oid oid;
+
+ revwalk_basic_setup_walk(NULL);
+ git_revwalk_sorting(_walk, GIT_SORT_TIME);
+
+ cl_git_pass(git_revwalk_push_ref(_walk, "refs/heads/br2"));
+ cl_git_pass(git_revwalk_push_ref(_walk, "refs/heads/master"));
+ cl_git_pass(git_oid_fromstr(&oid, "e90810b8df3e80c413d903f631643c716887138d"));
+ cl_git_pass(git_revwalk_push(_walk, &oid));
+
+ cl_git_pass(git_revwalk_next(&oid, _walk));
+ cl_assert(!git_oid_streq(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"));
+
+ cl_git_pass(git_revwalk_next(&oid, _walk));
+ cl_assert(!git_oid_streq(&oid, "e90810b8df3e80c413d903f631643c716887138d"));
+
+ cl_git_pass(git_revwalk_next(&oid, _walk));
+ cl_assert(!git_oid_streq(&oid, "6dcf9bf7541ee10456529833502442f385010c3d"));
+
+ cl_git_pass(git_revwalk_next(&oid, _walk));
+ cl_assert(!git_oid_streq(&oid, "a4a7dce85cf63874e984719f4fdd239f5145052f"));
+
+ cl_git_pass(git_revwalk_next(&oid, _walk));
+ cl_assert(!git_oid_streq(&oid, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"));
+
+ cl_git_pass(git_revwalk_next(&oid, _walk));
+ cl_assert(!git_oid_streq(&oid, "c47800c7266a2be04c571c04d5a6614691ea99bd"));
+
+ cl_git_pass(git_revwalk_next(&oid, _walk));
+ cl_assert(!git_oid_streq(&oid, "9fd738e8f7967c078dceed8190330fc8648ee56a"));
+
+ cl_git_pass(git_revwalk_next(&oid, _walk));
+ cl_assert(!git_oid_streq(&oid, "4a202b346bb0fb0db7eff3cffeb3c70babbd2045"));
+
+ cl_git_pass(git_revwalk_next(&oid, _walk));
+ cl_assert(!git_oid_streq(&oid, "5b5b025afb0b4c913b4c338a42934a3863bf3644"));
+
+ cl_git_pass(git_revwalk_next(&oid, _walk));
+ cl_assert(!git_oid_streq(&oid, "8496071c1b46c854b31185ea97743be6a8774479"));
+
+ cl_git_fail_with(git_revwalk_next(&oid, _walk), GIT_ITEROVER);
+}
diff --git a/tests/revwalk/hidecb.c b/tests/revwalk/hidecb.c
new file mode 100644
index 000000000..26ff183fa
--- /dev/null
+++ b/tests/revwalk/hidecb.c
@@ -0,0 +1,207 @@
+#include "clar_libgit2.h"
+/*
+* a4a7dce [0] Merge branch 'master' into br2
+|\
+| * 9fd738e [1] a fourth commit
+| * 4a202b3 [2] a third commit
+* | c47800c [3] branch commit one
+|/
+* 5b5b025 [5] another commit
+* 8496071 [4] testing
+*/
+static const char *commit_head = "a4a7dce85cf63874e984719f4fdd239f5145052f";
+
+static const char *commit_strs[] = {
+ "a4a7dce85cf63874e984719f4fdd239f5145052f", /* 0 */
+ "9fd738e8f7967c078dceed8190330fc8648ee56a", /* 1 */
+ "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", /* 2 */
+ "c47800c7266a2be04c571c04d5a6614691ea99bd", /* 3 */
+ "8496071c1b46c854b31185ea97743be6a8774479", /* 4 */
+ "5b5b025afb0b4c913b4c338a42934a3863bf3644", /* 5 */
+};
+
+#define commit_count 6
+
+static git_oid commit_ids[commit_count];
+static git_oid _head_id;
+static git_repository *_repo;
+
+
+void test_revwalk_hidecb__initialize(void)
+{
+ int i;
+
+ cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git")));
+ cl_git_pass(git_oid_fromstr(&_head_id, commit_head));
+
+ for (i = 0; i < commit_count; i++)
+ cl_git_pass(git_oid_fromstr(&commit_ids[i], commit_strs[i]));
+
+}
+
+void test_revwalk_hidecb__cleanup(void)
+{
+ git_repository_free(_repo);
+ _repo = NULL;
+}
+
+/* Hide all commits */
+static int hide_every_commit_cb(const git_oid *commit_id, void *data)
+{
+ GIT_UNUSED(commit_id);
+ GIT_UNUSED(data);
+
+ return 1;
+}
+
+/* Do not hide anything */
+static int hide_none_cb(const git_oid *commit_id, void *data)
+{
+ GIT_UNUSED(commit_id);
+ GIT_UNUSED(data);
+
+ return 0;
+}
+
+/* Hide some commits */
+static int hide_commit_cb(const git_oid *commit_id, void *data)
+{
+ GIT_UNUSED(commit_id);
+ GIT_UNUSED(data);
+
+ if (0 == git_oid_cmp(commit_id, &commit_ids[5]))
+ return 1;
+ else
+ return 0;
+
+}
+
+/* In payload data, pointer to a commit id is passed */
+static int hide_commit_use_payload_cb(const git_oid *commit_id, void *data)
+{
+ git_oid *hide_commit_id = data;
+ if (git_oid_cmp(commit_id, hide_commit_id) == 0)
+ return 1;
+ else
+ return 0;
+}
+
+void test_revwalk_hidecb__hide_all_cb(void)
+{
+ git_revwalk *walk;
+ git_oid id;
+
+ cl_git_pass(git_revwalk_new(&walk, _repo));
+ cl_git_pass(git_revwalk_add_hide_cb(walk, hide_every_commit_cb, NULL));
+ cl_git_pass(git_revwalk_push(walk, &_head_id));
+
+ /* First call to git_revwalk_next should return GIT_ITEROVER */
+ cl_assert_equal_i(GIT_ITEROVER, git_revwalk_next(&id, walk));
+
+ git_revwalk_free(walk);
+}
+
+
+void test_revwalk_hidecb__hide_none_cb(void)
+{
+ git_revwalk *walk;
+ int i, error;
+ git_oid id;
+
+ cl_git_pass(git_revwalk_new(&walk, _repo));
+ cl_git_pass(git_revwalk_add_hide_cb(walk, hide_none_cb, NULL));
+ cl_git_pass(git_revwalk_push(walk, &_head_id));
+
+ /* It should return all 6 commits */
+ i = 0;
+ while ((error = git_revwalk_next(&id, walk)) == 0)
+ i++;
+
+ cl_assert_equal_i(i, 6);
+ cl_assert_equal_i(error, GIT_ITEROVER);
+
+ git_revwalk_free(walk);
+}
+
+void test_revwalk_hidecb__add_hide_cb_multiple_times(void)
+{
+ git_revwalk *walk;
+
+ cl_git_pass(git_revwalk_new(&walk, _repo));
+ cl_git_pass(git_revwalk_add_hide_cb(walk, hide_every_commit_cb, NULL));
+ cl_git_fail(git_revwalk_add_hide_cb(walk, hide_every_commit_cb, NULL));
+
+ git_revwalk_free(walk);
+}
+
+void test_revwalk_hidecb__add_hide_cb_during_walking(void)
+{
+ git_revwalk *walk;
+ git_oid id;
+ int error;
+
+ cl_git_pass(git_revwalk_new(&walk, _repo));
+ cl_git_pass(git_revwalk_push(walk, &_head_id));
+
+ /* Start walking without adding hide callback */
+ cl_git_pass(git_revwalk_next(&id, walk));
+
+ /* Now add hide callback */
+ cl_git_pass(git_revwalk_add_hide_cb(walk, hide_none_cb, NULL));
+
+ /* walk should be reset */
+ error = git_revwalk_next(&id, walk);
+ cl_assert_equal_i(error, GIT_ITEROVER);
+
+ git_revwalk_free(walk);
+}
+
+void test_revwalk_hidecb__hide_some_commits(void)
+{
+ git_revwalk *walk;
+ git_oid id;
+ int i, error;
+
+ cl_git_pass(git_revwalk_new(&walk, _repo));
+ cl_git_pass(git_revwalk_push(walk, &_head_id));
+
+ /* Add hide callback */
+ cl_git_pass(git_revwalk_add_hide_cb(walk, hide_commit_cb, NULL));
+
+ i = 0;
+ while ((error = git_revwalk_next(&id, walk)) == 0) {
+ cl_assert_equal_i(git_oid_cmp(&id, &commit_ids[i]), 0);
+ i++;
+ }
+
+ cl_assert_equal_i(i, 4);
+ cl_assert_equal_i(error, GIT_ITEROVER);
+
+ git_revwalk_free(walk);
+}
+
+void test_revwalk_hidecb__test_payload(void)
+{
+ git_revwalk *walk;
+ git_oid id;
+ int i, error;
+
+ cl_git_pass(git_revwalk_new(&walk, _repo));
+ cl_git_pass(git_revwalk_push(walk, &_head_id));
+
+ /* Add hide callback, pass id of parent of initial commit as payload data */
+ cl_git_pass(git_revwalk_add_hide_cb(walk, hide_commit_use_payload_cb, &commit_ids[5]));
+
+ i = 0;
+ while ((error = git_revwalk_next(&id, walk)) == 0) {
+ cl_assert_equal_i(git_oid_cmp(&id, &commit_ids[i]), 0);
+ i++;
+ }
+
+ /* walker should return four commits */
+ cl_assert_equal_i(i, 4);
+ cl_assert_equal_i(error, GIT_ITEROVER);
+
+ git_revwalk_free(walk);
+}
+
diff --git a/tests/revwalk/mergebase.c b/tests/revwalk/mergebase.c
new file mode 100644
index 000000000..97663502c
--- /dev/null
+++ b/tests/revwalk/mergebase.c
@@ -0,0 +1,455 @@
+#include "clar_libgit2.h"
+#include "vector.h"
+#include <stdarg.h>
+
+static git_repository *_repo;
+static git_repository *_repo2;
+
+void test_revwalk_mergebase__initialize(void)
+{
+ cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git")));
+ cl_git_pass(git_repository_open(&_repo2, cl_fixture("twowaymerge.git")));
+}
+
+void test_revwalk_mergebase__cleanup(void)
+{
+ git_repository_free(_repo);
+ _repo = NULL;
+
+ git_repository_free(_repo2);
+ _repo2 = NULL;
+}
+
+void test_revwalk_mergebase__single1(void)
+{
+ git_oid result, one, two, expected;
+ size_t ahead, behind;
+
+ cl_git_pass(git_oid_fromstr(&one, "c47800c7266a2be04c571c04d5a6614691ea99bd "));
+ cl_git_pass(git_oid_fromstr(&two, "9fd738e8f7967c078dceed8190330fc8648ee56a"));
+ cl_git_pass(git_oid_fromstr(&expected, "5b5b025afb0b4c913b4c338a42934a3863bf3644"));
+
+ cl_git_pass(git_merge_base(&result, _repo, &one, &two));
+ cl_assert(git_oid_cmp(&result, &expected) == 0);
+
+ cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, &one, &two));
+ cl_assert_equal_sz(ahead, 2);
+ cl_assert_equal_sz(behind, 1);
+
+ cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, &two, &one));
+ cl_assert_equal_sz(ahead, 1);
+ cl_assert_equal_sz(behind, 2);
+}
+
+void test_revwalk_mergebase__single2(void)
+{
+ git_oid result, one, two, expected;
+ size_t ahead, behind;
+
+ cl_git_pass(git_oid_fromstr(&one, "763d71aadf09a7951596c9746c024e7eece7c7af"));
+ cl_git_pass(git_oid_fromstr(&two, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"));
+ cl_git_pass(git_oid_fromstr(&expected, "c47800c7266a2be04c571c04d5a6614691ea99bd"));
+
+ cl_git_pass(git_merge_base(&result, _repo, &one, &two));
+ cl_assert(git_oid_cmp(&result, &expected) == 0);
+
+ cl_git_pass(git_graph_ahead_behind( &ahead, &behind, _repo, &one, &two));
+ cl_assert_equal_sz(ahead, 4);
+ cl_assert_equal_sz(behind, 1);
+
+ cl_git_pass(git_graph_ahead_behind( &ahead, &behind, _repo, &two, &one));
+ cl_assert_equal_sz(ahead, 1);
+ cl_assert_equal_sz(behind, 4);
+}
+
+void test_revwalk_mergebase__merged_branch(void)
+{
+ git_oid result, one, two, expected;
+ size_t ahead, behind;
+
+ cl_git_pass(git_oid_fromstr(&one, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"));
+ cl_git_pass(git_oid_fromstr(&two, "9fd738e8f7967c078dceed8190330fc8648ee56a"));
+ cl_git_pass(git_oid_fromstr(&expected, "9fd738e8f7967c078dceed8190330fc8648ee56a"));
+
+ cl_git_pass(git_merge_base(&result, _repo, &one, &two));
+ cl_assert(git_oid_cmp(&result, &expected) == 0);
+
+ cl_git_pass(git_merge_base(&result, _repo, &two, &one));
+ cl_assert(git_oid_cmp(&result, &expected) == 0);
+
+ cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, &one, &two));
+ cl_assert_equal_sz(ahead, 0);
+ cl_assert_equal_sz(behind, 3);
+
+ cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, &two, &one));
+ cl_assert_equal_sz(ahead, 3);
+ cl_assert_equal_sz(behind, 0);
+}
+
+void test_revwalk_mergebase__two_way_merge(void)
+{
+ git_oid one, two;
+ size_t ahead, behind;
+
+ cl_git_pass(git_oid_fromstr(&one, "9b219343610c88a1187c996d0dc58330b55cee28"));
+ cl_git_pass(git_oid_fromstr(&two, "a953a018c5b10b20c86e69fef55ebc8ad4c5a417"));
+ cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo2, &one, &two));
+
+ cl_assert_equal_sz(ahead, 2);
+ cl_assert_equal_sz(behind, 8);
+
+ cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo2, &two, &one));
+
+ cl_assert_equal_sz(ahead, 8);
+ cl_assert_equal_sz(behind, 2);
+}
+
+void test_revwalk_mergebase__no_common_ancestor_returns_ENOTFOUND(void)
+{
+ git_oid result, one, two;
+ size_t ahead, behind;
+ int error;
+
+ cl_git_pass(git_oid_fromstr(&one, "763d71aadf09a7951596c9746c024e7eece7c7af"));
+ cl_git_pass(git_oid_fromstr(&two, "e90810b8df3e80c413d903f631643c716887138d"));
+
+ error = git_merge_base(&result, _repo, &one, &two);
+ cl_git_fail(error);
+
+ cl_assert_equal_i(GIT_ENOTFOUND, error);
+
+ cl_git_pass(git_graph_ahead_behind(&ahead, &behind, _repo, &one, &two));
+ cl_assert_equal_sz(2, ahead);
+ cl_assert_equal_sz(4, behind);
+}
+
+void test_revwalk_mergebase__prefer_youngest_merge_base(void)
+{
+ git_oid result, one, two, expected;
+
+ cl_git_pass(git_oid_fromstr(&one, "a4a7dce85cf63874e984719f4fdd239f5145052f "));
+ cl_git_pass(git_oid_fromstr(&two, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"));
+ cl_git_pass(git_oid_fromstr(&expected, "c47800c7266a2be04c571c04d5a6614691ea99bd"));
+
+ cl_git_pass(git_merge_base(&result, _repo, &one, &two));
+ cl_assert(git_oid_cmp(&result, &expected) == 0);
+}
+
+void test_revwalk_mergebase__no_off_by_one_missing(void)
+{
+ git_oid result, one, two;
+
+ cl_git_pass(git_oid_fromstr(&one, "1a443023183e3f2bfbef8ac923cd81c1018a18fd"));
+ cl_git_pass(git_oid_fromstr(&two, "9f13f7d0a9402c681f91dc590cf7b5470e6a77d2"));
+ cl_git_pass(git_merge_base(&result, _repo, &one, &two));
+}
+
+static void assert_mergebase_many(const char *expected_sha, int count, ...)
+{
+ va_list ap;
+ int i;
+ git_oid *oids;
+ git_oid oid, expected;
+ char *partial_oid;
+ git_object *object;
+
+ oids = git__malloc(count * sizeof(git_oid));
+ cl_assert(oids != NULL);
+
+ memset(oids, 0x0, count * sizeof(git_oid));
+
+ va_start(ap, count);
+
+ for (i = 0; i < count; ++i) {
+ partial_oid = va_arg(ap, char *);
+ cl_git_pass(git_oid_fromstrn(&oid, partial_oid, strlen(partial_oid)));
+
+ cl_git_pass(git_object_lookup_prefix(&object, _repo, &oid, strlen(partial_oid), GIT_OBJ_COMMIT));
+ git_oid_cpy(&oids[i], git_object_id(object));
+ git_object_free(object);
+ }
+
+ va_end(ap);
+
+ if (expected_sha == NULL)
+ cl_assert_equal_i(GIT_ENOTFOUND, git_merge_base_many(&oid, _repo, count, oids));
+ else {
+ cl_git_pass(git_merge_base_many(&oid, _repo, count, oids));
+ cl_git_pass(git_oid_fromstr(&expected, expected_sha));
+
+ cl_assert(git_oid_cmp(&expected, &oid) == 0);
+ }
+
+ git__free(oids);
+}
+
+void test_revwalk_mergebase__many_no_common_ancestor_returns_ENOTFOUND(void)
+{
+ assert_mergebase_many(NULL, 3, "41bc8c", "e90810", "a65fed");
+ assert_mergebase_many(NULL, 3, "e90810", "41bc8c", "a65fed");
+ assert_mergebase_many(NULL, 3, "e90810", "a65fed", "41bc8c");
+ assert_mergebase_many(NULL, 3, "a65fed", "e90810", "41bc8c");
+ assert_mergebase_many(NULL, 3, "a65fed", "41bc8c", "e90810");
+
+ assert_mergebase_many(NULL, 3, "e90810", "763d71", "a65fed");
+}
+
+void test_revwalk_mergebase__many_merge_branch(void)
+{
+ assert_mergebase_many("c47800c7266a2be04c571c04d5a6614691ea99bd", 3, "a65fed", "763d71", "849607");
+
+ assert_mergebase_many("c47800c7266a2be04c571c04d5a6614691ea99bd", 3, "763d71", "e90810", "a65fed");
+ assert_mergebase_many("c47800c7266a2be04c571c04d5a6614691ea99bd", 3, "763d71", "a65fed", "e90810");
+
+ assert_mergebase_many("c47800c7266a2be04c571c04d5a6614691ea99bd", 3, "a65fed", "763d71", "849607");
+ assert_mergebase_many("c47800c7266a2be04c571c04d5a6614691ea99bd", 3, "a65fed", "849607", "763d71");
+ assert_mergebase_many("8496071c1b46c854b31185ea97743be6a8774479", 3, "849607", "a65fed", "763d71");
+
+ assert_mergebase_many("5b5b025afb0b4c913b4c338a42934a3863bf3644", 5, "5b5b02", "763d71", "a4a7dc", "a65fed", "41bc8c");
+}
+
+static void assert_mergebase_octopus(const char *expected_sha, int count, ...)
+{
+ va_list ap;
+ int i;
+ git_oid *oids;
+ git_oid oid, expected;
+ char *partial_oid;
+ git_object *object;
+
+ oids = git__malloc(count * sizeof(git_oid));
+ cl_assert(oids != NULL);
+
+ memset(oids, 0x0, count * sizeof(git_oid));
+
+ va_start(ap, count);
+
+ for (i = 0; i < count; ++i) {
+ partial_oid = va_arg(ap, char *);
+ cl_git_pass(git_oid_fromstrn(&oid, partial_oid, strlen(partial_oid)));
+
+ cl_git_pass(git_object_lookup_prefix(&object, _repo, &oid, strlen(partial_oid), GIT_OBJ_COMMIT));
+ git_oid_cpy(&oids[i], git_object_id(object));
+ git_object_free(object);
+ }
+
+ va_end(ap);
+
+ if (expected_sha == NULL)
+ cl_assert_equal_i(GIT_ENOTFOUND, git_merge_base_octopus(&oid, _repo, count, oids));
+ else {
+ cl_git_pass(git_merge_base_octopus(&oid, _repo, count, oids));
+ cl_git_pass(git_oid_fromstr(&expected, expected_sha));
+
+ cl_assert(git_oid_cmp(&expected, &oid) == 0);
+ }
+
+ git__free(oids);
+}
+
+void test_revwalk_mergebase__octopus_no_common_ancestor_returns_ENOTFOUND(void)
+{
+ assert_mergebase_octopus(NULL, 3, "41bc8c", "e90810", "a65fed");
+ assert_mergebase_octopus(NULL, 3, "e90810", "41bc8c", "a65fed");
+ assert_mergebase_octopus(NULL, 3, "e90810", "a65fed", "41bc8c");
+ assert_mergebase_octopus(NULL, 3, "a65fed", "e90810", "41bc8c");
+ assert_mergebase_octopus(NULL, 3, "a65fed", "41bc8c", "e90810");
+
+ assert_mergebase_octopus(NULL, 3, "e90810", "763d71", "a65fed");
+
+ assert_mergebase_octopus(NULL, 3, "763d71", "e90810", "a65fed");
+ assert_mergebase_octopus(NULL, 3, "763d71", "a65fed", "e90810");
+
+ assert_mergebase_octopus(NULL, 5, "5b5b02", "763d71", "a4a7dc", "a65fed", "41bc8c");
+}
+
+void test_revwalk_mergebase__octopus_merge_branch(void)
+{
+ assert_mergebase_octopus("8496071c1b46c854b31185ea97743be6a8774479", 3, "a65fed", "763d71", "849607");
+
+ assert_mergebase_octopus("8496071c1b46c854b31185ea97743be6a8774479", 3, "a65fed", "763d71", "849607");
+ assert_mergebase_octopus("8496071c1b46c854b31185ea97743be6a8774479", 3, "a65fed", "849607", "763d71");
+ assert_mergebase_octopus("8496071c1b46c854b31185ea97743be6a8774479", 3, "849607", "a65fed", "763d71");
+}
+
+/*
+ * testrepo.git $ git log --graph --all
+ * * commit 763d71aadf09a7951596c9746c024e7eece7c7af
+ * | Author: nulltoken <emeric.fermas@gmail.com>
+ * | Date: Sun Oct 9 12:54:47 2011 +0200
+ * |
+ * | Add some files into subdirectories
+ * |
+ * | * commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750
+ * | | Author: Scott Chacon <schacon@gmail.com>
+ * | | Date: Tue Aug 9 19:33:46 2011 -0700
+ * | |
+ * | * commit be3563ae3f795b2b4353bcce3a527ad0a4f7f644
+ * | |\ Merge: 9fd738e c47800c
+ * | |/ Author: Scott Chacon <schacon@gmail.com>
+ * |/| Date: Tue May 25 11:58:27 2010 -0700
+ * | |
+ * | | Merge branch 'br2'
+ * | |
+ * | | * commit e90810b8df3e80c413d903f631643c716887138d
+ * | | | Author: Vicent Marti <tanoku@gmail.com>
+ * | | | Date: Thu Aug 5 18:42:20 2010 +0200
+ * | | |
+ * | | | Test commit 2
+ * | | |
+ * | | * commit 6dcf9bf7541ee10456529833502442f385010c3d
+ * | | Author: Vicent Marti <tanoku@gmail.com>
+ * | | Date: Thu Aug 5 18:41:33 2010 +0200
+ * | |
+ * | | Test commit 1
+ * | |
+ * | | * commit a4a7dce85cf63874e984719f4fdd239f5145052f
+ * | | |\ Merge: c47800c 9fd738e
+ * | |/ / Author: Scott Chacon <schacon@gmail.com>
+ * |/| / Date: Tue May 25 12:00:23 2010 -0700
+ * | |/
+ * | | Merge branch 'master' into br2
+ * | |
+ * | * commit 9fd738e8f7967c078dceed8190330fc8648ee56a
+ * | | Author: Scott Chacon <schacon@gmail.com>
+ * | | Date: Mon May 24 10:19:19 2010 -0700
+ * | |
+ * | | a fourth commit
+ * | |
+ * | * commit 4a202b346bb0fb0db7eff3cffeb3c70babbd2045
+ * | | Author: Scott Chacon <schacon@gmail.com>
+ * | | Date: Mon May 24 10:19:04 2010 -0700
+ * | |
+ * | | a third commit
+ * | |
+ * * | commit c47800c7266a2be04c571c04d5a6614691ea99bd
+ * |/ Author: Scott Chacon <schacon@gmail.com>
+ * | Date: Tue May 25 11:58:14 2010 -0700
+ * |
+ * | branch commit one
+ * |
+ * * commit 5b5b025afb0b4c913b4c338a42934a3863bf3644
+ * | Author: Scott Chacon <schacon@gmail.com>
+ * | Date: Tue May 11 13:38:42 2010 -0700
+ * |
+ * | another commit
+ * |
+ * * commit 8496071c1b46c854b31185ea97743be6a8774479
+ * Author: Scott Chacon <schacon@gmail.com>
+ * Date: Sat May 8 16:13:06 2010 -0700
+ *
+ * testing
+ *
+ * * commit 41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9
+ * | Author: Scott Chacon <schacon@gmail.com>
+ * | Date: Tue May 11 13:40:41 2010 -0700
+ * |
+ * | packed commit two
+ * |
+ * * commit 5001298e0c09ad9c34e4249bc5801c75e9754fa5
+ * Author: Scott Chacon <schacon@gmail.com>
+ * Date: Tue May 11 13:40:23 2010 -0700
+ *
+ * packed commit one
+ */
+
+/*
+ * twowaymerge.git $ git log --graph --all
+ * * commit 9b219343610c88a1187c996d0dc58330b55cee28
+ * |\ Merge: c37a783 2224e19
+ * | | Author: Scott J. Goldman <scottjg@github.com>
+ * | | Date: Tue Nov 27 20:31:04 2012 -0800
+ * | |
+ * | | Merge branch 'first-branch' into second-branch
+ * | |
+ * | * commit 2224e191514cb4bd8c566d80dac22dfcb1e9bb83
+ * | | Author: Scott J. Goldman <scottjg@github.com>
+ * | | Date: Tue Nov 27 20:28:51 2012 -0800
+ * | |
+ * | | j
+ * | |
+ * | * commit a41a49f8f5cd9b6cb14a076bf8394881ed0b4d19
+ * | | Author: Scott J. Goldman <scottjg@github.com>
+ * | | Date: Tue Nov 27 20:28:39 2012 -0800
+ * | |
+ * | | i
+ * | |
+ * | * commit 82bf9a1a10a4b25c1f14c9607b60970705e92545
+ * | | Author: Scott J. Goldman <scottjg@github.com>
+ * | | Date: Tue Nov 27 20:28:28 2012 -0800
+ * | |
+ * | | h
+ * | |
+ * * | commit c37a783c20d92ac92362a78a32860f7eebf938ef
+ * | | Author: Scott J. Goldman <scottjg@github.com>
+ * | | Date: Tue Nov 27 20:30:57 2012 -0800
+ * | |
+ * | | n
+ * | |
+ * * | commit 8b82fb1794cb1c8c7f172ec730a4c2db0ae3e650
+ * | | Author: Scott J. Goldman <scottjg@github.com>
+ * | | Date: Tue Nov 27 20:30:43 2012 -0800
+ * | |
+ * | | m
+ * | |
+ * * | commit 6ab5d28acbf3c3bdff276f7ccfdf29c1520e542f
+ * | | Author: Scott J. Goldman <scottjg@github.com>
+ * | | Date: Tue Nov 27 20:30:38 2012 -0800
+ * | |
+ * | | l
+ * | |
+ * * | commit 7b8c336c45fc6895c1c60827260fe5d798e5d247
+ * | | Author: Scott J. Goldman <scottjg@github.com>
+ * | | Date: Tue Nov 27 20:30:24 2012 -0800
+ * | |
+ * | | k
+ * | |
+ * | | * commit 1c30b88f5f3ee66d78df6520a7de9e89b890818b
+ * | | | Author: Scott J. Goldman <scottjg@github.com>
+ * | | | Date: Tue Nov 27 20:28:10 2012 -0800
+ * | | |
+ * | | | e
+ * | | |
+ * | | * commit 42b7311aa626e712891940c1ec5d5cba201946a4
+ * | | | Author: Scott J. Goldman <scottjg@github.com>
+ * | | | Date: Tue Nov 27 20:28:06 2012 -0800
+ * | | |
+ * | | | d
+ * | | |
+ * | | * commit a953a018c5b10b20c86e69fef55ebc8ad4c5a417
+ * | | |\ Merge: bd1732c cdf97fd
+ * | | |/ Author: Scott J. Goldman <scottjg@github.com>
+ * | |/| Date: Tue Nov 27 20:26:43 2012 -0800
+ * | | |
+ * | | | Merge branch 'first-branch'
+ * | | |
+ * | * | commit cdf97fd3bb48eb3827638bb33d208f5fd32d0aa6
+ * | | | Author: Scott J. Goldman <scottjg@github.com>
+ * | | | Date: Tue Nov 27 20:24:46 2012 -0800
+ * | | |
+ * | | | g
+ * | | |
+ * | * | commit ef0488f0b722f0be8bcb90a7730ac7efafd1d694
+ * | | | Author: Scott J. Goldman <scottjg@github.com>
+ * | | | Date: Tue Nov 27 20:24:39 2012 -0800
+ * | | |
+ * | | | f
+ * | | |
+ * | | * commit bd1732c43c68d712ad09e1d872b9be6d4b9efdc4
+ * | |/ Author: Scott J. Goldman <scottjg@github.com>
+ * | | Date: Tue Nov 27 17:43:58 2012 -0800
+ * | |
+ * | | c
+ * | |
+ * | * commit 0c8a3f1f3d5f421cf83048c7c73ee3b55a5e0f29
+ * |/ Author: Scott J. Goldman <scottjg@github.com>
+ * | Date: Tue Nov 27 17:43:48 2012 -0800
+ * |
+ * | b
+ * |
+ * * commit 1f4c0311a24b63f6fc209a59a1e404942d4a5006
+ * Author: Scott J. Goldman <scottjg@github.com>
+ * Date: Tue Nov 27 17:43:41 2012 -0800
+ *
+ * a
+ */
diff --git a/tests/revwalk/signatureparsing.c b/tests/revwalk/signatureparsing.c
new file mode 100644
index 000000000..5c7d8813d
--- /dev/null
+++ b/tests/revwalk/signatureparsing.c
@@ -0,0 +1,47 @@
+#include "clar_libgit2.h"
+
+static git_repository *_repo;
+static git_revwalk *_walk;
+
+void test_revwalk_signatureparsing__initialize(void)
+{
+ cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git")));
+ cl_git_pass(git_revwalk_new(&_walk, _repo));
+}
+
+void test_revwalk_signatureparsing__cleanup(void)
+{
+ git_revwalk_free(_walk);
+ _walk = NULL;
+
+ git_repository_free(_repo);
+ _repo = NULL;
+}
+
+void test_revwalk_signatureparsing__do_not_choke_when_name_contains_angle_brackets(void)
+{
+ git_reference *ref;
+ git_oid commit_oid;
+ git_commit *commit;
+ const git_signature *signature;
+
+ /*
+ * The branch below points at a commit with angle brackets in the committer/author name
+ * committer <Yu V. Bin Haacked> <foo@example.com> 1323847743 +0100
+ */
+ cl_git_pass(git_reference_lookup(&ref, _repo, "refs/heads/haacked"));
+
+ git_revwalk_push(_walk, git_reference_target(ref));
+ cl_git_pass(git_revwalk_next(&commit_oid, _walk));
+
+ cl_git_pass(git_commit_lookup(&commit, _repo, git_reference_target(ref)));
+
+ signature = git_commit_committer(commit);
+ cl_assert_equal_s("foo@example.com", signature->email);
+ cl_assert_equal_s("<Yu V. Bin Haacked>", signature->name);
+ cl_assert_equal_i(1323847743, (int)signature->when.time);
+ cl_assert_equal_i(60, signature->when.offset);
+
+ git_commit_free(commit);
+ git_reference_free(ref);
+}
diff --git a/tests/revwalk/simplify.c b/tests/revwalk/simplify.c
new file mode 100644
index 000000000..81c19d366
--- /dev/null
+++ b/tests/revwalk/simplify.c
@@ -0,0 +1,55 @@
+#include "clar_libgit2.h"
+
+void test_revwalk_simplify__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+/*
+ * a4a7dce [0] Merge branch 'master' into br2
+ |\
+ | * 9fd738e [1] a fourth commit
+ | * 4a202b3 [2] a third commit
+ * | c47800c [3] branch commit one
+ |/
+ * 5b5b025 [5] another commit
+ * 8496071 [4] testing
+*/
+static const char *commit_head = "a4a7dce85cf63874e984719f4fdd239f5145052f";
+
+static const char *expected_str[] = {
+ "a4a7dce85cf63874e984719f4fdd239f5145052f", /* 0 */
+ "c47800c7266a2be04c571c04d5a6614691ea99bd", /* 3 */
+ "8496071c1b46c854b31185ea97743be6a8774479", /* 4 */
+ "5b5b025afb0b4c913b4c338a42934a3863bf3644", /* 5 */
+};
+
+void test_revwalk_simplify__first_parent(void)
+{
+ git_repository *repo;
+ git_revwalk *walk;
+ git_oid id, expected[4];
+ int i, error;
+
+ for (i = 0; i < 4; i++) {
+ git_oid_fromstr(&expected[i], expected_str[i]);
+ }
+
+ repo = cl_git_sandbox_init("testrepo.git");
+ cl_git_pass(git_revwalk_new(&walk, repo));
+
+ git_oid_fromstr(&id, commit_head);
+ cl_git_pass(git_revwalk_push(walk, &id));
+ git_revwalk_simplify_first_parent(walk);
+
+ i = 0;
+ while ((error = git_revwalk_next(&id, walk)) == 0) {
+ git_oid_cmp(&id, &expected[i]);
+ i++;
+ }
+
+ cl_assert_equal_i(i, 4);
+ cl_assert_equal_i(error, GIT_ITEROVER);
+
+ git_revwalk_free(walk);
+}
diff --git a/tests/stash/drop.c b/tests/stash/drop.c
new file mode 100644
index 000000000..63ff0377c
--- /dev/null
+++ b/tests/stash/drop.c
@@ -0,0 +1,174 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+#include "stash_helpers.h"
+#include "refs.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);
+ signature = NULL;
+
+ git_repository_free(repo);
+ repo = NULL;
+
+ 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_git_fail_with(git_stash_drop(repo, 0), GIT_ENOTFOUND);
+}
+
+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_bypath(index, "zero.txt"));
+ cl_repo_commit_from_index(NULL, repo, signature, 0, "Initial commit");
+ cl_assert(git_path_exists("stash/zero.txt"));
+ git_index_free(index);
+
+ cl_git_mkfile("stash/one.txt", "content\n");
+ cl_git_pass(git_stash_save(
+ &oid, repo, signature, "First", GIT_STASH_INCLUDE_UNTRACKED));
+ cl_assert(!git_path_exists("stash/one.txt"));
+ cl_assert(git_path_exists("stash/zero.txt"));
+
+ cl_git_mkfile("stash/two.txt", "content\n");
+ cl_git_pass(git_stash_save(
+ &oid, repo, signature, "Second", GIT_STASH_INCLUDE_UNTRACKED));
+ cl_assert(!git_path_exists("stash/two.txt"));
+ cl_assert(git_path_exists("stash/zero.txt"));
+
+ cl_git_mkfile("stash/three.txt", "content\n");
+ cl_git_pass(git_stash_save(
+ &oid, repo, signature, "Third", GIT_STASH_INCLUDE_UNTRACKED));
+ cl_assert(!git_path_exists("stash/three.txt"));
+ cl_assert(git_path_exists("stash/zero.txt"));
+}
+
+void test_stash_drop__cannot_drop_a_non_existing_stashed_state(void)
+{
+ push_three_states();
+
+ cl_git_fail_with(git_stash_drop(repo, 666), GIT_ENOTFOUND);
+ cl_git_fail_with(git_stash_drop(repo, 42), GIT_ENOTFOUND);
+ cl_git_fail_with(git_stash_drop(repo, 3), GIT_ENOTFOUND);
+}
+
+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_git_fail_with(git_stash_drop(repo, 0), GIT_ENOTFOUND);
+}
+
+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_git_fail_with(git_stash_drop(repo, 0), GIT_ENOTFOUND);
+}
+
+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, GIT_REFS_STASH_FILE));
+
+ cl_git_pass(git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE));
+ entry = git_reflog_entry_byindex(reflog, 1);
+
+ git_oid_cpy(&oid, git_reflog_entry_id_old(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, repo, GIT_REFS_STASH_FILE));
+ entry = git_reflog_entry_byindex(reflog, 0);
+
+ cl_assert_equal_i(0, git_oid_cmp(&oid, git_reflog_entry_id_old(entry)));
+ cl_assert_equal_sz(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, GIT_REFS_STASH_FILE));
+ 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_git_fail_with(
+ git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE), GIT_ENOTFOUND);
+}
+
+void retrieve_top_stash_id(git_oid *out)
+{
+ git_object *top_stash;
+
+ cl_git_pass(git_revparse_single(&top_stash, repo, "stash@{0}"));
+ cl_git_pass(git_reference_name_to_id(out, repo, GIT_REFS_STASH_FILE));
+
+ cl_assert_equal_i(true, git_oid_cmp(out, git_object_id(top_stash)) == 0);
+
+ git_object_free(top_stash);
+}
+
+void test_stash_drop__dropping_the_top_stash_updates_the_stash_reference(void)
+{
+ git_object *next_top_stash;
+ git_oid oid;
+
+ push_three_states();
+
+ retrieve_top_stash_id(&oid);
+
+ cl_git_pass(git_revparse_single(&next_top_stash, repo, "stash@{1}"));
+ cl_assert(git_oid_cmp(&oid, git_object_id(next_top_stash)) != 0);
+
+ cl_git_pass(git_stash_drop(repo, 0));
+
+ retrieve_top_stash_id(&oid);
+
+ cl_git_pass(git_oid_cmp(&oid, git_object_id(next_top_stash)));
+
+ git_object_free(next_top_stash);
+}
diff --git a/tests/stash/foreach.c b/tests/stash/foreach.c
new file mode 100644
index 000000000..f1983625f
--- /dev/null
+++ b/tests/stash/foreach.c
@@ -0,0 +1,124 @@
+#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);
+ signature = NULL;
+
+ git_repository_free(repo);
+ repo = NULL;
+
+ 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/stash/save.c b/tests/stash/save.c
new file mode 100644
index 000000000..87c6d7e0f
--- /dev/null
+++ b/tests/stash/save.c
@@ -0,0 +1,390 @@
+#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);
+ signature = NULL;
+
+ git_repository_free(repo);
+ repo = NULL;
+
+ cl_git_pass(git_futils_rmdir_r("stash", NULL, GIT_RMDIR_REMOVE_FILES));
+ cl_fixture_cleanup("sorry-it-is-a-non-bare-only-party");
+}
+
+static void assert_object_oid(const char* revision, const char* expected_oid, git_otype type)
+{
+ int result;
+ git_object *obj;
+
+ result = git_revparse_single(&obj, repo, revision);
+
+ if (!expected_oid) {
+ cl_assert_equal_i(GIT_ENOTFOUND, result);
+ return;
+ } else
+ cl_assert_equal_i(0, result);
+
+ cl_git_pass(git_oid_streq(git_object_id(obj), expected_oid));
+ cl_assert_equal_i(type, git_object_type(obj));
+ git_object_free(obj);
+}
+
+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);
+}
+
+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(repo, "what", GIT_STATUS_INDEX_MODIFIED);
+ assert_status(repo, "how", GIT_STATUS_INDEX_MODIFIED);
+ assert_status(repo, "who", GIT_STATUS_CURRENT);
+ assert_status(repo, "when", GIT_STATUS_WT_NEW);
+ assert_status(repo, "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__untracked_skips_ignored(void)
+{
+ cl_git_append2file("stash/.gitignore", "bundle/vendor/\n");
+ cl_must_pass(p_mkdir("stash/bundle", 0777));
+ cl_must_pass(p_mkdir("stash/bundle/vendor", 0777));
+ cl_git_mkfile("stash/bundle/vendor/blah", "contents\n");
+
+ cl_assert(git_path_exists("stash/when")); /* untracked */
+ cl_assert(git_path_exists("stash/just.ignore")); /* ignored */
+ cl_assert(git_path_exists("stash/bundle/vendor/blah")); /* ignored */
+
+ cl_git_pass(git_stash_save(
+ &stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED));
+
+ cl_assert(!git_path_exists("stash/when"));
+ cl_assert(git_path_exists("stash/bundle/vendor/blah"));
+ cl_assert(git_path_exists("stash/just.ignore"));
+}
+
+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");
+
+ cl_assert(!git_path_exists("stash/just.ignore"));
+}
+
+#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_symbolic_create(&head, repo, "HEAD", "refs/heads/unborn", 1, NULL, NULL));
+
+ cl_assert_equal_i(GIT_EUNBORNBRANCH,
+ 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, NULL, NULL);
+
+ 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 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_bypath(index, "what"));
+ cl_git_pass(git_index_add_bypath(index, "who"));
+ cl_repo_commit_from_index(NULL, repo, signature, 0, "Initial commit");
+ 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(repo, "what", GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED);
+ assert_status(repo, "how", GIT_STATUS_INDEX_MODIFIED);
+ assert_status(repo, "who", GIT_STATUS_WT_MODIFIED);
+ assert_status(repo, "when", GIT_STATUS_WT_NEW);
+ assert_status(repo, "just.ignore", GIT_STATUS_IGNORED);
+
+ cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT));
+ assert_status(repo, "what", GIT_STATUS_CURRENT);
+ assert_status(repo, "how", GIT_STATUS_CURRENT);
+ assert_status(repo, "who", GIT_STATUS_CURRENT);
+ assert_status(repo, "when", GIT_STATUS_WT_NEW);
+ assert_status(repo, "just.ignore", GIT_STATUS_IGNORED);
+
+ cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED));
+ assert_status(repo, "what", GIT_STATUS_CURRENT);
+ assert_status(repo, "how", GIT_STATUS_CURRENT);
+ assert_status(repo, "who", GIT_STATUS_CURRENT);
+ assert_status(repo, "when", GIT_ENOTFOUND);
+ assert_status(repo, "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_must_pass(p_unlink("stash/when"));
+
+ assert_status(repo, "what", GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED);
+ assert_status(repo, "how", GIT_STATUS_INDEX_MODIFIED);
+ assert_status(repo, "who", GIT_STATUS_WT_MODIFIED);
+ assert_status(repo, "when", GIT_ENOTFOUND);
+ assert_status(repo, "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);
+}
+
+void test_stash_save__skip_submodules(void)
+{
+ git_repository *untracked_repo;
+ cl_git_pass(git_repository_init(&untracked_repo, "stash/untracked_repo", false));
+ cl_git_mkfile("stash/untracked_repo/content", "stuff");
+ git_repository_free(untracked_repo);
+
+ assert_status(repo, "untracked_repo/", GIT_STATUS_WT_NEW);
+
+ cl_git_pass(git_stash_save(
+ &stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED));
+
+ assert_status(repo, "untracked_repo/", GIT_STATUS_WT_NEW);
+}
diff --git a/tests/stash/stash_helpers.c b/tests/stash/stash_helpers.c
new file mode 100644
index 000000000..ff683eced
--- /dev/null
+++ b/tests/stash/stash_helpers.c
@@ -0,0 +1,52 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+#include "stash_helpers.h"
+
+void setup_stash(git_repository *repo, git_signature *signature)
+{
+ 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_bypath(index, "what"));
+ cl_git_pass(git_index_add_bypath(index, "how"));
+ cl_git_pass(git_index_add_bypath(index, "who"));
+ cl_git_pass(git_index_add_bypath(index, ".gitignore"));
+
+ cl_repo_commit_from_index(NULL, repo, signature, 0, "Initial commit");
+
+ 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_bypath(index, "what"));
+ cl_git_pass(git_index_add_bypath(index, "how"));
+ cl_git_pass(git_index_write(index));
+
+ cl_git_rewritefile("stash/what", "see you later\n"); /* bc99dc98b3eba0e9157e94769cd4d49cb49de449 */
+
+ git_index_free(index);
+}
+
+void assert_status(
+ git_repository *repo,
+ const char *path,
+ int status_flags)
+{
+ unsigned int status;
+
+ if (status_flags < 0)
+ cl_assert_equal_i(status_flags, git_status_file(&status, repo, path));
+ else {
+ cl_git_pass(git_status_file(&status, repo, path));
+ cl_assert_equal_i((unsigned int)status_flags, status);
+ }
+}
diff --git a/tests/stash/stash_helpers.h b/tests/stash/stash_helpers.h
new file mode 100644
index 000000000..66d758fe2
--- /dev/null
+++ b/tests/stash/stash_helpers.h
@@ -0,0 +1,8 @@
+void setup_stash(
+ git_repository *repo,
+ git_signature *signature);
+
+void assert_status(
+ git_repository *repo,
+ const char *path,
+ int status_flags);
diff --git a/tests/stash/submodules.c b/tests/stash/submodules.c
new file mode 100644
index 000000000..8cadca0f2
--- /dev/null
+++ b/tests/stash/submodules.c
@@ -0,0 +1,83 @@
+#include "clar_libgit2.h"
+#include "stash_helpers.h"
+#include "../submodule/submodule_helpers.h"
+
+static git_repository *repo;
+static git_signature *signature;
+static git_oid stash_tip_oid;
+
+static git_submodule *sm;
+
+void test_stash_submodules__initialize(void)
+{
+ cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */
+
+ repo = setup_fixture_submodules();
+
+ cl_git_pass(git_submodule_lookup(&sm, repo, "testrepo"));
+}
+
+void test_stash_submodules__cleanup(void)
+{
+ git_submodule_free(sm);
+ sm = NULL;
+
+ git_signature_free(signature);
+ signature = NULL;
+}
+
+void test_stash_submodules__does_not_stash_modified_submodules(void)
+{
+ static git_index *smindex;
+ static git_repository *smrepo;
+
+ assert_status(repo, "modified", GIT_STATUS_WT_MODIFIED);
+
+ /* modify file in submodule */
+ cl_git_rewritefile("submodules/testrepo/README", "heyheyhey");
+ assert_status(repo, "testrepo", GIT_STATUS_WT_MODIFIED);
+
+ /* add file to index in submodule */
+ cl_git_pass(git_submodule_open(&smrepo, sm));
+ cl_git_pass(git_repository_index(&smindex, smrepo));
+ cl_git_pass(git_index_add_bypath(smindex, "README"));
+
+ /* commit changed index of submodule */
+ cl_repo_commit_from_index(NULL, smrepo, NULL, 1372350000, "Modify it");
+ assert_status(repo, "testrepo", GIT_STATUS_WT_MODIFIED);
+
+ cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT));
+
+ assert_status(repo, "testrepo", GIT_STATUS_WT_MODIFIED);
+ assert_status(repo, "modified", GIT_STATUS_CURRENT);
+
+ git_index_free(smindex);
+ git_repository_free(smrepo);
+}
+
+void test_stash_submodules__stash_is_empty_with_modified_submodules(void)
+{
+ static git_index *smindex;
+ static git_repository *smrepo;
+
+ cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT));
+ assert_status(repo, "modified", GIT_STATUS_CURRENT);
+
+ /* modify file in submodule */
+ cl_git_rewritefile("submodules/testrepo/README", "heyheyhey");
+ assert_status(repo, "testrepo", GIT_STATUS_WT_MODIFIED);
+
+ /* add file to index in submodule */
+ cl_git_pass(git_submodule_open(&smrepo, sm));
+ cl_git_pass(git_repository_index(&smindex, smrepo));
+ cl_git_pass(git_index_add_bypath(smindex, "README"));
+
+ /* commit changed index of submodule */
+ cl_repo_commit_from_index(NULL, smrepo, NULL, 1372350000, "Modify it");
+ assert_status(repo, "testrepo", GIT_STATUS_WT_MODIFIED);
+
+ cl_git_fail_with(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT), GIT_ENOTFOUND);
+
+ git_index_free(smindex);
+ git_repository_free(smrepo);
+}
diff --git a/tests/status/ignore.c b/tests/status/ignore.c
new file mode 100644
index 000000000..a4e766fdf
--- /dev/null
+++ b/tests/status/ignore.c
@@ -0,0 +1,790 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+#include "git2/attr.h"
+#include "ignore.h"
+#include "attr.h"
+#include "status_helpers.h"
+
+static git_repository *g_repo = NULL;
+
+void test_status_ignore__initialize(void)
+{
+}
+
+void test_status_ignore__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+static void assert_ignored_(
+ bool expected, const char *filepath, const char *file, int line)
+{
+ int is_ignored = 0;
+ cl_git_pass_(
+ git_status_should_ignore(&is_ignored, g_repo, filepath), file, line);
+ clar__assert(
+ (expected != 0) == (is_ignored != 0),
+ file, line, "expected != is_ignored", filepath, 1);
+}
+#define assert_ignored(expected, filepath) \
+ assert_ignored_(expected, filepath, __FILE__, __LINE__)
+#define assert_is_ignored(filepath) \
+ assert_ignored_(true, filepath, __FILE__, __LINE__)
+#define refute_is_ignored(filepath) \
+ assert_ignored_(false, filepath, __FILE__, __LINE__)
+
+void test_status_ignore__0(void)
+{
+ struct {
+ const char *path;
+ int expected;
+ } test_cases[] = {
+ /* pattern "ign" from .gitignore */
+ { "file", 0 },
+ { "ign", 1 },
+ { "sub", 0 },
+ { "sub/file", 0 },
+ { "sub/ign", 1 },
+ { "sub/ign/file", 1 },
+ { "sub/ign/sub", 1 },
+ { "sub/ign/sub/file", 1 },
+ { "sub/sub", 0 },
+ { "sub/sub/file", 0 },
+ { "sub/sub/ign", 1 },
+ { "sub/sub/sub", 0 },
+ /* pattern "dir/" from .gitignore */
+ { "dir", 1 },
+ { "dir/", 1 },
+ { "sub/dir", 1 },
+ { "sub/dir/", 1 },
+ { "sub/dir/file", 1 }, /* contained in ignored parent */
+ { "sub/sub/dir", 0 }, /* dir is not actually a dir, but a file */
+ { NULL, 0 }
+ }, *one_test;
+
+ g_repo = cl_git_sandbox_init("attr");
+
+ for (one_test = test_cases; one_test->path != NULL; one_test++)
+ assert_ignored(one_test->expected, one_test->path);
+
+ /* confirm that ignore files were cached */
+ cl_assert(git_attr_cache__is_cached(
+ g_repo, GIT_ATTR_FILE__FROM_FILE, ".git/info/exclude"));
+ cl_assert(git_attr_cache__is_cached(
+ g_repo, GIT_ATTR_FILE__FROM_FILE, ".gitignore"));
+}
+
+
+void test_status_ignore__1(void)
+{
+ g_repo = cl_git_sandbox_init("attr");
+
+ cl_git_rewritefile("attr/.gitignore", "/*.txt\n/dir/\n");
+ git_attr_cache_flush(g_repo);
+
+ assert_is_ignored("root_test4.txt");
+ refute_is_ignored("sub/subdir_test2.txt");
+ assert_is_ignored("dir");
+ assert_is_ignored("dir/");
+ refute_is_ignored("sub/dir");
+ refute_is_ignored("sub/dir/");
+}
+
+void test_status_ignore__empty_repo_with_gitignore_rewrite(void)
+{
+ status_entry_single st;
+
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+
+ cl_git_mkfile(
+ "empty_standard_repo/look-ma.txt", "I'm going to be ignored!");
+
+ memset(&st, 0, sizeof(st));
+ cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st));
+ cl_assert(st.count == 1);
+ cl_assert(st.status == GIT_STATUS_WT_NEW);
+
+ cl_git_pass(git_status_file(&st.status, g_repo, "look-ma.txt"));
+ cl_assert(st.status == GIT_STATUS_WT_NEW);
+
+ refute_is_ignored("look-ma.txt");
+
+ cl_git_rewritefile("empty_standard_repo/.gitignore", "*.nomatch\n");
+
+ memset(&st, 0, sizeof(st));
+ cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st));
+ cl_assert(st.count == 2);
+ cl_assert(st.status == GIT_STATUS_WT_NEW);
+
+ cl_git_pass(git_status_file(&st.status, g_repo, "look-ma.txt"));
+ cl_assert(st.status == GIT_STATUS_WT_NEW);
+
+ refute_is_ignored("look-ma.txt");
+
+ cl_git_rewritefile("empty_standard_repo/.gitignore", "*.txt\n");
+
+ memset(&st, 0, sizeof(st));
+ cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st));
+ cl_assert(st.count == 2);
+ cl_assert(st.status == GIT_STATUS_IGNORED);
+
+ cl_git_pass(git_status_file(&st.status, g_repo, "look-ma.txt"));
+ cl_assert(st.status == GIT_STATUS_IGNORED);
+
+ assert_is_ignored("look-ma.txt");
+}
+
+void test_status_ignore__ignore_pattern_contains_space(void)
+{
+ unsigned int flags;
+ const mode_t mode = 0777;
+
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+ cl_git_rewritefile("empty_standard_repo/.gitignore", "foo bar.txt\n");
+
+ cl_git_mkfile(
+ "empty_standard_repo/foo bar.txt", "I'm going to be ignored!");
+
+ cl_git_pass(git_status_file(&flags, g_repo, "foo bar.txt"));
+ cl_assert(flags == GIT_STATUS_IGNORED);
+
+ cl_git_pass(git_futils_mkdir_r("empty_standard_repo/foo", NULL, mode));
+ cl_git_mkfile("empty_standard_repo/foo/look-ma.txt", "I'm not going to be ignored!");
+
+ cl_git_pass(git_status_file(&flags, g_repo, "foo/look-ma.txt"));
+ cl_assert(flags == GIT_STATUS_WT_NEW);
+}
+
+void test_status_ignore__ignore_pattern_ignorecase(void)
+{
+ unsigned int flags;
+ bool ignore_case;
+ git_index *index;
+
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+ cl_git_rewritefile("empty_standard_repo/.gitignore", "a.txt\n");
+
+ cl_git_mkfile("empty_standard_repo/A.txt", "Differs in case");
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ ignore_case = (git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0;
+ git_index_free(index);
+
+ cl_git_pass(git_status_file(&flags, g_repo, "A.txt"));
+ cl_assert(flags == ignore_case ? GIT_STATUS_IGNORED : GIT_STATUS_WT_NEW);
+}
+
+void test_status_ignore__subdirectories(void)
+{
+ status_entry_single st;
+
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+
+ cl_git_mkfile(
+ "empty_standard_repo/ignore_me", "I'm going to be ignored!");
+
+ cl_git_rewritefile("empty_standard_repo/.gitignore", "ignore_me\n");
+
+ memset(&st, 0, sizeof(st));
+ cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st));
+ cl_assert_equal_i(2, st.count);
+ cl_assert(st.status == GIT_STATUS_IGNORED);
+
+ cl_git_pass(git_status_file(&st.status, g_repo, "ignore_me"));
+ cl_assert(st.status == GIT_STATUS_IGNORED);
+
+ assert_is_ignored("ignore_me");
+
+ /* I've changed libgit2 so that the behavior here now differs from
+ * core git but seems to make more sense. In core git, the following
+ * items are skipped completed, even if --ignored is passed to status.
+ * It you mirror these steps and run "git status -uall --ignored" then
+ * you will not see "test/ignore_me/" in the results.
+ *
+ * However, we had a couple reports of this as a bug, plus there is a
+ * similar circumstance where we were differing for core git when you
+ * used a rooted path for an ignore, so I changed this behavior.
+ */
+ cl_git_pass(git_futils_mkdir_r(
+ "empty_standard_repo/test/ignore_me", NULL, 0775));
+ cl_git_mkfile(
+ "empty_standard_repo/test/ignore_me/file", "I'm going to be ignored!");
+ cl_git_mkfile(
+ "empty_standard_repo/test/ignore_me/file2", "Me, too!");
+
+ memset(&st, 0, sizeof(st));
+ cl_git_pass(git_status_foreach(g_repo, cb_status__single, &st));
+ cl_assert_equal_i(3, st.count);
+
+ cl_git_pass(git_status_file(&st.status, g_repo, "test/ignore_me/file"));
+ cl_assert(st.status == GIT_STATUS_IGNORED);
+
+ assert_is_ignored("test/ignore_me/file");
+}
+
+static void make_test_data(const char *reponame, const char **files)
+{
+ const char **scan;
+ size_t repolen = strlen(reponame) + 1;
+
+ g_repo = cl_git_sandbox_init(reponame);
+
+ for (scan = files; *scan != NULL; ++scan) {
+ cl_git_pass(git_futils_mkdir(
+ *scan + repolen, reponame,
+ 0777, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST));
+ cl_git_mkfile(*scan, "contents");
+ }
+}
+
+static const char *test_repo_1 = "empty_standard_repo";
+static const char *test_files_1[] = {
+ "empty_standard_repo/dir/a/ignore_me",
+ "empty_standard_repo/dir/b/ignore_me",
+ "empty_standard_repo/dir/ignore_me",
+ "empty_standard_repo/ignore_also/file",
+ "empty_standard_repo/ignore_me",
+ "empty_standard_repo/test/ignore_me/file",
+ "empty_standard_repo/test/ignore_me/file2",
+ "empty_standard_repo/test/ignore_me/and_me/file",
+ NULL
+};
+
+void test_status_ignore__subdirectories_recursion(void)
+{
+ /* Let's try again with recursing into ignored dirs turned on */
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ status_entry_counts counts;
+ static const char *paths_r[] = {
+ ".gitignore",
+ "dir/a/ignore_me",
+ "dir/b/ignore_me",
+ "dir/ignore_me",
+ "ignore_also/file",
+ "ignore_me",
+ "test/ignore_me/and_me/file",
+ "test/ignore_me/file",
+ "test/ignore_me/file2",
+ };
+ static const unsigned int statuses_r[] = {
+ GIT_STATUS_WT_NEW, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED,
+ GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED,
+ GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED,
+ };
+ static const char *paths_nr[] = {
+ ".gitignore",
+ "dir/a/ignore_me",
+ "dir/b/ignore_me",
+ "dir/ignore_me",
+ "ignore_also/",
+ "ignore_me",
+ "test/ignore_me/",
+ };
+ static const unsigned int statuses_nr[] = {
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED,
+ GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED,
+ };
+
+ make_test_data(test_repo_1, test_files_1);
+ cl_git_rewritefile("empty_standard_repo/.gitignore", "ignore_me\n/ignore_also\n");
+
+ memset(&counts, 0x0, sizeof(status_entry_counts));
+ counts.expected_entry_count = 9;
+ counts.expected_paths = paths_r;
+ counts.expected_statuses = statuses_r;
+
+ opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_RECURSE_IGNORED_DIRS;
+
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, cb_status__normal, &counts));
+
+ cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+ cl_assert_equal_i(0, counts.wrong_status_flags_count);
+ cl_assert_equal_i(0, counts.wrong_sorted_path);
+
+
+ memset(&counts, 0x0, sizeof(status_entry_counts));
+ counts.expected_entry_count = 7;
+ counts.expected_paths = paths_nr;
+ counts.expected_statuses = statuses_nr;
+
+ opts.flags = GIT_STATUS_OPT_DEFAULTS;
+
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, cb_status__normal, &counts));
+
+ cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+ cl_assert_equal_i(0, counts.wrong_status_flags_count);
+ cl_assert_equal_i(0, counts.wrong_sorted_path);
+}
+
+void test_status_ignore__subdirectories_not_at_root(void)
+{
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ status_entry_counts counts;
+ static const char *paths_1[] = {
+ "dir/.gitignore",
+ "dir/a/ignore_me",
+ "dir/b/ignore_me",
+ "dir/ignore_me",
+ "ignore_also/file",
+ "ignore_me",
+ "test/.gitignore",
+ "test/ignore_me/and_me/file",
+ "test/ignore_me/file",
+ "test/ignore_me/file2",
+ };
+ static const unsigned int statuses_1[] = {
+ GIT_STATUS_WT_NEW, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED,
+ GIT_STATUS_IGNORED, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_NEW, GIT_STATUS_IGNORED, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW,
+ };
+
+ make_test_data(test_repo_1, test_files_1);
+ cl_git_rewritefile("empty_standard_repo/dir/.gitignore", "ignore_me\n/ignore_also\n");
+ cl_git_rewritefile("empty_standard_repo/test/.gitignore", "and_me\n");
+
+ memset(&counts, 0x0, sizeof(status_entry_counts));
+ counts.expected_entry_count = 10;
+ counts.expected_paths = paths_1;
+ counts.expected_statuses = statuses_1;
+
+ opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_RECURSE_IGNORED_DIRS;
+
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, cb_status__normal, &counts));
+
+ cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+ cl_assert_equal_i(0, counts.wrong_status_flags_count);
+ cl_assert_equal_i(0, counts.wrong_sorted_path);
+}
+
+void test_status_ignore__leading_slash_ignores(void)
+{
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ status_entry_counts counts;
+ static const char *paths_2[] = {
+ "dir/.gitignore",
+ "dir/a/ignore_me",
+ "dir/b/ignore_me",
+ "dir/ignore_me",
+ "ignore_also/file",
+ "ignore_me",
+ "test/.gitignore",
+ "test/ignore_me/and_me/file",
+ "test/ignore_me/file",
+ "test/ignore_me/file2",
+ };
+ static const unsigned int statuses_2[] = {
+ GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW,
+ GIT_STATUS_IGNORED, GIT_STATUS_IGNORED, GIT_STATUS_IGNORED,
+ GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW,
+ };
+
+ make_test_data(test_repo_1, test_files_1);
+
+ cl_fake_home();
+ cl_git_mkfile("home/.gitignore", "/ignore_me\n");
+ {
+ git_config *cfg;
+ cl_git_pass(git_repository_config(&cfg, g_repo));
+ cl_git_pass(git_config_set_string(
+ cfg, "core.excludesfile", "~/.gitignore"));
+ git_config_free(cfg);
+ }
+
+ cl_git_rewritefile("empty_standard_repo/.git/info/exclude", "/ignore_also\n");
+ cl_git_rewritefile("empty_standard_repo/dir/.gitignore", "/ignore_me\n");
+ cl_git_rewritefile("empty_standard_repo/test/.gitignore", "/and_me\n");
+
+ memset(&counts, 0x0, sizeof(status_entry_counts));
+ counts.expected_entry_count = 10;
+ counts.expected_paths = paths_2;
+ counts.expected_statuses = statuses_2;
+
+ opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_RECURSE_IGNORED_DIRS;
+
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, cb_status__normal, &counts));
+
+ cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+ cl_assert_equal_i(0, counts.wrong_status_flags_count);
+ cl_assert_equal_i(0, counts.wrong_sorted_path);
+}
+
+void test_status_ignore__contained_dir_with_matching_name(void)
+{
+ static const char *test_files[] = {
+ "empty_standard_repo/subdir_match/aaa/subdir_match/file",
+ "empty_standard_repo/subdir_match/zzz_ignoreme",
+ NULL
+ };
+ static const char *expected_paths[] = {
+ "subdir_match/.gitignore",
+ "subdir_match/aaa/subdir_match/file",
+ "subdir_match/zzz_ignoreme",
+ };
+ static const unsigned int expected_statuses[] = {
+ GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, GIT_STATUS_IGNORED
+ };
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ status_entry_counts counts;
+
+ make_test_data("empty_standard_repo", test_files);
+ cl_git_mkfile(
+ "empty_standard_repo/subdir_match/.gitignore", "*_ignoreme\n");
+
+ refute_is_ignored("subdir_match/aaa/subdir_match/file");
+ assert_is_ignored("subdir_match/zzz_ignoreme");
+
+ memset(&counts, 0x0, sizeof(status_entry_counts));
+ counts.expected_entry_count = 3;
+ counts.expected_paths = expected_paths;
+ counts.expected_statuses = expected_statuses;
+
+ opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_RECURSE_IGNORED_DIRS;
+
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, cb_status__normal, &counts));
+
+ cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+ cl_assert_equal_i(0, counts.wrong_status_flags_count);
+ cl_assert_equal_i(0, counts.wrong_sorted_path);
+}
+
+void test_status_ignore__trailing_slash_star(void)
+{
+ static const char *test_files[] = {
+ "empty_standard_repo/file",
+ "empty_standard_repo/subdir/file",
+ "empty_standard_repo/subdir/sub2/sub3/file",
+ NULL
+ };
+
+ make_test_data("empty_standard_repo", test_files);
+ cl_git_mkfile(
+ "empty_standard_repo/subdir/.gitignore", "/**/*\n");
+
+ refute_is_ignored("file");
+ assert_is_ignored("subdir/sub2/sub3/file");
+ assert_is_ignored("subdir/file");
+}
+
+void test_status_ignore__adding_internal_ignores(void)
+{
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+
+ refute_is_ignored("one.txt");
+ refute_is_ignored("two.bar");
+
+ cl_git_pass(git_ignore_add_rule(g_repo, "*.nomatch\n"));
+
+ refute_is_ignored("one.txt");
+ refute_is_ignored("two.bar");
+
+ cl_git_pass(git_ignore_add_rule(g_repo, "*.txt\n"));
+
+ assert_is_ignored("one.txt");
+ refute_is_ignored("two.bar");
+
+ cl_git_pass(git_ignore_add_rule(g_repo, "*.bar\n"));
+
+ assert_is_ignored("one.txt");
+ assert_is_ignored("two.bar");
+
+ cl_git_pass(git_ignore_clear_internal_rules(g_repo));
+
+ refute_is_ignored("one.txt");
+ refute_is_ignored("two.bar");
+
+ cl_git_pass(git_ignore_add_rule(
+ g_repo, "multiple\n*.rules\n# comment line\n*.bar\n"));
+
+ refute_is_ignored("one.txt");
+ assert_is_ignored("two.bar");
+}
+
+void test_status_ignore__add_internal_as_first_thing(void)
+{
+ const char *add_me = "\n#################\n## Eclipse\n#################\n\n*.pydevproject\n.project\n.metadata\nbin/\ntmp/\n*.tmp\n\n";
+
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+
+ cl_git_pass(git_ignore_add_rule(g_repo, add_me));
+
+ assert_is_ignored("one.tmp");
+ refute_is_ignored("two.bar");
+}
+
+void test_status_ignore__internal_ignores_inside_deep_paths(void)
+{
+ const char *add_me = "Debug\nthis/is/deep\npatterned*/dir\n";
+
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+
+ cl_git_pass(git_ignore_add_rule(g_repo, add_me));
+
+ assert_is_ignored("Debug");
+ assert_is_ignored("and/Debug");
+ assert_is_ignored("really/Debug/this/file");
+ assert_is_ignored("Debug/what/I/say");
+
+ refute_is_ignored("and/NoDebug");
+ refute_is_ignored("NoDebug/this");
+ refute_is_ignored("please/NoDebug/this");
+
+ assert_is_ignored("this/is/deep");
+ /* pattern containing slash gets FNM_PATHNAME so all slashes must match */
+ refute_is_ignored("and/this/is/deep");
+ assert_is_ignored("this/is/deep/too");
+ /* pattern containing slash gets FNM_PATHNAME so all slashes must match */
+ refute_is_ignored("but/this/is/deep/and/ignored");
+
+ refute_is_ignored("this/is/not/deep");
+ refute_is_ignored("is/this/not/as/deep");
+ refute_is_ignored("this/is/deepish");
+ refute_is_ignored("xthis/is/deep");
+}
+
+void test_status_ignore__automatically_ignore_bad_files(void)
+{
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+
+ assert_is_ignored(".git");
+ assert_is_ignored("this/file/.");
+ assert_is_ignored("path/../funky");
+ refute_is_ignored("path/whatever.c");
+
+ cl_git_pass(git_ignore_add_rule(g_repo, "*.c\n"));
+
+ assert_is_ignored(".git");
+ assert_is_ignored("this/file/.");
+ assert_is_ignored("path/../funky");
+ assert_is_ignored("path/whatever.c");
+
+ cl_git_pass(git_ignore_clear_internal_rules(g_repo));
+
+ assert_is_ignored(".git");
+ assert_is_ignored("this/file/.");
+ assert_is_ignored("path/../funky");
+ refute_is_ignored("path/whatever.c");
+}
+
+void test_status_ignore__filenames_with_special_prefixes_do_not_interfere_with_status_retrieval(void)
+{
+ status_entry_single st;
+ char *test_cases[] = {
+ "!file",
+ "#blah",
+ "[blah]",
+ "[attr]",
+ "[attr]blah",
+ NULL
+ };
+ int i;
+
+ for (i = 0; *(test_cases + i) != NULL; i++) {
+ git_buf file = GIT_BUF_INIT;
+ char *file_name = *(test_cases + i);
+ git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
+
+ cl_git_pass(git_buf_joinpath(&file, "empty_standard_repo", file_name));
+ cl_git_mkfile(git_buf_cstr(&file), "Please don't ignore me!");
+
+ memset(&st, 0, sizeof(st));
+ cl_git_pass(git_status_foreach(repo, cb_status__single, &st));
+ cl_assert(st.count == 1);
+ cl_assert(st.status == GIT_STATUS_WT_NEW);
+
+ cl_git_pass(git_status_file(&st.status, repo, file_name));
+ cl_assert(st.status == GIT_STATUS_WT_NEW);
+
+ cl_git_sandbox_cleanup();
+ git_buf_free(&file);
+ }
+}
+
+void test_status_ignore__issue_1766_negated_ignores(void)
+{
+ unsigned int status;
+
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+
+ cl_git_pass(git_futils_mkdir_r(
+ "empty_standard_repo/a", NULL, 0775));
+ cl_git_mkfile(
+ "empty_standard_repo/a/.gitignore", "*\n!.gitignore\n");
+ cl_git_mkfile(
+ "empty_standard_repo/a/ignoreme", "I should be ignored\n");
+
+ refute_is_ignored("a/.gitignore");
+ assert_is_ignored("a/ignoreme");
+
+ cl_git_pass(git_futils_mkdir_r(
+ "empty_standard_repo/b", NULL, 0775));
+ cl_git_mkfile(
+ "empty_standard_repo/b/.gitignore", "*\n!.gitignore\n");
+ cl_git_mkfile(
+ "empty_standard_repo/b/ignoreme", "I should be ignored\n");
+
+ refute_is_ignored("b/.gitignore");
+ assert_is_ignored("b/ignoreme");
+
+ /* shouldn't have changed results from first couple either */
+ refute_is_ignored("a/.gitignore");
+ assert_is_ignored("a/ignoreme");
+
+ /* status should find the two ignore files and nothing else */
+
+ cl_git_pass(git_status_file(&status, g_repo, "a/.gitignore"));
+ cl_assert_equal_i(GIT_STATUS_WT_NEW, (int)status);
+
+ cl_git_pass(git_status_file(&status, g_repo, "a/ignoreme"));
+ cl_assert_equal_i(GIT_STATUS_IGNORED, (int)status);
+
+ cl_git_pass(git_status_file(&status, g_repo, "b/.gitignore"));
+ cl_assert_equal_i(GIT_STATUS_WT_NEW, (int)status);
+
+ cl_git_pass(git_status_file(&status, g_repo, "b/ignoreme"));
+ cl_assert_equal_i(GIT_STATUS_IGNORED, (int)status);
+
+ {
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ status_entry_counts counts;
+ static const char *paths[] = {
+ "a/.gitignore",
+ "a/ignoreme",
+ "b/.gitignore",
+ "b/ignoreme",
+ };
+ static const unsigned int statuses[] = {
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_IGNORED,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_IGNORED,
+ };
+
+ memset(&counts, 0x0, sizeof(status_entry_counts));
+ counts.expected_entry_count = 4;
+ counts.expected_paths = paths;
+ counts.expected_statuses = statuses;
+
+ opts.flags = GIT_STATUS_OPT_DEFAULTS;
+
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, cb_status__normal, &counts));
+
+ cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+ cl_assert_equal_i(0, counts.wrong_status_flags_count);
+ cl_assert_equal_i(0, counts.wrong_sorted_path);
+ }
+}
+
+static void add_one_to_index(const char *file)
+{
+ git_index *index;
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_index_add_bypath(index, file));
+ git_index_free(index);
+}
+
+/* Some further broken scenarios that have been reported */
+void test_status_ignore__more_breakage(void)
+{
+ static const char *test_files[] = {
+ "empty_standard_repo/d1/pfx-d2/d3/d4/d5/tracked",
+ "empty_standard_repo/d1/pfx-d2/d3/d4/d5/untracked",
+ "empty_standard_repo/d1/pfx-d2/d3/d4/untracked",
+ NULL
+ };
+
+ make_test_data("empty_standard_repo", test_files);
+ cl_git_mkfile(
+ "empty_standard_repo/.gitignore",
+ "/d1/pfx-*\n"
+ "!/d1/pfx-d2/\n"
+ "/d1/pfx-d2/*\n"
+ "!/d1/pfx-d2/d3/\n"
+ "/d1/pfx-d2/d3/*\n"
+ "!/d1/pfx-d2/d3/d4/\n");
+ add_one_to_index("d1/pfx-d2/d3/d4/d5/tracked");
+
+ {
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ status_entry_counts counts;
+ static const char *files[] = {
+ ".gitignore",
+ "d1/pfx-d2/d3/d4/d5/tracked",
+ "d1/pfx-d2/d3/d4/d5/untracked",
+ "d1/pfx-d2/d3/d4/untracked",
+ };
+ static const unsigned int statuses[] = {
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_NEW,
+ };
+
+ memset(&counts, 0x0, sizeof(status_entry_counts));
+ counts.expected_entry_count = 4;
+ counts.expected_paths = files;
+ counts.expected_statuses = statuses;
+ opts.flags = GIT_STATUS_OPT_DEFAULTS |
+ GIT_STATUS_OPT_INCLUDE_IGNORED |
+ GIT_STATUS_OPT_RECURSE_IGNORED_DIRS;
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, cb_status__normal, &counts));
+
+ cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+ cl_assert_equal_i(0, counts.wrong_status_flags_count);
+ cl_assert_equal_i(0, counts.wrong_sorted_path);
+ }
+
+ refute_is_ignored("d1/pfx-d2/d3/d4/d5/tracked");
+ refute_is_ignored("d1/pfx-d2/d3/d4/d5/untracked");
+ refute_is_ignored("d1/pfx-d2/d3/d4/untracked");
+}
+
+void test_status_ignore__negative_ignores_inside_ignores(void)
+{
+ static const char *test_files[] = {
+ "empty_standard_repo/top/mid/btm/tracked",
+ "empty_standard_repo/top/mid/btm/untracked",
+ NULL
+ };
+
+ make_test_data("empty_standard_repo", test_files);
+ cl_git_mkfile(
+ "empty_standard_repo/.gitignore",
+ "top\n!top/mid/btm\n");
+ add_one_to_index("top/mid/btm/tracked");
+
+ {
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ status_entry_counts counts;
+ static const char *files[] = {
+ ".gitignore", "top/mid/btm/tracked", "top/mid/btm/untracked",
+ };
+ static const unsigned int statuses[] = {
+ GIT_STATUS_WT_NEW, GIT_STATUS_INDEX_NEW, GIT_STATUS_WT_NEW,
+ };
+
+ memset(&counts, 0x0, sizeof(status_entry_counts));
+ counts.expected_entry_count = 3;
+ counts.expected_paths = files;
+ counts.expected_statuses = statuses;
+ opts.flags = GIT_STATUS_OPT_DEFAULTS |
+ GIT_STATUS_OPT_INCLUDE_IGNORED |
+ GIT_STATUS_OPT_RECURSE_IGNORED_DIRS;
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, cb_status__normal, &counts));
+
+ cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+ cl_assert_equal_i(0, counts.wrong_status_flags_count);
+ cl_assert_equal_i(0, counts.wrong_sorted_path);
+ }
+
+ refute_is_ignored("top/mid/btm/tracked");
+ refute_is_ignored("top/mid/btm/untracked");
+}
diff --git a/tests/status/renames.c b/tests/status/renames.c
new file mode 100644
index 000000000..24b8aca2b
--- /dev/null
+++ b/tests/status/renames.c
@@ -0,0 +1,711 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "path.h"
+#include "posix.h"
+#include "status_helpers.h"
+#include "util.h"
+#include "status.h"
+
+static git_repository *g_repo = NULL;
+
+void test_status_renames__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("renames");
+
+ cl_repo_set_bool(g_repo, "core.autocrlf", false);
+}
+
+void test_status_renames__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+static void _rename_helper(
+ git_repository *repo, const char *from, const char *to, const char *extra)
+{
+ git_buf oldpath = GIT_BUF_INIT, newpath = GIT_BUF_INIT;
+
+ cl_git_pass(git_buf_joinpath(
+ &oldpath, git_repository_workdir(repo), from));
+ cl_git_pass(git_buf_joinpath(
+ &newpath, git_repository_workdir(repo), to));
+
+ cl_git_pass(p_rename(oldpath.ptr, newpath.ptr));
+
+ if (extra)
+ cl_git_append2file(newpath.ptr, extra);
+
+ git_buf_free(&oldpath);
+ git_buf_free(&newpath);
+}
+
+#define rename_file(R,O,N) _rename_helper((R), (O), (N), NULL)
+#define rename_and_edit_file(R,O,N) \
+ _rename_helper((R), (O), (N), "Added at the end to keep similarity!")
+
+struct status_entry {
+ git_status_t status;
+ const char *oldname;
+ const char *newname;
+};
+
+static void check_status(
+ git_status_list *status_list,
+ struct status_entry *expected_list,
+ size_t expected_len)
+{
+ const git_status_entry *actual;
+ const struct status_entry *expected;
+ const char *oldname, *newname;
+ size_t i, files_in_status = git_status_list_entrycount(status_list);
+
+ cl_assert_equal_sz(expected_len, files_in_status);
+
+ for (i = 0; i < expected_len; i++) {
+ actual = git_status_byindex(status_list, i);
+ expected = &expected_list[i];
+
+ oldname = actual->head_to_index ? actual->head_to_index->old_file.path :
+ actual->index_to_workdir ? actual->index_to_workdir->old_file.path : NULL;
+
+ newname = actual->index_to_workdir ? actual->index_to_workdir->new_file.path :
+ actual->head_to_index ? actual->head_to_index->new_file.path : NULL;
+
+ cl_assert_equal_i_fmt(expected->status, actual->status, "%04x");
+
+ if (oldname)
+ cl_assert(git__strcmp(oldname, expected->oldname) == 0);
+ else
+ cl_assert(expected->oldname == NULL);
+
+ if (actual->status & (GIT_STATUS_INDEX_RENAMED|GIT_STATUS_WT_RENAMED)) {
+ if (newname)
+ cl_assert(git__strcmp(newname, expected->newname) == 0);
+ else
+ cl_assert(expected->newname == NULL);
+ }
+ }
+}
+
+void test_status_renames__head2index_one(void)
+{
+ git_index *index;
+ git_status_list *statuslist;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ struct status_entry expected[] = {
+ { GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", "newname.txt" },
+ };
+
+ opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ rename_file(g_repo, "ikeepsix.txt", "newname.txt");
+
+ cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
+ cl_git_pass(git_index_add_bypath(index, "newname.txt"));
+ cl_git_pass(git_index_write(index));
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+ check_status(statuslist, expected, 1);
+ git_status_list_free(statuslist);
+
+ git_index_free(index);
+}
+
+void test_status_renames__head2index_two(void)
+{
+ git_index *index;
+ git_status_list *statuslist;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ struct status_entry expected[] = {
+ { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED,
+ "sixserving.txt", "aaa.txt" },
+ { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED,
+ "untimely.txt", "bbb.txt" },
+ { GIT_STATUS_INDEX_RENAMED, "songof7cities.txt", "ccc.txt" },
+ { GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", "ddd.txt" },
+ };
+
+ opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ rename_file(g_repo, "ikeepsix.txt", "ddd.txt");
+ rename_and_edit_file(g_repo, "sixserving.txt", "aaa.txt");
+ rename_file(g_repo, "songof7cities.txt", "ccc.txt");
+ rename_and_edit_file(g_repo, "untimely.txt", "bbb.txt");
+
+ cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
+ cl_git_pass(git_index_remove_bypath(index, "sixserving.txt"));
+ cl_git_pass(git_index_remove_bypath(index, "songof7cities.txt"));
+ cl_git_pass(git_index_remove_bypath(index, "untimely.txt"));
+ cl_git_pass(git_index_add_bypath(index, "ddd.txt"));
+ cl_git_pass(git_index_add_bypath(index, "aaa.txt"));
+ cl_git_pass(git_index_add_bypath(index, "ccc.txt"));
+ cl_git_pass(git_index_add_bypath(index, "bbb.txt"));
+ cl_git_pass(git_index_write(index));
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+ check_status(statuslist, expected, 4);
+ git_status_list_free(statuslist);
+
+ git_index_free(index);
+}
+
+void test_status_renames__head2index_no_rename_from_rewrite(void)
+{
+ git_index *index;
+ git_status_list *statuslist;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ struct status_entry expected[] = {
+ { GIT_STATUS_INDEX_MODIFIED, "ikeepsix.txt", "ikeepsix.txt" },
+ { GIT_STATUS_INDEX_MODIFIED, "sixserving.txt", "sixserving.txt" },
+ };
+
+ opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ rename_file(g_repo, "ikeepsix.txt", "_temp_.txt");
+ rename_file(g_repo, "sixserving.txt", "ikeepsix.txt");
+ rename_file(g_repo, "_temp_.txt", "sixserving.txt");
+
+ cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt"));
+ cl_git_pass(git_index_add_bypath(index, "sixserving.txt"));
+ cl_git_pass(git_index_write(index));
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+ check_status(statuslist, expected, 2);
+ git_status_list_free(statuslist);
+
+ git_index_free(index);
+}
+
+void test_status_renames__head2index_rename_from_rewrite(void)
+{
+ git_index *index;
+ git_status_list *statuslist;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ struct status_entry expected[] = {
+ { GIT_STATUS_INDEX_RENAMED, "sixserving.txt", "ikeepsix.txt" },
+ { GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", "sixserving.txt" },
+ };
+
+ opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
+ opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ rename_file(g_repo, "ikeepsix.txt", "_temp_.txt");
+ rename_file(g_repo, "sixserving.txt", "ikeepsix.txt");
+ rename_file(g_repo, "_temp_.txt", "sixserving.txt");
+
+ cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt"));
+ cl_git_pass(git_index_add_bypath(index, "sixserving.txt"));
+ cl_git_pass(git_index_write(index));
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+ check_status(statuslist, expected, 2);
+ git_status_list_free(statuslist);
+
+ git_index_free(index);
+}
+
+void test_status_renames__index2workdir_one(void)
+{
+ git_status_list *statuslist;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ struct status_entry expected[] = {
+ { GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "newname.txt" },
+ };
+
+ opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
+ opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
+
+ rename_file(g_repo, "ikeepsix.txt", "newname.txt");
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+ check_status(statuslist, expected, 1);
+ git_status_list_free(statuslist);
+}
+
+void test_status_renames__index2workdir_two(void)
+{
+ git_status_list *statuslist;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ struct status_entry expected[] = {
+ { GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED,
+ "sixserving.txt", "aaa.txt" },
+ { GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED,
+ "untimely.txt", "bbb.txt" },
+ { GIT_STATUS_WT_RENAMED, "songof7cities.txt", "ccc.txt" },
+ { GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "ddd.txt" },
+ };
+
+ opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
+ opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
+
+ rename_file(g_repo, "ikeepsix.txt", "ddd.txt");
+ rename_and_edit_file(g_repo, "sixserving.txt", "aaa.txt");
+ rename_file(g_repo, "songof7cities.txt", "ccc.txt");
+ rename_and_edit_file(g_repo, "untimely.txt", "bbb.txt");
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+ check_status(statuslist, expected, 4);
+ git_status_list_free(statuslist);
+}
+
+void test_status_renames__index2workdir_rename_from_rewrite(void)
+{
+ git_index *index;
+ git_status_list *statuslist;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ struct status_entry expected[] = {
+ { GIT_STATUS_WT_RENAMED, "sixserving.txt", "ikeepsix.txt" },
+ { GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "sixserving.txt" },
+ };
+
+ opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
+ opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ rename_file(g_repo, "ikeepsix.txt", "_temp_.txt");
+ rename_file(g_repo, "sixserving.txt", "ikeepsix.txt");
+ rename_file(g_repo, "_temp_.txt", "sixserving.txt");
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+ check_status(statuslist, expected, 2);
+ git_status_list_free(statuslist);
+
+ git_index_free(index);
+}
+
+void test_status_renames__both_one(void)
+{
+ git_index *index;
+ git_status_list *statuslist;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ struct status_entry expected[] = {
+ { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED,
+ "ikeepsix.txt", "newname-workdir.txt" },
+ };
+
+ opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
+ opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
+ opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ rename_file(g_repo, "ikeepsix.txt", "newname-index.txt");
+
+ cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
+ cl_git_pass(git_index_add_bypath(index, "newname-index.txt"));
+ cl_git_pass(git_index_write(index));
+
+ rename_file(g_repo, "newname-index.txt", "newname-workdir.txt");
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+ check_status(statuslist, expected, 1);
+ git_status_list_free(statuslist);
+
+ git_index_free(index);
+}
+
+void test_status_renames__both_two(void)
+{
+ git_index *index;
+ git_status_list *statuslist;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ struct status_entry expected[] = {
+ { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED |
+ GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED,
+ "ikeepsix.txt", "ikeepsix-both.txt" },
+ { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED,
+ "sixserving.txt", "sixserving-index.txt" },
+ { GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED,
+ "songof7cities.txt", "songof7cities-workdir.txt" },
+ { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED,
+ "untimely.txt", "untimely-both.txt" },
+ };
+
+ opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
+ opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
+ opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ rename_and_edit_file(g_repo, "ikeepsix.txt", "ikeepsix-index.txt");
+ rename_and_edit_file(g_repo, "sixserving.txt", "sixserving-index.txt");
+ rename_file(g_repo, "untimely.txt", "untimely-index.txt");
+
+ cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
+ cl_git_pass(git_index_remove_bypath(index, "sixserving.txt"));
+ cl_git_pass(git_index_remove_bypath(index, "untimely.txt"));
+ cl_git_pass(git_index_add_bypath(index, "ikeepsix-index.txt"));
+ cl_git_pass(git_index_add_bypath(index, "sixserving-index.txt"));
+ cl_git_pass(git_index_add_bypath(index, "untimely-index.txt"));
+ cl_git_pass(git_index_write(index));
+
+ rename_and_edit_file(g_repo, "ikeepsix-index.txt", "ikeepsix-both.txt");
+ rename_and_edit_file(g_repo, "songof7cities.txt", "songof7cities-workdir.txt");
+ rename_file(g_repo, "untimely-index.txt", "untimely-both.txt");
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+ check_status(statuslist, expected, 4);
+ git_status_list_free(statuslist);
+
+ git_index_free(index);
+}
+
+
+void test_status_renames__both_rename_from_rewrite(void)
+{
+ git_index *index;
+ git_status_list *statuslist;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ struct status_entry expected[] = {
+ { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED,
+ "songof7cities.txt", "ikeepsix.txt" },
+ { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED,
+ "ikeepsix.txt", "sixserving.txt" },
+ { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED,
+ "sixserving.txt", "songof7cities.txt" },
+ };
+
+ opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
+ opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
+ opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
+ opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ rename_file(g_repo, "ikeepsix.txt", "_temp_.txt");
+ rename_file(g_repo, "sixserving.txt", "ikeepsix.txt");
+ rename_file(g_repo, "songof7cities.txt", "sixserving.txt");
+ rename_file(g_repo, "_temp_.txt", "songof7cities.txt");
+
+ cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt"));
+ cl_git_pass(git_index_add_bypath(index, "sixserving.txt"));
+ cl_git_pass(git_index_add_bypath(index, "songof7cities.txt"));
+ cl_git_pass(git_index_write(index));
+
+ rename_file(g_repo, "songof7cities.txt", "_temp_.txt");
+ rename_file(g_repo, "ikeepsix.txt", "songof7cities.txt");
+ rename_file(g_repo, "sixserving.txt", "ikeepsix.txt");
+ rename_file(g_repo, "_temp_.txt", "sixserving.txt");
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+ check_status(statuslist, expected, 3);
+ git_status_list_free(statuslist);
+
+ git_index_free(index);
+}
+
+void test_status_renames__rewrites_only_for_renames(void)
+{
+ git_index *index;
+ git_status_list *statuslist;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ struct status_entry expected[] = {
+ { GIT_STATUS_WT_MODIFIED, "ikeepsix.txt", "ikeepsix.txt" },
+ };
+
+ opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
+ opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
+ opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
+ opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ cl_git_rewritefile("renames/ikeepsix.txt",
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n" \
+ "This is enough content for the file to be rewritten.\n");
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+ check_status(statuslist, expected, 1);
+ git_status_list_free(statuslist);
+
+ git_index_free(index);
+}
+
+void test_status_renames__both_casechange_one(void)
+{
+ git_index *index;
+ git_status_list *statuslist;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ int index_caps;
+ struct status_entry expected_icase[] = {
+ { GIT_STATUS_INDEX_RENAMED,
+ "ikeepsix.txt", "IKeepSix.txt" },
+ };
+ struct status_entry expected_case[] = {
+ { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED,
+ "ikeepsix.txt", "IKEEPSIX.txt" },
+ };
+
+ opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
+ opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
+ opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ index_caps = git_index_caps(index);
+
+ rename_file(g_repo, "ikeepsix.txt", "IKeepSix.txt");
+
+ cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
+ cl_git_pass(git_index_add_bypath(index, "IKeepSix.txt"));
+ cl_git_pass(git_index_write(index));
+
+ /* on a case-insensitive file system, this change won't matter.
+ * on a case-sensitive one, it will.
+ */
+ rename_file(g_repo, "IKeepSix.txt", "IKEEPSIX.txt");
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+
+ check_status(statuslist, (index_caps & GIT_INDEXCAP_IGNORE_CASE) ?
+ expected_icase : expected_case, 1);
+
+ git_status_list_free(statuslist);
+
+ git_index_free(index);
+}
+
+void test_status_renames__both_casechange_two(void)
+{
+ git_index *index;
+ git_status_list *statuslist;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ int index_caps;
+ struct status_entry expected_icase[] = {
+ { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED |
+ GIT_STATUS_WT_MODIFIED,
+ "ikeepsix.txt", "IKeepSix.txt" },
+ { GIT_STATUS_INDEX_MODIFIED,
+ "sixserving.txt", "sixserving.txt" },
+ { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_MODIFIED,
+ "songof7cities.txt", "songof7.txt" },
+ { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED,
+ "untimely.txt", "untimeliest.txt" }
+ };
+ struct status_entry expected_case[] = {
+ { GIT_STATUS_INDEX_RENAMED |
+ GIT_STATUS_WT_MODIFIED | GIT_STATUS_WT_RENAMED,
+ "songof7cities.txt", "SONGOF7.txt" },
+ { GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_RENAMED,
+ "sixserving.txt", "SixServing.txt" },
+ { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED |
+ GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED,
+ "ikeepsix.txt", "ikeepsix.txt" },
+ { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED,
+ "untimely.txt", "untimeliest.txt" }
+ };
+
+ opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
+ opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
+ opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ index_caps = git_index_caps(index);
+
+ rename_and_edit_file(g_repo, "ikeepsix.txt", "IKeepSix.txt");
+ rename_and_edit_file(g_repo, "sixserving.txt", "sixserving.txt");
+ rename_file(g_repo, "songof7cities.txt", "songof7.txt");
+ rename_file(g_repo, "untimely.txt", "untimelier.txt");
+
+ cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
+ cl_git_pass(git_index_remove_bypath(index, "sixserving.txt"));
+ cl_git_pass(git_index_remove_bypath(index, "songof7cities.txt"));
+ cl_git_pass(git_index_remove_bypath(index, "untimely.txt"));
+ cl_git_pass(git_index_add_bypath(index, "IKeepSix.txt"));
+ cl_git_pass(git_index_add_bypath(index, "sixserving.txt"));
+ cl_git_pass(git_index_add_bypath(index, "songof7.txt"));
+ cl_git_pass(git_index_add_bypath(index, "untimelier.txt"));
+ cl_git_pass(git_index_write(index));
+
+ rename_and_edit_file(g_repo, "IKeepSix.txt", "ikeepsix.txt");
+ rename_file(g_repo, "sixserving.txt", "SixServing.txt");
+ rename_and_edit_file(g_repo, "songof7.txt", "SONGOF7.txt");
+ rename_file(g_repo, "untimelier.txt", "untimeliest.txt");
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+
+ check_status(statuslist, (index_caps & GIT_INDEXCAP_IGNORE_CASE) ?
+ expected_icase : expected_case, 4);
+
+ git_status_list_free(statuslist);
+
+ git_index_free(index);
+}
+
+void test_status_renames__zero_byte_file_does_not_fail(void)
+{
+ git_status_list *statuslist;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+
+ struct status_entry expected[] = {
+ { GIT_STATUS_WT_DELETED, "ikeepsix.txt", "ikeepsix.txt" },
+ { GIT_STATUS_WT_NEW, "zerobyte.txt", "zerobyte.txt" },
+ };
+
+ opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES |
+ GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
+ GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR |
+ GIT_STATUS_OPT_INCLUDE_IGNORED |
+ GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS |
+ GIT_STATUS_SHOW_INDEX_AND_WORKDIR |
+ GIT_STATUS_OPT_RECURSE_IGNORED_DIRS;
+
+ p_unlink("renames/ikeepsix.txt");
+ cl_git_mkfile("renames/zerobyte.txt", "");
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+ check_status(statuslist, expected, 2);
+ git_status_list_free(statuslist);
+}
+
+#ifdef GIT_USE_ICONV
+static char *nfc = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D";
+static char *nfd = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D";
+#endif
+
+void test_status_renames__precomposed_unicode_rename(void)
+{
+#ifdef GIT_USE_ICONV
+ git_status_list *statuslist;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ struct status_entry expected0[] = {
+ { GIT_STATUS_WT_NEW, nfd, NULL },
+ { GIT_STATUS_WT_DELETED, "sixserving.txt", NULL },
+ };
+ struct status_entry expected1[] = {
+ { GIT_STATUS_WT_RENAMED, "sixserving.txt", nfd },
+ };
+ struct status_entry expected2[] = {
+ { GIT_STATUS_WT_DELETED, "sixserving.txt", NULL },
+ { GIT_STATUS_WT_NEW, nfc, NULL },
+ };
+ struct status_entry expected3[] = {
+ { GIT_STATUS_WT_RENAMED, "sixserving.txt", nfc },
+ };
+
+ rename_file(g_repo, "sixserving.txt", nfc);
+
+ opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
+
+ cl_repo_set_bool(g_repo, "core.precomposeunicode", false);
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+ check_status(statuslist, expected0, ARRAY_SIZE(expected0));
+ git_status_list_free(statuslist);
+
+ opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+ check_status(statuslist, expected1, ARRAY_SIZE(expected1));
+ git_status_list_free(statuslist);
+
+ cl_repo_set_bool(g_repo, "core.precomposeunicode", true);
+
+ opts.flags &= ~GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+ check_status(statuslist, expected2, ARRAY_SIZE(expected2));
+ git_status_list_free(statuslist);
+
+ opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+ check_status(statuslist, expected3, ARRAY_SIZE(expected3));
+ git_status_list_free(statuslist);
+#endif
+}
+
+void test_status_renames__precomposed_unicode_toggle_is_rename(void)
+{
+#ifdef GIT_USE_ICONV
+ git_status_list *statuslist;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ struct status_entry expected0[] = {
+ { GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", nfd },
+ };
+ struct status_entry expected1[] = {
+ { GIT_STATUS_WT_RENAMED, nfd, nfc },
+ };
+ struct status_entry expected2[] = {
+ { GIT_STATUS_INDEX_RENAMED, nfd, nfc },
+ };
+ struct status_entry expected3[] = {
+ { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, nfd, nfd },
+ };
+
+ cl_repo_set_bool(g_repo, "core.precomposeunicode", false);
+ rename_file(g_repo, "ikeepsix.txt", nfd);
+
+ {
+ git_index *index;
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
+ cl_git_pass(git_index_add_bypath(index, nfd));
+ cl_git_pass(git_index_write(index));
+ git_index_free(index);
+ }
+
+ opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
+ GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+ check_status(statuslist, expected0, ARRAY_SIZE(expected0));
+ git_status_list_free(statuslist);
+
+ cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "commit nfd");
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+ cl_assert_equal_sz(0, git_status_list_entrycount(statuslist));
+ git_status_list_free(statuslist);
+
+ cl_repo_set_bool(g_repo, "core.precomposeunicode", true);
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+ check_status(statuslist, expected1, ARRAY_SIZE(expected1));
+ git_status_list_free(statuslist);
+
+ {
+ git_index *index;
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_index_remove_bypath(index, nfd));
+ cl_git_pass(git_index_add_bypath(index, nfc));
+ cl_git_pass(git_index_write(index));
+ git_index_free(index);
+ }
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+ check_status(statuslist, expected2, ARRAY_SIZE(expected2));
+ git_status_list_free(statuslist);
+
+ cl_repo_set_bool(g_repo, "core.precomposeunicode", false);
+
+ cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
+ check_status(statuslist, expected3, ARRAY_SIZE(expected3));
+ git_status_list_free(statuslist);
+#endif
+}
+
diff --git a/tests/status/single.c b/tests/status/single.c
new file mode 100644
index 000000000..292c9120a
--- /dev/null
+++ b/tests/status/single.c
@@ -0,0 +1,45 @@
+#include "clar_libgit2.h"
+#include "posix.h"
+
+static void
+cleanup__remove_file(void *_file)
+{
+ cl_must_pass(p_unlink((char *)_file));
+}
+
+/* test retrieving OID from a file apart from the ODB */
+void test_status_single__hash_single_file(void)
+{
+ static const char file_name[] = "new_file";
+ static const char file_contents[] = "new_file\n";
+ static const char file_hash[] = "d4fa8600b4f37d7516bef4816ae2c64dbf029e3a";
+
+ git_oid expected_id, actual_id;
+
+ /* initialization */
+ git_oid_fromstr(&expected_id, file_hash);
+ cl_git_mkfile(file_name, file_contents);
+ cl_set_cleanup(&cleanup__remove_file, (void *)file_name);
+
+ cl_git_pass(git_odb_hashfile(&actual_id, file_name, GIT_OBJ_BLOB));
+ cl_assert(git_oid_cmp(&expected_id, &actual_id) == 0);
+}
+
+/* test retrieving OID from an empty file apart from the ODB */
+void test_status_single__hash_single_empty_file(void)
+{
+ static const char file_name[] = "new_empty_file";
+ static const char file_contents[] = "";
+ static const char file_hash[] = "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391";
+
+ git_oid expected_id, actual_id;
+
+ /* initialization */
+ git_oid_fromstr(&expected_id, file_hash);
+ cl_git_mkfile(file_name, file_contents);
+ cl_set_cleanup(&cleanup__remove_file, (void *)file_name);
+
+ cl_git_pass(git_odb_hashfile(&actual_id, file_name, GIT_OBJ_BLOB));
+ cl_assert(git_oid_cmp(&expected_id, &actual_id) == 0);
+}
+
diff --git a/tests/status/status_data.h b/tests/status/status_data.h
new file mode 100644
index 000000000..8ad4235fd
--- /dev/null
+++ b/tests/status/status_data.h
@@ -0,0 +1,326 @@
+#include "status_helpers.h"
+
+// A utf-8 string with 83 characters, but 249 bytes.
+static const char *longname = "\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97";
+
+
+/* entries for a plain copy of tests/resources/status */
+
+static const char *entry_paths0[] = {
+ "file_deleted",
+ "ignored_file",
+ "modified_file",
+ "new_file",
+ "staged_changes",
+ "staged_changes_file_deleted",
+ "staged_changes_modified_file",
+ "staged_delete_file_deleted",
+ "staged_delete_modified_file",
+ "staged_new_file",
+ "staged_new_file_deleted_file",
+ "staged_new_file_modified_file",
+
+ "subdir/deleted_file",
+ "subdir/modified_file",
+ "subdir/new_file",
+
+ "\xe8\xbf\x99",
+};
+
+static const unsigned int entry_statuses0[] = {
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_IGNORED,
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_DELETED,
+ GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_INDEX_DELETED,
+ GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_NEW,
+ GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_DELETED,
+ GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_MODIFIED,
+
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_WT_NEW,
+
+ GIT_STATUS_WT_NEW,
+};
+
+static const int entry_count0 = 16;
+
+/* entries for a copy of tests/resources/status with all content
+ * deleted from the working directory
+ */
+
+static const char *entry_paths2[] = {
+ "current_file",
+ "file_deleted",
+ "modified_file",
+ "staged_changes",
+ "staged_changes_file_deleted",
+ "staged_changes_modified_file",
+ "staged_delete_file_deleted",
+ "staged_delete_modified_file",
+ "staged_new_file",
+ "staged_new_file_deleted_file",
+ "staged_new_file_modified_file",
+ "subdir.txt",
+ "subdir/current_file",
+ "subdir/deleted_file",
+ "subdir/modified_file",
+};
+
+static const unsigned int entry_statuses2[] = {
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_INDEX_DELETED,
+ GIT_STATUS_INDEX_DELETED,
+ GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_DELETED,
+};
+
+static const int entry_count2 = 15;
+
+/* entries for a copy of tests/resources/status with some mods */
+
+static const char *entry_paths3_icase[] = {
+ ".HEADER",
+ "42-is-not-prime.sigh",
+ "current_file",
+ "current_file/",
+ "file_deleted",
+ "ignored_file",
+ "modified_file",
+ "new_file",
+ "README.md",
+ "staged_changes",
+ "staged_changes_file_deleted",
+ "staged_changes_modified_file",
+ "staged_delete_file_deleted",
+ "staged_delete_modified_file",
+ "staged_new_file",
+ "staged_new_file_deleted_file",
+ "staged_new_file_modified_file",
+ "subdir",
+ "subdir/current_file",
+ "subdir/deleted_file",
+ "subdir/modified_file",
+ "\xe8\xbf\x99",
+};
+
+static const unsigned int entry_statuses3_icase[] = {
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_IGNORED,
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_INDEX_DELETED,
+ GIT_STATUS_WT_NEW | GIT_STATUS_INDEX_DELETED,
+ GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_NEW,
+};
+
+static const char *entry_paths3[] = {
+ ".HEADER",
+ "42-is-not-prime.sigh",
+ "README.md",
+ "current_file",
+ "current_file/",
+ "file_deleted",
+ "ignored_file",
+ "modified_file",
+ "new_file",
+ "staged_changes",
+ "staged_changes_file_deleted",
+ "staged_changes_modified_file",
+ "staged_delete_file_deleted",
+ "staged_delete_modified_file",
+ "staged_new_file",
+ "staged_new_file_deleted_file",
+ "staged_new_file_modified_file",
+ "subdir",
+ "subdir/current_file",
+ "subdir/deleted_file",
+ "subdir/modified_file",
+ "\xe8\xbf\x99",
+};
+
+static const unsigned int entry_statuses3[] = {
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_IGNORED,
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_INDEX_DELETED,
+ GIT_STATUS_WT_NEW | GIT_STATUS_INDEX_DELETED,
+ GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_NEW,
+};
+
+static const int entry_count3 = 22;
+
+
+/* entries for a copy of tests/resources/status with some mods
+ * and different options to the status call
+ */
+
+static const char *entry_paths4[] = {
+ ".new_file",
+ "current_file",
+ "current_file/current_file",
+ "current_file/modified_file",
+ "current_file/new_file",
+ "file_deleted",
+ "modified_file",
+ "new_file",
+ "staged_changes",
+ "staged_changes_file_deleted",
+ "staged_changes_modified_file",
+ "staged_delete_file_deleted",
+ "staged_delete_modified_file",
+ "staged_new_file",
+ "staged_new_file_deleted_file",
+ "staged_new_file_modified_file",
+ "subdir",
+ "subdir/current_file",
+ "subdir/deleted_file",
+ "subdir/modified_file",
+ "zzz_new_dir/new_file",
+ "zzz_new_file",
+ "\xe8\xbf\x99",
+};
+
+static const unsigned int entry_statuses4[] = {
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_INDEX_DELETED,
+ GIT_STATUS_WT_NEW | GIT_STATUS_INDEX_DELETED,
+ GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_NEW,
+};
+
+static const int entry_count4 = 23;
+
+
+/* entries for a copy of tests/resources/status with options
+ * passed to the status call in order to only get the differences
+ * between the HEAD and the index (changes to be committed)
+ */
+
+static const char *entry_paths5[] = {
+ "staged_changes",
+ "staged_changes_file_deleted",
+ "staged_changes_modified_file",
+ "staged_delete_file_deleted",
+ "staged_delete_modified_file",
+ "staged_new_file",
+ "staged_new_file_deleted_file",
+ "staged_new_file_modified_file",
+};
+
+static const unsigned int entry_statuses5[] = {
+ GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_INDEX_DELETED,
+ GIT_STATUS_INDEX_DELETED,
+ GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_INDEX_NEW,
+};
+
+static const int entry_count5 = 8;
+
+
+/* entries for a copy of tests/resources/status with options
+ * passed to the status call in order to only get the differences
+ * between the workdir and the index (changes not staged, untracked files)
+ */
+
+static const char *entry_paths6[] = {
+ "file_deleted",
+ "ignored_file",
+ "modified_file",
+ "new_file",
+ "staged_changes_file_deleted",
+ "staged_changes_modified_file",
+ "staged_delete_modified_file",
+ "staged_new_file_deleted_file",
+ "staged_new_file_modified_file",
+ "subdir/deleted_file",
+ "subdir/modified_file",
+ "subdir/new_file",
+ "\xe8\xbf\x99",
+};
+
+static const unsigned int entry_statuses6[] = {
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_IGNORED,
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_NEW,
+};
+
+static const int entry_count6 = 13;
diff --git a/tests/status/status_helpers.c b/tests/status/status_helpers.c
new file mode 100644
index 000000000..088279252
--- /dev/null
+++ b/tests/status/status_helpers.c
@@ -0,0 +1,94 @@
+#include "clar_libgit2.h"
+#include "status_helpers.h"
+
+int cb_status__normal(
+ const char *path, unsigned int status_flags, void *payload)
+{
+ status_entry_counts *counts = payload;
+
+ if (counts->debug)
+ cb_status__print(path, status_flags, NULL);
+
+ if (counts->entry_count >= counts->expected_entry_count)
+ counts->wrong_status_flags_count++;
+ else if (strcmp(path, counts->expected_paths[counts->entry_count]))
+ counts->wrong_sorted_path++;
+ else if (status_flags != counts->expected_statuses[counts->entry_count])
+ counts->wrong_status_flags_count++;
+
+ counts->entry_count++;
+ return 0;
+}
+
+int cb_status__count(const char *p, unsigned int s, void *payload)
+{
+ volatile int *count = (int *)payload;
+
+ GIT_UNUSED(p);
+ GIT_UNUSED(s);
+
+ (*count)++;
+
+ return 0;
+}
+
+int cb_status__single(const char *p, unsigned int s, void *payload)
+{
+ status_entry_single *data = (status_entry_single *)payload;
+
+ if (data->debug)
+ fprintf(stderr, "%02d: %s (%04x)\n", data->count, p, s);
+
+ data->count++;
+ data->status = s;
+
+ return 0;
+}
+
+int cb_status__print(
+ const char *path, unsigned int status_flags, void *payload)
+{
+ char istatus = ' ', wstatus = ' ';
+ int icount = 0, wcount = 0;
+
+ if (status_flags & GIT_STATUS_INDEX_NEW) {
+ istatus = 'A'; icount++;
+ }
+ if (status_flags & GIT_STATUS_INDEX_MODIFIED) {
+ istatus = 'M'; icount++;
+ }
+ if (status_flags & GIT_STATUS_INDEX_DELETED) {
+ istatus = 'D'; icount++;
+ }
+ if (status_flags & GIT_STATUS_INDEX_RENAMED) {
+ istatus = 'R'; icount++;
+ }
+ if (status_flags & GIT_STATUS_INDEX_TYPECHANGE) {
+ istatus = 'T'; icount++;
+ }
+
+ if (status_flags & GIT_STATUS_WT_NEW) {
+ wstatus = 'A'; wcount++;
+ }
+ if (status_flags & GIT_STATUS_WT_MODIFIED) {
+ wstatus = 'M'; wcount++;
+ }
+ if (status_flags & GIT_STATUS_WT_DELETED) {
+ wstatus = 'D'; wcount++;
+ }
+ if (status_flags & GIT_STATUS_WT_TYPECHANGE) {
+ wstatus = 'T'; wcount++;
+ }
+ if (status_flags & GIT_STATUS_IGNORED) {
+ wstatus = 'I'; wcount++;
+ }
+
+ fprintf(stderr, "%c%c %s (%d/%d%s)\n",
+ istatus, wstatus, path, icount, wcount,
+ (icount > 1 || wcount > 1) ? " INVALID COMBO" : "");
+
+ if (payload)
+ *((int *)payload) += 1;
+
+ return 0;
+}
diff --git a/tests/status/status_helpers.h b/tests/status/status_helpers.h
new file mode 100644
index 000000000..242076cc9
--- /dev/null
+++ b/tests/status/status_helpers.h
@@ -0,0 +1,49 @@
+#ifndef INCLUDE_cl_status_helpers_h__
+#define INCLUDE_cl_status_helpers_h__
+
+typedef struct {
+ int wrong_status_flags_count;
+ int wrong_sorted_path;
+ int entry_count;
+ const unsigned int* expected_statuses;
+ const char** expected_paths;
+ int expected_entry_count;
+ const char *file;
+ int line;
+ bool debug;
+} status_entry_counts;
+
+#define status_counts_init(counts, paths, statuses) do { \
+ memset(&(counts), 0, sizeof(counts)); \
+ (counts).expected_statuses = (statuses); \
+ (counts).expected_paths = (paths); \
+ (counts).file = __FILE__; \
+ (counts).line = __LINE__; \
+ } while (0)
+
+/* cb_status__normal takes payload of "status_entry_counts *" */
+
+extern int cb_status__normal(
+ const char *path, unsigned int status_flags, void *payload);
+
+
+/* cb_status__count takes payload of "int *" */
+
+extern int cb_status__count(const char *p, unsigned int s, void *payload);
+
+
+typedef struct {
+ int count;
+ unsigned int status;
+ bool debug;
+} status_entry_single;
+
+/* cb_status__single takes payload of "status_entry_single *" */
+
+extern int cb_status__single(const char *p, unsigned int s, void *payload);
+
+/* cb_status__print takes optional payload of "int *" */
+
+extern int cb_status__print(const char *p, unsigned int s, void *payload);
+
+#endif
diff --git a/tests/status/submodules.c b/tests/status/submodules.c
new file mode 100644
index 000000000..63cf73f36
--- /dev/null
+++ b/tests/status/submodules.c
@@ -0,0 +1,478 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+#include "status_helpers.h"
+#include "../submodule/submodule_helpers.h"
+
+static git_repository *g_repo = NULL;
+
+void test_status_submodules__initialize(void)
+{
+}
+
+void test_status_submodules__cleanup(void)
+{
+}
+
+void test_status_submodules__api(void)
+{
+ git_submodule *sm;
+
+ g_repo = setup_fixture_submodules();
+
+ cl_assert(git_submodule_lookup(NULL, g_repo, "nonexistent") == GIT_ENOTFOUND);
+
+ cl_assert(git_submodule_lookup(NULL, g_repo, "modified") == GIT_ENOTFOUND);
+
+ cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo"));
+ cl_assert(sm != NULL);
+ cl_assert_equal_s("testrepo", git_submodule_name(sm));
+ cl_assert_equal_s("testrepo", git_submodule_path(sm));
+ git_submodule_free(sm);
+}
+
+void test_status_submodules__0(void)
+{
+ int counts = 0;
+
+ g_repo = setup_fixture_submodules();
+
+ cl_assert(git_path_isdir("submodules/.git"));
+ cl_assert(git_path_isdir("submodules/testrepo/.git"));
+ cl_assert(git_path_isfile("submodules/.gitmodules"));
+
+ cl_git_pass(
+ git_status_foreach(g_repo, cb_status__count, &counts)
+ );
+
+ cl_assert_equal_i(6, counts);
+}
+
+static const char *expected_files[] = {
+ ".gitmodules",
+ "added",
+ "deleted",
+ "ignored",
+ "modified",
+ "untracked"
+};
+
+static unsigned int expected_status[] = {
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_INDEX_DELETED,
+ GIT_STATUS_IGNORED,
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_WT_NEW
+};
+
+static int cb_status__match(const char *p, unsigned int s, void *payload)
+{
+ status_entry_counts *counts = payload;
+ int idx = counts->entry_count++;
+
+ clar__assert_equal(
+ counts->file, counts->line,
+ "Status path mismatch", 1,
+ "%s", counts->expected_paths[idx], p);
+
+ clar__assert_equal(
+ counts->file, counts->line,
+ "Status code mismatch", 1,
+ "%o", counts->expected_statuses[idx], s);
+
+ return 0;
+}
+
+void test_status_submodules__1(void)
+{
+ status_entry_counts counts;
+
+ g_repo = setup_fixture_submodules();
+
+ cl_assert(git_path_isdir("submodules/.git"));
+ cl_assert(git_path_isdir("submodules/testrepo/.git"));
+ cl_assert(git_path_isfile("submodules/.gitmodules"));
+
+ status_counts_init(counts, expected_files, expected_status);
+
+ cl_git_pass( git_status_foreach(g_repo, cb_status__match, &counts) );
+
+ cl_assert_equal_i(6, counts.entry_count);
+}
+
+void test_status_submodules__single_file(void)
+{
+ unsigned int status = 0;
+ g_repo = setup_fixture_submodules();
+ cl_git_pass( git_status_file(&status, g_repo, "testrepo") );
+ cl_assert(!status);
+}
+
+void test_status_submodules__moved_head(void)
+{
+ git_submodule *sm;
+ git_repository *smrepo;
+ git_oid oid;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ status_entry_counts counts;
+ static const char *expected_files_with_sub[] = {
+ ".gitmodules",
+ "added",
+ "deleted",
+ "ignored",
+ "modified",
+ "testrepo",
+ "untracked"
+ };
+ static unsigned int expected_status_with_sub[] = {
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_INDEX_DELETED,
+ GIT_STATUS_IGNORED,
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_WT_NEW
+ };
+
+ g_repo = setup_fixture_submodules();
+
+ cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo"));
+ cl_git_pass(git_submodule_open(&smrepo, sm));
+ git_submodule_free(sm);
+
+ /* move submodule HEAD to c47800c7266a2be04c571c04d5a6614691ea99bd */
+ cl_git_pass(
+ git_oid_fromstr(&oid, "c47800c7266a2be04c571c04d5a6614691ea99bd"));
+ cl_git_pass(git_repository_set_head_detached(smrepo, &oid, NULL, NULL));
+
+ /* first do a normal status, which should now include the submodule */
+
+ opts.flags = GIT_STATUS_OPT_DEFAULTS;
+
+ status_counts_init(
+ counts, expected_files_with_sub, expected_status_with_sub);
+ cl_git_pass(
+ git_status_foreach_ext(g_repo, &opts, cb_status__match, &counts));
+ cl_assert_equal_i(7, counts.entry_count);
+
+ /* try again with EXCLUDE_SUBMODULES which should skip it */
+
+ opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
+
+ status_counts_init(counts, expected_files, expected_status);
+ cl_git_pass(
+ git_status_foreach_ext(g_repo, &opts, cb_status__match, &counts));
+ cl_assert_equal_i(6, counts.entry_count);
+
+ git_repository_free(smrepo);
+}
+
+void test_status_submodules__dirty_workdir_only(void)
+{
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ status_entry_counts counts;
+ static const char *expected_files_with_sub[] = {
+ ".gitmodules",
+ "added",
+ "deleted",
+ "ignored",
+ "modified",
+ "testrepo",
+ "untracked"
+ };
+ static unsigned int expected_status_with_sub[] = {
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_INDEX_DELETED,
+ GIT_STATUS_IGNORED,
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_WT_NEW
+ };
+
+ g_repo = setup_fixture_submodules();
+
+ cl_git_rewritefile("submodules/testrepo/README", "heyheyhey");
+ cl_git_mkfile("submodules/testrepo/all_new.txt", "never seen before");
+
+ /* first do a normal status, which should now include the submodule */
+
+ opts.flags = GIT_STATUS_OPT_DEFAULTS;
+
+ status_counts_init(
+ counts, expected_files_with_sub, expected_status_with_sub);
+ cl_git_pass(
+ git_status_foreach_ext(g_repo, &opts, cb_status__match, &counts));
+ cl_assert_equal_i(7, counts.entry_count);
+
+ /* try again with EXCLUDE_SUBMODULES which should skip it */
+
+ opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
+
+ status_counts_init(counts, expected_files, expected_status);
+ cl_git_pass(
+ git_status_foreach_ext(g_repo, &opts, cb_status__match, &counts));
+ cl_assert_equal_i(6, counts.entry_count);
+}
+
+void test_status_submodules__uninitialized(void)
+{
+ git_repository *cloned_repo;
+ git_status_list *statuslist;
+
+ g_repo = cl_git_sandbox_init("submod2");
+
+ cl_git_pass(git_clone(&cloned_repo, "submod2", "submod2-clone", NULL));
+
+ cl_git_pass(git_status_list_new(&statuslist, cloned_repo, NULL));
+ cl_assert_equal_i(0, git_status_list_entrycount(statuslist));
+
+ git_status_list_free(statuslist);
+ git_repository_free(cloned_repo);
+ cl_git_sandbox_cleanup();
+}
+
+void test_status_submodules__contained_untracked_repo(void)
+{
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ status_entry_counts counts;
+ git_repository *contained;
+ static const char *expected_files_not_ignored[] = {
+ ".gitmodules",
+ "added",
+ "deleted",
+ "modified",
+ "untracked"
+ };
+ static unsigned int expected_status_not_ignored[] = {
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_INDEX_DELETED,
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_WT_NEW,
+ };
+ static const char *expected_files_with_untracked[] = {
+ ".gitmodules",
+ "added",
+ "deleted",
+ "dir/file.md",
+ "modified",
+ "untracked"
+ };
+ static const char *expected_files_with_untracked_dir[] = {
+ ".gitmodules",
+ "added",
+ "deleted",
+ "dir/",
+ "modified",
+ "untracked"
+ };
+ static unsigned int expected_status_with_untracked[] = {
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_INDEX_DELETED,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_WT_NEW
+ };
+
+ g_repo = setup_fixture_submodules();
+
+ /* skip empty directory */
+
+ cl_must_pass(p_mkdir("submodules/dir", 0777));
+ opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED;
+
+ status_counts_init(
+ counts, expected_files_not_ignored, expected_status_not_ignored);
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, cb_status__match, &counts));
+ cl_assert_equal_i(5, counts.entry_count);
+
+ /* still skipping because empty == ignored */
+
+ opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
+
+ status_counts_init(
+ counts, expected_files_not_ignored, expected_status_not_ignored);
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, cb_status__match, &counts));
+ cl_assert_equal_i(5, counts.entry_count);
+
+ /* find non-ignored contents of directory */
+
+ cl_git_mkfile("submodules/dir/file.md", "hello");
+ opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
+
+ status_counts_init(
+ counts, expected_files_with_untracked, expected_status_with_untracked);
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, cb_status__match, &counts));
+ cl_assert_equal_i(6, counts.entry_count);
+
+ /* but skip if all content is ignored */
+
+ cl_git_append2file("submodules/.git/info/exclude", "\n*.md\n\n");
+ opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
+
+ status_counts_init(
+ counts, expected_files_not_ignored, expected_status_not_ignored);
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, cb_status__match, &counts));
+ cl_assert_equal_i(5, counts.entry_count);
+
+ /* same is true if it contains a git link */
+
+ cl_git_mkfile("submodules/dir/.git", "gitlink: ../.git");
+ opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
+
+ status_counts_init(
+ counts, expected_files_not_ignored, expected_status_not_ignored);
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, cb_status__match, &counts));
+ cl_assert_equal_i(5, counts.entry_count);
+
+ /* but if it contains tracked files, it should just show up as a
+ * directory and exclude the files in it
+ */
+
+ cl_git_mkfile("submodules/dir/another_file", "hello");
+ opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
+
+ status_counts_init(
+ counts, expected_files_with_untracked_dir,
+ expected_status_with_untracked);
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, cb_status__match, &counts));
+ cl_assert_equal_i(6, counts.entry_count);
+
+ /* that applies to a git repo with a .git directory too */
+
+ cl_must_pass(p_unlink("submodules/dir/.git"));
+ cl_git_pass(git_repository_init(&contained, "submodules/dir", false));
+ git_repository_free(contained);
+ opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
+
+ status_counts_init(
+ counts, expected_files_with_untracked_dir,
+ expected_status_with_untracked);
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, cb_status__match, &counts));
+ cl_assert_equal_i(6, counts.entry_count);
+
+ /* same result even if we don't recurse into subdirectories */
+
+ opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED;
+
+ status_counts_init(
+ counts, expected_files_with_untracked_dir,
+ expected_status_with_untracked);
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, cb_status__match, &counts));
+ cl_assert_equal_i(6, counts.entry_count);
+
+ /* and if we remove the untracked file, it goes back to ignored */
+
+ cl_must_pass(p_unlink("submodules/dir/another_file"));
+
+ status_counts_init(
+ counts, expected_files_not_ignored, expected_status_not_ignored);
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, cb_status__match, &counts));
+ cl_assert_equal_i(5, counts.entry_count);
+}
+
+void test_status_submodules__broken_stuff_that_git_allows(void)
+{
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ status_entry_counts counts;
+ git_repository *contained;
+ static const char *expected_files_with_broken[] = {
+ ".gitmodules",
+ "added",
+ "broken/tracked",
+ "deleted",
+ "ignored",
+ "modified",
+ "untracked"
+ };
+ static unsigned int expected_status_with_broken[] = {
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_INDEX_NEW,
+ GIT_STATUS_INDEX_DELETED,
+ GIT_STATUS_IGNORED,
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_WT_NEW,
+ };
+
+ g_repo = setup_fixture_submodules();
+
+ opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS |
+ GIT_STATUS_OPT_INCLUDE_IGNORED;
+
+ /* make a directory and stick a tracked item into the index */
+ {
+ git_index *idx;
+ cl_must_pass(p_mkdir("submodules/broken", 0777));
+ cl_git_mkfile("submodules/broken/tracked", "tracked content");
+ cl_git_pass(git_repository_index(&idx, g_repo));
+ cl_git_pass(git_index_add_bypath(idx, "broken/tracked"));
+ cl_git_pass(git_index_write(idx));
+ git_index_free(idx);
+ }
+
+ status_counts_init(
+ counts, expected_files_with_broken, expected_status_with_broken);
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, cb_status__match, &counts));
+ cl_assert_equal_i(7, counts.entry_count);
+
+ /* directory with tracked items that looks a little bit like a repo */
+
+ cl_must_pass(p_mkdir("submodules/broken/.git", 0777));
+ cl_must_pass(p_mkdir("submodules/broken/.git/info", 0777));
+ cl_git_mkfile("submodules/broken/.git/info/exclude", "# bogus");
+
+ status_counts_init(
+ counts, expected_files_with_broken, expected_status_with_broken);
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, cb_status__match, &counts));
+ cl_assert_equal_i(7, counts.entry_count);
+
+ /* directory with tracked items that is a repo */
+
+ cl_git_pass(git_futils_rmdir_r(
+ "submodules/broken/.git", NULL, GIT_RMDIR_REMOVE_FILES));
+ cl_git_pass(git_repository_init(&contained, "submodules/broken", false));
+ git_repository_free(contained);
+
+ status_counts_init(
+ counts, expected_files_with_broken, expected_status_with_broken);
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, cb_status__match, &counts));
+ cl_assert_equal_i(7, counts.entry_count);
+
+ /* directory with tracked items that claims to be a submodule but is not */
+
+ cl_git_pass(git_futils_rmdir_r(
+ "submodules/broken/.git", NULL, GIT_RMDIR_REMOVE_FILES));
+ cl_git_append2file("submodules/.gitmodules",
+ "\n[submodule \"broken\"]\n"
+ "\tpath = broken\n"
+ "\turl = https://github.com/not/used\n\n");
+
+ status_counts_init(
+ counts, expected_files_with_broken, expected_status_with_broken);
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, cb_status__match, &counts));
+ cl_assert_equal_i(7, counts.entry_count);
+}
+
diff --git a/tests/status/worktree.c b/tests/status/worktree.c
new file mode 100644
index 000000000..ca9068aba
--- /dev/null
+++ b/tests/status/worktree.c
@@ -0,0 +1,937 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+#include "ignore.h"
+#include "status_data.h"
+#include "posix.h"
+#include "util.h"
+#include "path.h"
+#include "../diff/diff_helpers.h"
+#include "git2/sys/diff.h"
+
+/**
+ * Cleanup
+ *
+ * This will be called once after each test finishes, even
+ * if the test failed
+ */
+void test_status_worktree__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+/**
+ * Tests - Status determination on a working tree
+ */
+/* this test is equivalent to t18-status.c:statuscb0 */
+void test_status_worktree__whole_repository(void)
+{
+ status_entry_counts counts;
+ git_repository *repo = cl_git_sandbox_init("status");
+
+ memset(&counts, 0x0, sizeof(status_entry_counts));
+ counts.expected_entry_count = entry_count0;
+ counts.expected_paths = entry_paths0;
+ counts.expected_statuses = entry_statuses0;
+
+ cl_git_pass(
+ git_status_foreach(repo, cb_status__normal, &counts)
+ );
+
+ cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+ cl_assert_equal_i(0, counts.wrong_status_flags_count);
+ cl_assert_equal_i(0, counts.wrong_sorted_path);
+}
+
+void assert_show(
+ const int entry_counts,
+ const char *entry_paths[],
+ const unsigned int entry_statuses[],
+ git_repository *repo,
+ git_status_show_t show,
+ unsigned int extra_flags)
+{
+ status_entry_counts counts;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+
+ memset(&counts, 0x0, sizeof(status_entry_counts));
+ counts.expected_entry_count = entry_counts;
+ counts.expected_paths = entry_paths;
+ counts.expected_statuses = entry_statuses;
+
+ opts.flags = GIT_STATUS_OPT_DEFAULTS | extra_flags;
+ opts.show = show;
+
+ cl_git_pass(
+ git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)
+ );
+
+ cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+ cl_assert_equal_i(0, counts.wrong_status_flags_count);
+ cl_assert_equal_i(0, counts.wrong_sorted_path);
+}
+
+void test_status_worktree__show_index_and_workdir(void)
+{
+ assert_show(entry_count0, entry_paths0, entry_statuses0,
+ cl_git_sandbox_init("status"), GIT_STATUS_SHOW_INDEX_AND_WORKDIR, 0);
+}
+
+void test_status_worktree__show_index_only(void)
+{
+ assert_show(entry_count5, entry_paths5, entry_statuses5,
+ cl_git_sandbox_init("status"), GIT_STATUS_SHOW_INDEX_ONLY, 0);
+}
+
+void test_status_worktree__show_workdir_only(void)
+{
+ assert_show(entry_count6, entry_paths6, entry_statuses6,
+ cl_git_sandbox_init("status"), GIT_STATUS_SHOW_WORKDIR_ONLY, 0);
+}
+
+/* this test is equivalent to t18-status.c:statuscb1 */
+void test_status_worktree__empty_repository(void)
+{
+ int count = 0;
+ git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
+
+ cl_git_pass(git_status_foreach(repo, cb_status__count, &count));
+
+ cl_assert_equal_i(0, count);
+}
+
+static int remove_file_cb(void *data, git_buf *file)
+{
+ const char *filename = git_buf_cstr(file);
+
+ GIT_UNUSED(data);
+
+ if (git__suffixcmp(filename, ".git") == 0)
+ return 0;
+
+ if (git_path_isdir(filename))
+ cl_git_pass(git_futils_rmdir_r(filename, NULL, GIT_RMDIR_REMOVE_FILES));
+ else
+ cl_git_pass(p_unlink(git_buf_cstr(file)));
+
+ return 0;
+}
+
+/* this test is equivalent to t18-status.c:statuscb2 */
+void test_status_worktree__purged_worktree(void)
+{
+ status_entry_counts counts;
+ git_repository *repo = cl_git_sandbox_init("status");
+ git_buf workdir = GIT_BUF_INIT;
+
+ /* first purge the contents of the worktree */
+ cl_git_pass(git_buf_sets(&workdir, git_repository_workdir(repo)));
+ cl_git_pass(git_path_direach(&workdir, 0, remove_file_cb, NULL));
+ git_buf_free(&workdir);
+
+ /* now get status */
+ memset(&counts, 0x0, sizeof(status_entry_counts));
+ counts.expected_entry_count = entry_count2;
+ counts.expected_paths = entry_paths2;
+ counts.expected_statuses = entry_statuses2;
+
+ cl_git_pass(
+ git_status_foreach(repo, cb_status__normal, &counts)
+ );
+
+ cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+ cl_assert_equal_i(0, counts.wrong_status_flags_count);
+ cl_assert_equal_i(0, counts.wrong_sorted_path);
+}
+
+/* this test is similar to t18-status.c:statuscb3 */
+void test_status_worktree__swap_subdir_and_file(void)
+{
+ status_entry_counts counts;
+ git_repository *repo = cl_git_sandbox_init("status");
+ git_index *index;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ bool ignore_case;
+
+ cl_git_pass(git_repository_index(&index, repo));
+ ignore_case = (git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0;
+ git_index_free(index);
+
+ /* first alter the contents of the worktree */
+ cl_git_pass(p_rename("status/current_file", "status/swap"));
+ cl_git_pass(p_rename("status/subdir", "status/current_file"));
+ cl_git_pass(p_rename("status/swap", "status/subdir"));
+
+ cl_git_mkfile("status/.HEADER", "dummy");
+ cl_git_mkfile("status/42-is-not-prime.sigh", "dummy");
+ cl_git_mkfile("status/README.md", "dummy");
+
+ /* now get status */
+ memset(&counts, 0x0, sizeof(status_entry_counts));
+ counts.expected_entry_count = entry_count3;
+ counts.expected_paths = ignore_case ? entry_paths3_icase : entry_paths3;
+ counts.expected_statuses = ignore_case ? entry_statuses3_icase : entry_statuses3;
+
+ opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_INCLUDE_IGNORED;
+
+ cl_git_pass(
+ git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)
+ );
+
+ cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+ cl_assert_equal_i(0, counts.wrong_status_flags_count);
+ cl_assert_equal_i(0, counts.wrong_sorted_path);
+}
+
+void test_status_worktree__swap_subdir_with_recurse_and_pathspec(void)
+{
+ status_entry_counts counts;
+ git_repository *repo = cl_git_sandbox_init("status");
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+
+ /* first alter the contents of the worktree */
+ cl_git_pass(p_rename("status/current_file", "status/swap"));
+ cl_git_pass(p_rename("status/subdir", "status/current_file"));
+ cl_git_pass(p_rename("status/swap", "status/subdir"));
+ cl_git_mkfile("status/.new_file", "dummy");
+ cl_git_pass(git_futils_mkdir_r("status/zzz_new_dir", NULL, 0777));
+ cl_git_mkfile("status/zzz_new_dir/new_file", "dummy");
+ cl_git_mkfile("status/zzz_new_file", "dummy");
+
+ /* now get status */
+ memset(&counts, 0x0, sizeof(status_entry_counts));
+ counts.expected_entry_count = entry_count4;
+ counts.expected_paths = entry_paths4;
+ counts.expected_statuses = entry_statuses4;
+
+ opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
+ /* TODO: set pathspec to "current_file" eventually */
+
+ cl_git_pass(
+ git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)
+ );
+
+ cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+ cl_assert_equal_i(0, counts.wrong_status_flags_count);
+ cl_assert_equal_i(0, counts.wrong_sorted_path);
+}
+
+/* this test is equivalent to t18-status.c:singlestatus0 */
+void test_status_worktree__single_file(void)
+{
+ int i;
+ unsigned int status_flags;
+ git_repository *repo = cl_git_sandbox_init("status");
+
+ for (i = 0; i < (int)entry_count0; i++) {
+ cl_git_pass(
+ git_status_file(&status_flags, repo, entry_paths0[i])
+ );
+ cl_assert(entry_statuses0[i] == status_flags);
+ }
+}
+
+/* this test is equivalent to t18-status.c:singlestatus1 */
+void test_status_worktree__single_nonexistent_file(void)
+{
+ int error;
+ unsigned int status_flags;
+ git_repository *repo = cl_git_sandbox_init("status");
+
+ error = git_status_file(&status_flags, repo, "nonexistent");
+ cl_git_fail(error);
+ cl_assert(error == GIT_ENOTFOUND);
+}
+
+/* this test is equivalent to t18-status.c:singlestatus2 */
+void test_status_worktree__single_nonexistent_file_empty_repo(void)
+{
+ int error;
+ unsigned int status_flags;
+ git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
+
+ error = git_status_file(&status_flags, repo, "nonexistent");
+ cl_git_fail(error);
+ cl_assert(error == GIT_ENOTFOUND);
+}
+
+/* this test is equivalent to t18-status.c:singlestatus3 */
+void test_status_worktree__single_file_empty_repo(void)
+{
+ unsigned int status_flags;
+ git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
+
+ cl_git_mkfile("empty_standard_repo/new_file", "new_file\n");
+
+ cl_git_pass(git_status_file(&status_flags, repo, "new_file"));
+ cl_assert(status_flags == GIT_STATUS_WT_NEW);
+}
+
+/* this test is equivalent to t18-status.c:singlestatus4 */
+void test_status_worktree__single_folder(void)
+{
+ int error;
+ unsigned int status_flags;
+ git_repository *repo = cl_git_sandbox_init("status");
+
+ error = git_status_file(&status_flags, repo, "subdir");
+ cl_git_fail(error);
+ cl_assert(error != GIT_ENOTFOUND);
+}
+
+
+void test_status_worktree__ignores(void)
+{
+ int i, ignored;
+ git_repository *repo = cl_git_sandbox_init("status");
+
+ for (i = 0; i < (int)entry_count0; i++) {
+ cl_git_pass(
+ git_status_should_ignore(&ignored, repo, entry_paths0[i])
+ );
+ cl_assert(ignored == (entry_statuses0[i] == GIT_STATUS_IGNORED));
+ }
+
+ cl_git_pass(
+ git_status_should_ignore(&ignored, repo, "nonexistent_file")
+ );
+ cl_assert(!ignored);
+
+ cl_git_pass(
+ git_status_should_ignore(&ignored, repo, "ignored_nonexistent_file")
+ );
+ cl_assert(ignored);
+}
+
+static int cb_status__check_592(const char *p, unsigned int s, void *payload)
+{
+ if (s != GIT_STATUS_WT_DELETED ||
+ (payload != NULL && strcmp(p, (const char *)payload) != 0))
+ return -1;
+
+ return 0;
+}
+
+void test_status_worktree__issue_592(void)
+{
+ git_repository *repo;
+ git_buf path = GIT_BUF_INIT;
+
+ repo = cl_git_sandbox_init("issue_592");
+ cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "l.txt"));
+ cl_git_pass(p_unlink(git_buf_cstr(&path)));
+ cl_assert(!git_path_exists("issue_592/l.txt"));
+
+ cl_git_pass(git_status_foreach(repo, cb_status__check_592, "l.txt"));
+
+ git_buf_free(&path);
+}
+
+void test_status_worktree__issue_592_2(void)
+{
+ git_repository *repo;
+ git_buf path = GIT_BUF_INIT;
+
+ repo = cl_git_sandbox_init("issue_592");
+ cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "c/a.txt"));
+ cl_git_pass(p_unlink(git_buf_cstr(&path)));
+ cl_assert(!git_path_exists("issue_592/c/a.txt"));
+
+ cl_git_pass(git_status_foreach(repo, cb_status__check_592, "c/a.txt"));
+
+ git_buf_free(&path);
+}
+
+void test_status_worktree__issue_592_3(void)
+{
+ git_repository *repo;
+ git_buf path = GIT_BUF_INIT;
+
+ 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_RMDIR_REMOVE_FILES));
+ cl_assert(!git_path_exists("issue_592/c/a.txt"));
+
+ cl_git_pass(git_status_foreach(repo, cb_status__check_592, "c/a.txt"));
+
+ git_buf_free(&path);
+}
+
+void test_status_worktree__issue_592_4(void)
+{
+ git_repository *repo;
+ git_buf path = GIT_BUF_INIT;
+
+ repo = cl_git_sandbox_init("issue_592");
+
+ cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "t/b.txt"));
+ cl_git_pass(p_unlink(git_buf_cstr(&path)));
+
+ cl_git_pass(git_status_foreach(repo, cb_status__check_592, "t/b.txt"));
+
+ git_buf_free(&path);
+}
+
+void test_status_worktree__issue_592_5(void)
+{
+ git_repository *repo;
+ git_buf path = GIT_BUF_INIT;
+
+ 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_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));
+
+ git_buf_free(&path);
+}
+
+void test_status_worktree__issue_592_ignores_0(void)
+{
+ int count = 0;
+ status_entry_single st;
+ git_repository *repo = cl_git_sandbox_init("issue_592");
+
+ cl_git_pass(git_status_foreach(repo, cb_status__count, &count));
+ cl_assert_equal_i(0, count);
+
+ cl_git_rewritefile("issue_592/.gitignore",
+ ".gitignore\n*.txt\nc/\n[tT]*/\n");
+
+ cl_git_pass(git_status_foreach(repo, cb_status__count, &count));
+ cl_assert_equal_i(1, count);
+
+ /* This is a situation where the behavior of libgit2 is
+ * different from core git. Core git will show ignored.txt
+ * in the list of ignored files, even though the directory
+ * "t" is ignored and the file is untracked because we have
+ * the explicit "*.txt" ignore rule. Libgit2 just excludes
+ * all untracked files that are contained within ignored
+ * directories without explicitly listing them.
+ */
+ cl_git_rewritefile("issue_592/t/ignored.txt", "ping");
+
+ memset(&st, 0, sizeof(st));
+ cl_git_pass(git_status_foreach(repo, cb_status__single, &st));
+ cl_assert_equal_i(1, st.count);
+ cl_assert(st.status == GIT_STATUS_IGNORED);
+
+ cl_git_rewritefile("issue_592/c/ignored_by_dir", "ping");
+
+ memset(&st, 0, sizeof(st));
+ cl_git_pass(git_status_foreach(repo, cb_status__single, &st));
+ cl_assert_equal_i(1, st.count);
+ cl_assert(st.status == GIT_STATUS_IGNORED);
+
+ cl_git_rewritefile("issue_592/t/ignored_by_dir_pattern", "ping");
+
+ memset(&st, 0, sizeof(st));
+ cl_git_pass(git_status_foreach(repo, cb_status__single, &st));
+ cl_assert_equal_i(1, st.count);
+ cl_assert(st.status == GIT_STATUS_IGNORED);
+}
+
+void test_status_worktree__issue_592_ignored_dirs_with_tracked_content(void)
+{
+ int count = 0;
+ git_repository *repo = cl_git_sandbox_init("issue_592b");
+
+ cl_git_pass(git_status_foreach(repo, cb_status__count, &count));
+ cl_assert_equal_i(1, count);
+
+ /* if we are really mimicking core git, then only ignored1.txt
+ * at the top level will show up in the ignores list here.
+ * everything else will be unmodified or skipped completely.
+ */
+}
+
+void test_status_worktree__conflict_with_diff3(void)
+{
+ git_repository *repo = cl_git_sandbox_init("status");
+ git_index *index;
+ unsigned int status;
+ 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 = "modified_file";
+ git_oid_fromstr(&ancestor_entry.id,
+ "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
+
+ our_entry.path = "modified_file";
+ git_oid_fromstr(&our_entry.id,
+ "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
+
+ their_entry.path = "modified_file";
+ git_oid_fromstr(&their_entry.id,
+ "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
+
+ cl_git_pass(git_status_file(&status, repo, "modified_file"));
+ cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status);
+
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_git_pass(git_index_remove(index, "modified_file", 0));
+ cl_git_pass(git_index_conflict_add(
+ index, &ancestor_entry, &our_entry, &their_entry));
+ cl_git_pass(git_index_write(index));
+ git_index_free(index);
+
+ cl_git_pass(git_status_file(&status, repo, "modified_file"));
+
+ cl_assert_equal_i(GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_NEW, status);
+}
+
+static const char *filemode_paths[] = {
+ "exec_off",
+ "exec_off2on_staged",
+ "exec_off2on_workdir",
+ "exec_off_untracked",
+ "exec_on",
+ "exec_on2off_staged",
+ "exec_on2off_workdir",
+ "exec_on_untracked",
+};
+
+static unsigned int filemode_statuses[] = {
+ GIT_STATUS_CURRENT,
+ GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_WT_NEW,
+ GIT_STATUS_CURRENT,
+ GIT_STATUS_INDEX_MODIFIED,
+ GIT_STATUS_WT_MODIFIED,
+ GIT_STATUS_WT_NEW
+};
+
+static const int filemode_count = 8;
+
+void test_status_worktree__filemode_changes(void)
+{
+ git_repository *repo = cl_git_sandbox_init("filemodes");
+ status_entry_counts counts;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+
+ /* overwrite stored filemode with platform appropriate value */
+ if (cl_is_chmod_supported())
+ cl_repo_set_bool(repo, "core.filemode", true);
+ else {
+ int i;
+
+ cl_repo_set_bool(repo, "core.filemode", false);
+
+ /* won't trust filesystem mode diffs, so these will appear unchanged */
+ for (i = 0; i < filemode_count; ++i)
+ if (filemode_statuses[i] == GIT_STATUS_WT_MODIFIED)
+ filemode_statuses[i] = GIT_STATUS_CURRENT;
+ }
+
+ opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_INCLUDE_IGNORED |
+ GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
+
+ memset(&counts, 0, sizeof(counts));
+ counts.expected_entry_count = filemode_count;
+ counts.expected_paths = filemode_paths;
+ counts.expected_statuses = filemode_statuses;
+
+ cl_git_pass(
+ git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)
+ );
+
+ cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+ cl_assert_equal_i(0, counts.wrong_status_flags_count);
+ cl_assert_equal_i(0, counts.wrong_sorted_path);
+}
+
+static int cb_status__interrupt(const char *p, unsigned int s, void *payload)
+{
+ volatile int *count = (int *)payload;
+
+ GIT_UNUSED(p);
+ GIT_UNUSED(s);
+
+ (*count)++;
+
+ return (*count == 8) ? -111 : 0;
+}
+
+void test_status_worktree__interruptable_foreach(void)
+{
+ int count = 0;
+ git_repository *repo = cl_git_sandbox_init("status");
+
+ cl_assert_equal_i(
+ -111, git_status_foreach(repo, cb_status__interrupt, &count)
+ );
+
+ cl_assert_equal_i(8, count);
+}
+
+void test_status_worktree__line_endings_dont_count_as_changes_with_autocrlf(void)
+{
+ git_repository *repo = cl_git_sandbox_init("status");
+ unsigned int status;
+
+ cl_repo_set_bool(repo, "core.autocrlf", true);
+
+ cl_git_rewritefile("status/current_file", "current_file\r\n");
+
+ cl_git_pass(git_status_file(&status, repo, "current_file"));
+
+ /* stat data on file should no longer match stat cache, even though
+ * file diff will be empty because of line-ending conversion - matches
+ * the Git command-line behavior here.
+ */
+ cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status);
+}
+
+void test_status_worktree__line_endings_dont_count_as_changes_with_autocrlf_issue_1397(void)
+{
+ git_repository *repo = cl_git_sandbox_init("issue_1397");
+ unsigned int status;
+
+ cl_repo_set_bool(repo, "core.autocrlf", true);
+
+ cl_git_pass(git_status_file(&status, repo, "crlf_file.txt"));
+
+ cl_assert_equal_i(GIT_STATUS_CURRENT, status);
+}
+
+void test_status_worktree__conflicted_item(void)
+{
+ git_repository *repo = cl_git_sandbox_init("status");
+ git_index *index;
+ unsigned int status;
+ 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 = "modified_file";
+ git_oid_fromstr(&ancestor_entry.id,
+ "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
+
+ our_entry.path = "modified_file";
+ git_oid_fromstr(&our_entry.id,
+ "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
+
+ their_entry.path = "modified_file";
+ git_oid_fromstr(&their_entry.id,
+ "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
+
+ cl_git_pass(git_status_file(&status, repo, "modified_file"));
+ cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status);
+
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_git_pass(git_index_conflict_add(index, &ancestor_entry,
+ &our_entry, &their_entry));
+
+ cl_git_pass(git_status_file(&status, repo, "modified_file"));
+ cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status);
+
+ git_index_free(index);
+}
+
+static void stage_and_commit(git_repository *repo, const char *path)
+{
+ git_index *index;
+
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_git_pass(git_index_add_bypath(index, path));
+ cl_repo_commit_from_index(NULL, repo, NULL, 1323847743, "Initial commit\n");
+ git_index_free(index);
+}
+
+static void assert_ignore_case(
+ bool should_ignore_case,
+ int expected_lower_cased_file_status,
+ int expected_camel_cased_file_status)
+{
+ unsigned int status;
+ git_buf lower_case_path = GIT_BUF_INIT, camel_case_path = GIT_BUF_INIT;
+ git_repository *repo, *repo2;
+
+ repo = cl_git_sandbox_init("empty_standard_repo");
+ cl_git_remove_placeholders(git_repository_path(repo), "dummy-marker.txt");
+
+ cl_repo_set_bool(repo, "core.ignorecase", should_ignore_case);
+
+ cl_git_pass(git_buf_joinpath(&lower_case_path,
+ git_repository_workdir(repo), "plop"));
+
+ cl_git_mkfile(git_buf_cstr(&lower_case_path), "");
+
+ stage_and_commit(repo, "plop");
+
+ cl_git_pass(git_repository_open(&repo2, "./empty_standard_repo"));
+
+ cl_git_pass(git_status_file(&status, repo2, "plop"));
+ cl_assert_equal_i(GIT_STATUS_CURRENT, status);
+
+ cl_git_pass(git_buf_joinpath(&camel_case_path,
+ git_repository_workdir(repo), "Plop"));
+
+ cl_git_pass(p_rename(git_buf_cstr(&lower_case_path), git_buf_cstr(&camel_case_path)));
+
+ cl_git_pass(git_status_file(&status, repo2, "plop"));
+ cl_assert_equal_i(expected_lower_cased_file_status, status);
+
+ cl_git_pass(git_status_file(&status, repo2, "Plop"));
+ cl_assert_equal_i(expected_camel_cased_file_status, status);
+
+ git_repository_free(repo2);
+ git_buf_free(&lower_case_path);
+ git_buf_free(&camel_case_path);
+}
+
+void test_status_worktree__file_status_honors_core_ignorecase_true(void)
+{
+ assert_ignore_case(true, GIT_STATUS_CURRENT, GIT_STATUS_CURRENT);
+}
+
+void test_status_worktree__file_status_honors_core_ignorecase_false(void)
+{
+ assert_ignore_case(false, GIT_STATUS_WT_DELETED, GIT_STATUS_WT_NEW);
+}
+
+void test_status_worktree__file_status_honors_case_ignorecase_regarding_untracked_files(void)
+{
+ git_repository *repo = cl_git_sandbox_init("status");
+ unsigned int status;
+ git_index *index;
+
+ cl_repo_set_bool(repo, "core.ignorecase", false);
+
+ repo = cl_git_sandbox_reopen();
+
+ /* Actually returns GIT_STATUS_IGNORED on Windows */
+ cl_git_fail_with(git_status_file(&status, repo, "NEW_FILE"), GIT_ENOTFOUND);
+
+ cl_git_pass(git_repository_index(&index, repo));
+
+ cl_git_pass(git_index_add_bypath(index, "new_file"));
+ cl_git_pass(git_index_write(index));
+ git_index_free(index);
+
+ /* Actually returns GIT_STATUS_IGNORED on Windows */
+ cl_git_fail_with(git_status_file(&status, repo, "NEW_FILE"), GIT_ENOTFOUND);
+}
+
+void test_status_worktree__simple_delete(void)
+{
+ git_repository *repo = cl_git_sandbox_init("renames");
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ int count;
+
+ opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH |
+ GIT_STATUS_OPT_EXCLUDE_SUBMODULES |
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
+
+ count = 0;
+ cl_git_pass(
+ git_status_foreach_ext(repo, &opts, cb_status__count, &count) );
+ cl_assert_equal_i(0, count);
+
+ cl_must_pass(p_unlink("renames/untimely.txt"));
+
+ count = 0;
+ cl_git_pass(
+ git_status_foreach_ext(repo, &opts, cb_status__count, &count) );
+ cl_assert_equal_i(1, count);
+}
+
+void test_status_worktree__simple_delete_indexed(void)
+{
+ git_repository *repo = cl_git_sandbox_init("renames");
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ git_status_list *status;
+
+ opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH |
+ GIT_STATUS_OPT_EXCLUDE_SUBMODULES |
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
+
+ cl_git_pass(git_status_list_new(&status, repo, &opts));
+ cl_assert_equal_sz(0, git_status_list_entrycount(status));
+ git_status_list_free(status);
+
+ cl_must_pass(p_unlink("renames/untimely.txt"));
+
+ cl_git_pass(git_status_list_new(&status, repo, &opts));
+ cl_assert_equal_sz(1, git_status_list_entrycount(status));
+ cl_assert_equal_i(
+ GIT_STATUS_WT_DELETED, git_status_byindex(status, 0)->status);
+ git_status_list_free(status);
+}
+
+static const char *icase_paths[] = { "B", "c", "g", "H" };
+static unsigned int icase_statuses[] = {
+ GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED,
+};
+
+static const char *case_paths[] = { "B", "H", "c", "g" };
+static unsigned int case_statuses[] = {
+ GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_DELETED,
+ GIT_STATUS_WT_DELETED, GIT_STATUS_WT_MODIFIED,
+};
+
+void test_status_worktree__sorting_by_case(void)
+{
+ git_repository *repo = cl_git_sandbox_init("icase");
+ git_index *index;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ bool native_ignore_case;
+ status_entry_counts counts;
+
+ cl_git_pass(git_repository_index(&index, repo));
+ native_ignore_case =
+ (git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0;
+ git_index_free(index);
+
+ memset(&counts, 0, sizeof(counts));
+ counts.expected_entry_count = 0;
+ counts.expected_paths = NULL;
+ counts.expected_statuses = NULL;
+ cl_git_pass(
+ git_status_foreach_ext(repo, &opts, cb_status__normal, &counts));
+ cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+ cl_assert_equal_i(0, counts.wrong_status_flags_count);
+ cl_assert_equal_i(0, counts.wrong_sorted_path);
+
+ cl_git_rewritefile("icase/B", "new stuff");
+ cl_must_pass(p_unlink("icase/c"));
+ cl_git_rewritefile("icase/g", "new stuff");
+ cl_must_pass(p_unlink("icase/H"));
+
+ memset(&counts, 0, sizeof(counts));
+ counts.expected_entry_count = 4;
+ if (native_ignore_case) {
+ counts.expected_paths = icase_paths;
+ counts.expected_statuses = icase_statuses;
+ } else {
+ counts.expected_paths = case_paths;
+ counts.expected_statuses = case_statuses;
+ }
+ cl_git_pass(
+ git_status_foreach_ext(repo, &opts, cb_status__normal, &counts));
+ cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+ cl_assert_equal_i(0, counts.wrong_status_flags_count);
+ cl_assert_equal_i(0, counts.wrong_sorted_path);
+
+ opts.flags = GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
+
+ memset(&counts, 0, sizeof(counts));
+ counts.expected_entry_count = 4;
+ counts.expected_paths = case_paths;
+ counts.expected_statuses = case_statuses;
+ cl_git_pass(
+ git_status_foreach_ext(repo, &opts, cb_status__normal, &counts));
+ cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+ cl_assert_equal_i(0, counts.wrong_status_flags_count);
+ cl_assert_equal_i(0, counts.wrong_sorted_path);
+
+ opts.flags = GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY;
+
+ memset(&counts, 0, sizeof(counts));
+ counts.expected_entry_count = 4;
+ counts.expected_paths = icase_paths;
+ counts.expected_statuses = icase_statuses;
+ cl_git_pass(
+ git_status_foreach_ext(repo, &opts, cb_status__normal, &counts));
+ cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+ cl_assert_equal_i(0, counts.wrong_status_flags_count);
+ cl_assert_equal_i(0, counts.wrong_sorted_path);
+}
+
+void test_status_worktree__long_filenames(void)
+{
+ char path[260*4+1];
+ const char *expected_paths[] = {path};
+ const unsigned int expected_statuses[] = {GIT_STATUS_WT_NEW};
+
+ git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ status_entry_counts counts = {0};
+
+ // Create directory with amazingly long filename
+ sprintf(path, "empty_standard_repo/%s", longname);
+ cl_git_pass(git_futils_mkdir_r(path, NULL, 0777));
+ sprintf(path, "empty_standard_repo/%s/foo", longname);
+ cl_git_mkfile(path, "dummy");
+
+ sprintf(path, "%s/foo", longname);
+ counts.expected_entry_count = 1;
+ counts.expected_paths = expected_paths;
+ counts.expected_statuses = expected_statuses;
+
+ opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY;
+ opts.flags = GIT_STATUS_OPT_DEFAULTS;
+
+ cl_git_pass(
+ git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) );
+ cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+ cl_assert_equal_i(0, counts.wrong_status_flags_count);
+ cl_assert_equal_i(0, counts.wrong_sorted_path);
+}
+
+/* The update stat cache tests mostly just mirror other tests and try
+ * to make sure that updating the stat cache doesn't change the results
+ * while reducing the amount of work that needs to be done
+ */
+
+static void check_status0(git_status_list *status)
+{
+ size_t i, max_i = git_status_list_entrycount(status);
+ cl_assert_equal_sz(entry_count0, max_i);
+ for (i = 0; i < max_i; ++i) {
+ const git_status_entry *entry = git_status_byindex(status, i);
+ cl_assert_equal_i(entry_statuses0[i], entry->status);
+ }
+}
+
+void test_status_worktree__update_stat_cache_0(void)
+{
+ git_repository *repo = cl_git_sandbox_init("status");
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ git_status_list *status;
+ git_diff_perfdata perf = GIT_DIFF_PERFDATA_INIT;
+
+ opts.flags = GIT_STATUS_OPT_DEFAULTS;
+
+ cl_git_pass(git_status_list_new(&status, repo, &opts));
+ check_status0(status);
+ cl_git_pass(git_status_list_get_perfdata(&perf, status));
+ cl_assert_equal_sz(13 + 3, perf.stat_calls);
+ cl_assert_equal_sz(5, perf.oid_calculations);
+
+ git_status_list_free(status);
+
+ opts.flags |= GIT_STATUS_OPT_UPDATE_INDEX;
+
+ cl_git_pass(git_status_list_new(&status, repo, &opts));
+ check_status0(status);
+ cl_git_pass(git_status_list_get_perfdata(&perf, status));
+ cl_assert_equal_sz(13 + 3, perf.stat_calls);
+ cl_assert_equal_sz(5, perf.oid_calculations);
+
+ git_status_list_free(status);
+
+ opts.flags &= ~GIT_STATUS_OPT_UPDATE_INDEX;
+
+ cl_git_pass(git_status_list_new(&status, repo, &opts));
+ check_status0(status);
+ cl_git_pass(git_status_list_get_perfdata(&perf, status));
+ cl_assert_equal_sz(13 + 3, perf.stat_calls);
+ cl_assert_equal_sz(0, perf.oid_calculations);
+
+ git_status_list_free(status);
+}
diff --git a/tests/status/worktree_init.c b/tests/status/worktree_init.c
new file mode 100644
index 000000000..296c27c86
--- /dev/null
+++ b/tests/status/worktree_init.c
@@ -0,0 +1,341 @@
+#include "clar_libgit2.h"
+#include "git2/sys/repository.h"
+
+#include "fileops.h"
+#include "ignore.h"
+#include "status_helpers.h"
+#include "posix.h"
+#include "util.h"
+#include "path.h"
+
+static void cleanup_new_repo(void *path)
+{
+ cl_fixture_cleanup((char *)path);
+}
+
+void test_status_worktree_init__cannot_retrieve_the_status_of_a_bare_repository(void)
+{
+ git_repository *repo;
+ unsigned int status = 0;
+
+ cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
+ cl_assert_equal_i(GIT_EBAREREPO, git_status_file(&status, repo, "dummy"));
+ git_repository_free(repo);
+}
+
+void test_status_worktree_init__first_commit_in_progress(void)
+{
+ git_repository *repo;
+ git_index *index;
+ status_entry_single result;
+
+ cl_set_cleanup(&cleanup_new_repo, "getting_started");
+
+ cl_git_pass(git_repository_init(&repo, "getting_started", 0));
+ cl_git_mkfile("getting_started/testfile.txt", "content\n");
+
+ memset(&result, 0, sizeof(result));
+ cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
+ cl_assert_equal_i(1, result.count);
+ cl_assert(result.status == GIT_STATUS_WT_NEW);
+
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_git_pass(git_index_add_bypath(index, "testfile.txt"));
+ cl_git_pass(git_index_write(index));
+
+ memset(&result, 0, sizeof(result));
+ cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
+ cl_assert_equal_i(1, result.count);
+ cl_assert(result.status == GIT_STATUS_INDEX_NEW);
+
+ git_index_free(index);
+ git_repository_free(repo);
+}
+
+
+
+void test_status_worktree_init__status_file_without_index_or_workdir(void)
+{
+ git_repository *repo;
+ unsigned int status = 0;
+ git_index *index;
+
+ cl_git_pass(p_mkdir("wd", 0777));
+
+ cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
+ cl_git_pass(git_repository_set_workdir(repo, "wd", false));
+
+ cl_git_pass(git_index_open(&index, "empty-index"));
+ cl_assert_equal_i(0, (int)git_index_entrycount(index));
+ git_repository_set_index(repo, index);
+
+ cl_git_pass(git_status_file(&status, repo, "branch_file.txt"));
+
+ cl_assert_equal_i(GIT_STATUS_INDEX_DELETED, status);
+
+ git_repository_free(repo);
+ git_index_free(index);
+ cl_git_pass(p_rmdir("wd"));
+}
+
+static void fill_index_wth_head_entries(git_repository *repo, git_index *index)
+{
+ git_oid oid;
+ git_commit *commit;
+ git_tree *tree;
+
+ cl_git_pass(git_reference_name_to_id(&oid, repo, "HEAD"));
+ 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));
+ cl_git_pass(git_index_write(index));
+
+ git_tree_free(tree);
+ git_commit_free(commit);
+}
+
+void test_status_worktree_init__status_file_with_clean_index_and_empty_workdir(void)
+{
+ git_repository *repo;
+ unsigned int status = 0;
+ git_index *index;
+
+ cl_git_pass(p_mkdir("wd", 0777));
+
+ cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
+ cl_git_pass(git_repository_set_workdir(repo, "wd", false));
+
+ cl_git_pass(git_index_open(&index, "my-index"));
+ fill_index_wth_head_entries(repo, index);
+
+ git_repository_set_index(repo, index);
+
+ cl_git_pass(git_status_file(&status, repo, "branch_file.txt"));
+
+ cl_assert_equal_i(GIT_STATUS_WT_DELETED, status);
+
+ git_repository_free(repo);
+ git_index_free(index);
+ cl_git_pass(p_rmdir("wd"));
+ cl_git_pass(p_unlink("my-index"));
+}
+
+void test_status_worktree_init__bracket_in_filename(void)
+{
+ git_repository *repo;
+ git_index *index;
+ status_entry_single result;
+ unsigned int status_flags;
+ int error;
+
+ #define FILE_WITH_BRACKET "LICENSE[1].md"
+ #define FILE_WITHOUT_BRACKET "LICENSE1.md"
+
+ cl_set_cleanup(&cleanup_new_repo, "with_bracket");
+
+ cl_git_pass(git_repository_init(&repo, "with_bracket", 0));
+ cl_git_mkfile("with_bracket/" FILE_WITH_BRACKET, "I have a bracket in my name\n");
+
+ /* file is new to working directory */
+
+ memset(&result, 0, sizeof(result));
+ cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
+ cl_assert_equal_i(1, result.count);
+ cl_assert(result.status == GIT_STATUS_WT_NEW);
+
+ cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET));
+ cl_assert(status_flags == GIT_STATUS_WT_NEW);
+
+ /* ignore the file */
+
+ cl_git_rewritefile("with_bracket/.gitignore", "*.md\n.gitignore\n");
+
+ memset(&result, 0, sizeof(result));
+ cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
+ cl_assert_equal_i(2, result.count);
+ cl_assert(result.status == GIT_STATUS_IGNORED);
+
+ cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET));
+ cl_assert(status_flags == GIT_STATUS_IGNORED);
+
+ /* don't ignore the file */
+
+ cl_git_rewritefile("with_bracket/.gitignore", ".gitignore\n");
+
+ memset(&result, 0, sizeof(result));
+ cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
+ cl_assert_equal_i(2, result.count);
+ cl_assert(result.status == GIT_STATUS_WT_NEW);
+
+ cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET));
+ cl_assert(status_flags == GIT_STATUS_WT_NEW);
+
+ /* add the file to the index */
+
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_git_pass(git_index_add_bypath(index, FILE_WITH_BRACKET));
+ cl_git_pass(git_index_write(index));
+
+ memset(&result, 0, sizeof(result));
+ cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
+ cl_assert_equal_i(2, result.count);
+ cl_assert(result.status == GIT_STATUS_INDEX_NEW);
+
+ cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET));
+ cl_assert(status_flags == GIT_STATUS_INDEX_NEW);
+
+ /* Create file without bracket */
+
+ cl_git_mkfile("with_bracket/" FILE_WITHOUT_BRACKET, "I have no bracket in my name!\n");
+
+ cl_git_pass(git_status_file(&status_flags, repo, FILE_WITHOUT_BRACKET));
+ cl_assert(status_flags == GIT_STATUS_WT_NEW);
+
+ cl_git_pass(git_status_file(&status_flags, repo, "LICENSE\\[1\\].md"));
+ cl_assert(status_flags == GIT_STATUS_INDEX_NEW);
+
+ error = git_status_file(&status_flags, repo, FILE_WITH_BRACKET);
+ cl_git_fail(error);
+ cl_assert_equal_i(GIT_EAMBIGUOUS, error);
+
+ git_index_free(index);
+ git_repository_free(repo);
+}
+
+void test_status_worktree_init__space_in_filename(void)
+{
+ git_repository *repo;
+ git_index *index;
+ status_entry_single result;
+ unsigned int status_flags;
+
+#define FILE_WITH_SPACE "LICENSE - copy.md"
+
+ cl_set_cleanup(&cleanup_new_repo, "with_space");
+ cl_git_pass(git_repository_init(&repo, "with_space", 0));
+ cl_git_mkfile("with_space/" FILE_WITH_SPACE, "I have a space in my name\n");
+
+ /* file is new to working directory */
+
+ memset(&result, 0, sizeof(result));
+ cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
+ cl_assert_equal_i(1, result.count);
+ cl_assert(result.status == GIT_STATUS_WT_NEW);
+
+ cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE));
+ cl_assert(status_flags == GIT_STATUS_WT_NEW);
+
+ /* ignore the file */
+
+ cl_git_rewritefile("with_space/.gitignore", "*.md\n.gitignore\n");
+
+ memset(&result, 0, sizeof(result));
+ cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
+ cl_assert_equal_i(2, result.count);
+ cl_assert(result.status == GIT_STATUS_IGNORED);
+
+ cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE));
+ cl_assert(status_flags == GIT_STATUS_IGNORED);
+
+ /* don't ignore the file */
+
+ cl_git_rewritefile("with_space/.gitignore", ".gitignore\n");
+
+ memset(&result, 0, sizeof(result));
+ cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
+ cl_assert_equal_i(2, result.count);
+ cl_assert(result.status == GIT_STATUS_WT_NEW);
+
+ cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE));
+ cl_assert(status_flags == GIT_STATUS_WT_NEW);
+
+ /* add the file to the index */
+
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_git_pass(git_index_add_bypath(index, FILE_WITH_SPACE));
+ cl_git_pass(git_index_write(index));
+
+ memset(&result, 0, sizeof(result));
+ cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
+ cl_assert_equal_i(2, result.count);
+ cl_assert(result.status == GIT_STATUS_INDEX_NEW);
+
+ cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_SPACE));
+ cl_assert(status_flags == GIT_STATUS_INDEX_NEW);
+
+ git_index_free(index);
+ git_repository_free(repo);
+}
+
+static int cb_status__expected_path(const char *p, unsigned int s, void *payload)
+{
+ const char *expected_path = (const char *)payload;
+
+ GIT_UNUSED(s);
+
+ if (payload == NULL)
+ cl_fail("Unexpected path");
+
+ cl_assert_equal_s(expected_path, p);
+
+ return 0;
+}
+
+void test_status_worktree_init__disable_pathspec_match(void)
+{
+ git_repository *repo;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ char *file_with_bracket = "LICENSE[1].md",
+ *imaginary_file_with_bracket = "LICENSE[1-2].md";
+
+ cl_set_cleanup(&cleanup_new_repo, "pathspec");
+ cl_git_pass(git_repository_init(&repo, "pathspec", 0));
+ cl_git_mkfile("pathspec/LICENSE[1].md", "screaming bracket\n");
+ cl_git_mkfile("pathspec/LICENSE1.md", "no bracket\n");
+
+ opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH;
+ opts.pathspec.count = 1;
+ opts.pathspec.strings = &file_with_bracket;
+
+ cl_git_pass(
+ git_status_foreach_ext(repo, &opts, cb_status__expected_path,
+ file_with_bracket)
+ );
+
+ /* Test passing a pathspec matching files in the workdir. */
+ /* Must not match because pathspecs are disabled. */
+ opts.pathspec.strings = &imaginary_file_with_bracket;
+ cl_git_pass(
+ git_status_foreach_ext(repo, &opts, cb_status__expected_path, NULL)
+ );
+
+ git_repository_free(repo);
+}
+
+void test_status_worktree_init__new_staged_file_must_handle_crlf(void)
+{
+ git_repository *repo;
+ git_index *index;
+ unsigned int status;
+
+ cl_set_cleanup(&cleanup_new_repo, "getting_started");
+ cl_git_pass(git_repository_init(&repo, "getting_started", 0));
+
+ /* Ensure that repo has core.autocrlf=true */
+ cl_repo_set_bool(repo, "core.autocrlf", true);
+
+ 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_bypath(index, "testfile.txt"));
+ cl_git_pass(git_index_write(index));
+
+ cl_git_pass(git_status_file(&status, repo, "testfile.txt"));
+ cl_assert_equal_i(GIT_STATUS_INDEX_NEW, status);
+
+ git_index_free(index);
+ git_repository_free(repo);
+}
+
diff --git a/tests/stress/diff.c b/tests/stress/diff.c
new file mode 100644
index 000000000..3d2092614
--- /dev/null
+++ b/tests/stress/diff.c
@@ -0,0 +1,146 @@
+#include "clar_libgit2.h"
+#include "../diff/diff_helpers.h"
+
+static git_repository *g_repo = NULL;
+
+void test_stress_diff__initialize(void)
+{
+}
+
+void test_stress_diff__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+#define ANOTHER_POEM \
+"OH, glorious are the guarded heights\nWhere guardian souls abide—\nSelf-exiled from our gross delights—\nAbove, beyond, outside:\nAn ampler arc their spirit swings—\nCommands a juster view—\nWe have their word for all these things,\nNo doubt their words are true.\n\nYet we, the bond slaves of our day,\nWhom dirt and danger press—\nCo-heirs of insolence, delay,\nAnd leagued unfaithfulness—\nSuch is our need must seek indeed\nAnd, having found, engage\nThe men who merely do the work\nFor which they draw the wage.\n\nFrom forge and farm and mine and bench,\nDeck, altar, outpost lone—\nMill, school, battalion, counter, trench,\nRail, senate, sheepfold, throne—\nCreation's cry goes up on high\nFrom age to cheated age:\n\"Send us the men who do the work\n\"For which they draw the wage!\"\n"
+
+static void test_with_many(int expected_new)
+{
+ git_index *index;
+ git_tree *tree, *new_tree;
+ git_diff *diff = NULL;
+ diff_expects exp;
+ git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(
+ git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}"));
+
+ cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/ikeepsix2.txt"));
+ cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
+ cl_git_pass(git_index_add_bypath(index, "ikeepsix2.txt"));
+ cl_git_pass(git_index_write(index));
+
+ cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, NULL, NULL, &exp));
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(expected_new + 1, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(expected_new + 2, exp.files);
+
+ opts.flags = GIT_DIFF_FIND_ALL;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, NULL, NULL, &exp));
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
+ cl_assert_equal_i(expected_new, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(expected_new + 1, exp.files);
+
+ git_diff_free(diff);
+
+ cl_repo_commit_from_index(NULL, g_repo, NULL, 1372350000, "yoyoyo");
+ cl_git_pass(git_revparse_single(
+ (git_object **)&new_tree, g_repo, "HEAD^{tree}"));
+
+ cl_git_pass(git_diff_tree_to_tree(
+ &diff, g_repo, tree, new_tree, &diffopts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, NULL, NULL, &exp));
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(expected_new + 1, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(expected_new + 2, exp.files);
+
+ opts.flags = GIT_DIFF_FIND_ALL;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, diff_file_cb, NULL, NULL, &exp));
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
+ cl_assert_equal_i(expected_new, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(expected_new + 1, exp.files);
+
+ git_diff_free(diff);
+
+ git_tree_free(new_tree);
+ git_tree_free(tree);
+ git_index_free(index);
+}
+
+void test_stress_diff__rename_big_files(void)
+{
+ git_index *index;
+ char tmp[64];
+ int i, j;
+ git_buf b = GIT_BUF_INIT;
+
+ g_repo = cl_git_sandbox_init("renames");
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ for (i = 0; i < 100; i += 1) {
+ snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i);
+ for (j = i * 256; j > 0; --j)
+ git_buf_printf(&b, "more content %d\n", i);
+ cl_git_mkfile(tmp, b.ptr);
+ }
+
+ for (i = 0; i < 100; i += 1) {
+ snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i);
+ cl_git_pass(git_index_add_bypath(index, tmp + strlen("renames/")));
+ }
+
+ git_buf_free(&b);
+ git_index_free(index);
+
+ test_with_many(100);
+}
+
+void test_stress_diff__rename_many_files(void)
+{
+ git_index *index;
+ char tmp[64];
+ int i;
+ git_buf b = GIT_BUF_INIT;
+
+ g_repo = cl_git_sandbox_init("renames");
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ git_buf_printf(&b, "%08d\n" ANOTHER_POEM "%08d\n" ANOTHER_POEM ANOTHER_POEM, 0, 0);
+
+ for (i = 0; i < 2500; i += 1) {
+ snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i);
+ snprintf(b.ptr, 9, "%08d", i);
+ b.ptr[8] = '\n';
+ cl_git_mkfile(tmp, b.ptr);
+ }
+ git_buf_free(&b);
+
+ for (i = 0; i < 2500; i += 1) {
+ snprintf(tmp, sizeof(tmp), "renames/newfile%03d", i);
+ cl_git_pass(git_index_add_bypath(index, tmp + strlen("renames/")));
+ }
+
+ git_index_free(index);
+
+ test_with_many(2500);
+}
diff --git a/tests/structinit/structinit.c b/tests/structinit/structinit.c
new file mode 100644
index 000000000..38bedada7
--- /dev/null
+++ b/tests/structinit/structinit.c
@@ -0,0 +1,127 @@
+#include "clar_libgit2.h"
+#include <git2/sys/config.h>
+#include <git2/sys/odb_backend.h>
+#include <git2/sys/refdb_backend.h>
+
+#define STRINGIFY(s) #s
+
+/* Checks two conditions for the specified structure:
+ * 1. That the initializers for the latest version produces the same
+ * in-memory representation.
+ * 2. That the function-based initializer supports all versions from 1...n,
+ * where n is the latest version (often represented by GIT_*_VERSION).
+ *
+ * Parameters:
+ * structname: The name of the structure to test, e.g. git_blame_options.
+ * structver: The latest version of the specified structure.
+ * macroinit: The macro that initializes the latest version of the structure.
+ * funcinitname: The function that initializes the structure. Must have the
+ * signature "int (structname* instance, int version)".
+ */
+#define CHECK_MACRO_FUNC_INIT_EQUAL(structname, structver, macroinit, funcinitname) \
+do { \
+ structname structname##_macro_latest = macroinit; \
+ structname structname##_func_latest; \
+ int structname##_curr_ver = structver - 1; \
+ cl_git_pass(funcinitname(&structname##_func_latest, structver)); \
+ cl_check_( \
+ memcmp(&structname##_macro_latest, &structname##_func_latest, \
+ sizeof(structname)) == 0, \
+ "Macro-based and function-based initializer for " STRINGIFY(structname) \
+ " are not equivalent."); \
+ \
+ while (structname##_curr_ver > 0) \
+ { \
+ structname macro; \
+ cl_git_pass(funcinitname(&macro, structname##_curr_ver)); \
+ structname##_curr_ver--; \
+ }\
+} while(0)
+
+void test_structinit_structinit__compare(void)
+{
+ /* blame */
+ CHECK_MACRO_FUNC_INIT_EQUAL( \
+ git_blame_options, GIT_BLAME_OPTIONS_VERSION, \
+ GIT_BLAME_OPTIONS_INIT, git_blame_init_options);
+
+ /* checkout */
+ CHECK_MACRO_FUNC_INIT_EQUAL( \
+ git_checkout_options, GIT_CHECKOUT_OPTIONS_VERSION, \
+ GIT_CHECKOUT_OPTIONS_INIT, git_checkout_init_options);
+
+ /* clone */
+ CHECK_MACRO_FUNC_INIT_EQUAL( \
+ git_clone_options, GIT_CLONE_OPTIONS_VERSION, \
+ GIT_CLONE_OPTIONS_INIT, git_clone_init_options);
+
+ /* diff */
+ CHECK_MACRO_FUNC_INIT_EQUAL( \
+ git_diff_options, GIT_DIFF_OPTIONS_VERSION, \
+ GIT_DIFF_OPTIONS_INIT, git_diff_init_options);
+
+ /* diff_find */
+ CHECK_MACRO_FUNC_INIT_EQUAL( \
+ git_diff_find_options, GIT_DIFF_FIND_OPTIONS_VERSION, \
+ GIT_DIFF_FIND_OPTIONS_INIT, git_diff_find_init_options);
+
+ /* merge_file_input */
+ CHECK_MACRO_FUNC_INIT_EQUAL( \
+ git_merge_file_input, GIT_MERGE_FILE_INPUT_VERSION, \
+ GIT_MERGE_FILE_INPUT_INIT, git_merge_file_init_input);
+
+ /* merge_file */
+ CHECK_MACRO_FUNC_INIT_EQUAL( \
+ git_merge_file_options, GIT_MERGE_FILE_OPTIONS_VERSION, \
+ GIT_MERGE_FILE_OPTIONS_INIT, git_merge_file_init_options);
+
+ /* merge_tree */
+ CHECK_MACRO_FUNC_INIT_EQUAL( \
+ git_merge_options, GIT_MERGE_OPTIONS_VERSION, \
+ GIT_MERGE_OPTIONS_INIT, git_merge_init_options);
+
+ /* push */
+ CHECK_MACRO_FUNC_INIT_EQUAL( \
+ git_push_options, GIT_PUSH_OPTIONS_VERSION, \
+ GIT_PUSH_OPTIONS_INIT, git_push_init_options);
+
+ /* remote */
+ CHECK_MACRO_FUNC_INIT_EQUAL( \
+ git_remote_callbacks, GIT_REMOTE_CALLBACKS_VERSION, \
+ GIT_REMOTE_CALLBACKS_INIT, git_remote_init_callbacks);
+
+ /* repository_init */
+ CHECK_MACRO_FUNC_INIT_EQUAL( \
+ git_repository_init_options, GIT_REPOSITORY_INIT_OPTIONS_VERSION, \
+ GIT_REPOSITORY_INIT_OPTIONS_INIT, git_repository_init_init_options);
+
+ /* revert */
+ CHECK_MACRO_FUNC_INIT_EQUAL( \
+ git_revert_options, GIT_REVERT_OPTIONS_VERSION, \
+ GIT_REVERT_OPTIONS_INIT, git_revert_init_options);
+
+ /* status */
+ CHECK_MACRO_FUNC_INIT_EQUAL( \
+ git_status_options, GIT_STATUS_OPTIONS_VERSION, \
+ GIT_STATUS_OPTIONS_INIT, git_status_init_options);
+
+ /* transport */
+ CHECK_MACRO_FUNC_INIT_EQUAL( \
+ git_transport, GIT_TRANSPORT_VERSION, \
+ GIT_TRANSPORT_INIT, git_transport_init);
+
+ /* config_backend */
+ CHECK_MACRO_FUNC_INIT_EQUAL( \
+ git_config_backend, GIT_CONFIG_BACKEND_VERSION, \
+ GIT_CONFIG_BACKEND_INIT, git_config_init_backend);
+
+ /* odb_backend */
+ CHECK_MACRO_FUNC_INIT_EQUAL( \
+ git_odb_backend, GIT_ODB_BACKEND_VERSION, \
+ GIT_ODB_BACKEND_INIT, git_odb_init_backend);
+
+ /* refdb_backend */
+ CHECK_MACRO_FUNC_INIT_EQUAL( \
+ git_refdb_backend, GIT_REFDB_BACKEND_VERSION, \
+ GIT_REFDB_BACKEND_INIT, git_refdb_init_backend);
+}
diff --git a/tests/submodule/add.c b/tests/submodule/add.c
new file mode 100644
index 000000000..9fdc7cc57
--- /dev/null
+++ b/tests/submodule/add.c
@@ -0,0 +1,120 @@
+#include "clar_libgit2.h"
+#include "posix.h"
+#include "path.h"
+#include "submodule_helpers.h"
+
+static git_repository *g_repo = NULL;
+
+void test_submodule_add__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+static void assert_submodule_url(const char* name, const char *url)
+{
+ git_config *cfg;
+ const char *s;
+ git_buf key = GIT_BUF_INIT;
+
+ cl_git_pass(git_repository_config(&cfg, g_repo));
+
+ cl_git_pass(git_buf_printf(&key, "submodule.%s.url", name));
+ cl_git_pass(git_config_get_string(&s, cfg, git_buf_cstr(&key)));
+ cl_assert_equal_s(s, url);
+
+ git_config_free(cfg);
+ git_buf_free(&key);
+}
+
+void test_submodule_add__url_absolute(void)
+{
+ git_submodule *sm;
+
+ g_repo = setup_fixture_submod2();
+
+ /* re-add existing submodule */
+ cl_git_fail_with(
+ GIT_EEXISTS,
+ git_submodule_add_setup(NULL, g_repo, "whatever", "sm_unchanged", 1));
+
+ /* add a submodule using a gitlink */
+
+ cl_git_pass(
+ git_submodule_add_setup(&sm, g_repo, "https://github.com/libgit2/libgit2.git", "sm_libgit2", 1)
+ );
+ git_submodule_free(sm);
+
+ cl_assert(git_path_isfile("submod2/" "sm_libgit2" "/.git"));
+
+ cl_assert(git_path_isdir("submod2/.git/modules"));
+ cl_assert(git_path_isdir("submod2/.git/modules/" "sm_libgit2"));
+ cl_assert(git_path_isfile("submod2/.git/modules/" "sm_libgit2" "/HEAD"));
+ assert_submodule_url("sm_libgit2", "https://github.com/libgit2/libgit2.git");
+
+ /* add a submodule not using a gitlink */
+
+ cl_git_pass(
+ git_submodule_add_setup(&sm, g_repo, "https://github.com/libgit2/libgit2.git", "sm_libgit2b", 0)
+ );
+ git_submodule_free(sm);
+
+ cl_assert(git_path_isdir("submod2/" "sm_libgit2b" "/.git"));
+ cl_assert(git_path_isfile("submod2/" "sm_libgit2b" "/.git/HEAD"));
+ cl_assert(!git_path_exists("submod2/.git/modules/" "sm_libgit2b"));
+ assert_submodule_url("sm_libgit2b", "https://github.com/libgit2/libgit2.git");
+}
+
+void test_submodule_add__url_relative(void)
+{
+ git_submodule *sm;
+ git_remote *remote;
+ git_strarray problems = {0};
+
+ /* default remote url is https://github.com/libgit2/false.git */
+ g_repo = cl_git_sandbox_init("testrepo2");
+
+ /* make sure we don't default to origin - rename origin -> test_remote */
+ cl_git_pass(git_remote_load(&remote, g_repo, "origin"));
+ cl_git_pass(git_remote_rename(&problems, remote, "test_remote"));
+ cl_assert_equal_i(0, problems.count);
+ git_strarray_free(&problems);
+ cl_git_fail(git_remote_load(&remote, g_repo, "origin"));
+ git_remote_free(remote);
+
+ cl_git_pass(
+ git_submodule_add_setup(&sm, g_repo, "../TestGitRepository", "TestGitRepository", 1)
+ );
+ git_submodule_free(sm);
+
+ assert_submodule_url("TestGitRepository", "https://github.com/libgit2/TestGitRepository");
+}
+
+void test_submodule_add__url_relative_to_origin(void)
+{
+ git_submodule *sm;
+
+ /* default remote url is https://github.com/libgit2/false.git */
+ g_repo = cl_git_sandbox_init("testrepo2");
+
+ cl_git_pass(
+ git_submodule_add_setup(&sm, g_repo, "../TestGitRepository", "TestGitRepository", 1)
+ );
+ git_submodule_free(sm);
+
+ assert_submodule_url("TestGitRepository", "https://github.com/libgit2/TestGitRepository");
+}
+
+void test_submodule_add__url_relative_to_workdir(void)
+{
+ git_submodule *sm;
+
+ /* In this repo, HEAD (master) has no remote tracking branc h*/
+ g_repo = cl_git_sandbox_init("testrepo");
+
+ cl_git_pass(
+ git_submodule_add_setup(&sm, g_repo, "./", "TestGitRepository", 1)
+ );
+ git_submodule_free(sm);
+
+ assert_submodule_url("TestGitRepository", git_repository_workdir(g_repo));
+}
diff --git a/tests/submodule/lookup.c b/tests/submodule/lookup.c
new file mode 100644
index 000000000..34de5923e
--- /dev/null
+++ b/tests/submodule/lookup.c
@@ -0,0 +1,271 @@
+#include "clar_libgit2.h"
+#include "submodule_helpers.h"
+#include "git2/sys/repository.h"
+#include "fileops.h"
+
+static git_repository *g_repo = NULL;
+
+void test_submodule_lookup__initialize(void)
+{
+ g_repo = setup_fixture_submod2();
+}
+
+void test_submodule_lookup__simple_lookup(void)
+{
+ assert_submodule_exists(g_repo, "sm_unchanged");
+
+ /* lookup pending change in .gitmodules that is not in HEAD */
+ assert_submodule_exists(g_repo, "sm_added_and_uncommited");
+
+ /* lookup pending change in .gitmodules that is not in HEAD nor index */
+ assert_submodule_exists(g_repo, "sm_gitmodules_only");
+
+ /* lookup git repo subdir that is not added as submodule */
+ refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS);
+
+ /* lookup existing directory that is not a submodule */
+ refute_submodule_exists(g_repo, "just_a_dir", GIT_ENOTFOUND);
+
+ /* lookup existing file that is not a submodule */
+ refute_submodule_exists(g_repo, "just_a_file", GIT_ENOTFOUND);
+
+ /* lookup non-existent item */
+ refute_submodule_exists(g_repo, "no_such_file", GIT_ENOTFOUND);
+}
+
+void test_submodule_lookup__accessors(void)
+{
+ git_submodule *sm;
+ const char *oid = "480095882d281ed676fe5b863569520e54a7d5c0";
+
+ cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged"));
+ cl_assert(git_submodule_owner(sm) == g_repo);
+ cl_assert_equal_s("sm_unchanged", git_submodule_name(sm));
+ cl_assert(git__suffixcmp(git_submodule_path(sm), "sm_unchanged") == 0);
+ cl_assert(git__suffixcmp(git_submodule_url(sm), "/submod2_target") == 0);
+
+ cl_assert(git_oid_streq(git_submodule_index_id(sm), oid) == 0);
+ cl_assert(git_oid_streq(git_submodule_head_id(sm), oid) == 0);
+ cl_assert(git_oid_streq(git_submodule_wd_id(sm), oid) == 0);
+
+ cl_assert(git_submodule_ignore(sm) == GIT_SUBMODULE_IGNORE_NONE);
+ cl_assert(git_submodule_update(sm) == GIT_SUBMODULE_UPDATE_CHECKOUT);
+
+ git_submodule_free(sm);
+
+
+ cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head"));
+ cl_assert_equal_s("sm_changed_head", git_submodule_name(sm));
+
+ cl_assert(git_oid_streq(git_submodule_index_id(sm), oid) == 0);
+ cl_assert(git_oid_streq(git_submodule_head_id(sm), oid) == 0);
+ cl_assert(git_oid_streq(git_submodule_wd_id(sm),
+ "3d9386c507f6b093471a3e324085657a3c2b4247") == 0);
+
+ git_submodule_free(sm);
+
+
+ cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited"));
+ cl_assert_equal_s("sm_added_and_uncommited", git_submodule_name(sm));
+
+ cl_assert(git_oid_streq(git_submodule_index_id(sm), oid) == 0);
+ cl_assert(git_submodule_head_id(sm) == NULL);
+ cl_assert(git_oid_streq(git_submodule_wd_id(sm), oid) == 0);
+
+ git_submodule_free(sm);
+
+
+ cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_missing_commits"));
+ cl_assert_equal_s("sm_missing_commits", git_submodule_name(sm));
+
+ cl_assert(git_oid_streq(git_submodule_index_id(sm), oid) == 0);
+ cl_assert(git_oid_streq(git_submodule_head_id(sm), oid) == 0);
+ cl_assert(git_oid_streq(git_submodule_wd_id(sm),
+ "5e4963595a9774b90524d35a807169049de8ccad") == 0);
+
+ git_submodule_free(sm);
+}
+
+typedef struct {
+ int count;
+} sm_lookup_data;
+
+static int sm_lookup_cb(git_submodule *sm, const char *name, void *payload)
+{
+ sm_lookup_data *data = payload;
+ data->count += 1;
+ cl_assert_equal_s(git_submodule_name(sm), name);
+ return 0;
+}
+
+void test_submodule_lookup__foreach(void)
+{
+ sm_lookup_data data;
+ memset(&data, 0, sizeof(data));
+ cl_git_pass(git_submodule_foreach(g_repo, sm_lookup_cb, &data));
+ cl_assert_equal_i(8, data.count);
+}
+
+void test_submodule_lookup__lookup_even_with_unborn_head(void)
+{
+ git_reference *head;
+
+ /* put us on an unborn branch */
+ cl_git_pass(git_reference_symbolic_create(
+ &head, g_repo, "HEAD", "refs/heads/garbage", 1, NULL, NULL));
+ git_reference_free(head);
+
+ test_submodule_lookup__simple_lookup(); /* baseline should still pass */
+}
+
+void test_submodule_lookup__lookup_even_with_missing_index(void)
+{
+ git_index *idx;
+
+ /* give the repo an empty index */
+ cl_git_pass(git_index_new(&idx));
+ git_repository_set_index(g_repo, idx);
+ git_index_free(idx);
+
+ test_submodule_lookup__simple_lookup(); /* baseline should still pass */
+}
+
+static void baseline_tests(void)
+{
+ /* small baseline that should work even if we change the index or make
+ * commits from the index
+ */
+ assert_submodule_exists(g_repo, "sm_unchanged");
+ assert_submodule_exists(g_repo, "sm_gitmodules_only");
+ refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS);
+}
+
+static void add_submodule_with_commit(const char *name)
+{
+ git_submodule *sm;
+ git_repository *smrepo;
+ git_index *idx;
+ git_buf p = GIT_BUF_INIT;
+
+ cl_git_pass(git_submodule_add_setup(&sm, g_repo,
+ "https://github.com/libgit2/libgit2.git", name, 1));
+
+ assert_submodule_exists(g_repo, name);
+
+ cl_git_pass(git_submodule_open(&smrepo, sm));
+ cl_git_pass(git_repository_index(&idx, smrepo));
+
+ cl_git_pass(git_buf_joinpath(&p, git_repository_workdir(smrepo), "file"));
+ cl_git_mkfile(p.ptr, "new file");
+ git_buf_free(&p);
+
+ cl_git_pass(git_index_add_bypath(idx, "file"));
+ cl_git_pass(git_index_write(idx));
+ git_index_free(idx);
+
+ cl_repo_commit_from_index(NULL, smrepo, NULL, 0, "initial commit");
+ git_repository_free(smrepo);
+
+ cl_git_pass(git_submodule_add_finalize(sm));
+
+ git_submodule_free(sm);
+}
+
+void test_submodule_lookup__just_added(void)
+{
+ git_submodule *sm;
+ git_buf snap1 = GIT_BUF_INIT, snap2 = GIT_BUF_INIT;
+ git_reference *original_head = NULL;
+
+ refute_submodule_exists(g_repo, "sm_just_added", GIT_ENOTFOUND);
+ refute_submodule_exists(g_repo, "sm_just_added_2", GIT_ENOTFOUND);
+ refute_submodule_exists(g_repo, "sm_just_added_idx", GIT_ENOTFOUND);
+ refute_submodule_exists(g_repo, "sm_just_added_head", GIT_ENOTFOUND);
+ refute_submodule_exists(g_repo, "mismatch_name", GIT_ENOTFOUND);
+ refute_submodule_exists(g_repo, "mismatch_path", GIT_ENOTFOUND);
+ baseline_tests();
+
+ cl_git_pass(git_futils_readbuffer(&snap1, "submod2/.gitmodules"));
+ cl_git_pass(git_repository_head(&original_head, g_repo));
+
+ cl_git_pass(git_submodule_add_setup(&sm, g_repo,
+ "https://github.com/libgit2/libgit2.git", "sm_just_added", 1));
+ git_submodule_free(sm);
+ assert_submodule_exists(g_repo, "sm_just_added");
+
+ cl_git_pass(git_submodule_add_setup(&sm, g_repo,
+ "https://github.com/libgit2/libgit2.git", "sm_just_added_2", 1));
+ assert_submodule_exists(g_repo, "sm_just_added_2");
+ cl_git_fail(git_submodule_add_finalize(sm)); /* fails if no HEAD */
+ git_submodule_free(sm);
+
+ add_submodule_with_commit("sm_just_added_head");
+ cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "commit new sm to head");
+ assert_submodule_exists(g_repo, "sm_just_added_head");
+
+ add_submodule_with_commit("sm_just_added_idx");
+ assert_submodule_exists(g_repo, "sm_just_added_idx");
+
+ cl_git_pass(git_futils_readbuffer(&snap2, "submod2/.gitmodules"));
+
+ cl_git_append2file(
+ "submod2/.gitmodules",
+ "\n[submodule \"mismatch_name\"]\n"
+ "\tpath = mismatch_path\n"
+ "\turl = https://example.com/example.git\n\n");
+
+ assert_submodule_exists(g_repo, "mismatch_name");
+ assert_submodule_exists(g_repo, "mismatch_path");
+ assert_submodule_exists(g_repo, "sm_just_added");
+ assert_submodule_exists(g_repo, "sm_just_added_2");
+ assert_submodule_exists(g_repo, "sm_just_added_idx");
+ assert_submodule_exists(g_repo, "sm_just_added_head");
+ baseline_tests();
+
+ cl_git_rewritefile("submod2/.gitmodules", snap2.ptr);
+ git_buf_free(&snap2);
+
+ refute_submodule_exists(g_repo, "mismatch_name", GIT_ENOTFOUND);
+ refute_submodule_exists(g_repo, "mismatch_path", GIT_ENOTFOUND);
+ assert_submodule_exists(g_repo, "sm_just_added");
+ assert_submodule_exists(g_repo, "sm_just_added_2");
+ assert_submodule_exists(g_repo, "sm_just_added_idx");
+ assert_submodule_exists(g_repo, "sm_just_added_head");
+ baseline_tests();
+
+ cl_git_rewritefile("submod2/.gitmodules", snap1.ptr);
+ git_buf_free(&snap1);
+
+ refute_submodule_exists(g_repo, "mismatch_name", GIT_ENOTFOUND);
+ refute_submodule_exists(g_repo, "mismatch_path", GIT_ENOTFOUND);
+ /* note error code change, because add_setup made a repo in the workdir */
+ refute_submodule_exists(g_repo, "sm_just_added", GIT_EEXISTS);
+ refute_submodule_exists(g_repo, "sm_just_added_2", GIT_EEXISTS);
+ /* these still exist in index and head respectively */
+ assert_submodule_exists(g_repo, "sm_just_added_idx");
+ assert_submodule_exists(g_repo, "sm_just_added_head");
+ baseline_tests();
+
+ {
+ git_index *idx;
+ cl_git_pass(git_repository_index(&idx, g_repo));
+ cl_git_pass(git_index_remove_bypath(idx, "sm_just_added_idx"));
+ cl_git_pass(git_index_remove_bypath(idx, "sm_just_added_head"));
+ cl_git_pass(git_index_write(idx));
+ git_index_free(idx);
+ }
+
+ refute_submodule_exists(g_repo, "sm_just_added_idx", GIT_EEXISTS);
+ assert_submodule_exists(g_repo, "sm_just_added_head");
+
+ {
+ git_signature *sig;
+ cl_git_pass(git_signature_now(&sig, "resetter", "resetter@email.com"));
+ cl_git_pass(git_reference_create(NULL, g_repo, "refs/heads/master", git_reference_target(original_head), 1, sig, "move head back"));
+ git_signature_free(sig);
+ git_reference_free(original_head);
+ }
+
+ refute_submodule_exists(g_repo, "sm_just_added_head", GIT_EEXISTS);
+}
+
diff --git a/tests/submodule/modify.c b/tests/submodule/modify.c
new file mode 100644
index 000000000..582d4166b
--- /dev/null
+++ b/tests/submodule/modify.c
@@ -0,0 +1,245 @@
+#include "clar_libgit2.h"
+#include "posix.h"
+#include "path.h"
+#include "submodule_helpers.h"
+
+static git_repository *g_repo = NULL;
+
+#define SM_LIBGIT2_URL "https://github.com/libgit2/libgit2.git"
+#define SM_LIBGIT2 "sm_libgit2"
+
+void test_submodule_modify__initialize(void)
+{
+ g_repo = setup_fixture_submod2();
+}
+
+static int delete_one_config(const git_config_entry *entry, void *payload)
+{
+ git_config *cfg = payload;
+ return git_config_delete_entry(cfg, entry->name);
+}
+
+static int init_one_submodule(
+ git_submodule *sm, const char *name, void *payload)
+{
+ GIT_UNUSED(name);
+ GIT_UNUSED(payload);
+ return git_submodule_init(sm, false);
+}
+
+void test_submodule_modify__init(void)
+{
+ git_config *cfg;
+ const char *str;
+
+ /* erase submodule data from .git/config */
+ cl_git_pass(git_repository_config(&cfg, g_repo));
+ cl_git_pass(
+ git_config_foreach_match(cfg, "submodule\\..*", delete_one_config, cfg));
+ git_config_free(cfg);
+
+ /* confirm no submodule data in config */
+ cl_git_pass(git_repository_config(&cfg, g_repo));
+ cl_git_fail(git_config_get_string(&str, cfg, "submodule.sm_unchanged.url"));
+ cl_git_fail(git_config_get_string(&str, cfg, "submodule.sm_changed_head.url"));
+ cl_git_fail(git_config_get_string(&str, cfg, "submodule.sm_added_and_uncommited.url"));
+ git_config_free(cfg);
+
+ /* call init and see that settings are copied */
+ cl_git_pass(git_submodule_foreach(g_repo, init_one_submodule, NULL));
+
+ git_submodule_reload_all(g_repo, 1);
+
+ /* confirm submodule data in config */
+ cl_git_pass(git_repository_config(&cfg, g_repo));
+ cl_git_pass(git_config_get_string(&str, cfg, "submodule.sm_unchanged.url"));
+ cl_assert(git__suffixcmp(str, "/submod2_target") == 0);
+ cl_git_pass(git_config_get_string(&str, cfg, "submodule.sm_changed_head.url"));
+ cl_assert(git__suffixcmp(str, "/submod2_target") == 0);
+ cl_git_pass(git_config_get_string(&str, cfg, "submodule.sm_added_and_uncommited.url"));
+ cl_assert(git__suffixcmp(str, "/submod2_target") == 0);
+ git_config_free(cfg);
+}
+
+static int sync_one_submodule(
+ git_submodule *sm, const char *name, void *payload)
+{
+ GIT_UNUSED(name);
+ GIT_UNUSED(payload);
+ return git_submodule_sync(sm);
+}
+
+static void assert_submodule_url_is_synced(
+ git_submodule *sm, const char *parent_key, const char *child_key)
+{
+ git_config *cfg;
+ const char *str;
+ git_repository *smrepo;
+
+ cl_git_pass(git_repository_config(&cfg, g_repo));
+ cl_git_pass(git_config_get_string(&str, cfg, parent_key));
+ cl_assert_equal_s(git_submodule_url(sm), str);
+ git_config_free(cfg);
+
+ cl_git_pass(git_submodule_open(&smrepo, sm));
+ cl_git_pass(git_repository_config(&cfg, smrepo));
+ cl_git_pass(git_config_get_string(&str, cfg, child_key));
+ cl_assert_equal_s(git_submodule_url(sm), str);
+ git_config_free(cfg);
+ git_repository_free(smrepo);
+}
+
+void test_submodule_modify__sync(void)
+{
+ git_submodule *sm1, *sm2, *sm3;
+ git_config *cfg;
+ const char *str;
+
+#define SM1 "sm_unchanged"
+#define SM2 "sm_changed_head"
+#define SM3 "sm_added_and_uncommited"
+
+ /* look up some submodules */
+ cl_git_pass(git_submodule_lookup(&sm1, g_repo, SM1));
+ cl_git_pass(git_submodule_lookup(&sm2, g_repo, SM2));
+ cl_git_pass(git_submodule_lookup(&sm3, g_repo, SM3));
+
+ /* At this point, the .git/config URLs for the submodules have
+ * not be rewritten with the absolute paths (although the
+ * .gitmodules have. Let's confirm that they DO NOT match
+ * yet, then we can do a sync to make them match...
+ */
+
+ /* check submodule info does not match before sync */
+ cl_git_pass(git_repository_config(&cfg, g_repo));
+ cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM1".url"));
+ cl_assert(strcmp(git_submodule_url(sm1), str) != 0);
+ cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM2".url"));
+ cl_assert(strcmp(git_submodule_url(sm2), str) != 0);
+ cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM3".url"));
+ cl_assert(strcmp(git_submodule_url(sm3), str) != 0);
+ git_config_free(cfg);
+
+ /* sync all the submodules */
+ cl_git_pass(git_submodule_foreach(g_repo, sync_one_submodule, NULL));
+
+ /* check that submodule config is updated */
+ assert_submodule_url_is_synced(
+ sm1, "submodule."SM1".url", "branch.origin.remote");
+ assert_submodule_url_is_synced(
+ sm2, "submodule."SM2".url", "branch.origin.remote");
+ assert_submodule_url_is_synced(
+ sm3, "submodule."SM3".url", "branch.origin.remote");
+
+ git_submodule_free(sm1);
+ git_submodule_free(sm2);
+ git_submodule_free(sm3);
+}
+
+void test_submodule_modify__edit_and_save(void)
+{
+ git_submodule *sm1, *sm2;
+ char *old_url;
+ git_submodule_ignore_t old_ignore;
+ git_submodule_update_t old_update;
+ git_repository *r2;
+ git_submodule_recurse_t old_fetchrecurse;
+
+ cl_git_pass(git_submodule_lookup(&sm1, g_repo, "sm_changed_head"));
+
+ old_url = git__strdup(git_submodule_url(sm1));
+
+ /* modify properties of submodule */
+ cl_git_pass(git_submodule_set_url(sm1, SM_LIBGIT2_URL));
+ old_ignore = git_submodule_set_ignore(sm1, GIT_SUBMODULE_IGNORE_UNTRACKED);
+ old_update = git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_REBASE);
+ old_fetchrecurse = git_submodule_set_fetch_recurse_submodules(
+ sm1, GIT_SUBMODULE_RECURSE_YES);
+
+ cl_assert_equal_s(SM_LIBGIT2_URL, git_submodule_url(sm1));
+ cl_assert_equal_i(
+ GIT_SUBMODULE_IGNORE_UNTRACKED, git_submodule_ignore(sm1));
+ cl_assert_equal_i(
+ GIT_SUBMODULE_UPDATE_REBASE, git_submodule_update(sm1));
+ cl_assert_equal_i(
+ GIT_SUBMODULE_RECURSE_YES, git_submodule_fetch_recurse_submodules(sm1));
+
+ /* revert without saving (and confirm setters return old value) */
+ cl_git_pass(git_submodule_set_url(sm1, old_url));
+ cl_assert_equal_i(
+ GIT_SUBMODULE_IGNORE_UNTRACKED,
+ git_submodule_set_ignore(sm1, GIT_SUBMODULE_IGNORE_RESET));
+ cl_assert_equal_i(
+ GIT_SUBMODULE_UPDATE_REBASE,
+ git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_RESET));
+ cl_assert_equal_i(
+ GIT_SUBMODULE_RECURSE_YES, git_submodule_set_fetch_recurse_submodules(
+ sm1, GIT_SUBMODULE_RECURSE_RESET));
+
+ /* check that revert was successful */
+ cl_assert_equal_s(old_url, git_submodule_url(sm1));
+ cl_assert_equal_i((int)old_ignore, (int)git_submodule_ignore(sm1));
+ cl_assert_equal_i((int)old_update, (int)git_submodule_update(sm1));
+ cl_assert_equal_i(
+ old_fetchrecurse, git_submodule_fetch_recurse_submodules(sm1));
+
+ /* modify properties of submodule (again) */
+ cl_git_pass(git_submodule_set_url(sm1, SM_LIBGIT2_URL));
+ git_submodule_set_ignore(sm1, GIT_SUBMODULE_IGNORE_UNTRACKED);
+ git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_REBASE);
+ git_submodule_set_fetch_recurse_submodules(sm1, GIT_SUBMODULE_RECURSE_YES);
+
+ /* call save */
+ cl_git_pass(git_submodule_save(sm1));
+
+ /* attempt to "revert" values */
+ git_submodule_set_ignore(sm1, GIT_SUBMODULE_IGNORE_RESET);
+ git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_RESET);
+
+ /* but ignore and update should NOT revert because the RESET
+ * should now be the newly saved value...
+ */
+ cl_assert_equal_i(
+ (int)GIT_SUBMODULE_IGNORE_UNTRACKED, (int)git_submodule_ignore(sm1));
+ cl_assert_equal_i(
+ (int)GIT_SUBMODULE_UPDATE_REBASE, (int)git_submodule_update(sm1));
+ cl_assert_equal_i(GIT_SUBMODULE_RECURSE_YES, git_submodule_fetch_recurse_submodules(sm1));
+
+ /* call reload and check that the new values are loaded */
+ cl_git_pass(git_submodule_reload(sm1, 0));
+
+ cl_assert_equal_s(SM_LIBGIT2_URL, git_submodule_url(sm1));
+ cl_assert_equal_i(
+ (int)GIT_SUBMODULE_IGNORE_UNTRACKED, (int)git_submodule_ignore(sm1));
+ cl_assert_equal_i(
+ (int)GIT_SUBMODULE_UPDATE_REBASE, (int)git_submodule_update(sm1));
+ cl_assert_equal_i(GIT_SUBMODULE_RECURSE_YES, git_submodule_fetch_recurse_submodules(sm1));
+
+ /* open a second copy of the repo and compare submodule */
+ cl_git_pass(git_repository_open(&r2, "submod2"));
+ cl_git_pass(git_submodule_lookup(&sm2, r2, "sm_changed_head"));
+
+ cl_assert_equal_s(SM_LIBGIT2_URL, git_submodule_url(sm2));
+ cl_assert_equal_i(
+ GIT_SUBMODULE_IGNORE_UNTRACKED, git_submodule_ignore(sm2));
+ cl_assert_equal_i(
+ GIT_SUBMODULE_UPDATE_REBASE, git_submodule_update(sm2));
+ cl_assert_equal_i(
+ GIT_SUBMODULE_RECURSE_NO, git_submodule_fetch_recurse_submodules(sm2));
+
+ /* set fetchRecurseSubmodules on-demand */
+ cl_git_pass(git_submodule_reload(sm1, 0));
+ git_submodule_set_fetch_recurse_submodules(sm1, GIT_SUBMODULE_RECURSE_ONDEMAND);
+ cl_assert_equal_i(
+ GIT_SUBMODULE_RECURSE_ONDEMAND, git_submodule_fetch_recurse_submodules(sm1));
+ /* call save */
+ cl_git_pass(git_submodule_save(sm1));
+ cl_git_pass(git_submodule_reload(sm1, 0));
+ cl_assert_equal_i(
+ GIT_SUBMODULE_RECURSE_ONDEMAND, git_submodule_fetch_recurse_submodules(sm1));
+
+ git_submodule_free(sm1);
+ git_submodule_free(sm2);
+ git_repository_free(r2);
+ git__free(old_url);
+}
diff --git a/tests/submodule/nosubs.c b/tests/submodule/nosubs.c
new file mode 100644
index 000000000..e343c1620
--- /dev/null
+++ b/tests/submodule/nosubs.c
@@ -0,0 +1,176 @@
+/* test the submodule APIs on repositories where there are no submodules */
+
+#include "clar_libgit2.h"
+#include "posix.h"
+#include "fileops.h"
+
+void test_submodule_nosubs__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_submodule_nosubs__lookup(void)
+{
+ git_repository *repo = cl_git_sandbox_init("status");
+ git_submodule *sm = NULL;
+
+ p_mkdir("status/subrepo", 0777);
+ cl_git_mkfile("status/subrepo/.git", "gitdir: ../.git");
+
+ cl_assert_equal_i(GIT_ENOTFOUND, git_submodule_lookup(&sm, repo, "subdir"));
+
+ cl_assert_equal_i(GIT_EEXISTS, git_submodule_lookup(&sm, repo, "subrepo"));
+
+ cl_git_pass(git_submodule_reload_all(repo, 0));
+
+ cl_assert_equal_i(GIT_ENOTFOUND, git_submodule_lookup(&sm, repo, "subdir"));
+
+ cl_assert_equal_i(GIT_EEXISTS, git_submodule_lookup(&sm, repo, "subrepo"));
+}
+
+void test_submodule_nosubs__immediate_reload(void)
+{
+ git_repository *repo = cl_git_sandbox_init("status");
+ cl_git_pass(git_submodule_reload_all(repo, 0));
+}
+
+static int fake_submod_cb(git_submodule *sm, const char *n, void *p)
+{
+ GIT_UNUSED(sm); GIT_UNUSED(n); GIT_UNUSED(p);
+ return 0;
+}
+
+void test_submodule_nosubs__foreach(void)
+{
+ git_repository *repo = cl_git_sandbox_init("status");
+ cl_git_pass(git_submodule_foreach(repo, fake_submod_cb, NULL));
+}
+
+void test_submodule_nosubs__add(void)
+{
+ git_repository *repo = cl_git_sandbox_init("status");
+ git_submodule *sm, *sm2;
+
+ cl_git_pass(git_submodule_add_setup(&sm, repo, "https://github.com/libgit2/libgit2.git", "submodules/libgit2", 1));
+
+ cl_git_pass(git_submodule_lookup(&sm2, repo, "submodules/libgit2"));
+ git_submodule_free(sm2);
+
+ cl_git_pass(git_submodule_foreach(repo, fake_submod_cb, NULL));
+ cl_git_pass(git_submodule_reload_all(repo, 0));
+
+ git_submodule_free(sm);
+}
+
+void test_submodule_nosubs__reload_add_reload(void)
+{
+ git_repository *repo = cl_git_sandbox_init("status");
+ git_submodule *sm;
+
+ cl_git_pass(git_submodule_reload_all(repo, 0));
+
+ /* try one add with a reload (to make sure no errors happen) */
+
+ cl_git_pass(git_submodule_add_setup(&sm, repo,
+ "https://github.com/libgit2/libgit2.git", "submodules/libgit2", 1));
+
+ cl_git_pass(git_submodule_reload_all(repo, 0));
+
+ cl_assert_equal_s("submodules/libgit2", git_submodule_name(sm));
+ git_submodule_free(sm);
+
+ cl_git_pass(git_submodule_lookup(&sm, repo, "submodules/libgit2"));
+ cl_assert_equal_s("submodules/libgit2", git_submodule_name(sm));
+ git_submodule_free(sm);
+
+ /* try one add without a reload (to make sure cache inval works, too) */
+
+ cl_git_pass(git_submodule_add_setup(&sm, repo,
+ "https://github.com/libgit2/libgit2.git", "libgit2-again", 1));
+ cl_assert_equal_s("libgit2-again", git_submodule_name(sm));
+ git_submodule_free(sm);
+
+ cl_git_pass(git_submodule_lookup(&sm, repo, "libgit2-again"));
+ cl_assert_equal_s("libgit2-again", git_submodule_name(sm));
+ git_submodule_free(sm);
+}
+
+void test_submodule_nosubs__bad_gitmodules(void)
+{
+ git_repository *repo = cl_git_sandbox_init("status");
+
+ cl_git_mkfile("status/.gitmodules", "[submodule \"foobar\"]\tpath=blargle\n\turl=\n\tbranch=\n\tupdate=flooble\n\n");
+ cl_git_fail(git_submodule_reload_all(repo, 0));
+
+ cl_git_rewritefile("status/.gitmodules", "[submodule \"foobar\"]\tpath=blargle\n\turl=\n\tbranch=\n\tupdate=rebase\n\n");
+ cl_git_pass(git_submodule_reload_all(repo, 0));
+
+ cl_git_pass(git_submodule_lookup(NULL, repo, "foobar"));
+ cl_assert_equal_i(GIT_ENOTFOUND, git_submodule_lookup(NULL, repo, "subdir"));
+}
+
+void test_submodule_nosubs__add_and_delete(void)
+{
+ git_repository *repo = cl_git_sandbox_init("status");
+ git_submodule *sm;
+ git_buf buf = GIT_BUF_INIT;
+
+ /* note lack of calls to git_submodule_reload_all - this *should* work */
+
+ cl_git_fail(git_submodule_lookup(NULL, repo, "libgit2"));
+ cl_git_fail(git_submodule_lookup(NULL, repo, "submodules/libgit2"));
+
+ /* create */
+
+ cl_git_pass(git_submodule_add_setup(
+ &sm, repo, "https://github.com/libgit2/libgit2.git", "submodules/libgit2", 1));
+ cl_assert_equal_s("submodules/libgit2", git_submodule_name(sm));
+ cl_assert_equal_s("submodules/libgit2", git_submodule_path(sm));
+ git_submodule_free(sm);
+
+ cl_git_pass(git_futils_readbuffer(&buf, "status/.gitmodules"));
+ cl_assert(strstr(buf.ptr, "[submodule \"submodules/libgit2\"]") != NULL);
+ cl_assert(strstr(buf.ptr, "path = submodules/libgit2") != NULL);
+ git_buf_free(&buf);
+
+ /* lookup */
+
+ cl_git_fail(git_submodule_lookup(&sm, repo, "libgit2"));
+ cl_git_pass(git_submodule_lookup(&sm, repo, "submodules/libgit2"));
+ cl_assert_equal_s("submodules/libgit2", git_submodule_name(sm));
+ cl_assert_equal_s("submodules/libgit2", git_submodule_path(sm));
+ git_submodule_free(sm);
+
+ /* update name */
+
+ cl_git_rewritefile(
+ "status/.gitmodules",
+ "[submodule \"libgit2\"]\n"
+ " path = submodules/libgit2\n"
+ " url = https://github.com/libgit2/libgit2.git\n");
+
+ cl_git_pass(git_submodule_lookup(&sm, repo, "libgit2"));
+ cl_assert_equal_s("libgit2", git_submodule_name(sm));
+ cl_assert_equal_s("submodules/libgit2", git_submodule_path(sm));
+ git_submodule_free(sm);
+ cl_git_pass(git_submodule_lookup(&sm, repo, "submodules/libgit2"));
+ git_submodule_free(sm);
+
+ /* revert name update */
+
+ cl_git_rewritefile(
+ "status/.gitmodules",
+ "[submodule \"submodules/libgit2\"]\n"
+ " path = submodules/libgit2\n"
+ " url = https://github.com/libgit2/libgit2.git\n");
+
+ cl_git_fail(git_submodule_lookup(&sm, repo, "libgit2"));
+ cl_git_pass(git_submodule_lookup(&sm, repo, "submodules/libgit2"));
+ git_submodule_free(sm);
+
+ /* remove completely */
+
+ cl_must_pass(p_unlink("status/.gitmodules"));
+ cl_git_fail(git_submodule_lookup(&sm, repo, "libgit2"));
+ cl_git_fail(git_submodule_lookup(&sm, repo, "submodules/libgit2"));
+}
diff --git a/tests/submodule/status.c b/tests/submodule/status.c
new file mode 100644
index 000000000..6473491b9
--- /dev/null
+++ b/tests/submodule/status.c
@@ -0,0 +1,360 @@
+#include "clar_libgit2.h"
+#include "posix.h"
+#include "path.h"
+#include "submodule_helpers.h"
+#include "fileops.h"
+#include "iterator.h"
+
+static git_repository *g_repo = NULL;
+
+void test_submodule_status__initialize(void)
+{
+ g_repo = setup_fixture_submod2();
+}
+
+void test_submodule_status__cleanup(void)
+{
+}
+
+void test_submodule_status__unchanged(void)
+{
+ unsigned int status = get_submodule_status(g_repo, "sm_unchanged");
+ unsigned int expected =
+ GIT_SUBMODULE_STATUS_IN_HEAD |
+ GIT_SUBMODULE_STATUS_IN_INDEX |
+ GIT_SUBMODULE_STATUS_IN_CONFIG |
+ GIT_SUBMODULE_STATUS_IN_WD;
+
+ cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
+ cl_assert(expected == status);
+}
+
+static void rm_submodule(const char *name)
+{
+ git_buf path = GIT_BUF_INIT;
+ cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), name));
+ cl_git_pass(git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES));
+ git_buf_free(&path);
+}
+
+static void add_submodule_to_index(const char *name)
+{
+ git_submodule *sm;
+ cl_git_pass(git_submodule_lookup(&sm, g_repo, name));
+ cl_git_pass(git_submodule_add_to_index(sm, true));
+ git_submodule_free(sm);
+}
+
+static void rm_submodule_from_index(const char *name)
+{
+ git_index *index;
+ size_t pos;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_assert(!git_index_find(&pos, index, name));
+ cl_git_pass(git_index_remove(index, name, 0));
+ cl_git_pass(git_index_write(index));
+ git_index_free(index);
+}
+
+/* 4 values of GIT_SUBMODULE_IGNORE to check */
+
+void test_submodule_status__ignore_none(void)
+{
+ unsigned int status;
+
+ rm_submodule("sm_unchanged");
+
+ refute_submodule_exists(g_repo, "just_a_dir", GIT_ENOTFOUND);
+ refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS);
+ refute_submodule_exists(g_repo, "not", GIT_EEXISTS);
+
+ status = get_submodule_status(g_repo, "sm_changed_index");
+ cl_assert((status & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED) != 0);
+
+ status = get_submodule_status(g_repo, "sm_changed_head");
+ cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0);
+
+ status = get_submodule_status(g_repo, "sm_changed_file");
+ cl_assert((status & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED) != 0);
+
+ status = get_submodule_status(g_repo, "sm_changed_untracked_file");
+ cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNTRACKED) != 0);
+
+ status = get_submodule_status(g_repo, "sm_missing_commits");
+ cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0);
+
+ status = get_submodule_status(g_repo, "sm_added_and_uncommited");
+ cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_ADDED) != 0);
+
+ /* removed sm_unchanged for deleted workdir */
+ status = get_submodule_status(g_repo, "sm_unchanged");
+ cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0);
+
+ /* now mkdir sm_unchanged to test uninitialized */
+ cl_git_pass(git_futils_mkdir("sm_unchanged", "submod2", 0755, 0));
+ status = get_submodule_status(g_repo, "sm_unchanged");
+ cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0);
+
+ /* update sm_changed_head in index */
+ add_submodule_to_index("sm_changed_head");
+ status = get_submodule_status(g_repo, "sm_changed_head");
+ cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_MODIFIED) != 0);
+
+ /* remove sm_changed_head from index */
+ rm_submodule_from_index("sm_changed_head");
+ status = get_submodule_status(g_repo, "sm_changed_head");
+ cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_DELETED) != 0);
+}
+
+static int set_sm_ignore(git_submodule *sm, const char *name, void *payload)
+{
+ git_submodule_ignore_t ignore = *(git_submodule_ignore_t *)payload;
+ GIT_UNUSED(name);
+ git_submodule_set_ignore(sm, ignore);
+ return 0;
+}
+
+void test_submodule_status__ignore_untracked(void)
+{
+ unsigned int status;
+ git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_UNTRACKED;
+
+ rm_submodule("sm_unchanged");
+ cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign));
+
+ refute_submodule_exists(g_repo, "just_a_dir", GIT_ENOTFOUND);
+ refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS);
+ refute_submodule_exists(g_repo, "not", GIT_EEXISTS);
+
+ status = get_submodule_status(g_repo, "sm_changed_index");
+ cl_assert((status & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED) != 0);
+
+ status = get_submodule_status(g_repo, "sm_changed_head");
+ cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0);
+
+ status = get_submodule_status(g_repo, "sm_changed_file");
+ cl_assert((status & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED) != 0);
+
+ status = get_submodule_status(g_repo, "sm_changed_untracked_file");
+ cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
+
+ status = get_submodule_status(g_repo, "sm_missing_commits");
+ cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0);
+
+ status = get_submodule_status(g_repo, "sm_added_and_uncommited");
+ cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_ADDED) != 0);
+
+ /* removed sm_unchanged for deleted workdir */
+ status = get_submodule_status(g_repo, "sm_unchanged");
+ cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0);
+
+ /* now mkdir sm_unchanged to test uninitialized */
+ cl_git_pass(git_futils_mkdir("sm_unchanged", "submod2", 0755, 0));
+ status = get_submodule_status(g_repo, "sm_unchanged");
+ cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0);
+
+ /* update sm_changed_head in index */
+ add_submodule_to_index("sm_changed_head");
+ status = get_submodule_status(g_repo, "sm_changed_head");
+ cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_MODIFIED) != 0);
+}
+
+void test_submodule_status__ignore_dirty(void)
+{
+ unsigned int status;
+ git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_DIRTY;
+
+ rm_submodule("sm_unchanged");
+ cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign));
+
+ refute_submodule_exists(g_repo, "just_a_dir", GIT_ENOTFOUND);
+ refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS);
+ refute_submodule_exists(g_repo, "not", GIT_EEXISTS);
+
+ status = get_submodule_status(g_repo, "sm_changed_index");
+ cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
+
+ status = get_submodule_status(g_repo, "sm_changed_head");
+ cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0);
+
+ status = get_submodule_status(g_repo, "sm_changed_file");
+ cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
+
+ status = get_submodule_status(g_repo, "sm_changed_untracked_file");
+ cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
+
+ status = get_submodule_status(g_repo, "sm_missing_commits");
+ cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0);
+
+ status = get_submodule_status(g_repo, "sm_added_and_uncommited");
+ cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_ADDED) != 0);
+
+ /* removed sm_unchanged for deleted workdir */
+ status = get_submodule_status(g_repo, "sm_unchanged");
+ cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0);
+
+ /* now mkdir sm_unchanged to test uninitialized */
+ cl_git_pass(git_futils_mkdir("sm_unchanged", "submod2", 0755, 0));
+ status = get_submodule_status(g_repo, "sm_unchanged");
+ cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0);
+
+ /* update sm_changed_head in index */
+ add_submodule_to_index("sm_changed_head");
+ status = get_submodule_status(g_repo, "sm_changed_head");
+ cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_MODIFIED) != 0);
+}
+
+void test_submodule_status__ignore_all(void)
+{
+ unsigned int status;
+ git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_ALL;
+
+ rm_submodule("sm_unchanged");
+ cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign));
+
+ refute_submodule_exists(g_repo, "just_a_dir", GIT_ENOTFOUND);
+ refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS);
+ refute_submodule_exists(g_repo, "not", GIT_EEXISTS);
+
+ status = get_submodule_status(g_repo, "sm_changed_index");
+ cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
+
+ status = get_submodule_status(g_repo, "sm_changed_head");
+ cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
+
+ status = get_submodule_status(g_repo, "sm_changed_file");
+ cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
+
+ status = get_submodule_status(g_repo, "sm_changed_untracked_file");
+ cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
+
+ status = get_submodule_status(g_repo, "sm_missing_commits");
+ cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
+
+ status = get_submodule_status(g_repo, "sm_added_and_uncommited");
+ cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
+
+ /* removed sm_unchanged for deleted workdir */
+ status = get_submodule_status(g_repo, "sm_unchanged");
+ cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
+
+ /* now mkdir sm_unchanged to test uninitialized */
+ cl_git_pass(git_futils_mkdir("sm_unchanged", "submod2", 0755, 0));
+ status = get_submodule_status(g_repo, "sm_unchanged");
+ cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
+
+ /* update sm_changed_head in index */
+ add_submodule_to_index("sm_changed_head");
+ status = get_submodule_status(g_repo, "sm_changed_head");
+ cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
+}
+
+typedef struct {
+ size_t counter;
+ const char **paths;
+ int *statuses;
+} submodule_expectations;
+
+static int confirm_submodule_status(
+ const char *path, unsigned int status_flags, void *payload)
+{
+ submodule_expectations *exp = payload;
+
+ while (exp->statuses[exp->counter] < 0)
+ exp->counter++;
+
+ cl_assert_equal_i(exp->statuses[exp->counter], (int)status_flags);
+ cl_assert_equal_s(exp->paths[exp->counter++], path);
+
+ GIT_UNUSED(status_flags);
+
+ return 0;
+}
+
+void test_submodule_status__iterator(void)
+{
+ git_iterator *iter;
+ const git_index_entry *entry;
+ size_t i;
+ static const char *expected[] = {
+ ".gitmodules",
+ "just_a_dir/",
+ "just_a_dir/contents",
+ "just_a_file",
+ "not-submodule/",
+ "not-submodule/README.txt",
+ "not/",
+ "not/README.txt",
+ "README.txt",
+ "sm_added_and_uncommited",
+ "sm_changed_file",
+ "sm_changed_head",
+ "sm_changed_index",
+ "sm_changed_untracked_file",
+ "sm_missing_commits",
+ "sm_unchanged",
+ NULL
+ };
+ static int expected_flags[] = {
+ GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED, /* ".gitmodules" */
+ -1, /* "just_a_dir/" will be skipped */
+ GIT_STATUS_CURRENT, /* "just_a_dir/contents" */
+ GIT_STATUS_CURRENT, /* "just_a_file" */
+ GIT_STATUS_WT_NEW, /* "not-submodule/" untracked item */
+ -1, /* "not-submodule/README.txt" */
+ GIT_STATUS_WT_NEW, /* "not/" untracked item */
+ -1, /* "not/README.txt" */
+ GIT_STATUS_CURRENT, /* "README.txt */
+ GIT_STATUS_INDEX_NEW, /* "sm_added_and_uncommited" */
+ GIT_STATUS_WT_MODIFIED, /* "sm_changed_file" */
+ GIT_STATUS_WT_MODIFIED, /* "sm_changed_head" */
+ GIT_STATUS_WT_MODIFIED, /* "sm_changed_index" */
+ GIT_STATUS_WT_MODIFIED, /* "sm_changed_untracked_file" */
+ GIT_STATUS_WT_MODIFIED, /* "sm_missing_commits" */
+ GIT_STATUS_CURRENT, /* "sm_unchanged" */
+ 0
+ };
+ submodule_expectations exp = { 0, expected, expected_flags };
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+
+ cl_git_pass(git_iterator_for_workdir(&iter, g_repo,
+ GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES, NULL, NULL));
+
+ for (i = 0; !git_iterator_advance(&entry, iter); ++i)
+ cl_assert_equal_s(expected[i], entry->path);
+
+ git_iterator_free(iter);
+
+ opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_INCLUDE_UNMODIFIED |
+ GIT_STATUS_OPT_INCLUDE_IGNORED |
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS |
+ GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY;
+
+ cl_git_pass(git_status_foreach_ext(
+ g_repo, &opts, confirm_submodule_status, &exp));
+}
+
+void test_submodule_status__untracked_dirs_containing_ignored_files(void)
+{
+ unsigned int status, expected;
+
+ cl_git_append2file(
+ "submod2/.git/modules/sm_unchanged/info/exclude", "\n*.ignored\n");
+
+ cl_git_pass(
+ git_futils_mkdir("sm_unchanged/directory", "submod2", 0755, 0));
+ cl_git_mkfile(
+ "submod2/sm_unchanged/directory/i_am.ignored",
+ "ignore this file, please\n");
+
+ status = get_submodule_status(g_repo, "sm_unchanged");
+ cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status));
+
+ expected = GIT_SUBMODULE_STATUS_IN_HEAD |
+ GIT_SUBMODULE_STATUS_IN_INDEX |
+ GIT_SUBMODULE_STATUS_IN_CONFIG |
+ GIT_SUBMODULE_STATUS_IN_WD;
+ cl_assert(status == expected);
+}
diff --git a/tests/submodule/submodule_helpers.c b/tests/submodule/submodule_helpers.c
new file mode 100644
index 000000000..c6d04b40a
--- /dev/null
+++ b/tests/submodule/submodule_helpers.c
@@ -0,0 +1,178 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "path.h"
+#include "util.h"
+#include "posix.h"
+#include "submodule_helpers.h"
+#include "git2/sys/repository.h"
+
+/* rewrite gitmodules -> .gitmodules
+ * rewrite the empty or relative urls inside each module
+ * rename the .gitted directory inside any submodule to .git
+ */
+void rewrite_gitmodules(const char *workdir)
+{
+ git_buf in_f = GIT_BUF_INIT, out_f = GIT_BUF_INIT, path = GIT_BUF_INIT;
+ FILE *in, *out;
+ char line[256];
+
+ cl_git_pass(git_buf_joinpath(&in_f, workdir, "gitmodules"));
+ cl_git_pass(git_buf_joinpath(&out_f, workdir, ".gitmodules"));
+
+ cl_assert((in = fopen(in_f.ptr, "rb")) != NULL);
+ cl_assert((out = fopen(out_f.ptr, "wb")) != NULL);
+
+ while (fgets(line, sizeof(line), in) != NULL) {
+ char *scan = line;
+
+ while (*scan == ' ' || *scan == '\t') scan++;
+
+ /* rename .gitted -> .git in submodule directories */
+ if (git__prefixcmp(scan, "path =") == 0) {
+ scan += strlen("path =");
+ while (*scan == ' ') scan++;
+
+ git_buf_joinpath(&path, workdir, scan);
+ git_buf_rtrim(&path);
+ git_buf_joinpath(&path, path.ptr, ".gitted");
+
+ if (!git_buf_oom(&path) && p_access(path.ptr, F_OK) == 0) {
+ git_buf_joinpath(&out_f, workdir, scan);
+ git_buf_rtrim(&out_f);
+ git_buf_joinpath(&out_f, out_f.ptr, ".git");
+
+ if (!git_buf_oom(&out_f))
+ p_rename(path.ptr, out_f.ptr);
+ }
+ }
+
+ /* copy non-"url =" lines verbatim */
+ if (git__prefixcmp(scan, "url =") != 0) {
+ fputs(line, out);
+ continue;
+ }
+
+ /* convert relative URLs in "url =" lines */
+ scan += strlen("url =");
+ while (*scan == ' ') scan++;
+
+ if (*scan == '.') {
+ git_buf_joinpath(&path, workdir, scan);
+ git_buf_rtrim(&path);
+ } else if (!*scan || *scan == '\n') {
+ git_buf_joinpath(&path, workdir, "../testrepo.git");
+ } else {
+ fputs(line, out);
+ continue;
+ }
+
+ git_path_prettify(&path, path.ptr, NULL);
+ git_buf_putc(&path, '\n');
+ cl_assert(!git_buf_oom(&path));
+
+ fwrite(line, scan - line, sizeof(char), out);
+ fputs(path.ptr, out);
+ }
+
+ fclose(in);
+ fclose(out);
+
+ cl_must_pass(p_unlink(in_f.ptr));
+
+ git_buf_free(&in_f);
+ git_buf_free(&out_f);
+ git_buf_free(&path);
+}
+
+static void cleanup_fixture_submodules(void *payload)
+{
+ cl_git_sandbox_cleanup(); /* either "submodules" or "submod2" */
+
+ if (payload)
+ cl_fixture_cleanup(payload);
+}
+
+git_repository *setup_fixture_submodules(void)
+{
+ git_repository *repo = cl_git_sandbox_init("submodules");
+
+ cl_fixture_sandbox("testrepo.git");
+
+ rewrite_gitmodules(git_repository_workdir(repo));
+ p_rename("submodules/testrepo/.gitted", "submodules/testrepo/.git");
+
+ cl_set_cleanup(cleanup_fixture_submodules, "testrepo.git");
+
+ cl_git_pass(git_repository_reinit_filesystem(repo, 1));
+
+ return repo;
+}
+
+git_repository *setup_fixture_submod2(void)
+{
+ git_repository *repo = cl_git_sandbox_init("submod2");
+
+ cl_fixture_sandbox("submod2_target");
+ p_rename("submod2_target/.gitted", "submod2_target/.git");
+
+ rewrite_gitmodules(git_repository_workdir(repo));
+ p_rename("submod2/not-submodule/.gitted", "submod2/not-submodule/.git");
+ p_rename("submod2/not/.gitted", "submod2/not/.git");
+
+ cl_set_cleanup(cleanup_fixture_submodules, "submod2_target");
+
+ cl_git_pass(git_repository_reinit_filesystem(repo, 1));
+
+ return repo;
+}
+
+void assert__submodule_exists(
+ git_repository *repo, const char *name,
+ const char *msg, const char *file, int line)
+{
+ git_submodule *sm;
+ int error = git_submodule_lookup(&sm, repo, name);
+ if (error)
+ cl_git_report_failure(error, file, line, msg);
+ cl_assert_at_line(sm != NULL, file, line);
+ git_submodule_free(sm);
+}
+
+void refute__submodule_exists(
+ git_repository *repo, const char *name, int expected_error,
+ const char *msg, const char *file, int line)
+{
+ git_submodule *sm;
+ clar__assert_equal(
+ file, line, msg, 1, "%i",
+ expected_error, (int)(git_submodule_lookup(&sm, repo, name)));
+}
+
+unsigned int get_submodule_status(git_repository *repo, const char *name)
+{
+ git_submodule *sm = NULL;
+ unsigned int status = 0;
+
+ cl_git_pass(git_submodule_lookup(&sm, repo, name));
+ cl_assert(sm);
+ cl_git_pass(git_submodule_status(&status, sm));
+ git_submodule_free(sm);
+
+ return status;
+}
+
+static int print_submodules(git_submodule *sm, const char *name, void *p)
+{
+ unsigned int loc = 0;
+ GIT_UNUSED(p);
+ git_submodule_location(&loc, sm);
+ fprintf(stderr, "# submodule %s (at %s) flags %x\n",
+ name, git_submodule_path(sm), loc);
+ return 0;
+}
+
+void dump_submodules(git_repository *repo)
+{
+ git_submodule_foreach(repo, print_submodules, NULL);
+}
+
diff --git a/tests/submodule/submodule_helpers.h b/tests/submodule/submodule_helpers.h
new file mode 100644
index 000000000..4b2620bfa
--- /dev/null
+++ b/tests/submodule/submodule_helpers.h
@@ -0,0 +1,21 @@
+extern void rewrite_gitmodules(const char *workdir);
+
+/* these will automatically set a cleanup callback */
+extern git_repository *setup_fixture_submodules(void);
+extern git_repository *setup_fixture_submod2(void);
+
+extern unsigned int get_submodule_status(git_repository *, const char *);
+
+extern void assert__submodule_exists(
+ git_repository *, const char *, const char *, const char *, int);
+
+#define assert_submodule_exists(repo,name) \
+ assert__submodule_exists(repo, name, "git_submodule_lookup(" #name ") failed", __FILE__, __LINE__)
+
+extern void refute__submodule_exists(
+ git_repository *, const char *, int err, const char *, const char *, int);
+
+#define refute_submodule_exists(repo,name,code) \
+ refute__submodule_exists(repo, name, code, "expected git_submodule_lookup(" #name ") to fail with error " #code, __FILE__, __LINE__)
+
+extern void dump_submodules(git_repository *repo);
diff --git a/tests/t00-core.c b/tests/t00-core.c
deleted file mode 100644
index 6d63d1ce1..000000000
--- a/tests/t00-core.c
+++ /dev/null
@@ -1,584 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#include "test_lib.h"
-
-#include "vector.h"
-#include "fileops.h"
-#include "filebuf.h"
-
-BEGIN_TEST(string0, "compare prefixes")
- must_be_true(git__prefixcmp("", "") == 0);
- must_be_true(git__prefixcmp("a", "") == 0);
- must_be_true(git__prefixcmp("", "a") < 0);
- must_be_true(git__prefixcmp("a", "b") < 0);
- must_be_true(git__prefixcmp("b", "a") > 0);
- must_be_true(git__prefixcmp("ab", "a") == 0);
- must_be_true(git__prefixcmp("ab", "ac") < 0);
- must_be_true(git__prefixcmp("ab", "aa") > 0);
-END_TEST
-
-BEGIN_TEST(string1, "compare suffixes")
- must_be_true(git__suffixcmp("", "") == 0);
- must_be_true(git__suffixcmp("a", "") == 0);
- must_be_true(git__suffixcmp("", "a") < 0);
- must_be_true(git__suffixcmp("a", "b") < 0);
- must_be_true(git__suffixcmp("b", "a") > 0);
- must_be_true(git__suffixcmp("ba", "a") == 0);
- must_be_true(git__suffixcmp("zaa", "ac") < 0);
- must_be_true(git__suffixcmp("zaz", "ac") > 0);
-END_TEST
-
-
-BEGIN_TEST(vector0, "initial size of 1 would cause writing past array bounds")
- git_vector x;
- int i;
- git_vector_init(&x, 1, NULL);
- for (i = 0; i < 10; ++i) {
- git_vector_insert(&x, (void*) 0xabc);
- }
- git_vector_free(&x);
-END_TEST
-
-BEGIN_TEST(vector1, "don't read past array bounds on remove()")
- git_vector x;
- // make initial capacity exact for our insertions.
- git_vector_init(&x, 3, NULL);
- git_vector_insert(&x, (void*) 0xabc);
- git_vector_insert(&x, (void*) 0xdef);
- git_vector_insert(&x, (void*) 0x123);
-
- git_vector_remove(&x, 0); // used to read past array bounds.
- git_vector_free(&x);
-END_TEST
-
-static int test_cmp(const void *a, const void *b)
-{
- return *(const int *)a - *(const int *)b;
-}
-
-BEGIN_TEST(vector2, "remove duplicates")
- git_vector x;
- int *ptrs[2];
-
- ptrs[0] = git__malloc(sizeof(int));
- ptrs[1] = git__malloc(sizeof(int));
-
- *ptrs[0] = 2;
- *ptrs[1] = 1;
-
- must_pass(git_vector_init(&x, 5, test_cmp));
- must_pass(git_vector_insert(&x, ptrs[0]));
- must_pass(git_vector_insert(&x, ptrs[1]));
- must_pass(git_vector_insert(&x, ptrs[1]));
- must_pass(git_vector_insert(&x, ptrs[0]));
- must_pass(git_vector_insert(&x, ptrs[1]));
- must_be_true(x.length == 5);
- git_vector_uniq(&x);
- must_be_true(x.length == 2);
- git_vector_free(&x);
-
- free(ptrs[0]);
- free(ptrs[1]);
-END_TEST
-
-
-BEGIN_TEST(path0, "get the dirname of a path")
- char dir[64], *dir2;
-
-#define DIRNAME_TEST(A, B) { \
- must_be_true(git_path_dirname_r(dir, sizeof(dir), A) >= 0); \
- must_be_true(strcmp(dir, B) == 0); \
- must_be_true((dir2 = git_path_dirname(A)) != NULL); \
- must_be_true(strcmp(dir2, B) == 0); \
- free(dir2); \
-}
-
- DIRNAME_TEST(NULL, ".");
- DIRNAME_TEST("", ".");
- DIRNAME_TEST("a", ".");
- DIRNAME_TEST("/", "/");
- DIRNAME_TEST("/usr", "/");
- DIRNAME_TEST("/usr/", "/");
- DIRNAME_TEST("/usr/lib", "/usr");
- DIRNAME_TEST("/usr/lib/", "/usr");
- DIRNAME_TEST("/usr/lib//", "/usr");
- DIRNAME_TEST("usr/lib", "usr");
- DIRNAME_TEST("usr/lib/", "usr");
- DIRNAME_TEST("usr/lib//", "usr");
- DIRNAME_TEST(".git/", ".");
-
-#undef DIRNAME_TEST
-
-END_TEST
-
-BEGIN_TEST(path1, "get the base name of a path")
- char base[64], *base2;
-
-#define BASENAME_TEST(A, B) { \
- must_be_true(git_path_basename_r(base, sizeof(base), A) >= 0); \
- must_be_true(strcmp(base, B) == 0); \
- must_be_true((base2 = git_path_basename(A)) != NULL); \
- must_be_true(strcmp(base2, B) == 0); \
- free(base2); \
-}
-
- BASENAME_TEST(NULL, ".");
- BASENAME_TEST("", ".");
- BASENAME_TEST("a", "a");
- BASENAME_TEST("/", "/");
- BASENAME_TEST("/usr", "usr");
- BASENAME_TEST("/usr/", "usr");
- BASENAME_TEST("/usr/lib", "lib");
- BASENAME_TEST("/usr/lib//", "lib");
- BASENAME_TEST("usr/lib", "lib");
-
-#undef BASENAME_TEST
-
-END_TEST
-
-BEGIN_TEST(path2, "get the latest component in a path")
- const char *dir;
-
-#define TOPDIR_TEST(A, B) { \
- must_be_true((dir = git_path_topdir(A)) != NULL); \
- must_be_true(strcmp(dir, B) == 0); \
-}
-
- TOPDIR_TEST(".git/", ".git/");
- TOPDIR_TEST("/.git/", ".git/");
- TOPDIR_TEST("usr/local/.git/", ".git/");
- TOPDIR_TEST("./.git/", ".git/");
- TOPDIR_TEST("/usr/.git/", ".git/");
- TOPDIR_TEST("/", "/");
- TOPDIR_TEST("a/", "a/");
-
- must_be_true(git_path_topdir("/usr/.git") == NULL);
- must_be_true(git_path_topdir(".") == NULL);
- must_be_true(git_path_topdir("") == NULL);
- must_be_true(git_path_topdir("a") == NULL);
-
-#undef TOPDIR_TEST
-END_TEST
-
-static int ensure_joinpath(const char *path_a, const char *path_b, const char *expected_path)
-{
- char joined_path[GIT_PATH_MAX];
- git_path_join(joined_path, path_a, path_b);
- return strcmp(joined_path, expected_path) == 0 ? GIT_SUCCESS : GIT_ERROR;
-}
-
-BEGIN_TEST(path5, "properly join path components")
- must_pass(ensure_joinpath("", "", ""));
- must_pass(ensure_joinpath("", "a", "a"));
- must_pass(ensure_joinpath("", "/a", "/a"));
- must_pass(ensure_joinpath("a", "", "a/"));
- must_pass(ensure_joinpath("a", "/", "a/"));
- must_pass(ensure_joinpath("a", "b", "a/b"));
- must_pass(ensure_joinpath("/", "a", "/a"));
- must_pass(ensure_joinpath("/", "", "/"));
- must_pass(ensure_joinpath("/a", "/b", "/a/b"));
- must_pass(ensure_joinpath("/a", "/b/", "/a/b/"));
- must_pass(ensure_joinpath("/a/", "b/", "/a/b/"));
- must_pass(ensure_joinpath("/a/", "/b/", "/a/b/"));
-END_TEST
-
-static int ensure_joinpath_n(const char *path_a, const char *path_b, const char *path_c, const char *path_d, const char *expected_path)
-{
- char joined_path[GIT_PATH_MAX];
- git_path_join_n(joined_path, 4, path_a, path_b, path_c, path_d);
- return strcmp(joined_path, expected_path) == 0 ? GIT_SUCCESS : GIT_ERROR;
-}
-
-BEGIN_TEST(path6, "properly join path components for more than one path")
- must_pass(ensure_joinpath_n("", "", "", "", ""));
- must_pass(ensure_joinpath_n("", "a", "", "", "a/"));
- must_pass(ensure_joinpath_n("a", "", "", "", "a/"));
- must_pass(ensure_joinpath_n("", "", "", "a", "a"));
- must_pass(ensure_joinpath_n("a", "b", "", "/c/d/", "a/b/c/d/"));
- must_pass(ensure_joinpath_n("a", "b", "", "/c/d", "a/b/c/d"));
-END_TEST
-
-typedef struct name_data {
- int count; /* return count */
- char *name; /* filename */
-} name_data;
-
-typedef struct walk_data {
- char *sub; /* sub-directory name */
- name_data *names; /* name state data */
-} walk_data;
-
-
-static char path_buffer[GIT_PATH_MAX];
-static char *top_dir = "dir-walk";
-static walk_data *state_loc;
-
-static int error(const char *fmt, ...)
-{
- va_list ap;
-
- va_start(ap, fmt);
- vfprintf(stderr, fmt, ap);
- va_end(ap);
- fprintf(stderr, "\n");
- return -1;
-}
-
-static int setup(walk_data *d)
-{
- name_data *n;
-
- if (p_mkdir(top_dir, 0755) < 0)
- return error("can't mkdir(\"%s\")", top_dir);
-
- if (p_chdir(top_dir) < 0)
- return error("can't chdir(\"%s\")", top_dir);
-
- if (strcmp(d->sub, ".") != 0)
- if (p_mkdir(d->sub, 0755) < 0)
- return error("can't mkdir(\"%s\")", d->sub);
-
- strcpy(path_buffer, d->sub);
- state_loc = d;
-
- for (n = d->names; n->name; n++) {
- git_file fd = p_creat(n->name, 0600);
- if (fd < 0)
- return GIT_ERROR;
- p_close(fd);
- n->count = 0;
- }
-
- return 0;
-}
-
-static int knockdown(walk_data *d)
-{
- name_data *n;
-
- for (n = d->names; n->name; n++) {
- if (p_unlink(n->name) < 0)
- return error("can't unlink(\"%s\")", n->name);
- }
-
- if (strcmp(d->sub, ".") != 0)
- if (p_rmdir(d->sub) < 0)
- return error("can't rmdir(\"%s\")", d->sub);
-
- if (p_chdir("..") < 0)
- return error("can't chdir(\"..\")");
-
- if (p_rmdir(top_dir) < 0)
- return error("can't rmdir(\"%s\")", top_dir);
-
- return 0;
-}
-
-static int check_counts(walk_data *d)
-{
- int ret = 0;
- name_data *n;
-
- for (n = d->names; n->name; n++) {
- if (n->count != 1)
- ret = error("count (%d, %s)", n->count, n->name);
- }
- return ret;
-}
-
-static int one_entry(void *state, char *path)
-{
- walk_data *d = (walk_data *) state;
- name_data *n;
-
- if (state != state_loc)
- return GIT_ERROR;
-
- if (path != path_buffer)
- return GIT_ERROR;
-
- for (n = d->names; n->name; n++) {
- if (!strcmp(n->name, path)) {
- n->count++;
- return 0;
- }
- }
-
- return GIT_ERROR;
-}
-
-
-static name_data dot_names[] = {
- { 0, "./a" },
- { 0, "./asdf" },
- { 0, "./pack-foo.pack" },
- { 0, NULL }
-};
-static walk_data dot = {
- ".",
- dot_names
-};
-
-BEGIN_TEST(dirent0, "make sure that the '.' folder is not traversed")
-
- must_pass(setup(&dot));
-
- must_pass(git_futils_direach(path_buffer,
- sizeof(path_buffer),
- one_entry,
- &dot));
-
- must_pass(check_counts(&dot));
-
- must_pass(knockdown(&dot));
-END_TEST
-
-static name_data sub_names[] = {
- { 0, "sub/a" },
- { 0, "sub/asdf" },
- { 0, "sub/pack-foo.pack" },
- { 0, NULL }
-};
-static walk_data sub = {
- "sub",
- sub_names
-};
-
-BEGIN_TEST(dirent1, "traverse a subfolder")
-
- must_pass(setup(&sub));
-
- must_pass(git_futils_direach(path_buffer,
- sizeof(path_buffer),
- one_entry,
- &sub));
-
- must_pass(check_counts(&sub));
-
- must_pass(knockdown(&sub));
-END_TEST
-
-static walk_data sub_slash = {
- "sub/",
- sub_names
-};
-
-BEGIN_TEST(dirent2, "traverse a slash-terminated subfolder")
-
- must_pass(setup(&sub_slash));
-
- must_pass(git_futils_direach(path_buffer,
- sizeof(path_buffer),
- one_entry,
- &sub_slash));
-
- must_pass(check_counts(&sub_slash));
-
- must_pass(knockdown(&sub_slash));
-END_TEST
-
-static name_data empty_names[] = {
- { 0, NULL }
-};
-static walk_data empty = {
- "empty",
- empty_names
-};
-
-static int dont_call_me(void *GIT_UNUSED(state), char *GIT_UNUSED(path))
-{
- GIT_UNUSED_ARG(state)
- GIT_UNUSED_ARG(path)
- return GIT_ERROR;
-}
-
-BEGIN_TEST(dirent3, "make sure that empty folders are not traversed")
-
- must_pass(setup(&empty));
-
- must_pass(git_futils_direach(path_buffer,
- sizeof(path_buffer),
- one_entry,
- &empty));
-
- must_pass(check_counts(&empty));
-
- /* make sure callback not called */
- must_pass(git_futils_direach(path_buffer,
- sizeof(path_buffer),
- dont_call_me,
- &empty));
-
- must_pass(knockdown(&empty));
-END_TEST
-
-static name_data odd_names[] = {
- { 0, "odd/.a" },
- { 0, "odd/..c" },
- /* the following don't work on cygwin/win32 */
- /* { 0, "odd/.b." }, */
- /* { 0, "odd/..d.." }, */
- { 0, NULL }
-};
-static walk_data odd = {
- "odd",
- odd_names
-};
-
-BEGIN_TEST(dirent4, "make sure that strange looking filenames ('..c') are traversed")
-
- must_pass(setup(&odd));
-
- must_pass(git_futils_direach(path_buffer,
- sizeof(path_buffer),
- one_entry,
- &odd));
-
- must_pass(check_counts(&odd));
-
- must_pass(knockdown(&odd));
-END_TEST
-
-BEGIN_TEST(filebuf0, "make sure git_filebuf_open doesn't delete an existing lock")
- git_filebuf file;
- int fd;
- char test[] = "test", testlock[] = "test.lock";
-
- fd = p_creat(testlock, 0744);
- must_pass(fd);
- must_pass(p_close(fd));
- must_fail(git_filebuf_open(&file, test, 0));
- must_pass(git_futils_exists(testlock));
- must_pass(p_unlink(testlock));
-END_TEST
-
-BEGIN_TEST(filebuf1, "make sure GIT_FILEBUF_APPEND works as expected")
- git_filebuf file;
- int fd;
- char test[] = "test";
-
- fd = p_creat(test, 0644);
- must_pass(fd);
- must_pass(p_write(fd, "libgit2 rocks\n", 14));
- must_pass(p_close(fd));
-
- must_pass(git_filebuf_open(&file, test, GIT_FILEBUF_APPEND));
- must_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks"));
- must_pass(git_filebuf_commit(&file));
-
- must_pass(p_unlink(test));
-END_TEST
-
-BEGIN_TEST(filebuf2, "make sure git_filebuf_write writes large buffer correctly")
- git_filebuf file;
- char test[] = "test";
- unsigned char buf[4096 * 4]; /* 2 * WRITE_BUFFER_SIZE */
-
- memset(buf, 0xfe, sizeof(buf));
- must_pass(git_filebuf_open(&file, test, 0));
- must_pass(git_filebuf_write(&file, buf, sizeof(buf)));
- must_pass(git_filebuf_commit(&file));
-
- must_pass(p_unlink(test));
-END_TEST
-
-static char *empty_tmp_dir = "test_gitfo_rmdir_recurs_test";
-
-static int setup_empty_tmp_dir()
-{
- char path[GIT_PATH_MAX];
-
- if (p_mkdir(empty_tmp_dir, 0755))
- return -1;
-
- git_path_join(path, empty_tmp_dir, "/one");
- if (p_mkdir(path, 0755))
- return -1;
-
- git_path_join(path, empty_tmp_dir, "/one/two_one");
- if (p_mkdir(path, 0755))
- return -1;
-
- git_path_join(path, empty_tmp_dir, "/one/two_two");
- if (p_mkdir(path, 0755))
- return -1;
-
- git_path_join(path, empty_tmp_dir, "/one/two_two/three");
- if (p_mkdir(path, 0755))
- return -1;
-
- git_path_join(path, empty_tmp_dir, "/two");
- if (p_mkdir(path, 0755))
- return -1;
-
- return 0;
-}
-
-BEGIN_TEST(rmdir0, "make sure empty dir can be deleted recusively")
- must_pass(setup_empty_tmp_dir());
- must_pass(git_futils_rmdir_r(empty_tmp_dir, 0));
-END_TEST
-
-BEGIN_TEST(rmdir1, "make sure non-empty dir cannot be deleted recusively")
- char file[GIT_PATH_MAX];
- int fd;
-
- must_pass(setup_empty_tmp_dir());
- git_path_join(file, empty_tmp_dir, "/two/file.txt");
- fd = p_creat(file, 0755);
- must_pass(fd);
- must_pass(p_close(fd));
- must_fail(git_futils_rmdir_r(empty_tmp_dir, 0));
- must_pass(p_unlink(file));
- must_pass(git_futils_rmdir_r(empty_tmp_dir, 0));
-END_TEST
-
-BEGIN_SUITE(core)
- ADD_TEST(string0);
- ADD_TEST(string1);
-
- ADD_TEST(vector0);
- ADD_TEST(vector1);
- ADD_TEST(vector2);
-
- ADD_TEST(path0);
- ADD_TEST(path1);
- ADD_TEST(path2);
- ADD_TEST(path5);
- ADD_TEST(path6);
-
- ADD_TEST(dirent0);
- ADD_TEST(dirent1);
- ADD_TEST(dirent2);
- ADD_TEST(dirent3);
- ADD_TEST(dirent4);
-
- ADD_TEST(filebuf0);
- ADD_TEST(filebuf1);
- ADD_TEST(filebuf2);
-
- ADD_TEST(rmdir0);
- ADD_TEST(rmdir1);
-END_SUITE
diff --git a/tests/t01-rawobj.c b/tests/t01-rawobj.c
deleted file mode 100644
index 255208532..000000000
--- a/tests/t01-rawobj.c
+++ /dev/null
@@ -1,638 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#include "test_lib.h"
-
-#include "odb.h"
-#include "hash.h"
-
-#include "t01-data.h"
-
-static int hash_object(git_oid *oid, git_rawobj *obj)
-{
- return git_odb_hash(oid, obj->data, obj->len, obj->type);
-}
-
-BEGIN_TEST(oid0, "validate size of oid objects")
- git_oid out;
- must_be_true(20 == GIT_OID_RAWSZ);
- must_be_true(40 == GIT_OID_HEXSZ);
- must_be_true(sizeof(out) == GIT_OID_RAWSZ);
- must_be_true(sizeof(out.id) == GIT_OID_RAWSZ);
-END_TEST
-
-BEGIN_TEST(oid1, "fail when parsing an empty string as oid")
- git_oid out;
- must_fail(git_oid_fromstr(&out, ""));
-END_TEST
-
-BEGIN_TEST(oid2, "fail when parsing an invalid string as oid")
- git_oid out;
- must_fail(git_oid_fromstr(&out, "moo"));
-END_TEST
-
-static int from_hex(unsigned int i)
-{
- if (i >= '0' && i <= '9')
- return i - '0';
- if (i >= 'a' && i <= 'f')
- return 10 + (i - 'a');
- if (i >= 'A' && i <= 'F')
- return 10 + (i - 'A');
- return -1;
-}
-
-BEGIN_TEST(oid3, "find all invalid characters when parsing an oid")
- git_oid out;
- unsigned char exp[] = {
- 0x16, 0xa6, 0x77, 0x70, 0xb7,
- 0xd8, 0xd7, 0x23, 0x17, 0xc4,
- 0xb7, 0x75, 0x21, 0x3c, 0x23,
- 0xa8, 0xbd, 0x74, 0xf5, 0xe0,
- };
- char in[41] = "16a67770b7d8d72317c4b775213c23a8bd74f5e0";
- unsigned int i;
-
- for (i = 0; i < 256; i++) {
- in[38] = (char)i;
-
- if (from_hex(i) >= 0) {
- exp[19] = (unsigned char)(from_hex(i) << 4);
- must_pass(git_oid_fromstr(&out, in));
- must_be_true(memcmp(out.id, exp, sizeof(out.id)) == 0);
- } else {
- must_fail(git_oid_fromstr(&out, in));
- }
- }
-END_TEST
-
-BEGIN_TEST(oid4, "fail when parsing an invalid oid string")
- git_oid out;
- must_fail(git_oid_fromstr(&out, "16a67770b7d8d72317c4b775213c23a8bd74f5ez"));
-END_TEST
-
-BEGIN_TEST(oid5, "succeed when parsing a valid oid string")
- git_oid out;
- unsigned char exp[] = {
- 0x16, 0xa6, 0x77, 0x70, 0xb7,
- 0xd8, 0xd7, 0x23, 0x17, 0xc4,
- 0xb7, 0x75, 0x21, 0x3c, 0x23,
- 0xa8, 0xbd, 0x74, 0xf5, 0xe0,
- };
-
- must_pass(git_oid_fromstr(&out, "16a67770b7d8d72317c4b775213c23a8bd74f5e0"));
- must_pass(memcmp(out.id, exp, sizeof(out.id)));
-
- must_pass(git_oid_fromstr(&out, "16A67770B7D8D72317C4b775213C23A8BD74F5E0"));
- must_pass(memcmp(out.id, exp, sizeof(out.id)));
-END_TEST
-
-BEGIN_TEST(oid6, "build a valid oid from raw bytes")
- git_oid out;
- unsigned char exp[] = {
- 0x16, 0xa6, 0x77, 0x70, 0xb7,
- 0xd8, 0xd7, 0x23, 0x17, 0xc4,
- 0xb7, 0x75, 0x21, 0x3c, 0x23,
- 0xa8, 0xbd, 0x74, 0xf5, 0xe0,
- };
-
- git_oid_fromraw(&out, exp);
- must_pass(memcmp(out.id, exp, sizeof(out.id)));
-END_TEST
-
-BEGIN_TEST(oid7, "properly copy an oid to another")
- git_oid a, b;
- unsigned char exp[] = {
- 0x16, 0xa6, 0x77, 0x70, 0xb7,
- 0xd8, 0xd7, 0x23, 0x17, 0xc4,
- 0xb7, 0x75, 0x21, 0x3c, 0x23,
- 0xa8, 0xbd, 0x74, 0xf5, 0xe0,
- };
-
- memset(&b, 0, sizeof(b));
- git_oid_fromraw(&a, exp);
- git_oid_cpy(&b, &a);
- must_pass(memcmp(a.id, exp, sizeof(a.id)));
-END_TEST
-
-BEGIN_TEST(oid8, "compare two oids (lesser than)")
- git_oid a, b;
- unsigned char a_in[] = {
- 0x16, 0xa6, 0x77, 0x70, 0xb7,
- 0xd8, 0xd7, 0x23, 0x17, 0xc4,
- 0xb7, 0x75, 0x21, 0x3c, 0x23,
- 0xa8, 0xbd, 0x74, 0xf5, 0xe0,
- };
- unsigned char b_in[] = {
- 0x16, 0xa6, 0x77, 0x70, 0xb7,
- 0xd8, 0xd7, 0x23, 0x17, 0xc4,
- 0xb7, 0x75, 0x21, 0x3c, 0x23,
- 0xa8, 0xbd, 0x74, 0xf5, 0xf0,
- };
-
- git_oid_fromraw(&a, a_in);
- git_oid_fromraw(&b, b_in);
- must_be_true(git_oid_cmp(&a, &b) < 0);
-END_TEST
-
-BEGIN_TEST(oid9, "compare two oids (equal)")
- git_oid a, b;
- unsigned char a_in[] = {
- 0x16, 0xa6, 0x77, 0x70, 0xb7,
- 0xd8, 0xd7, 0x23, 0x17, 0xc4,
- 0xb7, 0x75, 0x21, 0x3c, 0x23,
- 0xa8, 0xbd, 0x74, 0xf5, 0xe0,
- };
-
- git_oid_fromraw(&a, a_in);
- git_oid_fromraw(&b, a_in);
- must_be_true(git_oid_cmp(&a, &b) == 0);
-END_TEST
-
-BEGIN_TEST(oid10, "compare two oids (greater than)")
- git_oid a, b;
- unsigned char a_in[] = {
- 0x16, 0xa6, 0x77, 0x70, 0xb7,
- 0xd8, 0xd7, 0x23, 0x17, 0xc4,
- 0xb7, 0x75, 0x21, 0x3c, 0x23,
- 0xa8, 0xbd, 0x74, 0xf5, 0xe0,
- };
- unsigned char b_in[] = {
- 0x16, 0xa6, 0x77, 0x70, 0xb7,
- 0xd8, 0xd7, 0x23, 0x17, 0xc4,
- 0xb7, 0x75, 0x21, 0x3c, 0x23,
- 0xa8, 0xbd, 0x74, 0xf5, 0xd0,
- };
-
- git_oid_fromraw(&a, a_in);
- git_oid_fromraw(&b, b_in);
- must_be_true(git_oid_cmp(&a, &b) > 0);
-END_TEST
-
-BEGIN_TEST(oid11, "compare formated oids")
- const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0";
- git_oid in;
- char out[GIT_OID_HEXSZ + 1];
-
- must_pass(git_oid_fromstr(&in, exp));
-
- /* Format doesn't touch the last byte */
- out[GIT_OID_HEXSZ] = 'Z';
- git_oid_fmt(out, &in);
- must_be_true(out[GIT_OID_HEXSZ] == 'Z');
-
- /* Format produced the right result */
- out[GIT_OID_HEXSZ] = '\0';
- must_be_true(strcmp(exp, out) == 0);
-END_TEST
-
-BEGIN_TEST(oid12, "compare oids (allocate + format)")
- const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0";
- git_oid in;
- char *out;
-
- must_pass(git_oid_fromstr(&in, exp));
-
- out = git_oid_allocfmt(&in);
- must_be_true(out);
- must_be_true(strcmp(exp, out) == 0);
- free(out);
-END_TEST
-
-BEGIN_TEST(oid13, "compare oids (path format)")
- const char *exp1 = "16a0123456789abcdef4b775213c23a8bd74f5e0";
- const char *exp2 = "16/a0123456789abcdef4b775213c23a8bd74f5e0";
- git_oid in;
- char out[GIT_OID_HEXSZ + 2];
-
- must_pass(git_oid_fromstr(&in, exp1));
-
- /* Format doesn't touch the last byte */
- out[GIT_OID_HEXSZ + 1] = 'Z';
- git_oid_pathfmt(out, &in);
- must_be_true(out[GIT_OID_HEXSZ + 1] == 'Z');
-
- /* Format produced the right result */
- out[GIT_OID_HEXSZ + 1] = '\0';
- must_be_true(strcmp(exp2, out) == 0);
-END_TEST
-
-BEGIN_TEST(oid14, "convert raw oid to string")
- const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0";
- git_oid in;
- char out[GIT_OID_HEXSZ + 1];
- char *str;
- int i;
-
- must_pass(git_oid_fromstr(&in, exp));
-
- /* NULL buffer pointer, returns static empty string */
- str = git_oid_to_string(NULL, sizeof(out), &in);
- must_be_true(str && *str == '\0' && str != out);
-
- /* zero buffer size, returns static empty string */
- str = git_oid_to_string(out, 0, &in);
- must_be_true(str && *str == '\0' && str != out);
-
- /* NULL oid pointer, returns static empty string */
- str = git_oid_to_string(out, sizeof(out), NULL);
- must_be_true(str && *str == '\0' && str != out);
-
- /* n == 1, returns out as an empty string */
- str = git_oid_to_string(out, 1, &in);
- must_be_true(str && *str == '\0' && str == out);
-
- for (i = 1; i < GIT_OID_HEXSZ; i++) {
- out[i+1] = 'Z';
- str = git_oid_to_string(out, i+1, &in);
- /* returns out containing c-string */
- must_be_true(str && str == out);
- /* must be '\0' terminated */
- must_be_true(*(str+i) == '\0');
- /* must not touch bytes past end of string */
- must_be_true(*(str+(i+1)) == 'Z');
- /* i == n-1 charaters of string */
- must_pass(strncmp(exp, out, i));
- }
-
- /* returns out as hex formatted c-string */
- str = git_oid_to_string(out, sizeof(out), &in);
- must_be_true(str && str == out && *(str+GIT_OID_HEXSZ) == '\0');
- must_be_true(strcmp(exp, out) == 0);
-END_TEST
-
-BEGIN_TEST(oid15, "convert raw oid to string (big)")
- const char *exp = "16a0123456789abcdef4b775213c23a8bd74f5e0";
- git_oid in;
- char big[GIT_OID_HEXSZ + 1 + 3]; /* note + 4 => big buffer */
- char *str;
-
- must_pass(git_oid_fromstr(&in, exp));
-
- /* place some tail material */
- big[GIT_OID_HEXSZ+0] = 'W'; /* should be '\0' afterwards */
- big[GIT_OID_HEXSZ+1] = 'X'; /* should remain untouched */
- big[GIT_OID_HEXSZ+2] = 'Y'; /* ditto */
- big[GIT_OID_HEXSZ+3] = 'Z'; /* ditto */
-
- /* returns big as hex formatted c-string */
- str = git_oid_to_string(big, sizeof(big), &in);
- must_be_true(str && str == big && *(str+GIT_OID_HEXSZ) == '\0');
- must_be_true(strcmp(exp, big) == 0);
-
- /* check tail material is untouched */
- must_be_true(str && str == big && *(str+GIT_OID_HEXSZ+1) == 'X');
- must_be_true(str && str == big && *(str+GIT_OID_HEXSZ+2) == 'Y');
- must_be_true(str && str == big && *(str+GIT_OID_HEXSZ+3) == 'Z');
-END_TEST
-
-
-BEGIN_TEST(oid16, "make sure the OID shortener doesn't choke on duplicate sha1s")
-
- git_oid_shorten *os;
- int min_len;
-
- os = git_oid_shorten_new(0);
- must_be_true(os != NULL);
-
- git_oid_shorten_add(os, "22596363b3de40b06f981fb85d82312e8c0ed511");
- git_oid_shorten_add(os, "ce08fe4884650f067bd5703b6a59a8b3b3c99a09");
- git_oid_shorten_add(os, "16a0123456789abcdef4b775213c23a8bd74f5e0");
- min_len = git_oid_shorten_add(os, "ce08fe4884650f067bd5703b6a59a8b3b3c99a09");
-
- must_be_true(min_len == GIT_OID_HEXSZ + 1);
-
- git_oid_shorten_free(os);
-END_TEST
-
-BEGIN_TEST(oid17, "stress test for the git_oid_shorten object")
-
-#define MAX_OIDS 1000
-
- git_oid_shorten *os;
- char *oids[MAX_OIDS];
- char number_buffer[16];
- git_oid oid;
- size_t i, j;
-
- int min_len = 0, found_collision;
-
- os = git_oid_shorten_new(0);
- must_be_true(os != NULL);
-
- /*
- * Insert in the shortener 1000 unique SHA1 ids
- */
- for (i = 0; i < MAX_OIDS; ++i) {
- char *oid_text;
-
- sprintf(number_buffer, "%u", (unsigned int)i);
- git_hash_buf(&oid, number_buffer, strlen(number_buffer));
-
- oid_text = git__malloc(GIT_OID_HEXSZ + 1);
- git_oid_fmt(oid_text, &oid);
- oid_text[GIT_OID_HEXSZ] = 0;
-
- min_len = git_oid_shorten_add(os, oid_text);
- must_be_true(min_len >= 0);
-
- oids[i] = oid_text;
- }
-
- /*
- * Compare the first `min_char - 1` characters of each
- * SHA1 OID. If the minimizer worked, we should find at
- * least one collision
- */
- found_collision = 0;
- for (i = 0; i < MAX_OIDS; ++i) {
- for (j = 0; j < MAX_OIDS; ++j) {
- if (i != j && memcmp(oids[i], oids[j], min_len - 1) == 0)
- found_collision = 1;
- }
- }
- must_be_true(found_collision == 1);
-
- /*
- * Compare the first `min_char` characters of each
- * SHA1 OID. If the minimizer worked, every single preffix
- * should be unique.
- */
- found_collision = 0;
- for (i = 0; i < MAX_OIDS; ++i) {
- for (j = 0; j < MAX_OIDS; ++j) {
- if (i != j && memcmp(oids[i], oids[j], min_len) == 0)
- found_collision = 1;
- }
- }
- must_be_true(found_collision == 0);
-
- /* cleanup */
- for (i = 0; i < MAX_OIDS; ++i)
- free(oids[i]);
-
- git_oid_shorten_free(os);
-
-#undef MAX_OIDS
-END_TEST
-
-static char *hello_id = "22596363b3de40b06f981fb85d82312e8c0ed511";
-static char *hello_text = "hello world\n";
-
-static char *bye_id = "ce08fe4884650f067bd5703b6a59a8b3b3c99a09";
-static char *bye_text = "bye world\n";
-
-BEGIN_TEST(hash0, "normal hash by blocks")
- git_hash_ctx *ctx;
- git_oid id1, id2;
-
- must_be_true((ctx = git_hash_new_ctx()) != NULL);
-
- /* should already be init'd */
- git_hash_update(ctx, hello_text, strlen(hello_text));
- git_hash_final(&id2, ctx);
- must_pass(git_oid_fromstr(&id1, hello_id));
- must_be_true(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);
- must_pass(git_oid_fromstr(&id1, bye_id));
- must_be_true(git_oid_cmp(&id1, &id2) == 0);
-
- git_hash_free_ctx(ctx);
-END_TEST
-
-BEGIN_TEST(hash1, "hash whole buffer in a single call")
- git_oid id1, id2;
-
- must_pass(git_oid_fromstr(&id1, hello_id));
-
- git_hash_buf(&id2, hello_text, strlen(hello_text));
-
- must_be_true(git_oid_cmp(&id1, &id2) == 0);
-END_TEST
-
-BEGIN_TEST(hash2, "hash a vector")
- git_oid id1, id2;
- git_buf_vec vec[2];
-
- must_pass(git_oid_fromstr(&id1, hello_id));
-
- vec[0].data = hello_text;
- vec[0].len = 4;
- vec[1].data = hello_text+4;
- vec[1].len = strlen(hello_text)-4;
-
- git_hash_vec(&id2, vec, 2);
-
- must_be_true(git_oid_cmp(&id1, &id2) == 0);
-END_TEST
-
-BEGIN_TEST(objtype0, "convert type to string")
- must_be_true(!strcmp(git_object_type2string(GIT_OBJ_BAD), ""));
- must_be_true(!strcmp(git_object_type2string(GIT_OBJ__EXT1), ""));
- must_be_true(!strcmp(git_object_type2string(GIT_OBJ_COMMIT), "commit"));
- must_be_true(!strcmp(git_object_type2string(GIT_OBJ_TREE), "tree"));
- must_be_true(!strcmp(git_object_type2string(GIT_OBJ_BLOB), "blob"));
- must_be_true(!strcmp(git_object_type2string(GIT_OBJ_TAG), "tag"));
- must_be_true(!strcmp(git_object_type2string(GIT_OBJ__EXT2), ""));
- must_be_true(!strcmp(git_object_type2string(GIT_OBJ_OFS_DELTA), "OFS_DELTA"));
- must_be_true(!strcmp(git_object_type2string(GIT_OBJ_REF_DELTA), "REF_DELTA"));
-
- must_be_true(!strcmp(git_object_type2string(-2), ""));
- must_be_true(!strcmp(git_object_type2string(8), ""));
- must_be_true(!strcmp(git_object_type2string(1234), ""));
-END_TEST
-
-BEGIN_TEST(objtype1, "convert string to type")
- must_be_true(git_object_string2type(NULL) == GIT_OBJ_BAD);
- must_be_true(git_object_string2type("") == GIT_OBJ_BAD);
- must_be_true(git_object_string2type("commit") == GIT_OBJ_COMMIT);
- must_be_true(git_object_string2type("tree") == GIT_OBJ_TREE);
- must_be_true(git_object_string2type("blob") == GIT_OBJ_BLOB);
- must_be_true(git_object_string2type("tag") == GIT_OBJ_TAG);
- must_be_true(git_object_string2type("OFS_DELTA") == GIT_OBJ_OFS_DELTA);
- must_be_true(git_object_string2type("REF_DELTA") == GIT_OBJ_REF_DELTA);
-
- must_be_true(git_object_string2type("CoMmIt") == GIT_OBJ_BAD);
- must_be_true(git_object_string2type("hohoho") == GIT_OBJ_BAD);
-END_TEST
-
-BEGIN_TEST(objtype2, "check if an object type is loose")
- must_be_true(git_object_typeisloose(GIT_OBJ_BAD) == 0);
- must_be_true(git_object_typeisloose(GIT_OBJ__EXT1) == 0);
- must_be_true(git_object_typeisloose(GIT_OBJ_COMMIT) == 1);
- must_be_true(git_object_typeisloose(GIT_OBJ_TREE) == 1);
- must_be_true(git_object_typeisloose(GIT_OBJ_BLOB) == 1);
- must_be_true(git_object_typeisloose(GIT_OBJ_TAG) == 1);
- must_be_true(git_object_typeisloose(GIT_OBJ__EXT2) == 0);
- must_be_true(git_object_typeisloose(GIT_OBJ_OFS_DELTA) == 0);
- must_be_true(git_object_typeisloose(GIT_OBJ_REF_DELTA) == 0);
-
- must_be_true(git_object_typeisloose(-2) == 0);
- must_be_true(git_object_typeisloose(8) == 0);
- must_be_true(git_object_typeisloose(1234) == 0);
-END_TEST
-
-BEGIN_TEST(objhash0, "hash junk data")
- git_oid id, id_zero;
-
- must_pass(git_oid_fromstr(&id_zero, zero_id));
-
- /* invalid types: */
- junk_obj.data = some_data;
- must_fail(hash_object(&id, &junk_obj));
-
- junk_obj.type = GIT_OBJ__EXT1;
- must_fail(hash_object(&id, &junk_obj));
-
- junk_obj.type = GIT_OBJ__EXT2;
- must_fail(hash_object(&id, &junk_obj));
-
- junk_obj.type = GIT_OBJ_OFS_DELTA;
- must_fail(hash_object(&id, &junk_obj));
-
- junk_obj.type = GIT_OBJ_REF_DELTA;
- must_fail(hash_object(&id, &junk_obj));
-
- /* data can be NULL only if len is zero: */
- junk_obj.type = GIT_OBJ_BLOB;
- junk_obj.data = NULL;
- must_pass(hash_object(&id, &junk_obj));
- must_be_true(git_oid_cmp(&id, &id_zero) == 0);
-
- junk_obj.len = 1;
- must_fail(hash_object(&id, &junk_obj));
-END_TEST
-
-BEGIN_TEST(objhash1, "hash a commit object")
- git_oid id1, id2;
-
- must_pass(git_oid_fromstr(&id1, commit_id));
-
- must_pass(hash_object(&id2, &commit_obj));
-
- must_be_true(git_oid_cmp(&id1, &id2) == 0);
-END_TEST
-
-BEGIN_TEST(objhash2, "hash a tree object")
- git_oid id1, id2;
-
- must_pass(git_oid_fromstr(&id1, tree_id));
-
- must_pass(hash_object(&id2, &tree_obj));
-
- must_be_true(git_oid_cmp(&id1, &id2) == 0);
-END_TEST
-
-BEGIN_TEST(objhash3, "hash a tag object")
- git_oid id1, id2;
-
- must_pass(git_oid_fromstr(&id1, tag_id));
-
- must_pass(hash_object(&id2, &tag_obj));
-
- must_be_true(git_oid_cmp(&id1, &id2) == 0);
-END_TEST
-
-BEGIN_TEST(objhash4, "hash a zero-length object")
- git_oid id1, id2;
-
- must_pass(git_oid_fromstr(&id1, zero_id));
-
- must_pass(hash_object(&id2, &zero_obj));
-
- must_be_true(git_oid_cmp(&id1, &id2) == 0);
-END_TEST
-
-BEGIN_TEST(objhash5, "hash an one-byte long object")
- git_oid id1, id2;
-
- must_pass(git_oid_fromstr(&id1, one_id));
-
- must_pass(hash_object(&id2, &one_obj));
-
- must_be_true(git_oid_cmp(&id1, &id2) == 0);
-END_TEST
-
-BEGIN_TEST(objhash6, "hash a two-byte long object")
- git_oid id1, id2;
-
- must_pass(git_oid_fromstr(&id1, two_id));
-
- must_pass(hash_object(&id2, &two_obj));
-
- must_be_true(git_oid_cmp(&id1, &id2) == 0);
-END_TEST
-
-BEGIN_TEST(objhash7, "hash an object several bytes long")
- git_oid id1, id2;
-
- must_pass(git_oid_fromstr(&id1, some_id));
-
- must_pass(hash_object(&id2, &some_obj));
-
- must_be_true(git_oid_cmp(&id1, &id2) == 0);
-END_TEST
-
-BEGIN_SUITE(rawobjects)
- ADD_TEST(oid0);
- ADD_TEST(oid1);
- ADD_TEST(oid2);
- ADD_TEST(oid3);
- ADD_TEST(oid4);
- ADD_TEST(oid5);
- ADD_TEST(oid6);
- ADD_TEST(oid7);
- ADD_TEST(oid8);
- ADD_TEST(oid9);
- ADD_TEST(oid10);
- ADD_TEST(oid11);
- ADD_TEST(oid12);
- ADD_TEST(oid13);
- ADD_TEST(oid14);
- ADD_TEST(oid15);
- ADD_TEST(oid16);
- ADD_TEST(oid17);
-
- ADD_TEST(hash0);
- ADD_TEST(hash1);
- ADD_TEST(hash2);
-
- ADD_TEST(objtype0);
- ADD_TEST(objtype1);
- ADD_TEST(objtype2);
-
- ADD_TEST(objhash0);
- ADD_TEST(objhash1);
- ADD_TEST(objhash2);
- ADD_TEST(objhash3);
- ADD_TEST(objhash4);
- ADD_TEST(objhash5);
- ADD_TEST(objhash6);
- ADD_TEST(objhash7);
-END_SUITE
-
diff --git a/tests/t02-objread.c b/tests/t02-objread.c
deleted file mode 100644
index 4bcff2742..000000000
--- a/tests/t02-objread.c
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#include "test_lib.h"
-#include "test_helpers.h"
-#include "odb.h"
-
-#include "t02-data.h"
-#include "t02-oids.h"
-
-
-BEGIN_TEST(existsloose0, "check if a loose object exists on the odb")
- git_odb *db;
- git_oid id, id2;
-
- must_pass(write_object_files(odb_dir, &one));
- must_pass(git_odb_open(&db, odb_dir));
- must_pass(git_oid_fromstr(&id, one.id));
-
- must_be_true(git_odb_exists(db, &id));
-
- /* Test for a non-existant object */
- must_pass(git_oid_fromstr(&id2, "8b137891791fe96927ad78e64b0aad7bded08baa"));
- must_be_true(0 == git_odb_exists(db, &id2));
-
- git_odb_close(db);
- must_pass(remove_object_files(odb_dir, &one));
-END_TEST
-
-BEGIN_TEST(readloose0, "read a loose commit")
- git_odb *db;
- git_oid id;
- git_odb_object *obj;
-
- must_pass(write_object_files(odb_dir, &commit));
- must_pass(git_odb_open(&db, odb_dir));
- must_pass(git_oid_fromstr(&id, commit.id));
-
- must_pass(git_odb_read(&obj, db, &id));
- must_pass(cmp_objects((git_rawobj *)&obj->raw, &commit));
-
- git_odb_object_close(obj);
- git_odb_close(db);
- must_pass(remove_object_files(odb_dir, &commit));
-END_TEST
-
-BEGIN_TEST(readloose1, "read a loose tree")
- git_odb *db;
- git_oid id;
- git_odb_object *obj;
-
- must_pass(write_object_files(odb_dir, &tree));
- must_pass(git_odb_open(&db, odb_dir));
- must_pass(git_oid_fromstr(&id, tree.id));
-
- must_pass(git_odb_read(&obj, db, &id));
- must_pass(cmp_objects((git_rawobj *)&obj->raw, &tree));
-
- git_odb_object_close(obj);
- git_odb_close(db);
- must_pass(remove_object_files(odb_dir, &tree));
-END_TEST
-
-BEGIN_TEST(readloose2, "read a loose tag")
- git_odb *db;
- git_oid id;
- git_odb_object *obj;
-
- must_pass(write_object_files(odb_dir, &tag));
- must_pass(git_odb_open(&db, odb_dir));
- must_pass(git_oid_fromstr(&id, tag.id));
-
- must_pass(git_odb_read(&obj, db, &id));
- must_pass(cmp_objects((git_rawobj *)&obj->raw, &tag));
-
- git_odb_object_close(obj);
- git_odb_close(db);
- must_pass(remove_object_files(odb_dir, &tag));
-END_TEST
-
-BEGIN_TEST(readloose3, "read a loose zero-bytes object")
- git_odb *db;
- git_oid id;
- git_odb_object *obj;
-
- must_pass(write_object_files(odb_dir, &zero));
- must_pass(git_odb_open(&db, odb_dir));
- must_pass(git_oid_fromstr(&id, zero.id));
-
- must_pass(git_odb_read(&obj, db, &id));
- must_pass(cmp_objects((git_rawobj *)&obj->raw, &zero));
-
- git_odb_object_close(obj);
- git_odb_close(db);
- must_pass(remove_object_files(odb_dir, &zero));
-END_TEST
-
-BEGIN_TEST(readloose4, "read a one-byte long loose object")
- git_odb *db;
- git_oid id;
- git_odb_object *obj;
-
- must_pass(write_object_files(odb_dir, &one));
- must_pass(git_odb_open(&db, odb_dir));
- must_pass(git_oid_fromstr(&id, one.id));
-
- must_pass(git_odb_read(&obj, db, &id));
- must_pass(cmp_objects(&obj->raw, &one));
-
- git_odb_object_close(obj);
- git_odb_close(db);
- must_pass(remove_object_files(odb_dir, &one));
-END_TEST
-
-BEGIN_TEST(readloose5, "read a two-bytes long loose object")
- git_odb *db;
- git_oid id;
- git_odb_object *obj;
-
- must_pass(write_object_files(odb_dir, &two));
- must_pass(git_odb_open(&db, odb_dir));
- must_pass(git_oid_fromstr(&id, two.id));
-
- must_pass(git_odb_read(&obj, db, &id));
- must_pass(cmp_objects(&obj->raw, &two));
-
- git_odb_object_close(obj);
- git_odb_close(db);
- must_pass(remove_object_files(odb_dir, &two));
-END_TEST
-
-BEGIN_TEST(readloose6, "read a loose object which is several bytes long")
- git_odb *db;
- git_oid id;
- git_odb_object *obj;
-
- must_pass(write_object_files(odb_dir, &some));
- must_pass(git_odb_open(&db, odb_dir));
- must_pass(git_oid_fromstr(&id, some.id));
-
- must_pass(git_odb_read(&obj, db, &id));
- must_pass(cmp_objects(&obj->raw, &some));
-
- git_odb_object_close(obj);
- git_odb_close(db);
- must_pass(remove_object_files(odb_dir, &some));
-END_TEST
-
-BEGIN_TEST(readpack0, "read several packed objects")
- unsigned int i;
- git_odb *db;
-
- must_pass(git_odb_open(&db, ODB_FOLDER));
-
- for (i = 0; i < ARRAY_SIZE(packed_objects); ++i) {
- git_oid id;
- git_odb_object *obj;
-
- must_pass(git_oid_fromstr(&id, packed_objects[i]));
- must_be_true(git_odb_exists(db, &id) == 1);
- must_pass(git_odb_read(&obj, db, &id));
-
- git_odb_object_close(obj);
- }
-
- git_odb_close(db);
-END_TEST
-
-BEGIN_TEST(readheader0, "read only the header of several packed objects")
- unsigned int i;
- git_odb *db;
-
- must_pass(git_odb_open(&db, ODB_FOLDER));
-
- for (i = 0; i < ARRAY_SIZE(packed_objects); ++i) {
- git_oid id;
- git_odb_object *obj;
- size_t len;
- git_otype type;
-
- must_pass(git_oid_fromstr(&id, packed_objects[i]));
-
- must_pass(git_odb_read(&obj, db, &id));
- must_pass(git_odb_read_header(&len, &type, db, &id));
-
- must_be_true(obj->raw.len == len);
- must_be_true(obj->raw.type == type);
-
- git_odb_object_close(obj);
- }
-
- git_odb_close(db);
-END_TEST
-
-BEGIN_TEST(readheader1, "read only the header of several loose objects")
- unsigned int i;
- git_odb *db;
-
- must_pass(git_odb_open(&db, ODB_FOLDER));
-
- for (i = 0; i < ARRAY_SIZE(loose_objects); ++i) {
- git_oid id;
- git_odb_object *obj;
- size_t len;
- git_otype type;
-
- must_pass(git_oid_fromstr(&id, loose_objects[i]));
-
- must_be_true(git_odb_exists(db, &id) == 1);
-
- must_pass(git_odb_read(&obj, db, &id));
- must_pass(git_odb_read_header(&len, &type, db, &id));
-
- must_be_true(obj->raw.len == len);
- must_be_true(obj->raw.type == type);
-
- git_odb_object_close(obj);
- }
-
- git_odb_close(db);
-END_TEST
-
-BEGIN_SUITE(objread)
- ADD_TEST(existsloose0);
-
- ADD_TEST(readloose0);
- ADD_TEST(readloose1);
- ADD_TEST(readloose2);
- ADD_TEST(readloose3);
- ADD_TEST(readloose4);
- ADD_TEST(readloose5);
- ADD_TEST(readloose6);
-
-/*
- ADD_TEST(readloose_enc0);
- ADD_TEST(readloose_enc1);
- ADD_TEST(readloose_enc2);
- ADD_TEST(readloose_enc3);
- ADD_TEST(readloose_enc4);
- ADD_TEST(readloose_enc5);
- ADD_TEST(readloose_enc6);
-*/
-
- ADD_TEST(readpack0);
-
- ADD_TEST(readheader0);
- ADD_TEST(readheader1);
-END_SUITE
diff --git a/tests/t03-data.h b/tests/t03-data.h
deleted file mode 100644
index a4b73fec3..000000000
--- a/tests/t03-data.h
+++ /dev/null
@@ -1,344 +0,0 @@
-
-typedef struct object_data {
- char *id; /* object id (sha1) */
- char *dir; /* object store (fan-out) directory name */
- char *file; /* object store filename */
-} object_data;
-
-static object_data commit = {
- "3d7f8a6af076c8c3f20071a8935cdbe8228594d1",
- "test-objects/3d",
- "test-objects/3d/7f8a6af076c8c3f20071a8935cdbe8228594d1",
-};
-
-static unsigned char commit_data[] = {
- 0x74, 0x72, 0x65, 0x65, 0x20, 0x64, 0x66, 0x66,
- 0x32, 0x64, 0x61, 0x39, 0x30, 0x62, 0x32, 0x35,
- 0x34, 0x65, 0x31, 0x62, 0x65, 0x62, 0x38, 0x38,
- 0x39, 0x64, 0x31, 0x66, 0x31, 0x66, 0x31, 0x32,
- 0x38, 0x38, 0x62, 0x65, 0x31, 0x38, 0x30, 0x33,
- 0x37, 0x38, 0x32, 0x64, 0x66, 0x0a, 0x61, 0x75,
- 0x74, 0x68, 0x6f, 0x72, 0x20, 0x41, 0x20, 0x55,
- 0x20, 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61,
- 0x75, 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78,
- 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f,
- 0x6d, 0x3e, 0x20, 0x31, 0x32, 0x32, 0x37, 0x38,
- 0x31, 0x34, 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30,
- 0x30, 0x30, 0x30, 0x0a, 0x63, 0x6f, 0x6d, 0x6d,
- 0x69, 0x74, 0x74, 0x65, 0x72, 0x20, 0x43, 0x20,
- 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72,
- 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74,
- 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d,
- 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e,
- 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, 0x31, 0x34,
- 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, 0x30, 0x30,
- 0x30, 0x0a, 0x0a, 0x41, 0x20, 0x6f, 0x6e, 0x65,
- 0x2d, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x63, 0x6f,
- 0x6d, 0x6d, 0x69, 0x74, 0x20, 0x73, 0x75, 0x6d,
- 0x6d, 0x61, 0x72, 0x79, 0x0a, 0x0a, 0x54, 0x68,
- 0x65, 0x20, 0x62, 0x6f, 0x64, 0x79, 0x20, 0x6f,
- 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f,
- 0x6d, 0x6d, 0x69, 0x74, 0x20, 0x6d, 0x65, 0x73,
- 0x73, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x63, 0x6f,
- 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67,
- 0x20, 0x66, 0x75, 0x72, 0x74, 0x68, 0x65, 0x72,
- 0x20, 0x65, 0x78, 0x70, 0x6c, 0x61, 0x6e, 0x61,
- 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x6f, 0x66, 0x20,
- 0x74, 0x68, 0x65, 0x20, 0x70, 0x75, 0x72, 0x70,
- 0x6f, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74,
- 0x68, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67,
- 0x65, 0x73, 0x20, 0x69, 0x6e, 0x74, 0x72, 0x6f,
- 0x64, 0x75, 0x63, 0x65, 0x64, 0x20, 0x62, 0x79,
- 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6d,
- 0x6d, 0x69, 0x74, 0x2e, 0x0a, 0x0a, 0x53, 0x69,
- 0x67, 0x6e, 0x65, 0x64, 0x2d, 0x6f, 0x66, 0x2d,
- 0x62, 0x79, 0x3a, 0x20, 0x41, 0x20, 0x55, 0x20,
- 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61, 0x75,
- 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78, 0x61,
- 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
- 0x3e, 0x0a,
-};
-
-static git_rawobj commit_obj = {
- commit_data,
- sizeof(commit_data),
- GIT_OBJ_COMMIT
-};
-
-static object_data tree = {
- "dff2da90b254e1beb889d1f1f1288be1803782df",
- "test-objects/df",
- "test-objects/df/f2da90b254e1beb889d1f1f1288be1803782df",
-};
-
-static unsigned char tree_data[] = {
- 0x31, 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x6f,
- 0x6e, 0x65, 0x00, 0x8b, 0x13, 0x78, 0x91, 0x79,
- 0x1f, 0xe9, 0x69, 0x27, 0xad, 0x78, 0xe6, 0x4b,
- 0x0a, 0xad, 0x7b, 0xde, 0xd0, 0x8b, 0xdc, 0x31,
- 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x73, 0x6f,
- 0x6d, 0x65, 0x00, 0xfd, 0x84, 0x30, 0xbc, 0x86,
- 0x4c, 0xfc, 0xd5, 0xf1, 0x0e, 0x55, 0x90, 0xf8,
- 0xa4, 0x47, 0xe0, 0x1b, 0x94, 0x2b, 0xfe, 0x31,
- 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x74, 0x77,
- 0x6f, 0x00, 0x78, 0x98, 0x19, 0x22, 0x61, 0x3b,
- 0x2a, 0xfb, 0x60, 0x25, 0x04, 0x2f, 0xf6, 0xbd,
- 0x87, 0x8a, 0xc1, 0x99, 0x4e, 0x85, 0x31, 0x30,
- 0x30, 0x36, 0x34, 0x34, 0x20, 0x7a, 0x65, 0x72,
- 0x6f, 0x00, 0xe6, 0x9d, 0xe2, 0x9b, 0xb2, 0xd1,
- 0xd6, 0x43, 0x4b, 0x8b, 0x29, 0xae, 0x77, 0x5a,
- 0xd8, 0xc2, 0xe4, 0x8c, 0x53, 0x91,
-};
-
-static git_rawobj tree_obj = {
- tree_data,
- sizeof(tree_data),
- GIT_OBJ_TREE
-};
-
-static object_data tag = {
- "09d373e1dfdc16b129ceec6dd649739911541e05",
- "test-objects/09",
- "test-objects/09/d373e1dfdc16b129ceec6dd649739911541e05",
-};
-
-static unsigned char tag_data[] = {
- 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x33,
- 0x64, 0x37, 0x66, 0x38, 0x61, 0x36, 0x61, 0x66,
- 0x30, 0x37, 0x36, 0x63, 0x38, 0x63, 0x33, 0x66,
- 0x32, 0x30, 0x30, 0x37, 0x31, 0x61, 0x38, 0x39,
- 0x33, 0x35, 0x63, 0x64, 0x62, 0x65, 0x38, 0x32,
- 0x32, 0x38, 0x35, 0x39, 0x34, 0x64, 0x31, 0x0a,
- 0x74, 0x79, 0x70, 0x65, 0x20, 0x63, 0x6f, 0x6d,
- 0x6d, 0x69, 0x74, 0x0a, 0x74, 0x61, 0x67, 0x20,
- 0x76, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x0a, 0x74,
- 0x61, 0x67, 0x67, 0x65, 0x72, 0x20, 0x43, 0x20,
- 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72,
- 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74,
- 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d,
- 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e,
- 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, 0x31, 0x34,
- 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, 0x30, 0x30,
- 0x30, 0x0a, 0x0a, 0x54, 0x68, 0x69, 0x73, 0x20,
- 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74,
- 0x61, 0x67, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63,
- 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x72, 0x65,
- 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x76, 0x30,
- 0x2e, 0x30, 0x2e, 0x31, 0x0a,
-};
-
-static git_rawobj tag_obj = {
- tag_data,
- sizeof(tag_data),
- GIT_OBJ_TAG
-};
-
-static object_data zero = {
- "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
- "test-objects/e6",
- "test-objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391",
-};
-
-static unsigned char zero_data[] = {
- 0x00 /* dummy data */
-};
-
-static git_rawobj zero_obj = {
- zero_data,
- 0,
- GIT_OBJ_BLOB
-};
-
-static object_data one = {
- "8b137891791fe96927ad78e64b0aad7bded08bdc",
- "test-objects/8b",
- "test-objects/8b/137891791fe96927ad78e64b0aad7bded08bdc",
-};
-
-static unsigned char one_data[] = {
- 0x0a,
-};
-
-static git_rawobj one_obj = {
- one_data,
- sizeof(one_data),
- GIT_OBJ_BLOB
-};
-
-static object_data two = {
- "78981922613b2afb6025042ff6bd878ac1994e85",
- "test-objects/78",
- "test-objects/78/981922613b2afb6025042ff6bd878ac1994e85",
-};
-
-static unsigned char two_data[] = {
- 0x61, 0x0a,
-};
-
-static git_rawobj two_obj = {
- two_data,
- sizeof(two_data),
- GIT_OBJ_BLOB
-};
-
-static object_data some = {
- "fd8430bc864cfcd5f10e5590f8a447e01b942bfe",
- "test-objects/fd",
- "test-objects/fd/8430bc864cfcd5f10e5590f8a447e01b942bfe",
-};
-
-static unsigned char some_data[] = {
- 0x2f, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x54, 0x68,
- 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20,
- 0x69, 0x73, 0x20, 0x66, 0x72, 0x65, 0x65, 0x20,
- 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65,
- 0x3b, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x63, 0x61,
- 0x6e, 0x20, 0x72, 0x65, 0x64, 0x69, 0x73, 0x74,
- 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x69,
- 0x74, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72,
- 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x0a,
- 0x20, 0x2a, 0x20, 0x69, 0x74, 0x20, 0x75, 0x6e,
- 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20,
- 0x74, 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66,
- 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4e, 0x55,
- 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c,
- 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20,
- 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x2c,
- 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
- 0x20, 0x32, 0x2c, 0x0a, 0x20, 0x2a, 0x20, 0x61,
- 0x73, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73,
- 0x68, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74,
- 0x68, 0x65, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20,
- 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65,
- 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74,
- 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x20, 0x2a, 0x0a,
- 0x20, 0x2a, 0x20, 0x49, 0x6e, 0x20, 0x61, 0x64,
- 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74,
- 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x65,
- 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e,
- 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65,
- 0x20, 0x47, 0x4e, 0x55, 0x20, 0x47, 0x65, 0x6e,
- 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50, 0x75, 0x62,
- 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69, 0x63, 0x65,
- 0x6e, 0x73, 0x65, 0x2c, 0x0a, 0x20, 0x2a, 0x20,
- 0x74, 0x68, 0x65, 0x20, 0x61, 0x75, 0x74, 0x68,
- 0x6f, 0x72, 0x73, 0x20, 0x67, 0x69, 0x76, 0x65,
- 0x20, 0x79, 0x6f, 0x75, 0x20, 0x75, 0x6e, 0x6c,
- 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x20, 0x70,
- 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f,
- 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x6c, 0x69, 0x6e,
- 0x6b, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f,
- 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x0a, 0x20,
- 0x2a, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
- 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69,
- 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x69,
- 0x6e, 0x74, 0x6f, 0x20, 0x63, 0x6f, 0x6d, 0x62,
- 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73,
- 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x74,
- 0x68, 0x65, 0x72, 0x20, 0x70, 0x72, 0x6f, 0x67,
- 0x72, 0x61, 0x6d, 0x73, 0x2c, 0x0a, 0x20, 0x2a,
- 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x6f, 0x20,
- 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75,
- 0x74, 0x65, 0x20, 0x74, 0x68, 0x6f, 0x73, 0x65,
- 0x20, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x61,
- 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x77, 0x69,
- 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x61, 0x6e,
- 0x79, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, 0x69,
- 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x2a,
- 0x20, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x20,
- 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65,
- 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20,
- 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c,
- 0x65, 0x2e, 0x20, 0x20, 0x28, 0x54, 0x68, 0x65,
- 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c,
- 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20,
- 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a,
- 0x20, 0x2a, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72,
- 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20,
- 0x64, 0x6f, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x79,
- 0x20, 0x69, 0x6e, 0x20, 0x6f, 0x74, 0x68, 0x65,
- 0x72, 0x20, 0x72, 0x65, 0x73, 0x70, 0x65, 0x63,
- 0x74, 0x73, 0x3b, 0x20, 0x66, 0x6f, 0x72, 0x20,
- 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2c,
- 0x20, 0x74, 0x68, 0x65, 0x79, 0x20, 0x63, 0x6f,
- 0x76, 0x65, 0x72, 0x0a, 0x20, 0x2a, 0x20, 0x6d,
- 0x6f, 0x64, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
- 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74,
- 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2c,
- 0x20, 0x61, 0x6e, 0x64, 0x20, 0x64, 0x69, 0x73,
- 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f,
- 0x6e, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x6e,
- 0x6f, 0x74, 0x20, 0x6c, 0x69, 0x6e, 0x6b, 0x65,
- 0x64, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x0a, 0x20,
- 0x2a, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x6d, 0x62,
- 0x69, 0x6e, 0x65, 0x64, 0x20, 0x65, 0x78, 0x65,
- 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2e,
- 0x29, 0x0a, 0x20, 0x2a, 0x0a, 0x20, 0x2a, 0x20,
- 0x54, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c,
- 0x65, 0x20, 0x69, 0x73, 0x20, 0x64, 0x69, 0x73,
- 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64,
- 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20,
- 0x68, 0x6f, 0x70, 0x65, 0x20, 0x74, 0x68, 0x61,
- 0x74, 0x20, 0x69, 0x74, 0x20, 0x77, 0x69, 0x6c,
- 0x6c, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65,
- 0x66, 0x75, 0x6c, 0x2c, 0x20, 0x62, 0x75, 0x74,
- 0x0a, 0x20, 0x2a, 0x20, 0x57, 0x49, 0x54, 0x48,
- 0x4f, 0x55, 0x54, 0x20, 0x41, 0x4e, 0x59, 0x20,
- 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x59,
- 0x3b, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75,
- 0x74, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x20, 0x74,
- 0x68, 0x65, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x69,
- 0x65, 0x64, 0x20, 0x77, 0x61, 0x72, 0x72, 0x61,
- 0x6e, 0x74, 0x79, 0x20, 0x6f, 0x66, 0x0a, 0x20,
- 0x2a, 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, 0x41,
- 0x4e, 0x54, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54,
- 0x59, 0x20, 0x6f, 0x72, 0x20, 0x46, 0x49, 0x54,
- 0x4e, 0x45, 0x53, 0x53, 0x20, 0x46, 0x4f, 0x52,
- 0x20, 0x41, 0x20, 0x50, 0x41, 0x52, 0x54, 0x49,
- 0x43, 0x55, 0x4c, 0x41, 0x52, 0x20, 0x50, 0x55,
- 0x52, 0x50, 0x4f, 0x53, 0x45, 0x2e, 0x20, 0x20,
- 0x53, 0x65, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20,
- 0x47, 0x4e, 0x55, 0x0a, 0x20, 0x2a, 0x20, 0x47,
- 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50,
- 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69,
- 0x63, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x66, 0x6f,
- 0x72, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x64,
- 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x0a,
- 0x20, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x59, 0x6f,
- 0x75, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64,
- 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x72, 0x65,
- 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x20, 0x61,
- 0x20, 0x63, 0x6f, 0x70, 0x79, 0x20, 0x6f, 0x66,
- 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4e, 0x55,
- 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c,
- 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20,
- 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a,
- 0x20, 0x2a, 0x20, 0x61, 0x6c, 0x6f, 0x6e, 0x67,
- 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68,
- 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72,
- 0x61, 0x6d, 0x3b, 0x20, 0x73, 0x65, 0x65, 0x20,
- 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65,
- 0x20, 0x43, 0x4f, 0x50, 0x59, 0x49, 0x4e, 0x47,
- 0x2e, 0x20, 0x20, 0x49, 0x66, 0x20, 0x6e, 0x6f,
- 0x74, 0x2c, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65,
- 0x20, 0x74, 0x6f, 0x0a, 0x20, 0x2a, 0x20, 0x74,
- 0x68, 0x65, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20,
- 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65,
- 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74,
- 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x35, 0x31, 0x20,
- 0x46, 0x72, 0x61, 0x6e, 0x6b, 0x6c, 0x69, 0x6e,
- 0x20, 0x53, 0x74, 0x72, 0x65, 0x65, 0x74, 0x2c,
- 0x20, 0x46, 0x69, 0x66, 0x74, 0x68, 0x20, 0x46,
- 0x6c, 0x6f, 0x6f, 0x72, 0x2c, 0x0a, 0x20, 0x2a,
- 0x20, 0x42, 0x6f, 0x73, 0x74, 0x6f, 0x6e, 0x2c,
- 0x20, 0x4d, 0x41, 0x20, 0x30, 0x32, 0x31, 0x31,
- 0x30, 0x2d, 0x31, 0x33, 0x30, 0x31, 0x2c, 0x20,
- 0x55, 0x53, 0x41, 0x2e, 0x0a, 0x20, 0x2a, 0x2f,
- 0x0a,
-};
-
-static git_rawobj some_obj = {
- some_data,
- sizeof(some_data),
- GIT_OBJ_BLOB
-};
diff --git a/tests/t03-objwrite.c b/tests/t03-objwrite.c
deleted file mode 100644
index 31f611a5c..000000000
--- a/tests/t03-objwrite.c
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#include "test_lib.h"
-#include "fileops.h"
-#include "odb.h"
-
-static char *odb_dir = "test-objects";
-#include "t03-data.h"
-
-static int make_odb_dir(void)
-{
- if (p_mkdir(odb_dir, 0755) < 0) {
- int err = errno;
- fprintf(stderr, "can't make directory \"%s\"", odb_dir);
- if (err == EEXIST)
- fprintf(stderr, " (already exists)");
- fprintf(stderr, "\n");
- return -1;
- }
- return 0;
-}
-
-static int check_object_files(object_data *d)
-{
- if (git_futils_exists(d->dir) < 0)
- return -1;
- if (git_futils_exists(d->file) < 0)
- return -1;
- return 0;
-}
-
-static int cmp_objects(git_rawobj *o1, git_rawobj *o2)
-{
- if (o1->type != o2->type)
- return -1;
- if (o1->len != o2->len)
- return -1;
- if ((o1->len > 0) && (memcmp(o1->data, o2->data, o1->len) != 0))
- return -1;
- return 0;
-}
-
-static int remove_object_files(object_data *d)
-{
- if (p_unlink(d->file) < 0) {
- fprintf(stderr, "can't delete object file \"%s\"\n", d->file);
- return -1;
- }
- if ((p_rmdir(d->dir) < 0) && (errno != ENOTEMPTY)) {
- fprintf(stderr, "can't remove directory \"%s\"\n", d->dir);
- return -1;
- }
-
- if (p_rmdir(odb_dir) < 0) {
- fprintf(stderr, "can't remove directory \"%s\"\n", odb_dir);
- return -1;
- }
-
- return 0;
-}
-
-static int streaming_write(git_oid *oid, git_odb *odb, git_rawobj *raw)
-{
- git_odb_stream *stream;
- int error;
-
- if ((error = git_odb_open_wstream(&stream, odb, raw->len, raw->type)) < GIT_SUCCESS)
- return error;
-
- stream->write(stream, raw->data, raw->len);
-
- error = stream->finalize_write(oid, stream);
- stream->free(stream);
-
- return error;
-}
-
-BEGIN_TEST(write0, "write loose commit object")
- git_odb *db;
- git_oid id1, id2;
- git_odb_object *obj;
-
- must_pass(make_odb_dir());
- must_pass(git_odb_open(&db, odb_dir));
- must_pass(git_oid_fromstr(&id1, commit.id));
-
- must_pass(streaming_write(&id2, db, &commit_obj));
- must_be_true(git_oid_cmp(&id1, &id2) == 0);
- must_pass(check_object_files(&commit));
-
- must_pass(git_odb_read(&obj, db, &id1));
- must_pass(cmp_objects(&obj->raw, &commit_obj));
-
- git_odb_object_close(obj);
- git_odb_close(db);
- must_pass(remove_object_files(&commit));
-END_TEST
-
-BEGIN_TEST(write1, "write loose tree object")
- git_odb *db;
- git_oid id1, id2;
- git_odb_object *obj;
-
- must_pass(make_odb_dir());
- must_pass(git_odb_open(&db, odb_dir));
- must_pass(git_oid_fromstr(&id1, tree.id));
-
- must_pass(streaming_write(&id2, db, &tree_obj));
- must_be_true(git_oid_cmp(&id1, &id2) == 0);
- must_pass(check_object_files(&tree));
-
- must_pass(git_odb_read(&obj, db, &id1));
- must_pass(cmp_objects(&obj->raw, &tree_obj));
-
- git_odb_object_close(obj);
- git_odb_close(db);
- must_pass(remove_object_files(&tree));
-END_TEST
-
-BEGIN_TEST(write2, "write loose tag object")
- git_odb *db;
- git_oid id1, id2;
- git_odb_object *obj;
-
- must_pass(make_odb_dir());
- must_pass(git_odb_open(&db, odb_dir));
- must_pass(git_oid_fromstr(&id1, tag.id));
-
- must_pass(streaming_write(&id2, db, &tag_obj));
- must_be_true(git_oid_cmp(&id1, &id2) == 0);
- must_pass(check_object_files(&tag));
-
- must_pass(git_odb_read(&obj, db, &id1));
- must_pass(cmp_objects(&obj->raw, &tag_obj));
-
- git_odb_object_close(obj);
- git_odb_close(db);
- must_pass(remove_object_files(&tag));
-END_TEST
-
-BEGIN_TEST(write3, "write zero-length object")
- git_odb *db;
- git_oid id1, id2;
- git_odb_object *obj;
-
- must_pass(make_odb_dir());
- must_pass(git_odb_open(&db, odb_dir));
- must_pass(git_oid_fromstr(&id1, zero.id));
-
- must_pass(streaming_write(&id2, db, &zero_obj));
- must_be_true(git_oid_cmp(&id1, &id2) == 0);
- must_pass(check_object_files(&zero));
-
- must_pass(git_odb_read(&obj, db, &id1));
- must_pass(cmp_objects(&obj->raw, &zero_obj));
-
- git_odb_object_close(obj);
- git_odb_close(db);
- must_pass(remove_object_files(&zero));
-END_TEST
-
-BEGIN_TEST(write4, "write one-byte long object")
- git_odb *db;
- git_oid id1, id2;
- git_odb_object *obj;
-
- must_pass(make_odb_dir());
- must_pass(git_odb_open(&db, odb_dir));
- must_pass(git_oid_fromstr(&id1, one.id));
-
- must_pass(streaming_write(&id2, db, &one_obj));
- must_be_true(git_oid_cmp(&id1, &id2) == 0);
- must_pass(check_object_files(&one));
-
- must_pass(git_odb_read(&obj, db, &id1));
- must_pass(cmp_objects(&obj->raw, &one_obj));
-
- git_odb_object_close(obj);
- git_odb_close(db);
- must_pass(remove_object_files(&one));
-END_TEST
-
-BEGIN_TEST(write5, "write two-byte long object")
- git_odb *db;
- git_oid id1, id2;
- git_odb_object *obj;
-
- must_pass(make_odb_dir());
- must_pass(git_odb_open(&db, odb_dir));
- must_pass(git_oid_fromstr(&id1, two.id));
-
- must_pass(streaming_write(&id2, db, &two_obj));
- must_be_true(git_oid_cmp(&id1, &id2) == 0);
- must_pass(check_object_files(&two));
-
- must_pass(git_odb_read(&obj, db, &id1));
- must_pass(cmp_objects(&obj->raw, &two_obj));
-
- git_odb_object_close(obj);
- git_odb_close(db);
- must_pass(remove_object_files(&two));
-END_TEST
-
-BEGIN_TEST(write6, "write an object which is several bytes long")
- git_odb *db;
- git_oid id1, id2;
- git_odb_object *obj;
-
- must_pass(make_odb_dir());
- must_pass(git_odb_open(&db, odb_dir));
- must_pass(git_oid_fromstr(&id1, some.id));
-
- must_pass(streaming_write(&id2, db, &some_obj));
- must_be_true(git_oid_cmp(&id1, &id2) == 0);
- must_pass(check_object_files(&some));
-
- must_pass(git_odb_read(&obj, db, &id1));
- must_pass(cmp_objects(&obj->raw, &some_obj));
-
- git_odb_object_close(obj);
- git_odb_close(db);
- must_pass(remove_object_files(&some));
-END_TEST
-
-BEGIN_SUITE(objwrite)
- ADD_TEST(write0);
- ADD_TEST(write1);
- ADD_TEST(write2);
- ADD_TEST(write3);
- ADD_TEST(write4);
- ADD_TEST(write5);
- ADD_TEST(write6);
-END_SUITE
diff --git a/tests/t04-commit.c b/tests/t04-commit.c
deleted file mode 100644
index bb5171dd6..000000000
--- a/tests/t04-commit.c
+++ /dev/null
@@ -1,775 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#include "test_lib.h"
-#include "test_helpers.h"
-
-#include "commit.h"
-#include "signature.h"
-
-static char *test_commits_broken[] = {
-
-/* empty commit */
-"",
-
-/* random garbage */
-"asd97sa9du902e9a0jdsuusad09as9du098709aweu8987sd\n",
-
-/* broken endlines 1 */
-"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\r\n\
-parent 05452d6349abcd67aa396dfb28660d765d8b2a36\r\n\
-author Vicent Marti <tanoku@gmail.com> 1273848544 +0200\r\n\
-committer Vicent Marti <tanoku@gmail.com> 1273848544 +0200\r\n\
-\r\n\
-a test commit with broken endlines\r\n",
-
-/* broken endlines 2 */
-"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\
-parent 05452d6349abcd67aa396dfb28660d765d8b2a36\
-author Vicent Marti <tanoku@gmail.com> 1273848544 +0200\
-committer Vicent Marti <tanoku@gmail.com> 1273848544 +0200\
-\
-another test commit with broken endlines",
-
-/* starting endlines */
-"\ntree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\
-parent 05452d6349abcd67aa396dfb28660d765d8b2a36\n\
-author Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
-committer Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
-\n\
-a test commit with a starting endline\n",
-
-/* corrupted commit 1 */
-"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\
-parent 05452d6349abcd67aa396df",
-
-/* corrupted commit 2 */
-"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\
-parent ",
-
-/* corrupted commit 3 */
-"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\
-parent ",
-
-/* corrupted commit 4 */
-"tree f6c0dad3c7b3481caa9d73db21f91964894a945b\n\
-par",
-
-};
-
-
-static char *test_commits_working[] = {
-/* simple commit with no message */
-"tree 1810dff58d8a660512d4832e740f692884338ccd\n\
-author Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
-committer Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
-\n",
-
-/* simple commit, no parent */
-"tree 1810dff58d8a660512d4832e740f692884338ccd\n\
-author Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
-committer Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
-\n\
-a simple commit which works\n",
-
-/* simple commit, no parent, no newline in message */
-"tree 1810dff58d8a660512d4832e740f692884338ccd\n\
-author Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
-committer Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
-\n\
-a simple commit which works",
-
-/* simple commit, 1 parent */
-"tree 1810dff58d8a660512d4832e740f692884338ccd\n\
-parent e90810b8df3e80c413d903f631643c716887138d\n\
-author Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
-committer Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
-\n\
-a simple commit which works\n",
-};
-
-BEGIN_TEST(parse0, "parse the OID line in a commit")
-
- git_oid oid;
-
-#define TEST_OID_PASS(string, header) { \
- const char *ptr = string;\
- const char *ptr_original = ptr;\
- size_t len = strlen(ptr);\
- must_pass(git_oid__parse(&oid, &ptr, ptr + len, header));\
- must_be_true(ptr == ptr_original + len);\
-}
-
-#define TEST_OID_FAIL(string, header) { \
- const char *ptr = string;\
- size_t len = strlen(ptr);\
- must_fail(git_oid__parse(&oid, &ptr, ptr + len, header));\
-}
-
- TEST_OID_PASS("parent 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "parent ");
- TEST_OID_PASS("tree 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "tree ");
- TEST_OID_PASS("random_heading 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "random_heading ");
- TEST_OID_PASS("stuck_heading05452d6349abcd67aa396dfb28660d765d8b2a36\n", "stuck_heading");
- TEST_OID_PASS("tree 5F4BEFFC0759261D015AA63A3A85613FF2F235DE\n", "tree ");
- TEST_OID_PASS("tree 1A669B8AB81B5EB7D9DB69562D34952A38A9B504\n", "tree ");
- TEST_OID_PASS("tree 5B20DCC6110FCC75D31C6CEDEBD7F43ECA65B503\n", "tree ");
- TEST_OID_PASS("tree 173E7BF00EA5C33447E99E6C1255954A13026BE4\n", "tree ");
-
- TEST_OID_FAIL("parent 05452d6349abcd67aa396dfb28660d765d8b2a36", "parent ");
- TEST_OID_FAIL("05452d6349abcd67aa396dfb28660d765d8b2a36\n", "tree ");
- TEST_OID_FAIL("parent05452d6349abcd67aa396dfb28660d765d8b2a6a\n", "parent ");
- TEST_OID_FAIL("parent 05452d6349abcd67aa396dfb280d765d8b2a6\n", "parent ");
- TEST_OID_FAIL("tree 05452d6349abcd67aa396dfb28660d765d8b2a36\n", "tree ");
- TEST_OID_FAIL("parent 0545xd6349abcd67aa396dfb28660d765d8b2a36\n", "parent ");
- TEST_OID_FAIL("parent 0545xd6349abcd67aa396dfb28660d765d8b2a36FF\n", "parent ");
- TEST_OID_FAIL("", "tree ");
- TEST_OID_FAIL("", "");
-
-#undef TEST_OID_PASS
-#undef TEST_OID_FAIL
-
-END_TEST
-
-BEGIN_TEST(parse1, "parse the signature line in a commit")
-
-#define TEST_SIGNATURE_PASS(_string, _header, _name, _email, _time, _offset) { \
- const char *ptr = _string; \
- size_t len = strlen(_string);\
- git_signature person = {NULL, NULL, {0, 0}}; \
- must_pass(git_signature__parse(&person, &ptr, ptr + len, _header, '\n'));\
- must_be_true(strcmp(_name, person.name) == 0);\
- must_be_true(strcmp(_email, person.email) == 0);\
- must_be_true(_time == person.when.time);\
- must_be_true(_offset == person.when.offset);\
- free(person.name); free(person.email);\
-}
-
-#define TEST_SIGNATURE_FAIL(_string, _header) { \
- const char *ptr = _string; \
- size_t len = strlen(_string);\
- git_signature person = {NULL, NULL, {0, 0}}; \
- must_fail(git_signature__parse(&person, &ptr, ptr + len, _header, '\n'));\
- free(person.name); free(person.email);\
-}
-
- TEST_SIGNATURE_PASS(
- "author Vicent Marti <tanoku@gmail.com> 12345 \n",
- "author ",
- "Vicent Marti",
- "tanoku@gmail.com",
- 12345,
- 0);
-
- TEST_SIGNATURE_PASS(
- "author Vicent Marti <> 12345 \n",
- "author ",
- "Vicent Marti",
- "",
- 12345,
- 0);
-
- TEST_SIGNATURE_PASS(
- "author Vicent Marti <tanoku@gmail.com> 231301 +1020\n",
- "author ",
- "Vicent Marti",
- "tanoku@gmail.com",
- 231301,
- 620);
-
- TEST_SIGNATURE_PASS(
- "author Vicent Marti with an outrageously long name \
- which will probably overflow the buffer <tanoku@gmail.com> 12345 \n",
- "author ",
- "Vicent Marti with an outrageously long name \
- which will probably overflow the buffer",
- "tanoku@gmail.com",
- 12345,
- 0);
-
- TEST_SIGNATURE_PASS(
- "author Vicent Marti <tanokuwithaveryveryverylongemail\
- whichwillprobablyvoverflowtheemailbuffer@gmail.com> 12345 \n",
- "author ",
- "Vicent Marti",
- "tanokuwithaveryveryverylongemail\
- whichwillprobablyvoverflowtheemailbuffer@gmail.com",
- 12345,
- 0);
-
- TEST_SIGNATURE_PASS(
- "committer Vicent Marti <tanoku@gmail.com> 123456 +0000 \n",
- "committer ",
- "Vicent Marti",
- "tanoku@gmail.com",
- 123456,
- 0);
-
- TEST_SIGNATURE_PASS(
- "committer Vicent Marti <tanoku@gmail.com> 123456 +0100 \n",
- "committer ",
- "Vicent Marti",
- "tanoku@gmail.com",
- 123456,
- 60);
-
- TEST_SIGNATURE_PASS(
- "committer Vicent Marti <tanoku@gmail.com> 123456 -0100 \n",
- "committer ",
- "Vicent Marti",
- "tanoku@gmail.com",
- 123456,
- -60);
-
- /* Parse a signature without an author field */
- TEST_SIGNATURE_PASS(
- "committer <tanoku@gmail.com> 123456 -0100 \n",
- "committer ",
- "",
- "tanoku@gmail.com",
- 123456,
- -60);
-
- /* Parse a signature without an author field */
- TEST_SIGNATURE_PASS(
- "committer <tanoku@gmail.com> 123456 -0100 \n",
- "committer ",
- "",
- "tanoku@gmail.com",
- 123456,
- -60);
-
- /* Parse a signature with an empty author field */
- TEST_SIGNATURE_PASS(
- "committer <tanoku@gmail.com> 123456 -0100 \n",
- "committer ",
- "",
- "tanoku@gmail.com",
- 123456,
- -60);
-
- /* Parse a signature with an empty email field */
- TEST_SIGNATURE_PASS(
- "committer Vicent Marti <> 123456 -0100 \n",
- "committer ",
- "Vicent Marti",
- "",
- 123456,
- -60);
-
- /* Parse a signature with an empty email field */
- TEST_SIGNATURE_PASS(
- "committer Vicent Marti < > 123456 -0100 \n",
- "committer ",
- "Vicent Marti",
- "",
- 123456,
- -60);
-
- /* Parse a signature with empty name and email */
- TEST_SIGNATURE_PASS(
- "committer <> 123456 -0100 \n",
- "committer ",
- "",
- "",
- 123456,
- -60);
-
- /* Parse a signature with empty name and email */
- TEST_SIGNATURE_PASS(
- "committer <> 123456 -0100 \n",
- "committer ",
- "",
- "",
- 123456,
- -60);
-
- /* Parse a signature with empty name and email */
- TEST_SIGNATURE_PASS(
- "committer < > 123456 -0100 \n",
- "committer ",
- "",
- "",
- 123456,
- -60);
-
- /* Parse an obviously invalid signature */
- TEST_SIGNATURE_PASS(
- "committer foo<@bar> 123456 -0100 \n",
- "committer ",
- "foo",
- "@bar",
- 123456,
- -60);
-
- /* Parse an obviously invalid signature */
- TEST_SIGNATURE_PASS(
- "committer foo<@bar>123456 -0100 \n",
- "committer ",
- "foo",
- "@bar",
- 123456,
- -60);
-
- /* Parse an obviously invalid signature */
- TEST_SIGNATURE_PASS(
- "committer <>\n",
- "committer ",
- "",
- "",
- 0,
- 0);
-
- TEST_SIGNATURE_PASS(
- "committer Vicent Marti <tanoku@gmail.com> 123456 -1500 \n",
- "committer ",
- "Vicent Marti",
- "tanoku@gmail.com",
- 0,
- 0);
-
- TEST_SIGNATURE_PASS(
- "committer Vicent Marti <tanoku@gmail.com> 123456 +0163 \n",
- "committer ",
- "Vicent Marti",
- "tanoku@gmail.com",
- 0,
- 0);
-
- TEST_SIGNATURE_PASS(
- "author Vicent Marti <tanoku@gmail.com> notime \n",
- "author ",
- "Vicent Marti",
- "tanoku@gmail.com",
- 0,
- 0);
-
- TEST_SIGNATURE_PASS(
- "author Vicent Marti <tanoku@gmail.com> 123456 notimezone \n",
- "author ",
- "Vicent Marti",
- "tanoku@gmail.com",
- 0,
- 0);
-
- TEST_SIGNATURE_PASS(
- "author Vicent Marti <tanoku@gmail.com> notime +0100\n",
- "author ",
- "Vicent Marti",
- "tanoku@gmail.com",
- 0,
- 0);
-
- TEST_SIGNATURE_PASS(
- "author Vicent Marti <tanoku@gmail.com>\n",
- "author ",
- "Vicent Marti",
- "tanoku@gmail.com",
- 0,
- 0);
-
- TEST_SIGNATURE_PASS(
- "author A U Thor <author@example.com>, C O. Miter <comiter@example.com> 1234567890 -0700\n",
- "author ",
- "A U Thor",
- "author@example.com",
- 1234567890,
- -420);
-
- TEST_SIGNATURE_PASS(
- "author A U Thor <author@example.com> and others 1234567890 -0700\n",
- "author ",
- "A U Thor",
- "author@example.com",
- 1234567890,
- -420);
-
- TEST_SIGNATURE_PASS(
- "author A U Thor <author@example.com> and others 1234567890\n",
- "author ",
- "A U Thor",
- "author@example.com",
- 1234567890,
- 0);
-
- TEST_SIGNATURE_FAIL(
- "committer Vicent Marti tanoku@gmail.com> 123456 -0100 \n",
- "committer ");
-
- TEST_SIGNATURE_FAIL(
- "author Vicent Marti <tanoku@gmail.com> 12345 \n",
- "author ");
-
- TEST_SIGNATURE_FAIL(
- "author Vicent Marti <tanoku@gmail.com> 12345 \n",
- "committer ");
-
- TEST_SIGNATURE_FAIL(
- "author Vicent Marti 12345 \n",
- "author ");
-
- TEST_SIGNATURE_FAIL(
- "author Vicent Marti <broken@email 12345 \n",
- "author ");
-
- TEST_SIGNATURE_FAIL(
- "committer Vicent Marti ><\n",
- "committer ");
-
- TEST_SIGNATURE_FAIL(
- "author ",
- "author ");
-
-#undef TEST_SIGNATURE_PASS
-#undef TEST_SIGNATURE_FAIL
-
-END_TEST
-
-static int try_build_signature(const char *name, const char *email, git_time_t time, int offset)
-{
- git_signature *sign;
- int error = GIT_SUCCESS;
-
- if ((error = git_signature_new(&sign, name, email, time, offset)) < GIT_SUCCESS)
- return error;
-
- git_signature_free((git_signature *)sign);
-
- return error;
-}
-
-BEGIN_TEST(signature0, "creating a signature trims leading and trailing spaces")
- git_signature *sign;
- must_pass(git_signature_new(&sign, " nulltoken ", " emeric.fermas@gmail.com ", 1234567890, 60));
- must_be_true(strcmp(sign->name, "nulltoken") == 0);
- must_be_true(strcmp(sign->email, "emeric.fermas@gmail.com") == 0);
- git_signature_free((git_signature *)sign);
-END_TEST
-
-BEGIN_TEST(signature1, "can not create a signature with empty name or email")
- must_pass(try_build_signature("nulltoken", "emeric.fermas@gmail.com", 1234567890, 60));
-
- must_fail(try_build_signature("", "emeric.fermas@gmail.com", 1234567890, 60));
- must_fail(try_build_signature(" ", "emeric.fermas@gmail.com", 1234567890, 60));
- must_fail(try_build_signature("nulltoken", "", 1234567890, 60));
- must_fail(try_build_signature("nulltoken", " ", 1234567890, 60));
-END_TEST
-
-BEGIN_TEST(signature2, "creating a one character signature")
- git_signature *sign;
- must_pass(git_signature_new(&sign, "x", "foo@bar.baz", 1234567890, 60));
- must_be_true(strcmp(sign->name, "x") == 0);
- must_be_true(strcmp(sign->email, "foo@bar.baz") == 0);
- git_signature_free((git_signature *)sign);
-END_TEST
-
-BEGIN_TEST(signature3, "creating a two character signature")
- git_signature *sign;
- must_pass(git_signature_new(&sign, "xx", "x@y.z", 1234567890, 60));
- must_be_true(strcmp(sign->name, "xx") == 0);
- must_be_true(strcmp(sign->email, "x@y.z") == 0);
- git_signature_free((git_signature *)sign);
-END_TEST
-
-BEGIN_TEST(signature4, "creating a zero character signature")
- git_signature *sign;
- must_fail(git_signature_new(&sign, "", "x@y.z", 1234567890, 60));
- must_be_true(sign == NULL);
-END_TEST
-
-
-/* External declaration for testing the buffer parsing method */
-int commit_parse_buffer(git_commit *commit, void *data, size_t len);
-
-BEGIN_TEST(parse2, "parse a whole commit buffer")
- const int broken_commit_count = sizeof(test_commits_broken) / sizeof(*test_commits_broken);
- const int working_commit_count = sizeof(test_commits_working) / sizeof(*test_commits_working);
-
- int i;
- git_repository *repo;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
-
- for (i = 0; i < broken_commit_count; ++i) {
- git_commit *commit;
- commit = git__malloc(sizeof(git_commit));
- memset(commit, 0x0, sizeof(git_commit));
- commit->object.repo = repo;
-
- must_fail(commit_parse_buffer(
- commit,
- test_commits_broken[i],
- strlen(test_commits_broken[i]))
- );
-
- git_commit__free(commit);
- }
-
- for (i = 0; i < working_commit_count; ++i) {
- git_commit *commit;
-
- commit = git__malloc(sizeof(git_commit));
- memset(commit, 0x0, sizeof(git_commit));
- commit->object.repo = repo;
-
- must_pass(commit_parse_buffer(
- commit,
- test_commits_working[i],
- strlen(test_commits_working[i]))
- );
-
- git_commit__free(commit);
-
- commit = git__malloc(sizeof(git_commit));
- memset(commit, 0x0, sizeof(git_commit));
- commit->object.repo = repo;
-
- must_pass(commit_parse_buffer(
- commit,
- test_commits_working[i],
- strlen(test_commits_working[i]))
- );
-
- git_commit__free(commit);
- }
-
- git_repository_free(repo);
-END_TEST
-
-static const char *commit_ids[] = {
- "a4a7dce85cf63874e984719f4fdd239f5145052f", /* 0 */
- "9fd738e8f7967c078dceed8190330fc8648ee56a", /* 1 */
- "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", /* 2 */
- "c47800c7266a2be04c571c04d5a6614691ea99bd", /* 3 */
- "8496071c1b46c854b31185ea97743be6a8774479", /* 4 */
- "5b5b025afb0b4c913b4c338a42934a3863bf3644", /* 5 */
-};
-
-BEGIN_TEST(details0, "query the details on a parsed commit")
- const size_t commit_count = sizeof(commit_ids) / sizeof(const char *);
-
- unsigned int i;
- git_repository *repo;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
-
- for (i = 0; i < commit_count; ++i) {
- git_oid id;
- git_commit *commit;
-
- const git_signature *author, *committer;
- const char *message, *message_short;
- git_time_t commit_time;
- unsigned int parents, p;
- git_commit *parent = NULL, *old_parent = NULL;
-
- git_oid_fromstr(&id, commit_ids[i]);
-
- must_pass(git_commit_lookup(&commit, repo, &id));
-
- message = git_commit_message(commit);
- message_short = git_commit_message_short(commit);
- author = git_commit_author(commit);
- committer = git_commit_committer(commit);
- commit_time = git_commit_time(commit);
- parents = git_commit_parentcount(commit);
-
- must_be_true(strcmp(author->name, "Scott Chacon") == 0);
- must_be_true(strcmp(author->email, "schacon@gmail.com") == 0);
- must_be_true(strcmp(committer->name, "Scott Chacon") == 0);
- must_be_true(strcmp(committer->email, "schacon@gmail.com") == 0);
- must_be_true(strchr(message, '\n') != NULL);
- must_be_true(strchr(message_short, '\n') == NULL);
- must_be_true(commit_time > 0);
- must_be_true(parents <= 2);
- for (p = 0;p < parents;p++) {
- if (old_parent != NULL)
- git_commit_close(old_parent);
-
- old_parent = parent;
- must_pass(git_commit_parent(&parent, commit, p));
- must_be_true(parent != NULL);
- must_be_true(git_commit_author(parent) != NULL); // is it really a commit?
- }
- git_commit_close(old_parent);
- git_commit_close(parent);
-
- must_fail(git_commit_parent(&parent, commit, parents));
- git_commit_close(commit);
- }
-
- git_repository_free(repo);
-END_TEST
-
-#define COMMITTER_NAME "Vicent Marti"
-#define COMMITTER_EMAIL "vicent@github.com"
-#define COMMIT_MESSAGE "This commit has been created in memory\n\
-This is a commit created in memory and it will be written back to disk\n"
-
-static const char *tree_oid = "1810dff58d8a660512d4832e740f692884338ccd";
-
-BEGIN_TEST(write0, "write a new commit object from memory to disk")
- git_repository *repo;
- git_commit *commit;
- git_oid tree_id, parent_id, commit_id;
- git_signature *author, *committer;
- const git_signature *author1, *committer1;
- git_commit *parent;
- git_tree *tree;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
-
- git_oid_fromstr(&tree_id, tree_oid);
- must_pass(git_tree_lookup(&tree, repo, &tree_id));
-
- git_oid_fromstr(&parent_id, commit_ids[4]);
- must_pass(git_commit_lookup(&parent, repo, &parent_id));
-
- /* create signatures */
- must_pass(git_signature_new(&committer, COMMITTER_NAME, COMMITTER_EMAIL, 123456789, 60));
- must_pass(git_signature_new(&author, COMMITTER_NAME, COMMITTER_EMAIL, 987654321, 90));
-
- must_pass(git_commit_create_v(
- &commit_id, /* out id */
- repo,
- NULL, /* do not update the HEAD */
- author,
- committer,
- COMMIT_MESSAGE,
- tree,
- 1, parent));
-
- git_object_close((git_object *)parent);
- git_object_close((git_object *)tree);
-
- git_signature_free(committer);
- git_signature_free(author);
-
- must_pass(git_commit_lookup(&commit, repo, &commit_id));
-
- /* Check attributes were set correctly */
- author1 = git_commit_author(commit);
- must_be_true(author1 != NULL);
- must_be_true(strcmp(author1->name, COMMITTER_NAME) == 0);
- must_be_true(strcmp(author1->email, COMMITTER_EMAIL) == 0);
- must_be_true(author1->when.time == 987654321);
- must_be_true(author1->when.offset == 90);
-
- committer1 = git_commit_committer(commit);
- must_be_true(committer1 != NULL);
- must_be_true(strcmp(committer1->name, COMMITTER_NAME) == 0);
- must_be_true(strcmp(committer1->email, COMMITTER_EMAIL) == 0);
- must_be_true(committer1->when.time == 123456789);
- must_be_true(committer1->when.offset == 60);
-
- must_be_true(strcmp(git_commit_message(commit), COMMIT_MESSAGE) == 0);
-
- must_pass(remove_loose_object(REPOSITORY_FOLDER, (git_object *)commit));
-
- git_commit_close(commit);
- git_repository_free(repo);
-END_TEST
-
-#define ROOT_COMMIT_MESSAGE "This is a root commit\n\
-This is a root commit and should be the only one in this branch\n"
-
-BEGIN_TEST(root0, "create a root commit")
- git_repository *repo;
- git_commit *commit;
- git_oid tree_id, commit_id;
- const git_oid *branch_oid;
- git_signature *author, *committer;
- const char *branch_name = "refs/heads/root-commit-branch";
- git_reference *head, *branch;
- char *head_old;
- git_tree *tree;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
-
- git_oid_fromstr(&tree_id, tree_oid);
- must_pass(git_tree_lookup(&tree, repo, &tree_id));
-
- /* create signatures */
- must_pass(git_signature_new(&committer, COMMITTER_NAME, COMMITTER_EMAIL, 123456789, 60));
- must_pass(git_signature_new(&author, COMMITTER_NAME, COMMITTER_EMAIL, 987654321, 90));
-
- /* First we need to update HEAD so it points to our non-existant branch */
- must_pass(git_reference_lookup(&head, repo, "HEAD"));
- must_be_true(git_reference_type(head) == GIT_REF_SYMBOLIC);
- head_old = git__strdup(git_reference_target(head));
- must_be_true(head_old != NULL);
-
- must_pass(git_reference_set_target(head, branch_name));
-
- must_pass(git_commit_create_v(
- &commit_id, /* out id */
- repo,
- "HEAD",
- author,
- committer,
- ROOT_COMMIT_MESSAGE,
- tree,
- 0));
-
- git_object_close((git_object *)tree);
- git_signature_free(committer);
- git_signature_free(author);
-
- /*
- * The fact that creating a commit works has already been
- * tested. Here we just make sure it's our commit and that it was
- * written as a root commit.
- */
- must_pass(git_commit_lookup(&commit, repo, &commit_id));
- must_be_true(git_commit_parentcount(commit) == 0);
- must_pass(git_reference_lookup(&branch, repo, branch_name));
- branch_oid = git_reference_oid(branch);
- must_pass(git_oid_cmp(branch_oid, &commit_id));
- must_be_true(!strcmp(git_commit_message(commit), ROOT_COMMIT_MESSAGE));
-
- /* Remove the data we just added to the repo */
- must_pass(git_reference_lookup(&head, repo, "HEAD"));
- must_pass(git_reference_set_target(head, head_old));
- must_pass(git_reference_delete(branch));
- must_pass(remove_loose_object(REPOSITORY_FOLDER, (git_object *)commit));
- free(head_old);
- git_commit_close(commit);
- git_repository_free(repo);
-END_TEST
-
-BEGIN_SUITE(commit)
- ADD_TEST(parse0);
- ADD_TEST(parse1);
- ADD_TEST(parse2);
- ADD_TEST(details0);
-
- ADD_TEST(write0);
-
- ADD_TEST(root0);
-
- ADD_TEST(signature0);
- ADD_TEST(signature1);
- ADD_TEST(signature2);
- ADD_TEST(signature3);
- ADD_TEST(signature4);
-END_SUITE
diff --git a/tests/t05-revwalk.c b/tests/t05-revwalk.c
deleted file mode 100644
index ab509ab94..000000000
--- a/tests/t05-revwalk.c
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#include "test_lib.h"
-#include "test_helpers.h"
-
-/*
- $ git log --oneline --graph --decorate
- * a4a7dce (HEAD, br2) Merge branch 'master' into br2
- |\
- | * 9fd738e (master) a fourth commit
- | * 4a202b3 a third commit
- * | c47800c branch commit one
- |/
- * 5b5b025 another commit
- * 8496071 testing
-*/
-static const char *commit_head = "a4a7dce85cf63874e984719f4fdd239f5145052f";
-
-static const char *commit_ids[] = {
- "a4a7dce85cf63874e984719f4fdd239f5145052f", /* 0 */
- "9fd738e8f7967c078dceed8190330fc8648ee56a", /* 1 */
- "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", /* 2 */
- "c47800c7266a2be04c571c04d5a6614691ea99bd", /* 3 */
- "8496071c1b46c854b31185ea97743be6a8774479", /* 4 */
- "5b5b025afb0b4c913b4c338a42934a3863bf3644", /* 5 */
-};
-
-/* Careful: there are two possible topological sorts */
-static const int commit_sorting_topo[][6] = {
- {0, 1, 2, 3, 5, 4}, {0, 3, 1, 2, 5, 4}
-};
-
-static const int commit_sorting_time[][6] = {
- {0, 3, 1, 2, 5, 4}
-};
-
-static const int commit_sorting_topo_reverse[][6] = {
- {4, 5, 3, 2, 1, 0}, {4, 5, 2, 1, 3, 0}
-};
-
-static const int commit_sorting_time_reverse[][6] = {
- {4, 5, 2, 1, 3, 0}
-};
-
-#define commit_count 6
-static const int result_bytes = 24;
-
-
-static int get_commit_index(git_oid *raw_oid)
-{
- int i;
- char oid[40];
-
- git_oid_fmt(oid, raw_oid);
-
- for (i = 0; i < commit_count; ++i)
- if (memcmp(oid, commit_ids[i], 40) == 0)
- return i;
-
- return -1;
-}
-
-static int test_walk(git_revwalk *walk, const git_oid *root,
- int flags, const int possible_results[][6], int results_count)
-{
- git_oid oid;
-
- int i;
- int result_array[commit_count];
-
- git_revwalk_sorting(walk, flags);
- git_revwalk_push(walk, root);
-
- for (i = 0; i < commit_count; ++i)
- result_array[i] = -1;
-
- i = 0;
-
- while (git_revwalk_next(&oid, walk) == GIT_SUCCESS) {
- result_array[i++] = get_commit_index(&oid);
- /*{
- char str[41];
- git_oid_fmt(str, &oid);
- str[40] = 0;
- printf(" %d) %s\n", i, str);
- }*/
- }
-
- for (i = 0; i < results_count; ++i)
- if (memcmp(possible_results[i],
- result_array, result_bytes) == 0)
- return GIT_SUCCESS;
-
- return GIT_ERROR;
-}
-
-BEGIN_TEST(walk0, "do a simple walk on a repo with different sorting modes")
- git_oid id;
- git_repository *repo;
- git_revwalk *walk;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
- must_pass(git_revwalk_new(&walk, repo));
-
- git_oid_fromstr(&id, commit_head);
-
- must_pass(test_walk(walk, &id, GIT_SORT_TIME, commit_sorting_time, 1));
- must_pass(test_walk(walk, &id, GIT_SORT_TOPOLOGICAL, commit_sorting_topo, 2));
- must_pass(test_walk(walk, &id, GIT_SORT_TIME | GIT_SORT_REVERSE, commit_sorting_time_reverse, 1));
- must_pass(test_walk(walk, &id, GIT_SORT_TOPOLOGICAL | GIT_SORT_REVERSE, commit_sorting_topo_reverse, 2));
-
- git_revwalk_free(walk);
- git_repository_free(repo);
-END_TEST
-
-BEGIN_SUITE(revwalk)
- ADD_TEST(walk0);
-END_SUITE
diff --git a/tests/t06-index.c b/tests/t06-index.c
deleted file mode 100644
index 621e742b3..000000000
--- a/tests/t06-index.c
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#include "test_lib.h"
-#include "test_helpers.h"
-
-#include "index.h"
-
-#define TEST_INDEX_ENTRY_COUNT 109
-#define TEST_INDEX2_ENTRY_COUNT 1437
-
-struct test_entry {
- unsigned int index;
- char path[128];
- git_off_t file_size;
- git_time_t mtime;
-};
-
-struct test_entry TEST_ENTRIES[] = {
- {4, "Makefile", 5064, 0x4C3F7F33},
- {62, "tests/Makefile", 2631, 0x4C3F7F33},
- {36, "src/index.c", 10014, 0x4C43368D},
- {6, "git.git-authors", 2709, 0x4C3F7F33},
- {48, "src/revobject.h", 1448, 0x4C3F7FE2}
-};
-
-BEGIN_TEST(read0, "load an empty index")
- git_index *index;
-
- must_pass(git_index_open(&index, "in-memory-index"));
- must_be_true(index->on_disk == 0);
-
- must_be_true(git_index_entrycount(index) == 0);
- must_be_true(index->entries.sorted);
-
- git_index_free(index);
-END_TEST
-
-BEGIN_TEST(read1, "load a standard index (default test index)")
- git_index *index;
- unsigned int i;
- git_index_entry **entries;
-
- must_pass(git_index_open(&index, TEST_INDEX_PATH));
- must_be_true(index->on_disk);
-
- must_be_true(git_index_entrycount(index) == TEST_INDEX_ENTRY_COUNT);
- must_be_true(index->entries.sorted);
-
- entries = (git_index_entry **)index->entries.contents;
-
- for (i = 0; i < ARRAY_SIZE(TEST_ENTRIES); ++i) {
- git_index_entry *e = entries[TEST_ENTRIES[i].index];
-
- must_be_true(strcmp(e->path, TEST_ENTRIES[i].path) == 0);
- must_be_true(e->mtime.seconds == TEST_ENTRIES[i].mtime);
- must_be_true(e->file_size == TEST_ENTRIES[i].file_size);
- }
-
- git_index_free(index);
-END_TEST
-
-BEGIN_TEST(read2, "load a standard index (git.git index)")
- git_index *index;
-
- must_pass(git_index_open(&index, TEST_INDEX2_PATH));
- must_be_true(index->on_disk);
-
- must_be_true(git_index_entrycount(index) == TEST_INDEX2_ENTRY_COUNT);
- must_be_true(index->entries.sorted);
- must_be_true(index->tree != NULL);
-
- git_index_free(index);
-END_TEST
-
-BEGIN_TEST(find0, "find an entry on an index")
- git_index *index;
- unsigned int i;
-
- must_pass(git_index_open(&index, TEST_INDEX_PATH));
-
- for (i = 0; i < ARRAY_SIZE(TEST_ENTRIES); ++i) {
- int idx = git_index_find(index, TEST_ENTRIES[i].path);
- must_be_true((unsigned int)idx == TEST_ENTRIES[i].index);
- }
-
- git_index_free(index);
-END_TEST
-
-BEGIN_TEST(find1, "find an entry in an empty index")
- git_index *index;
- unsigned int i;
-
- must_pass(git_index_open(&index, "fake-index"));
-
- for (i = 0; i < ARRAY_SIZE(TEST_ENTRIES); ++i) {
- int idx = git_index_find(index, TEST_ENTRIES[i].path);
- must_be_true(idx == GIT_ENOTFOUND);
- }
-
- git_index_free(index);
-END_TEST
-
-BEGIN_TEST(write0, "write an index back to disk")
- git_index *index;
-
- must_pass(copy_file(TEST_INDEXBIG_PATH, "index_rewrite"));
-
- must_pass(git_index_open(&index, "index_rewrite"));
- must_be_true(index->on_disk);
-
- must_pass(git_index_write(index));
- must_pass(cmp_files(TEST_INDEXBIG_PATH, "index_rewrite"));
-
- git_index_free(index);
-
- p_unlink("index_rewrite");
-END_TEST
-
-BEGIN_TEST(sort0, "sort the entires in an index")
- /*
- * TODO: This no longer applies:
- * index sorting in Git uses some specific changes to the way
- * directories are sorted.
- *
- * We need to specificially check for this by creating a new
- * index, adding entries in random order and then
- * checking for consistency
- */
-END_TEST
-
-BEGIN_TEST(sort1, "sort the entires in an empty index")
- git_index *index;
-
- must_pass(git_index_open(&index, "fake-index"));
-
- /* FIXME: this test is slightly dumb */
- must_be_true(index->entries.sorted);
-
- git_index_free(index);
-END_TEST
-
-BEGIN_TEST(add0, "add a new file to the index")
- git_index *index;
- git_filebuf file;
- git_repository *repo;
- git_index_entry *entry;
- git_oid id1;
-
- /* Intialize a new repository */
- must_pass(git_repository_init(&repo, TEMP_REPO_FOLDER "myrepo", 0));
-
- /* Ensure we're the only guy in the room */
- must_pass(git_repository_index(&index, repo));
- must_pass(git_index_entrycount(index) == 0);
-
- /* Create a new file in the working directory */
- must_pass(git_futils_mkpath2file(TEMP_REPO_FOLDER "myrepo/test.txt"));
- must_pass(git_filebuf_open(&file, TEMP_REPO_FOLDER "myrepo/test.txt", 0));
- must_pass(git_filebuf_write(&file, "hey there\n", 10));
- must_pass(git_filebuf_commit(&file));
-
- /* Store the expected hash of the file/blob
- * This has been generated by executing the following
- * $ echo "hey there" | git hash-object --stdin
- */
- must_pass(git_oid_fromstr(&id1, "a8233120f6ad708f843d861ce2b7228ec4e3dec6"));
-
- /* Add the new file to the index */
- must_pass(git_index_add(index, "test.txt", 0));
-
- /* Wow... it worked! */
- must_pass(git_index_entrycount(index) == 1);
- entry = git_index_get(index, 0);
-
- /* And the built-in hashing mechanism worked as expected */
- must_be_true(git_oid_cmp(&id1, &entry->oid) == 0);
-
- git_index_free(index);
- git_repository_free(repo);
- must_pass(git_futils_rmdir_r(TEMP_REPO_FOLDER, 1));
-END_TEST
-
-BEGIN_SUITE(index)
- ADD_TEST(read0);
- ADD_TEST(read1);
- ADD_TEST(read2);
-
- ADD_TEST(find0);
- ADD_TEST(find1);
-
- ADD_TEST(write0);
-
- ADD_TEST(sort0);
- ADD_TEST(sort1);
-
- ADD_TEST(add0);
-END_SUITE
diff --git a/tests/t07-hashtable.c b/tests/t07-hashtable.c
deleted file mode 100644
index 7313f2cc9..000000000
--- a/tests/t07-hashtable.c
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#include "test_lib.h"
-#include "test_helpers.h"
-
-#include "hashtable.h"
-#include "hash.h"
-
-typedef struct _aux_object {
- int __bulk;
- git_oid id;
- int visited;
-} table_item;
-
-uint32_t hash_func(const void *key, int hash_id)
-{
- uint32_t r;
- const git_oid *id = key;
-
- memcpy(&r, id->id + (hash_id * sizeof(uint32_t)), sizeof(r));
- return r;
-}
-
-int hash_cmpkey(const void *a, const void *b)
-{
- return git_oid_cmp(a, b);
-}
-
-BEGIN_TEST(table0, "create a new hashtable")
-
- git_hashtable *table = NULL;
-
- table = git_hashtable_alloc(55, hash_func, hash_cmpkey);
- must_be_true(table != NULL);
- must_be_true(table->size_mask + 1 == 64);
-
- git_hashtable_free(table);
-
-END_TEST
-
-BEGIN_TEST(table1, "fill the hashtable with random entries")
-
- const int objects_n = 32;
- int i;
-
- table_item *objects;
- git_hashtable *table = NULL;
-
- table = git_hashtable_alloc(objects_n * 2, hash_func, hash_cmpkey);
- must_be_true(table != NULL);
-
- objects = git__malloc(objects_n * sizeof(table_item));
- memset(objects, 0x0, objects_n * sizeof(table_item));
-
- /* populate the hash table */
- for (i = 0; i < objects_n; ++i) {
- git_hash_buf(&(objects[i].id), &i, sizeof(int));
- must_pass(git_hashtable_insert(table, &(objects[i].id), &(objects[i])));
- }
-
- /* make sure all the inserted objects can be found */
- for (i = 0; i < objects_n; ++i) {
- git_oid id;
- table_item *ob;
-
- git_hash_buf(&id, &i, sizeof(int));
- ob = (table_item *)git_hashtable_lookup(table, &id);
-
- must_be_true(ob != NULL);
- must_be_true(ob == &(objects[i]));
- }
-
- /* make sure we cannot find inexisting objects */
- for (i = 0; i < 50; ++i) {
- int hash_id;
- git_oid id;
-
- hash_id = (rand() % 50000) + objects_n;
- git_hash_buf(&id, &hash_id, sizeof(int));
- must_be_true(git_hashtable_lookup(table, &id) == NULL);
- }
-
- git_hashtable_free(table);
- free(objects);
-
-END_TEST
-
-
-BEGIN_TEST(table2, "make sure the table resizes automatically")
-
- const int objects_n = 64;
- int i;
- unsigned int old_size;
- table_item *objects;
- git_hashtable *table = NULL;
-
- table = git_hashtable_alloc(objects_n, hash_func, hash_cmpkey);
- must_be_true(table != NULL);
-
- objects = git__malloc(objects_n * sizeof(table_item));
- memset(objects, 0x0, objects_n * sizeof(table_item));
-
- old_size = table->size_mask + 1;
-
- /* populate the hash table -- should be automatically resized */
- for (i = 0; i < objects_n; ++i) {
- git_hash_buf(&(objects[i].id), &i, sizeof(int));
- must_pass(git_hashtable_insert(table, &(objects[i].id), &(objects[i])));
- }
-
- must_be_true(table->size_mask > old_size);
-
- /* make sure all the inserted objects can be found */
- for (i = 0; i < objects_n; ++i) {
- git_oid id;
- table_item *ob;
-
- git_hash_buf(&id, &i, sizeof(int));
- ob = (table_item *)git_hashtable_lookup(table, &id);
-
- must_be_true(ob != NULL);
- must_be_true(ob == &(objects[i]));
- }
-
- git_hashtable_free(table);
- free(objects);
-
-END_TEST
-
-BEGIN_TEST(tableit0, "iterate through all the contents of the table")
-
- const int objects_n = 32;
- int i;
- table_item *objects, *ob;
- const void *GIT_UNUSED(_unused);
-
- git_hashtable *table = NULL;
-
- table = git_hashtable_alloc(objects_n * 2, hash_func, hash_cmpkey);
- must_be_true(table != NULL);
-
- objects = git__malloc(objects_n * sizeof(table_item));
- memset(objects, 0x0, objects_n * sizeof(table_item));
-
- /* populate the hash table */
- for (i = 0; i < objects_n; ++i) {
- git_hash_buf(&(objects[i].id), &i, sizeof(int));
- must_pass(git_hashtable_insert(table, &(objects[i].id), &(objects[i])));
- }
-
- GIT_HASHTABLE_FOREACH(table, _unused, ob,
- ob->visited = 1;
- );
-
- /* make sure all nodes have been visited */
- for (i = 0; i < objects_n; ++i)
- must_be_true(objects[i].visited);
-
- git_hashtable_free(table);
- free(objects);
-END_TEST
-
-
-BEGIN_SUITE(hashtable)
- ADD_TEST(table0);
- ADD_TEST(table1);
- ADD_TEST(table2);
- ADD_TEST(tableit0);
-END_SUITE
-
diff --git a/tests/t08-tag.c b/tests/t08-tag.c
deleted file mode 100644
index b0d4af87b..000000000
--- a/tests/t08-tag.c
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#include "test_lib.h"
-#include "test_helpers.h"
-
-#include "tag.h"
-
-static const char *tag1_id = "b25fa35b38051e4ae45d4222e795f9df2e43f1d1";
-static const char *tag2_id = "7b4384978d2493e851f9cca7858815fac9b10980";
-static const char *tagged_commit = "e90810b8df3e80c413d903f631643c716887138d";
-
-BEGIN_TEST(read0, "read and parse a tag from the repository")
- git_repository *repo;
- git_tag *tag1, *tag2;
- git_commit *commit;
- git_oid id1, id2, id_commit;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
-
- git_oid_fromstr(&id1, tag1_id);
- git_oid_fromstr(&id2, tag2_id);
- git_oid_fromstr(&id_commit, tagged_commit);
-
- must_pass(git_tag_lookup(&tag1, repo, &id1));
-
- must_be_true(strcmp(git_tag_name(tag1), "test") == 0);
- must_be_true(git_tag_type(tag1) == GIT_OBJ_TAG);
-
- must_pass(git_tag_target((git_object **)&tag2, tag1));
- must_be_true(tag2 != NULL);
-
- must_be_true(git_oid_cmp(&id2, git_tag_id(tag2)) == 0);
-
- must_pass(git_tag_target((git_object **)&commit, tag2));
- must_be_true(commit != NULL);
-
- must_be_true(git_oid_cmp(&id_commit, git_commit_id(commit)) == 0);
-
- git_tag_close(tag1);
- git_tag_close(tag2);
- git_commit_close(commit);
- git_repository_free(repo);
-END_TEST
-
-BEGIN_TEST(read1, "list all tag names from the repository")
- git_repository *repo;
- git_strarray tag_list;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
- must_pass(git_tag_list(&tag_list, repo));
-
- must_be_true(tag_list.count == 3);
-
- git_strarray_free(&tag_list);
- git_repository_free(repo);
-END_TEST
-
-static int ensure_tag_pattern_match(git_repository *repo, const char *pattern, const size_t expected_matches)
-{
- git_strarray tag_list;
- int error = GIT_SUCCESS;
-
- if ((error = git_tag_list_match(&tag_list, pattern, repo)) < GIT_SUCCESS)
- goto exit;
-
- if (tag_list.count != expected_matches)
- error = GIT_ERROR;
-
-exit:
- git_strarray_free(&tag_list);
- return error;
-}
-
-BEGIN_TEST(read2, "list all tag names from the repository matching a specified pattern")
- git_repository *repo;
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
- must_pass(ensure_tag_pattern_match(repo, "", 3));
- must_pass(ensure_tag_pattern_match(repo, "*", 3));
- must_pass(ensure_tag_pattern_match(repo, "t*", 1));
- must_pass(ensure_tag_pattern_match(repo, "*b", 2));
- must_pass(ensure_tag_pattern_match(repo, "e", 0));
- must_pass(ensure_tag_pattern_match(repo, "e90810b", 1));
- must_pass(ensure_tag_pattern_match(repo, "e90810[ab]", 1));
- git_repository_free(repo);
-END_TEST
-
-
-#define TAGGER_NAME "Vicent Marti"
-#define TAGGER_EMAIL "vicent@github.com"
-#define TAGGER_MESSAGE "This is my tag.\n\nThere are many tags, but this one is mine\n"
-
-BEGIN_TEST(write0, "write a tag to the repository and read it again")
- git_repository *repo;
- git_tag *tag;
- git_oid target_id, tag_id;
- git_signature *tagger;
- const git_signature *tagger1;
- git_reference *ref_tag;
- git_object *target;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
-
- git_oid_fromstr(&target_id, tagged_commit);
- must_pass(git_object_lookup(&target, repo, &target_id, GIT_OBJ_COMMIT));
-
- /* create signature */
- must_pass(git_signature_new(&tagger, TAGGER_NAME, TAGGER_EMAIL, 123456789, 60));
-
- must_pass(git_tag_create(
- &tag_id, /* out id */
- repo,
- "the-tag",
- target,
- tagger,
- TAGGER_MESSAGE,
- 0));
-
- git_object_close(target);
- git_signature_free(tagger);
-
- must_pass(git_tag_lookup(&tag, repo, &tag_id));
- must_be_true(git_oid_cmp(git_tag_target_oid(tag), &target_id) == 0);
-
- /* Check attributes were set correctly */
- tagger1 = git_tag_tagger(tag);
- must_be_true(tagger1 != NULL);
- must_be_true(strcmp(tagger1->name, TAGGER_NAME) == 0);
- must_be_true(strcmp(tagger1->email, TAGGER_EMAIL) == 0);
- must_be_true(tagger1->when.time == 123456789);
- must_be_true(tagger1->when.offset == 60);
-
- must_be_true(strcmp(git_tag_message(tag), TAGGER_MESSAGE) == 0);
-
- must_pass(git_reference_lookup(&ref_tag, repo, "refs/tags/the-tag"));
- must_be_true(git_oid_cmp(git_reference_oid(ref_tag), &tag_id) == 0);
- must_pass(git_reference_delete(ref_tag));
-
- must_pass(remove_loose_object(REPOSITORY_FOLDER, (git_object *)tag));
-
- git_tag_close(tag);
- git_repository_free(repo);
-
-END_TEST
-
-BEGIN_TEST(write2, "Attempt to write a tag bearing the same name than an already existing tag")
- git_repository *repo;
- git_oid target_id, tag_id;
- git_signature *tagger;
- git_object *target;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
-
- git_oid_fromstr(&target_id, tagged_commit);
- must_pass(git_object_lookup(&target, repo, &target_id, GIT_OBJ_COMMIT));
-
- /* create signature */
- must_pass(git_signature_new(&tagger, TAGGER_NAME, TAGGER_EMAIL, 123456789, 60));
-
- must_fail(git_tag_create(
- &tag_id, /* out id */
- repo,
- "e90810b",
- target,
- tagger,
- TAGGER_MESSAGE,
- 0));
-
- git_object_close(target);
- git_signature_free(tagger);
-
- git_repository_free(repo);
-
-END_TEST
-
-BEGIN_TEST(write3, "Replace an already existing tag")
- git_repository *repo;
- git_oid target_id, tag_id, old_tag_id;
- git_signature *tagger;
- git_reference *ref_tag;
- git_object *target;
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- git_oid_fromstr(&target_id, tagged_commit);
- must_pass(git_object_lookup(&target, repo, &target_id, GIT_OBJ_COMMIT));
-
- must_pass(git_reference_lookup(&ref_tag, repo, "refs/tags/e90810b"));
- git_oid_cpy(&old_tag_id, git_reference_oid(ref_tag));
-
- /* create signature */
- must_pass(git_signature_new(&tagger, TAGGER_NAME, TAGGER_EMAIL, 123456789, 60));
-
- must_pass(git_tag_create(
- &tag_id, /* out id */
- repo,
- "e90810b",
- target,
- tagger,
- TAGGER_MESSAGE,
- 1));
-
- git_object_close(target);
- git_signature_free(tagger);
-
- must_pass(git_reference_lookup(&ref_tag, repo, "refs/tags/e90810b"));
- must_be_true(git_oid_cmp(git_reference_oid(ref_tag), &tag_id) == 0);
- must_be_true(git_oid_cmp(git_reference_oid(ref_tag), &old_tag_id) != 0);
-
- close_temp_repo(repo);
-
-END_TEST
-
-BEGIN_TEST(write4, "write a lightweight tag to the repository and read it again")
- git_repository *repo;
- git_oid target_id, object_id;
- git_reference *ref_tag;
- git_object *target;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
-
- git_oid_fromstr(&target_id, tagged_commit);
- must_pass(git_object_lookup(&target, repo, &target_id, GIT_OBJ_COMMIT));
-
- must_pass(git_tag_create_lightweight(
- &object_id,
- repo,
- "light-tag",
- target,
- 0));
-
- git_object_close(target);
-
- must_be_true(git_oid_cmp(&object_id, &target_id) == 0);
-
- must_pass(git_reference_lookup(&ref_tag, repo, "refs/tags/light-tag"));
- must_be_true(git_oid_cmp(git_reference_oid(ref_tag), &target_id) == 0);
-
- must_pass(git_tag_delete(repo, "light-tag"));
-
- git_repository_free(repo);
-END_TEST
-
-BEGIN_TEST(write5, "Attempt to write a lightweight tag bearing the same name than an already existing tag")
- git_repository *repo;
- git_oid target_id, object_id, existing_object_id;
- git_object *target;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
-
- git_oid_fromstr(&target_id, tagged_commit);
- must_pass(git_object_lookup(&target, repo, &target_id, GIT_OBJ_COMMIT));
-
- must_fail(git_tag_create_lightweight(
- &object_id,
- repo,
- "e90810b",
- target,
- 0));
-
- git_oid_fromstr(&existing_object_id, tag2_id);
- must_be_true(git_oid_cmp(&object_id, &existing_object_id) == 0);
-
- git_object_close(target);
-
- git_repository_free(repo);
-END_TEST
-
-BEGIN_TEST(delete0, "Delete an already existing tag")
- git_repository *repo;
- git_reference *ref_tag;
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- must_pass(git_tag_delete(repo, "e90810b"));
-
- must_fail(git_reference_lookup(&ref_tag, repo, "refs/tags/e90810b"));
-
- close_temp_repo(repo);
-END_TEST
-
-BEGIN_SUITE(tag)
- ADD_TEST(read0);
- ADD_TEST(read1);
- ADD_TEST(read2);
-
- ADD_TEST(write0);
- ADD_TEST(write2);
- ADD_TEST(write3);
- ADD_TEST(write4);
- ADD_TEST(write5);
-
- ADD_TEST(delete0);
-
-END_SUITE
diff --git a/tests/t09-tree.c b/tests/t09-tree.c
deleted file mode 100644
index 543aea8d4..000000000
--- a/tests/t09-tree.c
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#include "test_lib.h"
-#include "test_helpers.h"
-
-#include "tree.h"
-
-static const char *tree_oid = "1810dff58d8a660512d4832e740f692884338ccd";
-
-static const char *blob_oid = "fa49b077972391ad58037050f2a75f74e3671e92";
-static const char *first_tree = "181037049a54a1eb5fab404658a3a250b44335d7";
-static const char *second_tree = "f60079018b664e4e79329a7ef9559c8d9e0378d1";
-static const char *third_tree = "eb86d8b81d6adbd5290a935d6c9976882de98488";
-
-#if 0
-static int print_tree(git_repository *repo, const git_oid *tree_oid, int depth)
-{
- static const char *indent = " ";
- git_tree *tree;
- unsigned int i;
-
- if (git_tree_lookup(&tree, repo, tree_oid) < GIT_SUCCESS)
- return GIT_ERROR;
-
- for (i = 0; i < git_tree_entrycount(tree); ++i) {
- const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
- char entry_oid[40];
-
- git_oid_fmt(entry_oid, &entry->oid);
- printf("%.*s%o [%.*s] %s\n", depth*2, indent, entry->attr, 40, entry_oid, entry->filename);
-
- if (entry->attr == S_IFDIR) {
- if (print_tree(repo, &entry->oid, depth + 1) < GIT_SUCCESS) {
- git_tree_close(tree);
- return GIT_ERROR;
- }
- }
- }
-
- git_tree_close(tree);
- return GIT_SUCCESS;
-}
-#endif
-
-BEGIN_TEST(read0, "acces randomly the entries on a loaded tree")
- git_oid id;
- git_repository *repo;
- git_tree *tree;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
-
- git_oid_fromstr(&id, tree_oid);
-
- must_pass(git_tree_lookup(&tree, repo, &id));
-
- must_be_true(git_tree_entry_byname(tree, "README") != NULL);
- must_be_true(git_tree_entry_byname(tree, "NOTEXISTS") == NULL);
- must_be_true(git_tree_entry_byname(tree, "") == NULL);
- must_be_true(git_tree_entry_byindex(tree, 0) != NULL);
- must_be_true(git_tree_entry_byindex(tree, 2) != NULL);
- must_be_true(git_tree_entry_byindex(tree, 3) == NULL);
- must_be_true(git_tree_entry_byindex(tree, (unsigned int)-1) == NULL);
-
- git_tree_close(tree);
- git_repository_free(repo);
-END_TEST
-
-BEGIN_TEST(read1, "read a tree from the repository")
- git_oid id;
- git_repository *repo;
- git_tree *tree;
- const git_tree_entry *entry;
- git_object *obj;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
-
- git_oid_fromstr(&id, tree_oid);
-
- must_pass(git_tree_lookup(&tree, repo, &id));
-
- must_be_true(git_tree_entrycount(tree) == 3);
-
- /* GH-86: git_object_lookup() should also check the type if the object comes from the cache */
- must_be_true(git_object_lookup(&obj, repo, &id, GIT_OBJ_TREE) == 0);
- must_be_true(obj != NULL);
- git_object_close(obj);
- obj = NULL;
- must_be_true(git_object_lookup(&obj, repo, &id, GIT_OBJ_BLOB) == GIT_EINVALIDTYPE);
- must_be_true(obj == NULL);
-
- entry = git_tree_entry_byname(tree, "README");
- must_be_true(entry != NULL);
-
- must_be_true(strcmp(git_tree_entry_name(entry), "README") == 0);
-
- must_pass(git_tree_entry_2object(&obj, repo, entry));
- must_be_true(obj != NULL);
-
- git_object_close(obj);
- git_tree_close(tree);
- git_repository_free(repo);
-END_TEST
-
-#if 0
-BEGIN_TEST(write0, "write a tree from an index")
- git_repository *repo;
- git_index *index;
- git_oid tree_oid;
-
- must_pass(git_repository_open(&repo, "/tmp/redtmp/.git"));
- must_pass(git_repository_index(&index, repo));
-
- must_pass(git_tree_create_fromindex(&tree_oid, index));
- must_pass(print_tree(repo, &tree_oid, 0));
-
- git_repository_free(repo);
-END_TEST
-#endif
-
-BEGIN_TEST(write2, "write a tree from a memory")
- git_repository *repo;
- git_treebuilder *builder;
- git_tree *tree;
- git_oid id, bid, rid, id2;
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
- git_oid_fromstr(&id, first_tree);
- git_oid_fromstr(&id2, second_tree);
- git_oid_fromstr(&bid, blob_oid);
-
- //create a second tree from first tree using `git_treebuilder_insert` on REPOSITORY_FOLDER.
- must_pass(git_tree_lookup(&tree, repo, &id));
- must_pass(git_treebuilder_create(&builder, tree));
-
- must_fail(git_treebuilder_insert(NULL, builder, "", &bid, 0100644));
- must_fail(git_treebuilder_insert(NULL, builder, "/", &bid, 0100644));
- must_fail(git_treebuilder_insert(NULL, builder, "folder/new.txt", &bid, 0100644));
-
- must_pass(git_treebuilder_insert(NULL,builder,"new.txt",&bid,0100644));
- must_pass(git_treebuilder_write(&rid,repo,builder));
-
- must_be_true(git_oid_cmp(&rid, &id2) == 0);
-
- git_treebuilder_free(builder);
- git_tree_close(tree);
- close_temp_repo(repo);
-END_TEST
-
-BEGIN_TEST(write3, "write a hierarchical tree from a memory")
- git_repository *repo;
- git_treebuilder *builder;
- git_tree *tree;
- git_oid id, bid, subtree_id, id2, id3;
- git_oid id_hiearar;
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
- git_oid_fromstr(&id, first_tree);
- git_oid_fromstr(&id2, second_tree);
- git_oid_fromstr(&id3, third_tree);
- git_oid_fromstr(&bid, blob_oid);
-
- //create subtree
- must_pass(git_treebuilder_create(&builder, NULL));
- must_pass(git_treebuilder_insert(NULL,builder,"new.txt",&bid,0100644));
- must_pass(git_treebuilder_write(&subtree_id,repo,builder));
- git_treebuilder_free(builder);
-
- // create parent tree
- must_pass(git_tree_lookup(&tree, repo, &id));
- must_pass(git_treebuilder_create(&builder, tree));
- must_pass(git_treebuilder_insert(NULL,builder,"new",&subtree_id,040000));
- must_pass(git_treebuilder_write(&id_hiearar,repo,builder));
- git_treebuilder_free(builder);
- git_tree_close(tree);
-
- must_be_true(git_oid_cmp(&id_hiearar, &id3) == 0);
-
- // check data is correct
- must_pass(git_tree_lookup(&tree, repo, &id_hiearar));
- must_be_true(2 == git_tree_entrycount(tree));
- git_tree_close(tree);
-
- close_temp_repo(repo);
-
-END_TEST
-
-BEGIN_SUITE(tree)
- //ADD_TEST(print0);
- ADD_TEST(read0);
- ADD_TEST(read1);
- //ADD_TEST(write0);
- //ADD_TEST(write1);
- ADD_TEST(write2);
- ADD_TEST(write3);
-END_SUITE
-
diff --git a/tests/t10-refs.c b/tests/t10-refs.c
deleted file mode 100644
index f80c3f510..000000000
--- a/tests/t10-refs.c
+++ /dev/null
@@ -1,1146 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#include "test_lib.h"
-#include "test_helpers.h"
-
-#include "repository.h"
-
-#include "git2/reflog.h"
-#include "reflog.h"
-
-static const char *loose_tag_ref_name = "refs/tags/e90810b";
-static const char *non_existing_tag_ref_name = "refs/tags/i-do-not-exist";
-
-BEGIN_TEST(readtag0, "lookup a loose tag reference")
- git_repository *repo;
- git_reference *reference;
- git_object *object;
- char ref_name_from_tag_name[GIT_REFNAME_MAX];
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
-
- must_pass(git_reference_lookup(&reference, repo, loose_tag_ref_name));
- must_be_true(reference->type & GIT_REF_OID);
- must_be_true((reference->type & GIT_REF_PACKED) == 0);
- must_be_true(strcmp(reference->name, loose_tag_ref_name) == 0);
-
- must_pass(git_object_lookup(&object, repo, git_reference_oid(reference), GIT_OBJ_ANY));
- must_be_true(object != NULL);
- must_be_true(git_object_type(object) == GIT_OBJ_TAG);
-
- /* Ensure the name of the tag matches the name of the reference */
- git_path_join(ref_name_from_tag_name, GIT_REFS_TAGS_DIR, git_tag_name((git_tag *)object));
- must_be_true(strcmp(ref_name_from_tag_name, loose_tag_ref_name) == 0);
-
- git_object_close(object);
- git_repository_free(repo);
-END_TEST
-
-BEGIN_TEST(readtag1, "lookup a loose tag reference that doesn't exist")
- git_repository *repo;
- git_reference *reference;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
- must_fail(git_reference_lookup(&reference, repo, non_existing_tag_ref_name));
-
- git_repository_free(repo);
-END_TEST
-
-static const char *head_tracker_sym_ref_name = "head-tracker";
-static const char *current_head_target = "refs/heads/master";
-static const char *current_master_tip = "be3563ae3f795b2b4353bcce3a527ad0a4f7f644";
-
-BEGIN_TEST(readsym0, "lookup a symbolic reference")
- git_repository *repo;
- git_reference *reference, *resolved_ref;
- git_object *object;
- git_oid id;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
-
- must_pass(git_reference_lookup(&reference, repo, GIT_HEAD_FILE));
- must_be_true(reference->type & GIT_REF_SYMBOLIC);
- must_be_true((reference->type & GIT_REF_PACKED) == 0);
- must_be_true(strcmp(reference->name, GIT_HEAD_FILE) == 0);
-
- must_pass(git_reference_resolve(&resolved_ref, reference));
- must_be_true(resolved_ref->type == GIT_REF_OID);
-
- must_pass(git_object_lookup(&object, repo, git_reference_oid(resolved_ref), GIT_OBJ_ANY));
- must_be_true(object != NULL);
- must_be_true(git_object_type(object) == GIT_OBJ_COMMIT);
-
- git_oid_fromstr(&id, current_master_tip);
- must_be_true(git_oid_cmp(&id, git_object_id(object)) == 0);
-
- git_object_close(object);
- git_repository_free(repo);
-END_TEST
-
-BEGIN_TEST(readsym1, "lookup a nested symbolic reference")
- git_repository *repo;
- git_reference *reference, *resolved_ref;
- git_object *object;
- git_oid id;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
-
- must_pass(git_reference_lookup(&reference, repo, head_tracker_sym_ref_name));
- must_be_true(reference->type & GIT_REF_SYMBOLIC);
- must_be_true((reference->type & GIT_REF_PACKED) == 0);
- must_be_true(strcmp(reference->name, head_tracker_sym_ref_name) == 0);
-
- must_pass(git_reference_resolve(&resolved_ref, reference));
- must_be_true(resolved_ref->type == GIT_REF_OID);
-
- must_pass(git_object_lookup(&object, repo, git_reference_oid(resolved_ref), GIT_OBJ_ANY));
- must_be_true(object != NULL);
- must_be_true(git_object_type(object) == GIT_OBJ_COMMIT);
-
- git_oid_fromstr(&id, current_master_tip);
- must_be_true(git_oid_cmp(&id, git_object_id(object)) == 0);
-
- git_object_close(object);
- git_repository_free(repo);
-END_TEST
-
-BEGIN_TEST(readsym2, "lookup the HEAD and resolve the master branch")
- git_repository *repo;
- git_reference *reference, *resolved_ref, *comp_base_ref;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
-
- must_pass(git_reference_lookup(&reference, repo, head_tracker_sym_ref_name));
- must_pass(git_reference_resolve(&resolved_ref, reference));
- comp_base_ref = resolved_ref;
-
- must_pass(git_reference_lookup(&reference, repo, GIT_HEAD_FILE));
- must_pass(git_reference_resolve(&resolved_ref, reference));
- must_pass(git_oid_cmp(git_reference_oid(comp_base_ref), git_reference_oid(resolved_ref)));
-
- must_pass(git_reference_lookup(&reference, repo, current_head_target));
- must_pass(git_reference_resolve(&resolved_ref, reference));
- must_pass(git_oid_cmp(git_reference_oid(comp_base_ref), git_reference_oid(resolved_ref)));
-
- git_repository_free(repo);
-END_TEST
-
-BEGIN_TEST(readsym3, "lookup the master branch and then the HEAD")
- git_repository *repo;
- git_reference *reference, *master_ref, *resolved_ref;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
-
- must_pass(git_reference_lookup(&master_ref, repo, current_head_target));
- must_pass(git_reference_lookup(&reference, repo, GIT_HEAD_FILE));
-
- must_pass(git_reference_resolve(&resolved_ref, reference));
- must_pass(git_oid_cmp(git_reference_oid(master_ref), git_reference_oid(resolved_ref)));
-
- git_repository_free(repo);
-END_TEST
-
-static const char *packed_head_name = "refs/heads/packed";
-static const char *packed_test_head_name = "refs/heads/packed-test";
-
-BEGIN_TEST(readpacked0, "lookup a packed reference")
- git_repository *repo;
- git_reference *reference;
- git_object *object;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
-
- must_pass(git_reference_lookup(&reference, repo, packed_head_name));
- must_be_true(reference->type & GIT_REF_OID);
- must_be_true((reference->type & GIT_REF_PACKED) != 0);
- must_be_true(strcmp(reference->name, packed_head_name) == 0);
-
- must_pass(git_object_lookup(&object, repo, git_reference_oid(reference), GIT_OBJ_ANY));
- must_be_true(object != NULL);
- must_be_true(git_object_type(object) == GIT_OBJ_COMMIT);
-
- git_object_close(object);
- git_repository_free(repo);
-END_TEST
-
-BEGIN_TEST(readpacked1, "assure that a loose reference is looked up before a packed reference")
- git_repository *repo;
- git_reference *reference;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
- must_pass(git_reference_lookup(&reference, repo, packed_head_name));
- must_pass(git_reference_lookup(&reference, repo, packed_test_head_name));
- must_be_true(reference->type & GIT_REF_OID);
- must_be_true((reference->type & GIT_REF_PACKED) == 0);
- must_be_true(strcmp(reference->name, packed_test_head_name) == 0);
-
- git_repository_free(repo);
-END_TEST
-
-BEGIN_TEST(create0, "create a new symbolic reference")
- git_reference *new_reference, *looked_up_ref, *resolved_ref;
- git_repository *repo, *repo2;
- git_oid id;
- char ref_path[GIT_PATH_MAX];
-
- const char *new_head_tracker = "another-head-tracker";
-
- git_oid_fromstr(&id, current_master_tip);
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- /* Retrieve the physical path to the symbolic ref for further cleaning */
- git_path_join(ref_path, repo->path_repository, new_head_tracker);
-
- /* Create and write the new symbolic reference */
- must_pass(git_reference_create_symbolic(&new_reference, repo, new_head_tracker, current_head_target, 0));
-
- /* Ensure the reference can be looked-up... */
- must_pass(git_reference_lookup(&looked_up_ref, repo, new_head_tracker));
- must_be_true(looked_up_ref->type & GIT_REF_SYMBOLIC);
- must_be_true((looked_up_ref->type & GIT_REF_PACKED) == 0);
- must_be_true(strcmp(looked_up_ref->name, new_head_tracker) == 0);
-
- /* ...peeled.. */
- must_pass(git_reference_resolve(&resolved_ref, looked_up_ref));
- must_be_true(resolved_ref->type == GIT_REF_OID);
-
- /* ...and that it points to the current master tip */
- must_be_true(git_oid_cmp(&id, git_reference_oid(resolved_ref)) == 0);
-
- git_repository_free(repo);
-
- /* Similar test with a fresh new repository */
- must_pass(git_repository_open(&repo2, TEMP_REPO_FOLDER));
-
- must_pass(git_reference_lookup(&looked_up_ref, repo2, new_head_tracker));
- must_pass(git_reference_resolve(&resolved_ref, looked_up_ref));
- must_be_true(git_oid_cmp(&id, git_reference_oid(resolved_ref)) == 0);
-
- close_temp_repo(repo2);
-END_TEST
-
-BEGIN_TEST(create1, "create a deep symbolic reference")
- git_reference *new_reference, *looked_up_ref, *resolved_ref;
- git_repository *repo;
- git_oid id;
- char ref_path[GIT_PATH_MAX];
-
- const char *new_head_tracker = "deep/rooted/tracker";
-
- git_oid_fromstr(&id, current_master_tip);
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- git_path_join(ref_path, repo->path_repository, new_head_tracker);
- must_pass(git_reference_create_symbolic(&new_reference, repo, new_head_tracker, current_head_target, 0));
- must_pass(git_reference_lookup(&looked_up_ref, repo, new_head_tracker));
- must_pass(git_reference_resolve(&resolved_ref, looked_up_ref));
- must_be_true(git_oid_cmp(&id, git_reference_oid(resolved_ref)) == 0);
-
- close_temp_repo(repo);
-END_TEST
-
-BEGIN_TEST(create2, "create a new OID reference")
- git_reference *new_reference, *looked_up_ref;
- git_repository *repo, *repo2;
- git_oid id;
- char ref_path[GIT_PATH_MAX];
-
- const char *new_head = "refs/heads/new-head";
-
- git_oid_fromstr(&id, current_master_tip);
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- /* Retrieve the physical path to the symbolic ref for further cleaning */
- git_path_join(ref_path, repo->path_repository, new_head);
-
- /* Create and write the new object id reference */
- must_pass(git_reference_create_oid(&new_reference, repo, new_head, &id, 0));
-
- /* Ensure the reference can be looked-up... */
- must_pass(git_reference_lookup(&looked_up_ref, repo, new_head));
- must_be_true(looked_up_ref->type & GIT_REF_OID);
- must_be_true((looked_up_ref->type & GIT_REF_PACKED) == 0);
- must_be_true(strcmp(looked_up_ref->name, new_head) == 0);
-
- /* ...and that it points to the current master tip */
- must_be_true(git_oid_cmp(&id, git_reference_oid(looked_up_ref)) == 0);
-
- git_repository_free(repo);
-
- /* Similar test with a fresh new repository */
- must_pass(git_repository_open(&repo2, TEMP_REPO_FOLDER));
-
- must_pass(git_reference_lookup(&looked_up_ref, repo2, new_head));
- must_be_true(git_oid_cmp(&id, git_reference_oid(looked_up_ref)) == 0);
-
- close_temp_repo(repo2);
-END_TEST
-
-BEGIN_TEST(create3, "Can not create a new OID reference which targets at an unknown id")
- git_reference *new_reference, *looked_up_ref;
- git_repository *repo;
- git_oid id;
-
- const char *new_head = "refs/heads/new-head";
-
- git_oid_fromstr(&id, "deadbeef3f795b2b4353bcce3a527ad0a4f7f644");
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
-
- /* Create and write the new object id reference */
- must_fail(git_reference_create_oid(&new_reference, repo, new_head, &id, 0));
-
- /* Ensure the reference can't be looked-up... */
- must_fail(git_reference_lookup(&looked_up_ref, repo, new_head));
-
- git_repository_free(repo);
-END_TEST
-
-static const char *ref_name = "refs/heads/other";
-static const char *ref_master_name = "refs/heads/master";
-static const char *ref_branch_name = "refs/heads/branch";
-static const char *ref_test_name = "refs/heads/test";
-BEGIN_TEST(overwrite0, "Overwrite an existing symbolic reference")
- git_reference *ref, *branch_ref;
- git_repository *repo;
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- /* The target needds to exist and we need to check the name has changed */
- must_pass(git_reference_create_symbolic(&branch_ref, repo, ref_branch_name, ref_master_name, 0));
- must_pass(git_reference_create_symbolic(&ref, repo, ref_name, ref_branch_name, 0));
- /* Ensure it points to the right place*/
- must_pass(git_reference_lookup(&ref, repo, ref_name));
- must_be_true(git_reference_type(ref) & GIT_REF_SYMBOLIC);
- must_be_true(!strcmp(git_reference_target(ref), ref_branch_name));
-
- /* Ensure we can't create it unless we force it to */
- must_fail(git_reference_create_symbolic(&ref, repo, ref_name, ref_master_name, 0));
- must_pass(git_reference_create_symbolic(&ref, repo, ref_name, ref_master_name, 1));
-
- /* Ensure it points to the right place */
- must_pass(git_reference_lookup(&ref, repo, ref_name));
- must_be_true(git_reference_type(ref) & GIT_REF_SYMBOLIC);
- must_be_true(!strcmp(git_reference_target(ref), ref_master_name));
-
- close_temp_repo(repo);
-END_TEST
-
-BEGIN_TEST(overwrite1, "Overwrite an existing object id reference")
- git_reference *ref;
- git_repository *repo;
- git_oid id;
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- must_pass(git_reference_lookup(&ref, repo, ref_master_name));
- must_be_true(ref->type & GIT_REF_OID);
- git_oid_cpy(&id, git_reference_oid(ref));
-
- /* Create it */
- must_pass(git_reference_create_oid(&ref, repo, ref_name, &id, 0));
-
- must_pass(git_reference_lookup(&ref, repo, ref_test_name));
- must_be_true(ref->type & GIT_REF_OID);
- git_oid_cpy(&id, git_reference_oid(ref));
-
- /* Ensure we can't overwrite unless we force it */
- must_fail(git_reference_create_oid(&ref, repo, ref_name, &id, 0));
- must_pass(git_reference_create_oid(&ref, repo, ref_name, &id, 1));
-
- /* Ensure it has been overwritten */
- must_pass(git_reference_lookup(&ref, repo, ref_name));
- must_be_true(!git_oid_cmp(&id, git_reference_oid(ref)));
-
- close_temp_repo(repo);
-END_TEST
-
-BEGIN_TEST(overwrite2, "Overwrite an existing object id reference with a symbolic one")
- git_reference *ref;
- git_repository *repo;
- git_oid id;
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- must_pass(git_reference_lookup(&ref, repo, ref_master_name));
- must_be_true(ref->type & GIT_REF_OID);
- git_oid_cpy(&id, git_reference_oid(ref));
-
- must_pass(git_reference_create_oid(&ref, repo, ref_name, &id, 0));
- must_fail(git_reference_create_symbolic(&ref, repo, ref_name, ref_master_name, 0));
- must_pass(git_reference_create_symbolic(&ref, repo, ref_name, ref_master_name, 1));
-
- /* Ensure it points to the right place */
- must_pass(git_reference_lookup(&ref, repo, ref_name));
- must_be_true(git_reference_type(ref) & GIT_REF_SYMBOLIC);
- must_be_true(!strcmp(git_reference_target(ref), ref_master_name));
-
- close_temp_repo(repo);
-END_TEST
-
-BEGIN_TEST(overwrite3, "Overwrite an existing symbolic reference with an object id one")
- git_reference *ref;
- git_repository *repo;
- git_oid id;
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- must_pass(git_reference_lookup(&ref, repo, ref_master_name));
- must_be_true(ref->type & GIT_REF_OID);
- git_oid_cpy(&id, git_reference_oid(ref));
-
- /* Create the symbolic ref */
- must_pass(git_reference_create_symbolic(&ref, repo, ref_name, ref_master_name, 0));
- /* It shouldn't overwrite unless we tell it to */
- must_fail(git_reference_create_oid(&ref, repo, ref_name, &id, 0));
- must_pass(git_reference_create_oid(&ref, repo, ref_name, &id, 1));
-
- /* Ensure it points to the right place */
- must_pass(git_reference_lookup(&ref, repo, ref_name));
- must_be_true(git_reference_type(ref) & GIT_REF_OID);
- must_be_true(!git_oid_cmp(git_reference_oid(ref), &id));
-
- close_temp_repo(repo);
-END_TEST
-
-BEGIN_TEST(pack0, "create a packfile for an empty folder")
- git_repository *repo;
- char temp_path[GIT_PATH_MAX];
- const int mode = 0755; /* or 0777 ? */
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- git_path_join_n(temp_path, 3, repo->path_repository, GIT_REFS_HEADS_DIR, "empty_dir");
- must_pass(git_futils_mkdir_r(temp_path, mode));
-
- must_pass(git_reference_packall(repo));
-
- close_temp_repo(repo);
-END_TEST
-
-BEGIN_TEST(pack1, "create a packfile from all the loose rn a repo")
- git_repository *repo;
- git_reference *reference;
- char temp_path[GIT_PATH_MAX];
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- /* Ensure a known loose ref can be looked up */
- must_pass(git_reference_lookup(&reference, repo, loose_tag_ref_name));
- must_be_true((reference->type & GIT_REF_PACKED) == 0);
- must_be_true(strcmp(reference->name, loose_tag_ref_name) == 0);
-
- /*
- * We are now trying to pack also a loose reference
- * called `points_to_blob`, to make sure we can properly
- * pack weak tags
- */
- must_pass(git_reference_packall(repo));
-
- /* Ensure the packed-refs file exists */
- git_path_join(temp_path, repo->path_repository, GIT_PACKEDREFS_FILE);
- must_pass(git_futils_exists(temp_path));
-
- /* Ensure the known ref can still be looked up but is now packed */
- must_pass(git_reference_lookup(&reference, repo, loose_tag_ref_name));
- must_be_true((reference->type & GIT_REF_PACKED) != 0);
- must_be_true(strcmp(reference->name, loose_tag_ref_name) == 0);
-
- /* Ensure the known ref has been removed from the loose folder structure */
- git_path_join(temp_path, repo->path_repository, loose_tag_ref_name);
- must_pass(!git_futils_exists(temp_path));
-
- close_temp_repo(repo);
-END_TEST
-
-BEGIN_TEST(rename0, "rename a loose reference")
- git_reference *looked_up_ref, *another_looked_up_ref;
- git_repository *repo;
- char temp_path[GIT_PATH_MAX];
- const char *new_name = "refs/tags/Nemo/knows/refs.kung-fu";
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- /* Ensure the ref doesn't exist on the file system */
- git_path_join(temp_path, repo->path_repository, new_name);
- must_pass(!git_futils_exists(temp_path));
-
- /* Retrieval of the reference to rename */
- must_pass(git_reference_lookup(&looked_up_ref, repo, loose_tag_ref_name));
-
- /* ... which is indeed loose */
- must_be_true((looked_up_ref->type & GIT_REF_PACKED) == 0);
-
- /* Now that the reference is renamed... */
- must_pass(git_reference_rename(looked_up_ref, new_name, 0));
- must_be_true(!strcmp(looked_up_ref->name, new_name));
-
- /* ...It can't be looked-up with the old name... */
- must_fail(git_reference_lookup(&another_looked_up_ref, repo, loose_tag_ref_name));
-
- /* ...but the new name works ok... */
- must_pass(git_reference_lookup(&another_looked_up_ref, repo, new_name));
- must_be_true(!strcmp(another_looked_up_ref->name, new_name));
-
- /* .. the ref is still loose... */
- must_be_true((another_looked_up_ref->type & GIT_REF_PACKED) == 0);
- must_be_true((looked_up_ref->type & GIT_REF_PACKED) == 0);
-
- /* ...and the ref can be found in the file system */
- git_path_join(temp_path, repo->path_repository, new_name);
- must_pass(git_futils_exists(temp_path));
-
- close_temp_repo(repo);
-END_TEST
-
-BEGIN_TEST(rename1, "rename a packed reference (should make it loose)")
- git_reference *looked_up_ref, *another_looked_up_ref;
- git_repository *repo;
- char temp_path[GIT_PATH_MAX];
- const char *brand_new_name = "refs/heads/brand_new_name";
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- /* Ensure the ref doesn't exist on the file system */
- git_path_join(temp_path, repo->path_repository, packed_head_name);
- must_pass(!git_futils_exists(temp_path));
-
- /* The reference can however be looked-up... */
- must_pass(git_reference_lookup(&looked_up_ref, repo, packed_head_name));
-
- /* .. and it's packed */
- must_be_true((looked_up_ref->type & GIT_REF_PACKED) != 0);
-
- /* Now that the reference is renamed... */
- must_pass(git_reference_rename(looked_up_ref, brand_new_name, 0));
- must_be_true(!strcmp(looked_up_ref->name, brand_new_name));
-
- /* ...It can't be looked-up with the old name... */
- must_fail(git_reference_lookup(&another_looked_up_ref, repo, packed_head_name));
-
- /* ...but the new name works ok... */
- must_pass(git_reference_lookup(&another_looked_up_ref, repo, brand_new_name));
- must_be_true(!strcmp(another_looked_up_ref->name, brand_new_name));
-
- /* .. the ref is no longer packed... */
- must_be_true((another_looked_up_ref->type & GIT_REF_PACKED) == 0);
- must_be_true((looked_up_ref->type & GIT_REF_PACKED) == 0);
-
- /* ...and the ref now happily lives in the file system */
- git_path_join(temp_path, repo->path_repository, brand_new_name);
- must_pass(git_futils_exists(temp_path));
-
- close_temp_repo(repo);
-END_TEST
-
-BEGIN_TEST(rename2, "renaming a packed reference does not pack another reference which happens to be in both loose and pack state")
- git_reference *looked_up_ref, *another_looked_up_ref;
- git_repository *repo;
- char temp_path[GIT_PATH_MAX];
- const char *brand_new_name = "refs/heads/brand_new_name";
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- /* Ensure the other reference exists on the file system */
- git_path_join(temp_path, repo->path_repository, packed_test_head_name);
- must_pass(git_futils_exists(temp_path));
-
- /* Lookup the other reference */
- must_pass(git_reference_lookup(&another_looked_up_ref, repo, packed_test_head_name));
-
- /* Ensure it's loose */
- must_be_true((another_looked_up_ref->type & GIT_REF_PACKED) == 0);
-
- /* Lookup the reference to rename */
- must_pass(git_reference_lookup(&looked_up_ref, repo, packed_head_name));
-
- /* Ensure it's packed */
- must_be_true((looked_up_ref->type & GIT_REF_PACKED) != 0);
-
- /* Now that the reference is renamed... */
- must_pass(git_reference_rename(looked_up_ref, brand_new_name, 0));
-
- /* Lookup the other reference */
- must_pass(git_reference_lookup(&another_looked_up_ref, repo, packed_test_head_name));
-
- /* Ensure it's loose */
- must_be_true((another_looked_up_ref->type & GIT_REF_PACKED) == 0);
-
- /* Ensure the other ref still exists on the file system */
- must_pass(git_futils_exists(temp_path));
-
- close_temp_repo(repo);
-END_TEST
-
-BEGIN_TEST(rename3, "can not rename a reference with the name of an existing reference")
- git_reference *looked_up_ref;
- git_repository *repo;
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- /* An existing reference... */
- must_pass(git_reference_lookup(&looked_up_ref, repo, packed_head_name));
-
- /* Can not be renamed to the name of another existing reference. */
- must_fail(git_reference_rename(looked_up_ref, packed_test_head_name, 0));
-
- /* Failure to rename it hasn't corrupted its state */
- must_pass(git_reference_lookup(&looked_up_ref, repo, packed_head_name));
- must_be_true(!strcmp(looked_up_ref->name, packed_head_name));
-
- close_temp_repo(repo);
-END_TEST
-
-BEGIN_TEST(rename4, "can not rename a reference with an invalid name")
- git_reference *looked_up_ref;
- git_repository *repo;
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- /* An existing oid reference... */
- must_pass(git_reference_lookup(&looked_up_ref, repo, packed_test_head_name));
-
- /* Can not be renamed with an invalid name. */
- must_fail(git_reference_rename(looked_up_ref, "Hello! I'm a very invalid name.", 0));
-
- /* Can not be renamed outside of the refs hierarchy. */
- must_fail(git_reference_rename(looked_up_ref, "i-will-sudo-you", 0));
-
- /* Failure to rename it hasn't corrupted its state */
- must_pass(git_reference_lookup(&looked_up_ref, repo, packed_test_head_name));
- must_be_true(!strcmp(looked_up_ref->name, packed_test_head_name));
-
- close_temp_repo(repo);
-END_TEST
-
-BEGIN_TEST(rename5, "can force-rename a packed reference with the name of an existing loose and packed reference")
- git_reference *looked_up_ref;
- git_repository *repo;
- git_oid oid;
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- /* An existing reference... */
- must_pass(git_reference_lookup(&looked_up_ref, repo, packed_head_name));
- git_oid_cpy(&oid, git_reference_oid(looked_up_ref));
-
- /* Can be force-renamed to the name of another existing reference. */
- must_pass(git_reference_rename(looked_up_ref, packed_test_head_name, 1));
-
- /* Check we actually renamed it */
- must_pass(git_reference_lookup(&looked_up_ref, repo, packed_test_head_name));
- must_be_true(!strcmp(looked_up_ref->name, packed_test_head_name));
- must_be_true(!git_oid_cmp(&oid, git_reference_oid(looked_up_ref)));
-
- /* And that the previous one doesn't exist any longer */
- must_fail(git_reference_lookup(&looked_up_ref, repo, packed_head_name));
-
- close_temp_repo(repo);
-END_TEST
-
-BEGIN_TEST(rename6, "can force-rename a loose reference with the name of an existing loose reference")
- git_reference *looked_up_ref;
- git_repository *repo;
- git_oid oid;
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- /* An existing reference... */
- must_pass(git_reference_lookup(&looked_up_ref, repo, "refs/heads/br2"));
- git_oid_cpy(&oid, git_reference_oid(looked_up_ref));
-
- /* Can be force-renamed to the name of another existing reference. */
- must_pass(git_reference_rename(looked_up_ref, "refs/heads/test", 1));
-
- /* Check we actually renamed it */
- must_pass(git_reference_lookup(&looked_up_ref, repo, "refs/heads/test"));
- must_be_true(!strcmp(looked_up_ref->name, "refs/heads/test"));
- must_be_true(!git_oid_cmp(&oid, git_reference_oid(looked_up_ref)));
-
- /* And that the previous one doesn't exist any longer */
- must_fail(git_reference_lookup(&looked_up_ref, repo, "refs/heads/br2"));
-
- close_temp_repo(repo);
-END_TEST
-
-static const char *ref_one_name = "refs/heads/one/branch";
-static const char *ref_one_name_new = "refs/heads/two/branch";
-static const char *ref_two_name = "refs/heads/two";
-
-BEGIN_TEST(rename7, "can not overwrite name of existing reference")
- git_reference *ref, *ref_one, *ref_one_new, *ref_two;
- git_repository *repo;
- git_oid id;
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- must_pass(git_reference_lookup(&ref, repo, ref_master_name));
- must_be_true(ref->type & GIT_REF_OID);
-
- git_oid_cpy(&id, git_reference_oid(ref));
-
- /* Create loose references */
- must_pass(git_reference_create_oid(&ref_one, repo, ref_one_name, &id, 0));
- must_pass(git_reference_create_oid(&ref_two, repo, ref_two_name, &id, 0));
-
- /* Pack everything */
- must_pass(git_reference_packall(repo));
-
- /* Attempt to create illegal reference */
- must_fail(git_reference_create_oid(&ref_one_new, repo, ref_one_name_new, &id, 0));
-
- /* Illegal reference couldn't be created so this is supposed to fail */
- must_fail(git_reference_lookup(&ref_one_new, repo, ref_one_name_new));
-
- close_temp_repo(repo);
-END_TEST
-
-static const char *ref_two_name_new = "refs/heads/two/two";
-
-BEGIN_TEST(rename8, "can be renamed to a new name prefixed with the old name")
- git_reference *ref, *ref_two, *looked_up_ref;
- git_repository *repo;
- git_oid id;
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- must_pass(git_reference_lookup(&ref, repo, ref_master_name));
- must_be_true(ref->type & GIT_REF_OID);
-
- git_oid_cpy(&id, git_reference_oid(ref));
-
- /* Create loose references */
- must_pass(git_reference_create_oid(&ref_two, repo, ref_two_name, &id, 0));
-
- /* An existing reference... */
- must_pass(git_reference_lookup(&looked_up_ref, repo, ref_two_name));
-
- /* Can be rename to a new name starting with the old name. */
- must_pass(git_reference_rename(looked_up_ref, ref_two_name_new, 0));
-
- /* Check we actually renamed it */
- must_pass(git_reference_lookup(&looked_up_ref, repo, ref_two_name_new));
- must_be_true(!strcmp(looked_up_ref->name, ref_two_name_new));
- must_fail(git_reference_lookup(&looked_up_ref, repo, ref_two_name));
-
- close_temp_repo(repo);
-END_TEST
-
-BEGIN_TEST(delete0, "deleting a ref which is both packed and loose should remove both tracks in the filesystem")
- git_reference *looked_up_ref, *another_looked_up_ref;
- git_repository *repo;
- char temp_path[GIT_PATH_MAX];
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- /* Ensure the loose reference exists on the file system */
- git_path_join(temp_path, repo->path_repository, packed_test_head_name);
- must_pass(git_futils_exists(temp_path));
-
- /* Lookup the reference */
- must_pass(git_reference_lookup(&looked_up_ref, repo, packed_test_head_name));
-
- /* Ensure it's the loose version that has been found */
- must_be_true((looked_up_ref->type & GIT_REF_PACKED) == 0);
-
- /* Now that the reference is deleted... */
- must_pass(git_reference_delete(looked_up_ref));
-
- /* Looking up the reference once again should not retrieve it */
- must_fail(git_reference_lookup(&another_looked_up_ref, repo, packed_test_head_name));
-
- /* Ensure the loose reference doesn't exist any longer on the file system */
- must_pass(!git_futils_exists(temp_path));
-
- close_temp_repo(repo);
-END_TEST
-
-static int ensure_refname_normalized(int is_oid_ref, const char *input_refname, const char *expected_refname)
-{
- int error = GIT_SUCCESS;
- char buffer_out[GIT_REFNAME_MAX];
-
- if (is_oid_ref)
- error = git_reference__normalize_name_oid(buffer_out, sizeof(buffer_out), input_refname);
- else
- error = git_reference__normalize_name(buffer_out, sizeof(buffer_out), input_refname);
-
- if (error < GIT_SUCCESS)
- return error;
-
- if (expected_refname == NULL)
- return error;
-
- if (strcmp(buffer_out, expected_refname))
- error = GIT_ERROR;
-
- return error;
-}
-
-#define OID_REF 1
-#define SYM_REF 0
-
-BEGIN_TEST(normalize0, "normalize a direct (OID) reference name")
- must_fail(ensure_refname_normalized(OID_REF, "a", NULL));
- must_fail(ensure_refname_normalized(OID_REF, "", NULL));
- must_fail(ensure_refname_normalized(OID_REF, "refs/heads/a/", NULL));
- must_fail(ensure_refname_normalized(OID_REF, "refs/heads/a.", NULL));
- must_fail(ensure_refname_normalized(OID_REF, "refs/heads/a.lock", NULL));
- must_pass(ensure_refname_normalized(OID_REF, "refs/dummy/a", NULL));
- must_pass(ensure_refname_normalized(OID_REF, "refs/stash", NULL));
- must_pass(ensure_refname_normalized(OID_REF, "refs/tags/a", "refs/tags/a"));
- must_pass(ensure_refname_normalized(OID_REF, "refs/heads/a/b", "refs/heads/a/b"));
- must_pass(ensure_refname_normalized(OID_REF, "refs/heads/a./b", "refs/heads/a./b"));
- must_fail(ensure_refname_normalized(OID_REF, "refs/heads/foo?bar", NULL));
- must_fail(ensure_refname_normalized(OID_REF, "refs/heads\foo", NULL));
- must_pass(ensure_refname_normalized(OID_REF, "refs/heads/v@ation", "refs/heads/v@ation"));
- must_pass(ensure_refname_normalized(OID_REF, "refs///heads///a", "refs/heads/a"));
- must_fail(ensure_refname_normalized(OID_REF, "refs/heads/.a/b", NULL));
- must_fail(ensure_refname_normalized(OID_REF, "refs/heads/foo/../bar", NULL));
- must_fail(ensure_refname_normalized(OID_REF, "refs/heads/foo..bar", NULL));
- must_fail(ensure_refname_normalized(OID_REF, "refs/heads/./foo", NULL));
- must_fail(ensure_refname_normalized(OID_REF, "refs/heads/v@{ation", NULL));
-END_TEST
-
-BEGIN_TEST(normalize1, "normalize a symbolic reference name")
- must_pass(ensure_refname_normalized(SYM_REF, "a", "a"));
- must_pass(ensure_refname_normalized(SYM_REF, "a/b", "a/b"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs///heads///a", "refs/heads/a"));
- must_fail(ensure_refname_normalized(SYM_REF, "", NULL));
- must_fail(ensure_refname_normalized(SYM_REF, "heads\foo", NULL));
-END_TEST
-
-/* Ported from JGit, BSD licence.
- * See https://github.com/spearce/JGit/commit/e4bf8f6957bbb29362575d641d1e77a02d906739 */
-BEGIN_TEST(normalize2, "tests borrowed from JGit")
-
-/* EmptyString */
- must_fail(ensure_refname_normalized(SYM_REF, "", NULL));
- must_fail(ensure_refname_normalized(SYM_REF, "/", NULL));
-
-/* MustHaveTwoComponents */
- must_fail(ensure_refname_normalized(OID_REF, "master", NULL));
- must_pass(ensure_refname_normalized(SYM_REF, "heads/master", "heads/master"));
-
-/* ValidHead */
-
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/master", "refs/heads/master"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/pu", "refs/heads/pu"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/z", "refs/heads/z"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/FoO", "refs/heads/FoO"));
-
-/* ValidTag */
- must_pass(ensure_refname_normalized(SYM_REF, "refs/tags/v1.0", "refs/tags/v1.0"));
-
-/* NoLockSuffix */
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master.lock", NULL));
-
-/* NoDirectorySuffix */
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master/", NULL));
-
-/* NoSpace */
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/i haz space", NULL));
-
-/* NoAsciiControlCharacters */
- {
- char c;
- char buffer[GIT_REFNAME_MAX];
- for (c = '\1'; c < ' '; c++) {
- strncpy(buffer, "refs/heads/mast", 15);
- strncpy(buffer + 15, (const char *)&c, 1);
- strncpy(buffer + 16, "er", 2);
- buffer[18 - 1] = '\0';
- must_fail(ensure_refname_normalized(SYM_REF, buffer, NULL));
- }
- }
-
-/* NoBareDot */
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/.", NULL));
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/..", NULL));
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/./master", NULL));
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/../master", NULL));
-
-/* NoLeadingOrTrailingDot */
- must_fail(ensure_refname_normalized(SYM_REF, ".", NULL));
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/.bar", NULL));
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/..bar", NULL));
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/bar.", NULL));
-
-/* ContainsDot */
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/m.a.s.t.e.r", "refs/heads/m.a.s.t.e.r"));
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master..pu", NULL));
-
-/* NoMagicRefCharacters */
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master^", NULL));
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/^master", NULL));
- must_fail(ensure_refname_normalized(SYM_REF, "^refs/heads/master", NULL));
-
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master~", NULL));
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/~master", NULL));
- must_fail(ensure_refname_normalized(SYM_REF, "~refs/heads/master", NULL));
-
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master:", NULL));
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/:master", NULL));
- must_fail(ensure_refname_normalized(SYM_REF, ":refs/heads/master", NULL));
-
-/* ShellGlob */
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master?", NULL));
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/?master", NULL));
- must_fail(ensure_refname_normalized(SYM_REF, "?refs/heads/master", NULL));
-
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master[", NULL));
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/[master", NULL));
- must_fail(ensure_refname_normalized(SYM_REF, "[refs/heads/master", NULL));
-
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master*", NULL));
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/*master", NULL));
- must_fail(ensure_refname_normalized(SYM_REF, "*refs/heads/master", NULL));
-
-/* ValidSpecialCharacters */
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/!", "refs/heads/!"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/\"", "refs/heads/\""));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/#", "refs/heads/#"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/$", "refs/heads/$"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/%", "refs/heads/%"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/&", "refs/heads/&"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/'", "refs/heads/'"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/(", "refs/heads/("));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/)", "refs/heads/)"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/+", "refs/heads/+"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/,", "refs/heads/,"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/-", "refs/heads/-"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/;", "refs/heads/;"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/<", "refs/heads/<"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/=", "refs/heads/="));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/>", "refs/heads/>"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/@", "refs/heads/@"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/]", "refs/heads/]"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/_", "refs/heads/_"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/`", "refs/heads/`"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/{", "refs/heads/{"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/|", "refs/heads/|"));
- must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/}", "refs/heads/}"));
-
- // This is valid on UNIX, but not on Windows
- // hence we make in invalid due to non-portability
- //
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/\\", NULL));
-
-/* UnicodeNames */
- /*
- * Currently this fails.
- * must_pass(ensure_refname_normalized(SYM_REF, "refs/heads/\u00e5ngstr\u00f6m", "refs/heads/\u00e5ngstr\u00f6m"));
- */
-
-/* RefLogQueryIsValidRef */
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master@{1}", NULL));
- must_fail(ensure_refname_normalized(SYM_REF, "refs/heads/master@{1.hour.ago}", NULL));
-END_TEST
-
-BEGIN_TEST(list0, "try to list all the references in our test repo")
- git_repository *repo;
- git_strarray ref_list;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
- must_pass(git_reference_listall(&ref_list, repo, GIT_REF_LISTALL));
-
- /*{
- unsigned short i;
- for (i = 0; i < ref_list.count; ++i)
- printf("# %s\n", ref_list.strings[i]);
- }*/
-
- /* We have exactly 8 refs in total if we include the packed ones:
- * there is a reference that exists both in the packfile and as
- * loose, but we only list it once */
- must_be_true(ref_list.count == 8);
-
- git_strarray_free(&ref_list);
- git_repository_free(repo);
-END_TEST
-
-BEGIN_TEST(list1, "try to list only the symbolic references")
- git_repository *repo;
- git_strarray ref_list;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
- must_pass(git_reference_listall(&ref_list, repo, GIT_REF_SYMBOLIC));
- must_be_true(ref_list.count == 0); /* no symrefs in the test repo */
-
- git_strarray_free(&ref_list);
- git_repository_free(repo);
-END_TEST
-
-static const char *new_ref = "refs/heads/test-reflog";
-#define commit_msg "commit: bla bla"
-
-static int assert_signature(git_signature *expected, git_signature *actual)
-{
- if (actual == NULL)
- return GIT_ERROR;
-
- if (strcmp(expected->name, actual->name) != 0)
- return GIT_ERROR;
-
- if (strcmp(expected->email, actual->email) != 0)
- return GIT_ERROR;
-
- if (expected->when.offset != actual->when.offset)
- return GIT_ERROR;
-
- if (expected->when.time != actual->when.time)
- return GIT_ERROR;
-
- return GIT_SUCCESS;
-}
-
-BEGIN_TEST(reflog0, "write a reflog for a given reference and ensure it can be read back")
- git_repository *repo, *repo2;
- git_reference *ref, *lookedup_ref;
- git_oid oid;
- git_signature *committer;
- git_reflog *reflog;
- git_reflog_entry *entry;
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- /* Create a new branch pointing at the HEAD */
- git_oid_fromstr(&oid, current_master_tip);
- must_pass(git_reference_create_oid(&ref, repo, new_ref, &oid, 0));
- must_pass(git_reference_lookup(&ref, repo, new_ref));
-
- must_pass(git_signature_now(&committer, "foo", "foo@bar"));
-
- must_pass(git_reflog_write(ref, NULL, committer, NULL));
- must_fail(git_reflog_write(ref, NULL, committer, "no\nnewline"));
- must_pass(git_reflog_write(ref, &oid, committer, commit_msg));
-
- git_repository_free(repo);
-
- /* Reopen a new instance of the repository */
- must_pass(git_repository_open(&repo2, TEMP_REPO_FOLDER));
-
- /* Lookup the preivously created branch */
- must_pass(git_reference_lookup(&lookedup_ref, repo2, new_ref));
-
- /* Read and parse the reflog for this branch */
- must_pass(git_reflog_read(&reflog, lookedup_ref));
- must_be_true(reflog->entries.length == 2);
-
- entry = (git_reflog_entry *)git_vector_get(&reflog->entries, 0);
- must_pass(assert_signature(committer, entry->committer));
- must_be_true(strcmp("0000000000000000000000000000000000000000", entry->oid_old) == 0);
- must_be_true(strcmp(current_master_tip, entry->oid_cur) == 0);
- must_be_true(entry->msg == NULL);
-
- entry = (git_reflog_entry *)git_vector_get(&reflog->entries, 1);
- must_pass(assert_signature(committer, entry->committer));
- must_be_true(strcmp(current_master_tip, entry->oid_old) == 0);
- must_be_true(strcmp(current_master_tip, entry->oid_cur) == 0);
- must_be_true(strcmp(commit_msg, entry->msg) == 0);
-
- git_signature_free(committer);
- git_reflog_free(reflog);
- close_temp_repo(repo2);
-END_TEST
-
-BEGIN_TEST(reflog1, "avoid writing an obviously wrong reflog")
- git_repository *repo;
- git_reference *ref;
- git_oid oid;
- git_signature *committer;
-
- must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
-
- /* Create a new branch pointing at the HEAD */
- git_oid_fromstr(&oid, current_master_tip);
- must_pass(git_reference_create_oid(&ref, repo, new_ref, &oid, 0));
- must_pass(git_reference_lookup(&ref, repo, new_ref));
-
- must_pass(git_signature_now(&committer, "foo", "foo@bar"));
-
- /* Write the reflog for the new branch */
- must_pass(git_reflog_write(ref, NULL, committer, NULL));
-
- /* Try to update the reflog with wrong information:
- * It's no new reference, so the ancestor OID cannot
- * be NULL. */
- must_fail(git_reflog_write(ref, NULL, committer, NULL));
-
- git_signature_free(committer);
-
- close_temp_repo(repo);
-END_TEST
-
-BEGIN_SUITE(refs)
- ADD_TEST(readtag0);
- ADD_TEST(readtag1);
-
- ADD_TEST(readsym0);
- ADD_TEST(readsym1);
- ADD_TEST(readsym2);
- ADD_TEST(readsym3);
-
- ADD_TEST(readpacked0);
- ADD_TEST(readpacked1);
-
- ADD_TEST(create0);
- ADD_TEST(create1);
- ADD_TEST(create2);
- ADD_TEST(create3);
-
- ADD_TEST(overwrite0);
- ADD_TEST(overwrite1);
- ADD_TEST(overwrite2);
- ADD_TEST(overwrite3);
-
- ADD_TEST(normalize0);
- ADD_TEST(normalize1);
- ADD_TEST(normalize2);
-
- ADD_TEST(pack0);
- ADD_TEST(pack1);
-
- ADD_TEST(rename0);
- ADD_TEST(rename1);
- ADD_TEST(rename2);
- ADD_TEST(rename3);
- ADD_TEST(rename4);
- ADD_TEST(rename5);
- ADD_TEST(rename6);
- ADD_TEST(rename7);
- ADD_TEST(rename8);
-
- ADD_TEST(delete0);
-
- ADD_TEST(list0);
- ADD_TEST(list1);
-
- ADD_TEST(reflog0);
- ADD_TEST(reflog1);
-END_SUITE
diff --git a/tests/t12-repo.c b/tests/t12-repo.c
deleted file mode 100644
index 9e6876d64..000000000
--- a/tests/t12-repo.c
+++ /dev/null
@@ -1,463 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#include "test_lib.h"
-#include "test_helpers.h"
-
-#include "odb.h"
-#include "git2/odb_backend.h"
-#include "repository.h"
-
-typedef struct {
- git_odb_backend base;
- int position;
-} fake_backend;
-
-git_odb_backend *new_backend(int position)
-{
- fake_backend *b;
-
- b = git__malloc(sizeof(fake_backend));
- if (b == NULL)
- return NULL;
-
- memset(b, 0x0, sizeof(fake_backend));
- b->position = position;
- return (git_odb_backend *)b;
-}
-
-int test_backend_sorting(git_odb *odb)
-{
- unsigned int i;
-
- for (i = 0; i < odb->backends.length; ++i) {
- fake_backend *internal = *((fake_backend **)git_vector_get(&odb->backends, i));
-
- if (internal == NULL)
- return GIT_ERROR;
-
- if (internal->position != (int)i)
- return GIT_ERROR;
- }
-
- return GIT_SUCCESS;
-}
-
-BEGIN_TEST(odb0, "assure that ODB backends are properly sorted")
- git_odb *odb;
- must_pass(git_odb_new(&odb));
- must_pass(git_odb_add_backend(odb, new_backend(0), 5));
- must_pass(git_odb_add_backend(odb, new_backend(2), 3));
- must_pass(git_odb_add_backend(odb, new_backend(1), 4));
- must_pass(git_odb_add_backend(odb, new_backend(3), 1));
- must_pass(test_backend_sorting(odb));
- git_odb_close(odb);
-END_TEST
-
-BEGIN_TEST(odb1, "assure that alternate backends are properly sorted")
- git_odb *odb;
- must_pass(git_odb_new(&odb));
- must_pass(git_odb_add_backend(odb, new_backend(0), 5));
- must_pass(git_odb_add_backend(odb, new_backend(2), 3));
- must_pass(git_odb_add_backend(odb, new_backend(1), 4));
- must_pass(git_odb_add_backend(odb, new_backend(3), 1));
- must_pass(git_odb_add_alternate(odb, new_backend(4), 5));
- must_pass(git_odb_add_alternate(odb, new_backend(6), 3));
- must_pass(git_odb_add_alternate(odb, new_backend(5), 4));
- must_pass(git_odb_add_alternate(odb, new_backend(7), 1));
- must_pass(test_backend_sorting(odb));
- git_odb_close(odb);
-END_TEST
-
-
-#define STANDARD_REPOSITORY 0
-#define BARE_REPOSITORY 1
-
-static int ensure_repository_init(
- const char *working_directory,
- int repository_kind,
- const char *expected_path_index,
- const char *expected_path_repository,
- const char *expected_working_directory)
-{
- char path_odb[GIT_PATH_MAX];
- git_repository *repo;
-
- if (git_futils_isdir(working_directory) == GIT_SUCCESS)
- return GIT_ERROR;
-
- git_path_join(path_odb, expected_path_repository, GIT_OBJECTS_DIR);
-
- if (git_repository_init(&repo, working_directory, repository_kind) < GIT_SUCCESS)
- return GIT_ERROR;
-
- if (repo->path_workdir != NULL || expected_working_directory != NULL) {
- if (git__suffixcmp(repo->path_workdir, expected_working_directory) != 0)
- goto cleanup;
- }
-
- if (git__suffixcmp(repo->path_odb, path_odb) != 0)
- goto cleanup;
-
- if (git__suffixcmp(repo->path_repository, expected_path_repository) != 0)
- goto cleanup;
-
- if (repo->path_index != NULL || expected_path_index != NULL) {
- if (git__suffixcmp(repo->path_index, expected_path_index) != 0)
- goto cleanup;
-
-#ifdef GIT_WIN32
- if ((GetFileAttributes(repo->path_repository) & FILE_ATTRIBUTE_HIDDEN) == 0)
- goto cleanup;
-#endif
-
- if (git_repository_is_bare(repo) == 1)
- goto cleanup;
- } else if (git_repository_is_bare(repo) == 0)
- goto cleanup;
-
- if (git_repository_is_empty(repo) == 0)
- goto cleanup;
-
- git_repository_free(repo);
- git_futils_rmdir_r(working_directory, 1);
-
- return GIT_SUCCESS;
-
-cleanup:
- git_repository_free(repo);
- git_futils_rmdir_r(working_directory, 1);
- return GIT_ERROR;
-}
-
-BEGIN_TEST(init0, "initialize a standard repo")
- char path_index[GIT_PATH_MAX], path_repository[GIT_PATH_MAX];
-
- git_path_join(path_repository, TEMP_REPO_FOLDER, GIT_DIR);
- git_path_join(path_index, path_repository, GIT_INDEX_FILE);
-
- must_pass(ensure_repository_init(TEMP_REPO_FOLDER, STANDARD_REPOSITORY, path_index, path_repository, TEMP_REPO_FOLDER));
- must_pass(ensure_repository_init(TEMP_REPO_FOLDER_NS, STANDARD_REPOSITORY, path_index, path_repository, TEMP_REPO_FOLDER));
-END_TEST
-
-BEGIN_TEST(init1, "initialize a bare repo")
- char path_repository[GIT_PATH_MAX];
-
- git_path_join(path_repository, TEMP_REPO_FOLDER, "");
-
- must_pass(ensure_repository_init(TEMP_REPO_FOLDER, BARE_REPOSITORY, NULL, path_repository, NULL));
- must_pass(ensure_repository_init(TEMP_REPO_FOLDER_NS, BARE_REPOSITORY, NULL, path_repository, NULL));
-END_TEST
-
-BEGIN_TEST(init2, "Initialize and open a bare repo with a relative path escaping out of the current working directory")
- char path_repository[GIT_PATH_MAX];
- char current_workdir[GIT_PATH_MAX];
- const int mode = 0755; /* or 0777 ? */
- git_repository* repo;
-
- must_pass(p_getcwd(current_workdir, sizeof(current_workdir)));
-
- git_path_join(path_repository, TEMP_REPO_FOLDER, "a/b/c/");
- must_pass(git_futils_mkdir_r(path_repository, mode));
-
- must_pass(chdir(path_repository));
-
- must_pass(git_repository_init(&repo, "../d/e.git", 1));
- must_pass(git__suffixcmp(repo->path_repository, "/a/b/d/e.git/"));
-
- git_repository_free(repo);
-
- must_pass(git_repository_open(&repo, "../d/e.git"));
-
- git_repository_free(repo);
-
- must_pass(chdir(current_workdir));
- must_pass(git_futils_rmdir_r(TEMP_REPO_FOLDER, 1));
-END_TEST
-
-#define EMPTY_BARE_REPOSITORY_FOLDER TEST_RESOURCES "/empty_bare.git/"
-
-BEGIN_TEST(open0, "Open a bare repository that has just been initialized by git")
- git_repository *repo;
-
- must_pass(copydir_recurs(EMPTY_BARE_REPOSITORY_FOLDER, TEMP_REPO_FOLDER));
- must_pass(remove_placeholders(TEMP_REPO_FOLDER, "dummy-marker.txt"));
-
- must_pass(git_repository_open(&repo, TEMP_REPO_FOLDER));
- must_be_true(git_repository_path(repo, GIT_REPO_PATH) != NULL);
- must_be_true(git_repository_path(repo, GIT_REPO_PATH_WORKDIR) == NULL);
-
- git_repository_free(repo);
- must_pass(git_futils_rmdir_r(TEMP_REPO_FOLDER, 1));
-END_TEST
-
-#define EMPTY_REPOSITORY_FOLDER TEST_RESOURCES "/empty_standard_repo/.gitted/"
-
-BEGIN_TEST(open1, "Open a standard repository that has just been initialized by git")
- git_repository *repo;
-
- must_pass(copydir_recurs(EMPTY_REPOSITORY_FOLDER, TEST_STD_REPO_FOLDER));
- must_pass(remove_placeholders(TEST_STD_REPO_FOLDER, "dummy-marker.txt"));
-
- must_pass(git_repository_open(&repo, TEST_STD_REPO_FOLDER));
- must_be_true(git_repository_path(repo, GIT_REPO_PATH) != NULL);
- must_be_true(git_repository_path(repo, GIT_REPO_PATH_WORKDIR) != NULL);
-
- git_repository_free(repo);
- must_pass(git_futils_rmdir_r(TEMP_REPO_FOLDER, 1));
-END_TEST
-
-
-BEGIN_TEST(open2, "Open a bare repository with a relative path escaping out of the current working directory")
- char new_current_workdir[GIT_PATH_MAX];
- char current_workdir[GIT_PATH_MAX];
- char path_repository[GIT_PATH_MAX];
-
- const int mode = 0755; /* or 0777 ? */
- git_repository* repo;
-
- /* Setup the repository to open */
- must_pass(p_getcwd(current_workdir, sizeof(current_workdir)));
- strcpy(path_repository, current_workdir);
- git_path_join_n(path_repository, 3, path_repository, TEMP_REPO_FOLDER, "a/d/e.git");
- must_pass(copydir_recurs(REPOSITORY_FOLDER, path_repository));
-
- /* Change the current working directory */
- git_path_join(new_current_workdir, TEMP_REPO_FOLDER, "a/b/c/");
- must_pass(git_futils_mkdir_r(new_current_workdir, mode));
- must_pass(chdir(new_current_workdir));
-
- must_pass(git_repository_open(&repo, "../../d/e.git"));
-
- git_repository_free(repo);
-
- must_pass(chdir(current_workdir));
- must_pass(git_futils_rmdir_r(TEMP_REPO_FOLDER, 1));
-END_TEST
-
-BEGIN_TEST(empty0, "test if a repository is empty or not")
-
- git_repository *repo_empty, *repo_normal;
-
- must_pass(git_repository_open(&repo_normal, REPOSITORY_FOLDER));
- must_be_true(git_repository_is_empty(repo_normal) == 0);
- git_repository_free(repo_normal);
-
- must_pass(git_repository_open(&repo_empty, EMPTY_BARE_REPOSITORY_FOLDER));
- must_be_true(git_repository_is_empty(repo_empty) == 1);
- git_repository_free(repo_empty);
-END_TEST
-
-BEGIN_TEST(detached0, "test if HEAD is detached")
- git_repository *repo;
- git_reference *ref;
- git_oid oid;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
-
- must_be_true(git_repository_head_detached(repo) == 0);
-
- /* detach the HEAD */
- git_oid_fromstr(&oid, "c47800c7266a2be04c571c04d5a6614691ea99bd");
- must_pass(git_reference_create_oid(&ref, repo, "HEAD", &oid, 1));
- must_be_true(git_repository_head_detached(repo) == 1);
-
- /* take the reop back to it's original state */
- must_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/master", 1));
- must_be_true(git_repository_head_detached(repo) == 0);
-
- git_repository_free(repo);
-END_TEST
-
-BEGIN_TEST(orphan0, "test if HEAD is orphan")
- git_repository *repo;
- git_reference *ref;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
-
- must_be_true(git_repository_head_orphan(repo) == 0);
-
- /* orphan HEAD */
- must_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/orphan", 1));
- must_be_true(git_repository_head_orphan(repo) == 1);
-
- /* take the reop back to it's original state */
- must_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/master", 1));
- must_be_true(git_repository_head_orphan(repo) == 0);
-
- git_repository_free(repo);
-END_TEST
-
-#define DISCOVER_FOLDER TEST_RESOURCES "/discover.git"
-
-#define SUB_REPOSITORY_FOLDER_NAME "sub_repo"
-#define SUB_REPOSITORY_FOLDER DISCOVER_FOLDER "/" SUB_REPOSITORY_FOLDER_NAME
-#define SUB_REPOSITORY_FOLDER_SUB SUB_REPOSITORY_FOLDER "/sub"
-#define SUB_REPOSITORY_FOLDER_SUB_SUB SUB_REPOSITORY_FOLDER_SUB "/subsub"
-#define SUB_REPOSITORY_FOLDER_SUB_SUB_SUB SUB_REPOSITORY_FOLDER_SUB_SUB "/subsubsub"
-
-#define REPOSITORY_ALTERNATE_FOLDER DISCOVER_FOLDER "/alternate_sub_repo"
-#define REPOSITORY_ALTERNATE_FOLDER_SUB REPOSITORY_ALTERNATE_FOLDER "/sub"
-#define REPOSITORY_ALTERNATE_FOLDER_SUB_SUB REPOSITORY_ALTERNATE_FOLDER_SUB "/subsub"
-#define REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB REPOSITORY_ALTERNATE_FOLDER_SUB_SUB "/subsubsub"
-
-#define ALTERNATE_MALFORMED_FOLDER1 DISCOVER_FOLDER "/alternate_malformed_repo1"
-#define ALTERNATE_MALFORMED_FOLDER2 DISCOVER_FOLDER "/alternate_malformed_repo2"
-#define ALTERNATE_MALFORMED_FOLDER3 DISCOVER_FOLDER "/alternate_malformed_repo3"
-#define ALTERNATE_NOT_FOUND_FOLDER DISCOVER_FOLDER "/alternate_not_found_repo"
-
-static int ensure_repository_discover(const char *start_path, const char *ceiling_dirs, const char *expected_path)
-{
- int error;
- char found_path[GIT_PATH_MAX];
-
- error = git_repository_discover(found_path, sizeof(found_path), start_path, 0, ceiling_dirs);
- //across_fs is always 0 as we can't automate the filesystem change tests
-
- if (error < GIT_SUCCESS)
- return error;
-
- return strcmp(found_path, expected_path) ? GIT_ERROR : GIT_SUCCESS;
-}
-
-static int write_file(const char *path, const char *content)
-{
- int error;
- git_file file;
-
- if (git_futils_exists(path) == GIT_SUCCESS) {
- error = p_unlink(path);
-
- if (error < GIT_SUCCESS)
- return error;
- }
-
- file = git_futils_creat_withpath(path, 0644);
- if (file < GIT_SUCCESS)
- return file;
-
- error = p_write(file, content, strlen(content) * sizeof(char));
-
- p_close(file);
-
- return error;
-}
-
-//no check is performed on ceiling_dirs length, so be sure it's long enough
-static int append_ceiling_dir(char *ceiling_dirs, const char *path)
-{
- int len = strlen(ceiling_dirs);
- int error;
-
- error = git_path_prettify_dir(ceiling_dirs + len + (len ? 1 : 0), path, NULL);
- if (error < GIT_SUCCESS)
- return git__rethrow(error, "Failed to append ceiling directory.");
-
- if (len)
- ceiling_dirs[len] = GIT_PATH_LIST_SEPARATOR;
-
- return GIT_SUCCESS;
-}
-
-BEGIN_TEST(discover0, "test discover")
- git_repository *repo;
- char ceiling_dirs[GIT_PATH_MAX * 2] = "";
- char repository_path[GIT_PATH_MAX];
- char sub_repository_path[GIT_PATH_MAX];
- char found_path[GIT_PATH_MAX];
- int mode = 0755;
-
- must_pass(append_ceiling_dir(ceiling_dirs,TEST_RESOURCES));
- git_futils_mkdir_r(DISCOVER_FOLDER, mode);
-
- must_be_true(git_repository_discover(repository_path, sizeof(repository_path), DISCOVER_FOLDER, 0, ceiling_dirs) == GIT_ENOTAREPO);
-
- must_pass(git_repository_init(&repo, DISCOVER_FOLDER, 1));
- must_pass(git_repository_discover(repository_path, sizeof(repository_path), DISCOVER_FOLDER, 0, ceiling_dirs));
- git_repository_free(repo);
-
- must_pass(git_repository_init(&repo, SUB_REPOSITORY_FOLDER, 0));
- must_pass(git_futils_mkdir_r(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, mode));
- must_pass(git_repository_discover(sub_repository_path, sizeof(sub_repository_path), SUB_REPOSITORY_FOLDER, 0, ceiling_dirs));
-
- must_pass(git_futils_mkdir_r(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, mode));
- must_pass(ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB, ceiling_dirs, sub_repository_path));
- must_pass(ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB_SUB, ceiling_dirs, sub_repository_path));
- must_pass(ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, ceiling_dirs, sub_repository_path));
-
- must_pass(git_futils_mkdir_r(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, mode));
- must_pass(write_file(REPOSITORY_ALTERNATE_FOLDER "/" DOT_GIT, "gitdir: ../" SUB_REPOSITORY_FOLDER_NAME "/" DOT_GIT));
- must_pass(write_file(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB "/" DOT_GIT, "gitdir: ../../../" SUB_REPOSITORY_FOLDER_NAME "/" DOT_GIT));
- must_pass(write_file(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB "/" DOT_GIT, "gitdir: ../../../../"));
- must_pass(ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER, ceiling_dirs, sub_repository_path));
- must_pass(ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB, ceiling_dirs, sub_repository_path));
- must_pass(ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB, ceiling_dirs, sub_repository_path));
- must_pass(ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, ceiling_dirs, repository_path));
-
- must_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER1, mode));
- must_pass(write_file(ALTERNATE_MALFORMED_FOLDER1 "/" DOT_GIT, "Anything but not gitdir:"));
- must_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER2, mode));
- must_pass(write_file(ALTERNATE_MALFORMED_FOLDER2 "/" DOT_GIT, "gitdir:"));
- must_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER3, mode));
- must_pass(write_file(ALTERNATE_MALFORMED_FOLDER3 "/" DOT_GIT, "gitdir: \n\n\n"));
- must_pass(git_futils_mkdir_r(ALTERNATE_NOT_FOUND_FOLDER, mode));
- must_pass(write_file(ALTERNATE_NOT_FOUND_FOLDER "/" DOT_GIT, "gitdir: a_repository_that_surely_does_not_exist"));
- must_fail(git_repository_discover(found_path, sizeof(found_path), ALTERNATE_MALFORMED_FOLDER1, 0, ceiling_dirs));
- must_fail(git_repository_discover(found_path, sizeof(found_path), ALTERNATE_MALFORMED_FOLDER2, 0, ceiling_dirs));
- must_fail(git_repository_discover(found_path, sizeof(found_path), ALTERNATE_MALFORMED_FOLDER3, 0, ceiling_dirs));
- must_fail(git_repository_discover(found_path, sizeof(found_path), ALTERNATE_NOT_FOUND_FOLDER, 0, ceiling_dirs));
-
- must_pass(append_ceiling_dir(ceiling_dirs, SUB_REPOSITORY_FOLDER));
- //this must pass as ceiling_directories cannot predent the current
- //working directory to be checked
- must_pass(git_repository_discover(found_path, sizeof(found_path), SUB_REPOSITORY_FOLDER, 0, ceiling_dirs));
- must_fail(git_repository_discover(found_path, sizeof(found_path), SUB_REPOSITORY_FOLDER_SUB, 0, ceiling_dirs));
- must_fail(git_repository_discover(found_path, sizeof(found_path), SUB_REPOSITORY_FOLDER_SUB_SUB, 0, ceiling_dirs));
- must_fail(git_repository_discover(found_path, sizeof(found_path), SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, 0, ceiling_dirs));
-
- //.gitfile redirection should not be affected by ceiling directories
- must_pass(ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER, ceiling_dirs, sub_repository_path));
- must_pass(ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB, ceiling_dirs, sub_repository_path));
- must_pass(ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB, ceiling_dirs, sub_repository_path));
- must_pass(ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, ceiling_dirs, repository_path));
-
- must_pass(git_futils_rmdir_r(DISCOVER_FOLDER, 1));
- git_repository_free(repo);
-END_TEST
-
-BEGIN_SUITE(repository)
- ADD_TEST(odb0);
- ADD_TEST(odb1);
- ADD_TEST(init0);
- ADD_TEST(init1);
- ADD_TEST(init2);
- ADD_TEST(open0);
- ADD_TEST(open1);
- ADD_TEST(open2);
- ADD_TEST(empty0);
- ADD_TEST(detached0);
- ADD_TEST(orphan0);
- ADD_TEST(discover0);
-END_SUITE
-
diff --git a/tests/t13-threads.c b/tests/t13-threads.c
deleted file mode 100644
index 3888b70ce..000000000
--- a/tests/t13-threads.c
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#include "test_lib.h"
-#include "test_helpers.h"
-#include "cache.h"
-
-
-typedef struct {
- git_cached_obj cached;
- unsigned int __dummy;
-} ttest_obj;
-
-BEGIN_TEST(cache0, "run several threads polling the cache at the same time")
-
-END_TEST
-
-BEGIN_SUITE(threads)
- ADD_TEST(cache0);
-END_SUITE
diff --git a/tests/t15-config.c b/tests/t15-config.c
deleted file mode 100644
index aa5ba28f6..000000000
--- a/tests/t15-config.c
+++ /dev/null
@@ -1,325 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#include "test_lib.h"
-#include "test_helpers.h"
-
-#include <git2.h>
-#include "filebuf.h"
-
-#define CONFIG_BASE TEST_RESOURCES "/config"
-
-/*
- * This one is so we know the code isn't completely broken
- */
-BEGIN_TEST(config0, "read a simple configuration")
- git_config *cfg;
- int i;
-
- must_pass(git_config_open_ondisk(&cfg, CONFIG_BASE "/config0"));
- must_pass(git_config_get_int(cfg, "core.repositoryformatversion", &i));
- must_be_true(i == 0);
- must_pass(git_config_get_bool(cfg, "core.filemode", &i));
- must_be_true(i == 1);
- must_pass(git_config_get_bool(cfg, "core.bare", &i));
- must_be_true(i == 0);
- must_pass(git_config_get_bool(cfg, "core.logallrefupdates", &i));
- must_be_true(i == 1);
-
- git_config_free(cfg);
-END_TEST
-
-/*
- * [this "that"] and [this "That] are different namespaces. Make sure
- * each returns the correct one.
- */
-BEGIN_TEST(config1, "case sensitivity")
- git_config *cfg;
- int i;
- const char *str;
-
- must_pass(git_config_open_ondisk(&cfg, CONFIG_BASE "/config1"));
-
- must_pass(git_config_get_string(cfg, "this.that.other", &str));
- must_be_true(!strcmp(str, "true"));
- must_pass(git_config_get_string(cfg, "this.That.other", &str));
- must_be_true(!strcmp(str, "yes"));
-
- must_pass(git_config_get_bool(cfg, "this.that.other", &i));
- must_be_true(i == 1);
- must_pass(git_config_get_bool(cfg, "this.That.other", &i));
- must_be_true(i == 1);
-
- /* This one doesn't exist */
- must_fail(git_config_get_bool(cfg, "this.thaT.other", &i));
-
- git_config_free(cfg);
-END_TEST
-
-/*
- * If \ is the last non-space character on the line, we read the next
- * one, separating each line with SP.
- */
-BEGIN_TEST(config2, "parse a multiline value")
- git_config *cfg;
- const char *str;
-
- must_pass(git_config_open_ondisk(&cfg, CONFIG_BASE "/config2"));
-
- must_pass(git_config_get_string(cfg, "this.That.and", &str));
- must_be_true(!strcmp(str, "one one one two two three three"));
-
- git_config_free(cfg);
-END_TEST
-
-/*
- * This kind of subsection declaration is case-insensitive
- */
-BEGIN_TEST(config3, "parse a [section.subsection] header")
- git_config *cfg;
- const char *str;
-
- must_pass(git_config_open_ondisk(&cfg, CONFIG_BASE "/config3"));
-
- must_pass(git_config_get_string(cfg, "section.subsection.var", &str));
- must_be_true(!strcmp(str, "hello"));
-
- /* Avoid a false positive */
- str = "nohello";
- must_pass(git_config_get_string(cfg, "section.subSectIon.var", &str));
- must_be_true(!strcmp(str, "hello"));
-
- git_config_free(cfg);
-END_TEST
-
-BEGIN_TEST(config4, "a variable name on its own is valid")
- git_config *cfg;
-const char *str;
-int i;
-
- must_pass(git_config_open_ondisk(&cfg, CONFIG_BASE "/config4"));
-
- must_pass(git_config_get_string(cfg, "some.section.variable", &str));
- must_be_true(str == NULL);
-
- must_pass(git_config_get_bool(cfg, "some.section.variable", &i));
- must_be_true(i == 1);
-
-
- git_config_free(cfg);
-END_TEST
-
-BEGIN_TEST(config5, "test number suffixes")
- git_config *cfg;
- long int i;
-
- must_pass(git_config_open_ondisk(&cfg, CONFIG_BASE "/config5"));
-
- must_pass(git_config_get_long(cfg, "number.simple", &i));
- must_be_true(i == 1);
-
- must_pass(git_config_get_long(cfg, "number.k", &i));
- must_be_true(i == 1 * 1024);
-
- must_pass(git_config_get_long(cfg, "number.kk", &i));
- must_be_true(i == 1 * 1024);
-
- must_pass(git_config_get_long(cfg, "number.m", &i));
- must_be_true(i == 1 * 1024 * 1024);
-
- must_pass(git_config_get_long(cfg, "number.mm", &i));
- must_be_true(i == 1 * 1024 * 1024);
-
- must_pass(git_config_get_long(cfg, "number.g", &i));
- must_be_true(i == 1 * 1024 * 1024 * 1024);
-
- must_pass(git_config_get_long(cfg, "number.gg", &i));
- must_be_true(i == 1 * 1024 * 1024 * 1024);
-
- git_config_free(cfg);
-END_TEST
-
-BEGIN_TEST(config6, "test blank lines")
- git_config *cfg;
- int i;
-
- must_pass(git_config_open_ondisk(&cfg, CONFIG_BASE "/config6"));
-
- must_pass(git_config_get_bool(cfg, "valid.subsection.something", &i));
- must_be_true(i == 1);
-
- must_pass(git_config_get_bool(cfg, "something.else.something", &i));
- must_be_true(i == 0);
-
- git_config_free(cfg);
-END_TEST
-
-BEGIN_TEST(config7, "test for invalid ext headers")
- git_config *cfg;
-
- must_fail(git_config_open_ondisk(&cfg, CONFIG_BASE "/config7"));
-
-END_TEST
-
-BEGIN_TEST(config8, "don't fail on empty files")
- git_config *cfg;
-
- must_pass(git_config_open_ondisk(&cfg, CONFIG_BASE "/config8"));
-
- git_config_free(cfg);
-END_TEST
-
-BEGIN_TEST(config9, "replace a value")
- git_config *cfg;
- int i;
-
- /* By freeing the config, we make sure we flush the values */
- must_pass(git_config_open_ondisk(&cfg, CONFIG_BASE "/config9"));
- must_pass(git_config_set_int(cfg, "core.dummy", 5));
- git_config_free(cfg);
-
- must_pass(git_config_open_ondisk(&cfg, CONFIG_BASE "/config9"));
- must_pass(git_config_get_int(cfg, "core.dummy", &i));
- must_be_true(i == 5);
- git_config_free(cfg);
-
- must_pass(git_config_open_ondisk(&cfg, CONFIG_BASE "/config9"));
- must_pass(git_config_set_int(cfg, "core.dummy", 1));
- git_config_free(cfg);
-
-END_TEST
-
-BEGIN_TEST(config10, "a repo's config overrides the global config")
- git_repository *repo;
- git_config *cfg;
- int version;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
- must_pass(git_repository_config(&cfg, repo, CONFIG_BASE "/.gitconfig", NULL));
- must_pass(git_config_get_int(cfg, "core.repositoryformatversion", &version));
- must_be_true(version == 0);
- git_config_free(cfg);
- git_repository_free(repo);
-END_TEST
-
-BEGIN_TEST(config11, "fall back to the global config")
- git_repository *repo;
- git_config *cfg;
- int num;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
- must_pass(git_repository_config(&cfg, repo, CONFIG_BASE "/.gitconfig", NULL));
- must_pass(git_config_get_int(cfg, "core.something", &num));
- must_be_true(num == 2);
- git_config_free(cfg);
- git_repository_free(repo);
-END_TEST
-
-BEGIN_TEST(config12, "delete a value")
- git_config *cfg;
- int i;
-
- /* By freeing the config, we make sure we flush the values */
- must_pass(git_config_open_ondisk(&cfg, CONFIG_BASE "/config9"));
- must_pass(git_config_set_int(cfg, "core.dummy", 5));
- git_config_free(cfg);
-
- must_pass(git_config_open_ondisk(&cfg, CONFIG_BASE "/config9"));
- must_pass(git_config_delete(cfg, "core.dummy"));
- git_config_free(cfg);
-
- must_pass(git_config_open_ondisk(&cfg, CONFIG_BASE "/config9"));
- must_be_true(git_config_get_int(cfg, "core.dummy", &i) == GIT_ENOTFOUND);
- must_pass(git_config_set_int(cfg, "core.dummy", 1));
- git_config_free(cfg);
-END_TEST
-
-BEGIN_TEST(config13, "can't delete a non-existent value")
- git_config *cfg;
-
- /* By freeing the config, we make sure we flush the values */
- must_pass(git_config_open_ondisk(&cfg, CONFIG_BASE "/config9"));
- must_be_true(git_config_delete(cfg, "core.imaginary") == GIT_ENOTFOUND);
- git_config_free(cfg);
-END_TEST
-
-BEGIN_TEST(config14, "don't fail horribly if a section header is in the last line")
- git_config *cfg;
-
- /* By freeing the config, we make sure we flush the values */
- must_pass(git_config_open_ondisk(&cfg, CONFIG_BASE "/config10"));
- git_config_free(cfg);
-END_TEST
-
-BEGIN_TEST(config15, "add a variable in an existing section")
- git_config *cfg;
- int i;
-
- /* By freeing the config, we make sure we flush the values */
- must_pass(git_config_open_ondisk(&cfg, CONFIG_BASE "/config10"));
- must_pass(git_config_set_int(cfg, "empty.tmp", 5));
- must_pass(git_config_get_int(cfg, "empty.tmp", &i));
- must_be_true(i == 5);
- must_pass(git_config_delete(cfg, "empty.tmp"));
- git_config_free(cfg);
-END_TEST
-
-BEGIN_TEST(config16, "add a variable in a new section")
- git_config *cfg;
- int i;
- git_filebuf buf;
-
- /* By freeing the config, we make sure we flush the values */
- must_pass(git_config_open_ondisk(&cfg, CONFIG_BASE "/config10"));
- must_pass(git_config_set_int(cfg, "section.tmp", 5));
- must_pass(git_config_get_int(cfg, "section.tmp", &i));
- must_be_true(i == 5);
- must_pass(git_config_delete(cfg, "section.tmp"));
- git_config_free(cfg);
-
- /* As the section wasn't removed, owerwrite the file */
- must_pass(git_filebuf_open(&buf, CONFIG_BASE "/config10", 0));
- must_pass(git_filebuf_write(&buf, "[empty]\n", STRLEN("[empty]\n")));
- must_pass(git_filebuf_commit(&buf));
-END_TEST
-
-BEGIN_SUITE(config)
- ADD_TEST(config0);
- ADD_TEST(config1);
- ADD_TEST(config2);
- ADD_TEST(config3);
- ADD_TEST(config4);
- ADD_TEST(config5);
- ADD_TEST(config6);
- ADD_TEST(config7);
- ADD_TEST(config8);
- ADD_TEST(config9);
- ADD_TEST(config10);
- ADD_TEST(config11);
- ADD_TEST(config12);
- ADD_TEST(config13);
- ADD_TEST(config14);
- ADD_TEST(config15);
- ADD_TEST(config16);
-END_SUITE
diff --git a/tests/t16-remotes.c b/tests/t16-remotes.c
deleted file mode 100644
index 4bc2f55d7..000000000
--- a/tests/t16-remotes.c
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#include "test_lib.h"
-#include "test_helpers.h"
-
-#include <git2.h>
-
-BEGIN_TEST(remotes0, "remote parsing works")
- git_remote *remote;
- git_repository *repo;
- git_config *cfg;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
- must_pass(git_repository_config(&cfg, repo, NULL, NULL));
- must_pass(git_remote_get(&remote, cfg, "test"));
- must_be_true(!strcmp(git_remote_name(remote), "test"));
- must_be_true(!strcmp(git_remote_url(remote), "git://github.com/libgit2/libgit2"));
-
- git_remote_free(remote);
- git_config_free(cfg);
- git_repository_free(repo);
-END_TEST
-
-BEGIN_TEST(refspec0, "remote with refspec works")
- git_remote *remote;
- git_repository *repo;
- git_config *cfg;
- const git_refspec *refspec = NULL;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
- must_pass(git_repository_config(&cfg, repo, NULL, NULL));
- must_pass(git_remote_get(&remote, cfg, "test"));
- refspec = git_remote_fetchspec(remote);
- must_be_true(refspec != NULL);
- must_be_true(!strcmp(git_refspec_src(refspec), "refs/heads/*"));
- must_be_true(!strcmp(git_refspec_dst(refspec), "refs/remotes/test/*"));
- git_remote_free(remote);
- git_config_free(cfg);
- git_repository_free(repo);
-END_TEST
-
-BEGIN_TEST(refspec1, "remote fnmatch works as expected")
- git_remote *remote;
- git_repository *repo;
- git_config *cfg;
- const git_refspec *refspec = NULL;
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
- must_pass(git_repository_config(&cfg, repo, NULL, NULL));
- must_pass(git_remote_get(&remote, cfg, "test"));
- refspec = git_remote_fetchspec(remote);
- must_be_true(refspec != NULL);
- must_pass(git_refspec_src_match(refspec, "refs/heads/master"));
- must_pass(git_refspec_src_match(refspec, "refs/heads/multi/level/branch"));
- git_remote_free(remote);
- git_config_free(cfg);
- git_repository_free(repo);
-END_TEST
-
-BEGIN_TEST(refspec2, "refspec transform")
- git_remote *remote;
- git_repository *repo;
- git_config *cfg;
- const git_refspec *refspec = NULL;
- char ref[1024] = {0};
-
- must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
- must_pass(git_repository_config(&cfg, repo, NULL, NULL));
- must_pass(git_remote_get(&remote, cfg, "test"));
- refspec = git_remote_fetchspec(remote);
- must_be_true(refspec != NULL);
- must_pass(git_refspec_transform(ref, sizeof(ref), refspec, "refs/heads/master"));
- must_be_true(!strcmp(ref, "refs/remotes/test/master"));
- git_remote_free(remote);
- git_config_free(cfg);
- git_repository_free(repo);
-END_TEST
-
-BEGIN_SUITE(remotes)
- ADD_TEST(remotes0)
- ADD_TEST(refspec0)
- ADD_TEST(refspec1)
- ADD_TEST(refspec2)
-END_SUITE
diff --git a/tests/t17-bufs.c b/tests/t17-bufs.c
deleted file mode 100644
index 2cbd8c87a..000000000
--- a/tests/t17-bufs.c
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#include "test_lib.h"
-#include "test_helpers.h"
-
-#include <git2.h>
-#include "buffer.h"
-
-const char *test_string = "Have you seen that? Have you seeeen that??";
-
-BEGIN_TEST(buf0, "check that resizing works properly")
- git_buf buf = GIT_BUF_INIT;
- git_buf_puts(&buf, test_string);
-
- must_be_true(git_buf_oom(&buf) == 0);
- must_be_true(strcmp(git_buf_cstr(&buf), test_string) == 0);
-
- git_buf_puts(&buf, test_string);
- must_be_true(strlen(git_buf_cstr(&buf)) == strlen(test_string) * 2);
- git_buf_free(&buf);
-END_TEST
-
-BEGIN_TEST(buf1, "check that printf works properly")
- git_buf buf = GIT_BUF_INIT;
-
- git_buf_printf(&buf, "%s %s %d ", "shoop", "da", 23);
- must_be_true(git_buf_oom(&buf) == 0);
- must_be_true(strcmp(git_buf_cstr(&buf), "shoop da 23 ") == 0);
-
- git_buf_printf(&buf, "%s %d", "woop", 42);
- must_be_true(git_buf_oom(&buf) == 0);
- must_be_true(strcmp(git_buf_cstr(&buf), "shoop da 23 woop 42") == 0);
- git_buf_free(&buf);
-END_TEST
-
-BEGIN_SUITE(buffers)
- ADD_TEST(buf0)
- ADD_TEST(buf1)
-END_SUITE
diff --git a/tests/t18-status.c b/tests/t18-status.c
deleted file mode 100644
index c30c541df..000000000
--- a/tests/t18-status.c
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#include "test_lib.h"
-#include "test_helpers.h"
-#include "fileops.h"
-#include "git2/status.h"
-
-static const char *test_blob_oid = "d4fa8600b4f37d7516bef4816ae2c64dbf029e3a";
-
-#define STATUS_WORKDIR_FOLDER TEST_RESOURCES "/status/"
-#define STATUS_REPOSITORY_TEMP_FOLDER TEMP_REPO_FOLDER ".gitted/"
-
-BEGIN_TEST(file0, "test retrieving OID from a file apart from the ODB")
- git_oid expected_id, actual_id;
- char filename[] = "new_file";
- int fd;
-
- fd = p_creat(filename, 0644);
- must_pass(fd);
- must_pass(p_write(fd, "new_file\n", 9));
- must_pass(p_close(fd));
-
- must_pass(git_odb_hashfile(&actual_id, filename, GIT_OBJ_BLOB));
-
- must_pass(git_oid_fromstr(&expected_id, test_blob_oid));
- must_be_true(git_oid_cmp(&expected_id, &actual_id) == 0);
-
- must_pass(p_unlink(filename));
-END_TEST
-
-static const char *entry_paths[] = {
- "current_file",
- "file_deleted",
- "modified_file",
- "new_file",
- "staged_changes",
- "staged_changes_file_deleted",
- "staged_changes_modified_file",
- "staged_delete_file_deleted",
- "staged_delete_modified_file",
- "staged_new_file",
- "staged_new_file_deleted_file",
- "staged_new_file_modified_file",
-
- "subdir/current_file",
- "subdir/deleted_file",
- "subdir/modified_file",
- "subdir/new_file",
-};
-static const unsigned int entry_statuses[] = {
- GIT_STATUS_CURRENT,
- GIT_STATUS_WT_DELETED,
- GIT_STATUS_WT_MODIFIED,
- GIT_STATUS_WT_NEW,
- GIT_STATUS_INDEX_MODIFIED,
- GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_DELETED,
- GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED,
- GIT_STATUS_INDEX_DELETED,
- GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_NEW,
- GIT_STATUS_INDEX_NEW,
- GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_DELETED,
- GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_MODIFIED,
-
- GIT_STATUS_CURRENT,
- GIT_STATUS_WT_DELETED,
- GIT_STATUS_WT_MODIFIED,
- GIT_STATUS_WT_NEW,
-};
-#define ENTRY_COUNT 16
-
-static unsigned int get_expected_entry_status(const char *path)
-{
- int i;
-
- for (i = 0; i < ENTRY_COUNT; ++i)
- if (!strcmp(path, entry_paths[i]))
- return entry_statuses[i];
-
- return (unsigned int)-1;
-}
-
-struct status_entry_counts {
- int wrong_status_flags_count;
- int entry_count;
-};
-
-static int status_cb(const char *path, unsigned int status_flags, void *payload)
-{
- unsigned int expected_status_flags = get_expected_entry_status(path);
- struct status_entry_counts *counts = (struct status_entry_counts *)payload;
-
- counts->entry_count++;
- if (status_flags != expected_status_flags)
- counts->wrong_status_flags_count++;
-
- return GIT_SUCCESS;
-}
-
-BEGIN_TEST(statuscb0, "test retrieving status for worktree of repository")
- git_repository *repo;
- struct status_entry_counts counts;
-
- must_pass(copydir_recurs(STATUS_WORKDIR_FOLDER, TEMP_REPO_FOLDER));
- must_pass(git_futils_mv_atomic(STATUS_REPOSITORY_TEMP_FOLDER, TEST_STD_REPO_FOLDER));
- must_pass(git_repository_open(&repo, TEST_STD_REPO_FOLDER));
-
- memset(&counts, 0x0, sizeof(struct status_entry_counts));
- git_status_foreach(repo, status_cb, &counts);
- must_be_true(counts.entry_count == ENTRY_COUNT);
- must_be_true(counts.wrong_status_flags_count == 0);
-
- git_repository_free(repo);
-
- git_futils_rmdir_r(TEMP_REPO_FOLDER, 1);
-END_TEST
-
-BEGIN_TEST(singlestatus0, "test retrieving status for single file")
- git_repository *repo;
- unsigned int status_flags;
- int i;
-
- must_pass(copydir_recurs(STATUS_WORKDIR_FOLDER, TEMP_REPO_FOLDER));
- must_pass(git_futils_mv_atomic(STATUS_REPOSITORY_TEMP_FOLDER, TEST_STD_REPO_FOLDER));
- must_pass(git_repository_open(&repo, TEST_STD_REPO_FOLDER));
-
- for (i = 0; i < ENTRY_COUNT; ++i) {
- must_pass(git_status_file(&status_flags, repo, entry_paths[i]));
- must_be_true(status_flags == entry_statuses[i]);
- }
-
- git_repository_free(repo);
-
- git_futils_rmdir_r(TEMP_REPO_FOLDER, 1);
-END_TEST
-
-BEGIN_TEST(singlestatus1, "test retrieving status for nonexistent file")
- git_repository *repo;
- unsigned int status_flags;
- int error;
-
- must_pass(copydir_recurs(STATUS_WORKDIR_FOLDER, TEMP_REPO_FOLDER));
- must_pass(git_futils_mv_atomic(STATUS_REPOSITORY_TEMP_FOLDER, TEST_STD_REPO_FOLDER));
- must_pass(git_repository_open(&repo, TEST_STD_REPO_FOLDER));
-
- // "nonexistent" does not exist in HEAD, Index or the worktree
- error = git_status_file(&status_flags, repo, "nonexistent");
- must_be_true(error == GIT_ENOTFOUND);
-
- git_repository_free(repo);
-
- git_futils_rmdir_r(TEMP_REPO_FOLDER, 1);
-END_TEST
-
-BEGIN_SUITE(status)
- ADD_TEST(file0);
-
- ADD_TEST(statuscb0);
-
- ADD_TEST(singlestatus0);
- ADD_TEST(singlestatus1);
-END_SUITE \ No newline at end of file
diff --git a/tests/test_helpers.c b/tests/test_helpers.c
deleted file mode 100644
index 0900430e1..000000000
--- a/tests/test_helpers.c
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#include "common.h"
-#include "test_helpers.h"
-#include "fileops.h"
-
-int write_object_data(char *file, void *data, size_t len)
-{
- git_file fd;
- int ret;
-
- if ((fd = p_creat(file, S_IREAD | S_IWRITE)) < 0)
- return -1;
- ret = p_write(fd, data, len);
- p_close(fd);
-
- return ret;
-}
-
-int write_object_files(const char *odb_dir, object_data *d)
-{
- if (p_mkdir(odb_dir, 0755) < 0) {
- int err = errno;
- fprintf(stderr, "can't make directory \"%s\"", odb_dir);
- if (err == EEXIST)
- fprintf(stderr, " (already exists)");
- fprintf(stderr, "\n");
- return -1;
- }
-
- if ((p_mkdir(d->dir, 0755) < 0) && (errno != EEXIST)) {
- fprintf(stderr, "can't make object directory \"%s\"\n", d->dir);
- return -1;
- }
- if (write_object_data(d->file, d->bytes, d->blen) < 0) {
- fprintf(stderr, "can't write object file \"%s\"\n", d->file);
- return -1;
- }
-
- return 0;
-}
-
-int remove_object_files(const char *odb_dir, object_data *d)
-{
- if (p_unlink(d->file) < 0) {
- fprintf(stderr, "can't delete object file \"%s\"\n", d->file);
- return -1;
- }
- if ((p_rmdir(d->dir) < 0) && (errno != ENOTEMPTY)) {
- fprintf(stderr, "can't remove object directory \"%s\"\n", d->dir);
- return -1;
- }
-
- if (p_rmdir(odb_dir) < 0) {
- fprintf(stderr, "can't remove directory \"%s\"\n", odb_dir);
- return -1;
- }
-
- return 0;
-}
-
-int remove_loose_object(const char *repository_folder, git_object *object)
-{
- static const char *objects_folder = "objects/";
-
- char *ptr, *full_path, *top_folder;
- int path_length, objects_length;
-
- assert(repository_folder && object);
-
- objects_length = strlen(objects_folder);
- path_length = strlen(repository_folder);
- ptr = full_path = git__malloc(path_length + objects_length + GIT_OID_HEXSZ + 3);
-
- strcpy(ptr, repository_folder);
- strcpy(ptr + path_length, objects_folder);
-
- ptr = top_folder = ptr + path_length + objects_length;
- *ptr++ = '/';
- git_oid_pathfmt(ptr, git_object_id(object));
- ptr += GIT_OID_HEXSZ + 1;
- *ptr = 0;
-
- if (p_unlink(full_path) < 0) {
- fprintf(stderr, "can't delete object file \"%s\"\n", full_path);
- return -1;
- }
-
- *top_folder = 0;
-
- if ((p_rmdir(full_path) < 0) && (errno != ENOTEMPTY)) {
- fprintf(stderr, "can't remove object directory \"%s\"\n", full_path);
- return -1;
- }
-
- free(full_path);
-
- return GIT_SUCCESS;
-}
-
-int cmp_objects(git_rawobj *o, object_data *d)
-{
- if (o->type != git_object_string2type(d->type))
- return -1;
- if (o->len != d->dlen)
- return -1;
- if ((o->len > 0) && (memcmp(o->data, d->data, o->len) != 0))
- return -1;
- return 0;
-}
-
-int copy_file(const char *src, const char *dst)
-{
- git_fbuffer source_buf;
- git_file dst_fd;
- int error = GIT_ERROR;
-
- if (git_futils_readbuffer(&source_buf, src) < GIT_SUCCESS)
- return GIT_ENOTFOUND;
-
- dst_fd = git_futils_creat_withpath(dst, 0644);
- if (dst_fd < 0)
- goto cleanup;
-
- error = p_write(dst_fd, source_buf.data, source_buf.len);
-
-cleanup:
- git_futils_freebuffer(&source_buf);
- p_close(dst_fd);
-
- return error;
-}
-
-int cmp_files(const char *a, const char *b)
-{
- git_fbuffer buf_a, buf_b;
- int error = GIT_ERROR;
-
- if (git_futils_readbuffer(&buf_a, a) < GIT_SUCCESS)
- return GIT_ERROR;
-
- if (git_futils_readbuffer(&buf_b, b) < GIT_SUCCESS) {
- git_futils_freebuffer(&buf_a);
- return GIT_ERROR;
- }
-
- if (buf_a.len == buf_b.len && !memcmp(buf_a.data, buf_b.data, buf_a.len))
- error = GIT_SUCCESS;
-
- git_futils_freebuffer(&buf_a);
- git_futils_freebuffer(&buf_b);
-
- return error;
-}
-
-typedef struct {
- size_t src_len, dst_len;
- char *dst;
-} copydir_data;
-
-static int copy_filesystem_element_recurs(void *_data, char *source)
-{
- copydir_data *data = (copydir_data *)_data;
-
- data->dst[data->dst_len] = 0;
- git_path_join(data->dst, data->dst, source + data->src_len);
-
- if (git_futils_isdir(source) == GIT_SUCCESS)
- return git_futils_direach(source, GIT_PATH_MAX, copy_filesystem_element_recurs, _data);
-
- return copy_file(source, data->dst);
-}
-
-int copydir_recurs(const char *source_directory_path, const char *destination_directory_path)
-{
- char source_buffer[GIT_PATH_MAX];
- char dest_buffer[GIT_PATH_MAX];
- copydir_data data;
-
- /* Source has to exist, Destination hast to _not_ exist */
- if (git_futils_isdir(source_directory_path) != GIT_SUCCESS ||
- git_futils_isdir(destination_directory_path) == GIT_SUCCESS)
- return GIT_EINVALIDPATH;
-
- git_path_join(source_buffer, source_directory_path, "");
- data.src_len = strlen(source_buffer);
-
- git_path_join(dest_buffer, destination_directory_path, "");
- data.dst = dest_buffer;
- data.dst_len = strlen(dest_buffer);
-
- return copy_filesystem_element_recurs(&data, source_buffer);
-}
-
-int open_temp_repo(git_repository **repo, const char *path)
-{
- if (copydir_recurs(path, TEMP_REPO_FOLDER) < GIT_SUCCESS) {
- printf("\nFailed to create temporary folder. Aborting test suite.\n");
- exit(-1);
- }
-
- return git_repository_open(repo, TEMP_REPO_FOLDER);
-}
-
-void close_temp_repo(git_repository *repo)
-{
- git_repository_free(repo);
- if (git_futils_rmdir_r(TEMP_REPO_FOLDER, 1) < GIT_SUCCESS) {
- printf("\nFailed to remove temporary folder. Aborting test suite.\n");
- exit(-1);
- }
-}
-
-static int remove_placeholders_recurs(void *filename, char *path)
-{
- char passed_filename[GIT_PATH_MAX];
- char *data = (char *)filename;
-
- if (!git_futils_isdir(path))
- return git_futils_direach(path, GIT_PATH_MAX, remove_placeholders_recurs, data);
-
- if (git_path_basename_r(passed_filename, sizeof(passed_filename), path) < GIT_SUCCESS)
- return GIT_EINVALIDPATH;
-
- if (!strcmp(data, passed_filename))
- return p_unlink(path);
-
- return GIT_SUCCESS;
-}
-
-int remove_placeholders(char *directory_path, char *filename)
-{
- char buffer[GIT_PATH_MAX];
-
- if (git_futils_isdir(directory_path))
- return GIT_EINVALIDPATH;
-
- strcpy(buffer, directory_path);
- return remove_placeholders_recurs(filename, buffer);
-}
diff --git a/tests/test_helpers.h b/tests/test_helpers.h
deleted file mode 100644
index 75027dd6f..000000000
--- a/tests/test_helpers.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#ifndef INCLUDE_test_helpers_h__
-#define INCLUDE_test_helpers_h__
-
-#include "test_lib.h"
-#include <git2.h>
-
-#include "odb.h"
-
-#define TEST_REPOSITORY_NAME "testrepo.git"
-#define REPOSITORY_FOLDER TEST_RESOURCES "/" TEST_REPOSITORY_NAME "/"
-#define ODB_FOLDER (REPOSITORY_FOLDER "objects/")
-#define TEST_INDEX_PATH (REPOSITORY_FOLDER "index")
-#define TEST_INDEX2_PATH (TEST_RESOURCES "/gitgit.index")
-#define TEST_INDEXBIG_PATH (TEST_RESOURCES "/big.index")
-
-#define TEMP_FOLDER ""
-#define TEMP_REPO_FOLDER TEMP_FOLDER TEST_REPOSITORY_NAME "/"
-#define TEMP_REPO_FOLDER_NS TEMP_FOLDER TEST_REPOSITORY_NAME
-#define TEST_STD_REPO_FOLDER TEMP_REPO_FOLDER ".git/"
-
-typedef struct object_data {
- unsigned char *bytes; /* (compressed) bytes stored in object store */
- size_t blen; /* length of data in object store */
- char *id; /* object id (sha1) */
- char *type; /* object type */
- char *dir; /* object store (fan-out) directory name */
- char *file; /* object store filename */
- unsigned char *data; /* (uncompressed) object data */
- size_t dlen; /* length of (uncompressed) object data */
-} object_data;
-
-extern int write_object_data(char *file, void *data, size_t len);
-
-extern int write_object_files(const char *odb_dir, object_data *d);
-
-extern int remove_object_files(const char *odb_dir, object_data *d);
-
-extern int cmp_objects(git_rawobj *o, object_data *d);
-
-extern int remove_loose_object(const char *odb_dir, git_object *object);
-
-extern int cmp_files(const char *a, const char *b);
-extern int copy_file(const char *source, const char *dest);
-extern int rmdir_recurs(const char *directory_path);
-extern int copydir_recurs(const char *source_directory_path, const char *destination_directory_path);
-extern int remove_placeholders(char *directory_path, char *filename);
-
-extern int open_temp_repo(git_repository **repo, const char *path);
-extern void close_temp_repo(git_repository *repo);
-
-#endif
-/* INCLUDE_test_helpers_h__ */
diff --git a/tests/test_lib.c b/tests/test_lib.c
deleted file mode 100755
index a4c39dfde..000000000
--- a/tests/test_lib.c
+++ /dev/null
@@ -1,198 +0,0 @@
-#include <assert.h>
-#include <setjmp.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <math.h>
-
-#include "test_lib.h"
-
-#define DO_ALLOC(TYPE) ((TYPE*) malloc(sizeof(TYPE)))
-#define GIT_MAX_TEST_CASES 64
-
-struct git_test {
- char *name;
- char *message;
- char *failed_pos;
- char *description;
- char *error_message;
-
- git_testfunc function;
- unsigned failed:1, ran:1;
- jmp_buf *jump;
-};
-
-struct git_testsuite {
- char *name;
- int count, fail_count;
- git_test *list[GIT_MAX_TEST_CASES];
-};
-
-static void test_free(git_test *t)
-{
- if (t) {
- free(t->name);
- free(t->description);
- free(t->failed_pos);
- free(t->message);
- free(t->error_message);
- free(t);
- }
-}
-
-static void test_run(git_test *tc)
-{
- jmp_buf buf;
- tc->jump = &buf;
-
- if (setjmp(buf) == 0) {
- tc->ran = 1;
- (tc->function)(tc);
- }
-
- tc->jump = 0;
-}
-
-static git_test *create_test(git_testfunc function)
-{
- git_test *t = DO_ALLOC(git_test);
-
- memset(t, 0x0, sizeof(git_test));
- t->function = function;
-
- return t;
-}
-
-void git_test__init(git_test *t, const char *name, const char *description)
-{
- t->name = strdup(name);
- t->description = strdup(description);
-}
-
-
-/*-------------------------------------------------------------------------*
- * Public assert methods
- *-------------------------------------------------------------------------*/
-
-static void fail_test(git_test *tc, const char *file, int line, const char *message)
-{
- char buf[1024];
- const char *last_error = git_lasterror();
-
- snprintf(buf, 1024, "%s:%d", file, line);
-
- tc->failed = 1;
- tc->message = strdup(message);
- tc->failed_pos = strdup(buf);
-
- if (last_error)
- tc->error_message = strdup(last_error);
-
- if (tc->jump != 0)
- longjmp(*(tc->jump), 0);
-}
-
-void git_test__fail(git_test *tc, const char *file, int line, const char *message)
-{
- fail_test(tc, file, line, message);
-}
-
-void git_test__assert(git_test *tc, const char *file, int line, const char *message, int condition)
-{
- if (condition == 0)
- fail_test(tc, file, line, message);
-}
-
-void git_test__assert_pass(git_test *tc, const char *file, int line, const char *message, int ret_value)
-{
- if (ret_value < 0)
- fail_test(tc, file, line, message);
-}
-
-/*-------------------------------------------------------------------------*
- * Test Suite
- *-------------------------------------------------------------------------*/
-
-static void testsuite_init(git_testsuite *ts)
-{
- ts->count = 0;
- ts->fail_count = 0;
- memset(ts->list, 0, sizeof(ts->list));
-}
-
-git_testsuite *git_testsuite_new(const char *name)
-{
- git_testsuite *ts = DO_ALLOC(git_testsuite);
- testsuite_init(ts);
- ts->name = strdup(name);
- return ts;
-}
-
-static void free_suite(git_testsuite *ts)
-{
- unsigned int n;
-
- for (n = 0; n < GIT_MAX_TEST_CASES; n++)
- if (ts->list[n])
- test_free(ts->list[n]);
-
- free(ts->name);
- free(ts);
-}
-
-void git_testsuite_add(git_testsuite *ts, git_testfunc test)
-{
- assert(ts->count < GIT_MAX_TEST_CASES);
- ts->list[ts->count++] = create_test(test);
-}
-
-static void print_details(git_testsuite *ts)
-{
- int i;
- int failCount = 0;
-
- if (ts->fail_count == 0) {
- const char *testWord = ts->count == 1 ? "test" : "tests";
- printf("OK (%d %s)\n", ts->count, testWord);
- } else {
- printf("Failed (%d failures):\n", ts->fail_count);
-
- for (i = 0 ; i < ts->count ; ++i) {
- git_test *tc = ts->list[i];
- if (tc->failed) {
- failCount++;
- printf(" %d) \"%s\" [test %s @ %s]\n\t%s\n",
- failCount, tc->description, tc->name, tc->failed_pos, tc->message);
- if (tc->error_message)
- printf("\tError: %s\n", tc->error_message);
- }
- }
- }
-}
-
-int git_testsuite_run(git_testsuite *ts)
-{
- int i, fail_count;
-
- printf("Suite \"%s\": ", ts->name);
-
- for (i = 0 ; i < ts->count ; ++i) {
- git_test *tc = ts->list[i];
-
- test_run(tc);
- if (tc->failed) {
- ts->fail_count++;
- putchar('F');
- } else
- putchar('.');
-
- fflush(stdout);
- }
- printf("\n ");
- print_details(ts);
- fail_count = ts->fail_count;
-
- free_suite(ts);
- return fail_count;
-}
-
diff --git a/tests/test_lib.h b/tests/test_lib.h
deleted file mode 100755
index fc75ed771..000000000
--- a/tests/test_lib.h
+++ /dev/null
@@ -1,53 +0,0 @@
-#ifndef __LIBGIT2_TEST_H__
-#define __LIBGIT2_TEST_H__
-
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "common.h"
-#include <git2.h>
-
-#define DECLARE_SUITE(SNAME) extern git_testsuite *libgit2_suite_##SNAME(void)
-#define SUITE_NAME(SNAME) libgit2_suite_##SNAME
-
-#define BEGIN_SUITE(SNAME) \
- git_testsuite *libgit2_suite_##SNAME(void) {\
- git_testsuite *_gitsuite = git_testsuite_new(#SNAME);
-
-#define ADD_TEST(TNAME) \
- git_testsuite_add(_gitsuite, _gittest__##TNAME);
-
-#define END_SUITE \
- return _gitsuite;\
- }
-
-#define BEGIN_TEST(TNAME, DESC) \
- static void _gittest__##TNAME(git_test *_gittest) { \
- git_test__init(_gittest, #TNAME, DESC); \
- git_clearerror();\
- {\
-
-#define END_TEST }}
-
-typedef struct git_test git_test;
-typedef struct git_testsuite git_testsuite;
-typedef void (*git_testfunc)(git_test *);
-typedef git_testsuite *(*libgit2_suite)(void);
-
-void git_test__init(git_test *t, const char *name, const char *description);
-void git_test__fail(git_test *tc, const char *file, int line, const char *message);
-void git_test__assert(git_test *tc, const char *file, int line, const char *message, int condition);
-void git_test__assert_pass(git_test *tc, const char *file, int line, const char *message, int ret_value);
-
-#define must_pass(expr) git_test__assert_pass(_gittest, __FILE__, __LINE__, "Method failed: " #expr, (expr))
-#define must_fail(expr) git_test__assert(_gittest, __FILE__, __LINE__, "Expected method to fail: " #expr, (expr) < 0)
-#define must_be_true(expr) git_test__assert(_gittest, __FILE__, __LINE__, "Expression is not true: " #expr, !!(expr))
-
-git_testsuite *git_testsuite_new(const char *name);
-void git_testsuite_add(git_testsuite *ts, git_testfunc test);
-int git_testsuite_run(git_testsuite *ts);
-
-#endif
-
diff --git a/tests/test_main.c b/tests/test_main.c
deleted file mode 100644
index c9f8da3a4..000000000
--- a/tests/test_main.c
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * This file is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2,
- * as published by the Free Software Foundation.
- *
- * In addition to the permissions in the GNU General Public License,
- * the authors give you unlimited permission to link the compiled
- * version of this file into combinations with other programs,
- * and to distribute those combinations without any restriction
- * coming from the use of this file. (The General Public License
- * restrictions do apply in other respects; for example, they cover
- * modification of the file, and distribution when not linked into
- * a combined executable.)
- *
- * This file is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; see the file COPYING. If not, write to
- * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#include <string.h>
-#include <git2.h>
-
-#include "test_lib.h"
-#include "test_helpers.h"
-
-DECLARE_SUITE(core);
-DECLARE_SUITE(rawobjects);
-DECLARE_SUITE(objread);
-DECLARE_SUITE(objwrite);
-DECLARE_SUITE(commit);
-DECLARE_SUITE(revwalk);
-DECLARE_SUITE(index);
-DECLARE_SUITE(hashtable);
-DECLARE_SUITE(tag);
-DECLARE_SUITE(tree);
-DECLARE_SUITE(refs);
-DECLARE_SUITE(repository);
-DECLARE_SUITE(threads);
-DECLARE_SUITE(config);
-DECLARE_SUITE(remotes);
-DECLARE_SUITE(buffers);
-DECLARE_SUITE(status);
-
-static libgit2_suite suite_methods[]= {
- SUITE_NAME(core),
- SUITE_NAME(rawobjects),
- SUITE_NAME(objread),
- SUITE_NAME(objwrite),
- SUITE_NAME(commit),
- SUITE_NAME(revwalk),
- SUITE_NAME(index),
- SUITE_NAME(hashtable),
- SUITE_NAME(tag),
- SUITE_NAME(tree),
- SUITE_NAME(refs),
- SUITE_NAME(repository),
- SUITE_NAME(threads),
- SUITE_NAME(config),
- SUITE_NAME(remotes),
- SUITE_NAME(buffers),
- SUITE_NAME(status),
-};
-
-#define GIT_SUITE_COUNT (ARRAY_SIZE(suite_methods))
-
-#ifdef GIT_WIN32
-int __cdecl
-#else
-int
-#endif
-main(int GIT_UNUSED(argc), char *GIT_UNUSED(argv[]))
-{
- unsigned int i, failures;
-
- GIT_UNUSED_ARG(argc);
- GIT_UNUSED_ARG(argv);
-
- failures = 0;
-
- for (i = 0; i < GIT_SUITE_COUNT; ++i)
- failures += git_testsuite_run(suite_methods[i]());
-
- return failures ? -1 : 0;
-}
-
diff --git a/tests/tests.supp b/tests/tests.supp
deleted file mode 100644
index fe9d965dc..000000000
--- a/tests/tests.supp
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- ignore-zlib-cond
- Memcheck:Cond
- obj:*libz.so*
-}
-
diff --git a/tests/threads/basic.c b/tests/threads/basic.c
new file mode 100644
index 000000000..a329ee7f9
--- /dev/null
+++ b/tests/threads/basic.c
@@ -0,0 +1,36 @@
+#include "clar_libgit2.h"
+
+#include "cache.h"
+
+
+static git_repository *g_repo;
+
+void test_threads_basic__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo");
+}
+
+void test_threads_basic__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+
+void test_threads_basic__cache(void)
+{
+ // run several threads polling the cache at the same time
+ cl_assert(1 == 1);
+}
+
+void test_threads_basic__multiple_init(void)
+{
+ git_repository *nested_repo;
+
+ git_threads_init();
+ cl_git_pass(git_repository_open(&nested_repo, cl_fixture("testrepo.git")));
+ git_repository_free(nested_repo);
+
+ git_threads_shutdown();
+ cl_git_pass(git_repository_open(&nested_repo, cl_fixture("testrepo.git")));
+ git_repository_free(nested_repo);
+}
diff --git a/tests/threads/diff.c b/tests/threads/diff.c
new file mode 100644
index 000000000..79b85800b
--- /dev/null
+++ b/tests/threads/diff.c
@@ -0,0 +1,182 @@
+#include "clar_libgit2.h"
+#include "thread_helpers.h"
+
+static git_repository *_repo;
+static git_tree *_a, *_b;
+static git_atomic _counts[4];
+static int _check_counts;
+
+#define THREADS 20
+
+void test_threads_diff__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+static void setup_trees(void)
+{
+ git_index *idx;
+
+ _repo = cl_git_sandbox_reopen(); /* reopen sandbox to flush caches */
+
+ /* avoid competing to load initial index */
+ cl_git_pass(git_repository_index(&idx, _repo));
+ git_index_free(idx);
+
+ cl_git_pass(git_revparse_single(
+ (git_object **)&_a, _repo, "0017bd4ab1^{tree}"));
+ cl_git_pass(git_revparse_single(
+ (git_object **)&_b, _repo, "26a125ee1b^{tree}"));
+
+ memset(_counts, 0, sizeof(_counts));
+}
+
+static void free_trees(void)
+{
+ git_tree_free(_a); _a = NULL;
+ git_tree_free(_b); _b = NULL;
+
+ if (_check_counts) {
+ cl_assert_equal_i(288, git_atomic_get(&_counts[0]));
+ cl_assert_equal_i(112, git_atomic_get(&_counts[1]));
+ cl_assert_equal_i( 80, git_atomic_get(&_counts[2]));
+ cl_assert_equal_i( 96, git_atomic_get(&_counts[3]));
+ }
+}
+
+static void *run_index_diffs(void *arg)
+{
+ int thread = *(int *)arg;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ size_t i;
+ int exp[4] = { 0, 0, 0, 0 };
+
+ switch (thread & 0x03) {
+ case 0: /* diff index to workdir */;
+ cl_git_pass(git_diff_index_to_workdir(&diff, _repo, NULL, &opts));
+ break;
+ case 1: /* diff tree 'a' to index */;
+ cl_git_pass(git_diff_tree_to_index(&diff, _repo, _a, NULL, &opts));
+ break;
+ case 2: /* diff tree 'b' to index */;
+ cl_git_pass(git_diff_tree_to_index(&diff, _repo, _b, NULL, &opts));
+ break;
+ case 3: /* diff index to workdir (explicit index) */;
+ {
+ git_index *idx;
+ cl_git_pass(git_repository_index(&idx, _repo));
+ cl_git_pass(git_diff_index_to_workdir(&diff, _repo, idx, &opts));
+ git_index_free(idx);
+ break;
+ }
+ }
+
+ /* keep some diff stats to make sure results are as expected */
+
+ i = git_diff_num_deltas(diff);
+ git_atomic_add(&_counts[0], (int32_t)i);
+ exp[0] = (int)i;
+
+ while (i > 0) {
+ switch (git_diff_get_delta(diff, --i)->status) {
+ case GIT_DELTA_MODIFIED: exp[1]++; git_atomic_inc(&_counts[1]); break;
+ case GIT_DELTA_ADDED: exp[2]++; git_atomic_inc(&_counts[2]); break;
+ case GIT_DELTA_DELETED: exp[3]++; git_atomic_inc(&_counts[3]); break;
+ default: break;
+ }
+ }
+
+ switch (thread & 0x03) {
+ case 0: case 3:
+ cl_assert_equal_i(8, exp[0]); cl_assert_equal_i(4, exp[1]);
+ cl_assert_equal_i(0, exp[2]); cl_assert_equal_i(4, exp[3]);
+ break;
+ case 1:
+ cl_assert_equal_i(12, exp[0]); cl_assert_equal_i(3, exp[1]);
+ cl_assert_equal_i(7, exp[2]); cl_assert_equal_i(2, exp[3]);
+ break;
+ case 2:
+ cl_assert_equal_i(8, exp[0]); cl_assert_equal_i(3, exp[1]);
+ cl_assert_equal_i(3, exp[2]); cl_assert_equal_i(2, exp[3]);
+ break;
+ }
+
+ git_diff_free(diff);
+ giterr_clear();
+
+ return arg;
+}
+
+void test_threads_diff__concurrent_diffs(void)
+{
+ _repo = cl_git_sandbox_init("status");
+ _check_counts = 1;
+
+ run_in_parallel(
+ 5, 32, run_index_diffs, setup_trees, free_trees);
+}
+
+static void *run_index_diffs_with_modifier(void *arg)
+{
+ int thread = *(int *)arg;
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ git_index *idx = NULL;
+
+ cl_git_pass(git_repository_index(&idx, _repo));
+
+ /* have first thread altering the index as we go */
+ if (thread == 0) {
+ int i;
+
+ for (i = 0; i < 300; ++i) {
+ switch (i & 0x03) {
+ case 0: (void)git_index_add_bypath(idx, "new_file"); break;
+ case 1: (void)git_index_remove_bypath(idx, "modified_file"); break;
+ case 2: (void)git_index_remove_bypath(idx, "new_file"); break;
+ case 3: (void)git_index_add_bypath(idx, "modified_file"); break;
+ }
+ git_thread_yield();
+ }
+
+ goto done;
+ }
+
+ /* only use explicit index in this test to prevent reloading */
+
+ switch (thread & 0x03) {
+ case 0: /* diff index to workdir */;
+ cl_git_pass(git_diff_index_to_workdir(&diff, _repo, idx, &opts));
+ break;
+ case 1: /* diff tree 'a' to index */;
+ cl_git_pass(git_diff_tree_to_index(&diff, _repo, _a, idx, &opts));
+ break;
+ case 2: /* diff tree 'b' to index */;
+ cl_git_pass(git_diff_tree_to_index(&diff, _repo, _b, idx, &opts));
+ break;
+ case 3: /* diff index to workdir reversed */;
+ opts.flags |= GIT_DIFF_REVERSE;
+ cl_git_pass(git_diff_index_to_workdir(&diff, _repo, idx, &opts));
+ break;
+ }
+
+ /* results will be unpredictable with index modifier thread running */
+
+ git_diff_free(diff);
+
+done:
+ git_index_free(idx);
+ giterr_clear();
+
+ return arg;
+}
+
+void test_threads_diff__with_concurrent_index_modified(void)
+{
+ _repo = cl_git_sandbox_init("status");
+ _check_counts = 0;
+
+ run_in_parallel(
+ 5, 16, run_index_diffs_with_modifier, setup_trees, free_trees);
+}
diff --git a/tests/threads/iterator.c b/tests/threads/iterator.c
new file mode 100644
index 000000000..8aeae1a6c
--- /dev/null
+++ b/tests/threads/iterator.c
@@ -0,0 +1,49 @@
+#include "clar_libgit2.h"
+#include "thread_helpers.h"
+#include "iterator.h"
+
+static git_repository *_repo;
+
+void test_threads_iterator__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+static void *run_workdir_iterator(void *arg)
+{
+ int error = 0;
+ git_iterator *iter;
+ const git_index_entry *entry = NULL;
+
+ cl_git_pass(git_iterator_for_workdir(
+ &iter, _repo, GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL));
+
+ while (!error) {
+ if (entry && entry->mode == GIT_FILEMODE_TREE) {
+ error = git_iterator_advance_into(&entry, iter);
+
+ if (error == GIT_ENOTFOUND)
+ error = git_iterator_advance(&entry, iter);
+ } else {
+ error = git_iterator_advance(&entry, iter);
+ }
+
+ if (!error)
+ (void)git_iterator_current_is_ignored(iter);
+ }
+
+ cl_assert_equal_i(GIT_ITEROVER, error);
+
+ git_iterator_free(iter);
+ giterr_clear();
+ return arg;
+}
+
+
+void test_threads_iterator__workdir(void)
+{
+ _repo = cl_git_sandbox_init("status");
+
+ run_in_parallel(
+ 1, 20, run_workdir_iterator, NULL, NULL);
+}
diff --git a/tests/threads/refdb.c b/tests/threads/refdb.c
new file mode 100644
index 000000000..94a21f259
--- /dev/null
+++ b/tests/threads/refdb.c
@@ -0,0 +1,221 @@
+#include "clar_libgit2.h"
+#include "git2/refdb.h"
+#include "refdb.h"
+
+static git_repository *g_repo;
+static int g_expected = 0;
+
+void test_threads_refdb__initialize(void)
+{
+ g_repo = NULL;
+}
+
+void test_threads_refdb__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+ g_repo = NULL;
+}
+
+#define REPEAT 20
+#define THREADS 20
+
+static void *iterate_refs(void *arg)
+{
+ git_reference_iterator *i;
+ git_reference *ref;
+ int count = 0;
+
+ cl_git_pass(git_reference_iterator_new(&i, g_repo));
+
+ for (count = 0; !git_reference_next(&ref, i); ++count) {
+ cl_assert(ref != NULL);
+ git_reference_free(ref);
+ }
+
+ if (g_expected > 0)
+ cl_assert_equal_i(g_expected, count);
+
+ git_reference_iterator_free(i);
+
+ giterr_clear();
+ return arg;
+}
+
+void test_threads_refdb__iterator(void)
+{
+ int r, t;
+ git_thread th[THREADS];
+ int id[THREADS];
+ git_oid head;
+ git_reference *ref;
+ char name[128];
+ git_refdb *refdb;
+
+ g_repo = cl_git_sandbox_init("testrepo2");
+
+ cl_git_pass(git_reference_name_to_id(&head, g_repo, "HEAD"));
+
+ /* make a bunch of references */
+
+ for (r = 0; r < 200; ++r) {
+ snprintf(name, sizeof(name), "refs/heads/direct-%03d", r);
+ cl_git_pass(git_reference_create(&ref, g_repo, name, &head, 0, NULL, NULL));
+ git_reference_free(ref);
+ }
+
+ cl_git_pass(git_repository_refdb(&refdb, g_repo));
+ cl_git_pass(git_refdb_compress(refdb));
+ git_refdb_free(refdb);
+
+ g_expected = 206;
+
+ for (r = 0; r < REPEAT; ++r) {
+ g_repo = cl_git_sandbox_reopen(); /* reopen to flush caches */
+
+ for (t = 0; t < THREADS; ++t) {
+ id[t] = t;
+#ifdef GIT_THREADS
+ cl_git_pass(git_thread_create(&th[t], NULL, iterate_refs, &id[t]));
+#else
+ th[t] = t;
+ iterate_refs(&id[t]);
+#endif
+ }
+
+#ifdef GIT_THREADS
+ for (t = 0; t < THREADS; ++t) {
+ cl_git_pass(git_thread_join(&th[t], NULL));
+ }
+#endif
+
+ memset(th, 0, sizeof(th));
+ }
+}
+
+static void *create_refs(void *arg)
+{
+ int *id = arg, i;
+ git_oid head;
+ char name[128];
+ git_reference *ref[10];
+
+ cl_git_pass(git_reference_name_to_id(&head, g_repo, "HEAD"));
+
+ for (i = 0; i < 10; ++i) {
+ snprintf(name, sizeof(name), "refs/heads/thread-%03d-%02d", *id, i);
+ cl_git_pass(git_reference_create(&ref[i], g_repo, name, &head, 0, NULL, NULL));
+
+ if (i == 5) {
+ git_refdb *refdb;
+ cl_git_pass(git_repository_refdb(&refdb, g_repo));
+ cl_git_pass(git_refdb_compress(refdb));
+ git_refdb_free(refdb);
+ }
+ }
+
+ for (i = 0; i < 10; ++i)
+ git_reference_free(ref[i]);
+
+ giterr_clear();
+ return arg;
+}
+
+static void *delete_refs(void *arg)
+{
+ int *id = arg, i;
+ git_reference *ref;
+ char name[128];
+
+ for (i = 0; i < 10; ++i) {
+ snprintf(
+ name, sizeof(name), "refs/heads/thread-%03d-%02d", (*id) & ~0x3, i);
+
+ if (!git_reference_lookup(&ref, g_repo, name)) {
+ cl_git_pass(git_reference_delete(ref));
+ git_reference_free(ref);
+ }
+
+ if (i == 5) {
+ git_refdb *refdb;
+ cl_git_pass(git_repository_refdb(&refdb, g_repo));
+ cl_git_pass(git_refdb_compress(refdb));
+ git_refdb_free(refdb);
+ }
+ }
+
+ giterr_clear();
+ return arg;
+}
+
+void test_threads_refdb__edit_while_iterate(void)
+{
+ int r, t;
+ int id[THREADS];
+ git_oid head;
+ git_reference *ref;
+ char name[128];
+ git_refdb *refdb;
+
+#ifdef GIT_THREADS
+ git_thread th[THREADS];
+#endif
+
+ g_repo = cl_git_sandbox_init("testrepo2");
+
+ cl_git_pass(git_reference_name_to_id(&head, g_repo, "HEAD"));
+
+ /* make a bunch of references */
+
+ for (r = 0; r < 50; ++r) {
+ snprintf(name, sizeof(name), "refs/heads/starter-%03d", r);
+ cl_git_pass(git_reference_create(&ref, g_repo, name, &head, 0, NULL, NULL));
+ git_reference_free(ref);
+ }
+
+ cl_git_pass(git_repository_refdb(&refdb, g_repo));
+ cl_git_pass(git_refdb_compress(refdb));
+ git_refdb_free(refdb);
+
+ g_expected = -1;
+
+ g_repo = cl_git_sandbox_reopen(); /* reopen to flush caches */
+
+ for (t = 0; t < THREADS; ++t) {
+ void *(*fn)(void *arg);
+
+ switch (t & 0x3) {
+ case 0: fn = create_refs; break;
+ case 1: fn = delete_refs; break;
+ default: fn = iterate_refs; break;
+ }
+
+ id[t] = t;
+
+ /* It appears with all reflog writing changes, etc., that this
+ * test has started to fail quite frequently, so let's disable it
+ * for now by just running on a single thread...
+ */
+/* #ifdef GIT_THREADS */
+/* cl_git_pass(git_thread_create(&th[t], NULL, fn, &id[t])); */
+/* #else */
+ fn(&id[t]);
+/* #endif */
+ }
+
+#ifdef GIT_THREADS
+/* for (t = 0; t < THREADS; ++t) { */
+/* cl_git_pass(git_thread_join(th[t], NULL)); */
+/* } */
+
+ memset(th, 0, sizeof(th));
+
+ for (t = 0; t < THREADS; ++t) {
+ id[t] = t;
+ cl_git_pass(git_thread_create(&th[t], NULL, iterate_refs, &id[t]));
+ }
+
+ for (t = 0; t < THREADS; ++t) {
+ cl_git_pass(git_thread_join(&th[t], NULL));
+ }
+#endif
+}
diff --git a/tests/threads/thread_helpers.c b/tests/threads/thread_helpers.c
new file mode 100644
index 000000000..760a7bd33
--- /dev/null
+++ b/tests/threads/thread_helpers.c
@@ -0,0 +1,44 @@
+#include "clar_libgit2.h"
+#include "thread_helpers.h"
+
+void run_in_parallel(
+ int repeats,
+ int threads,
+ void *(*func)(void *),
+ void (*before_test)(void),
+ void (*after_test)(void))
+{
+ int r, t, *id = git__calloc(threads, sizeof(int));
+#ifdef GIT_THREADS
+ git_thread *th = git__calloc(threads, sizeof(git_thread));
+ cl_assert(th != NULL);
+#else
+ void *th = NULL;
+#endif
+
+ cl_assert(id != NULL);
+
+ for (r = 0; r < repeats; ++r) {
+ if (before_test) before_test();
+
+ for (t = 0; t < threads; ++t) {
+ id[t] = t;
+#ifdef GIT_THREADS
+ cl_git_pass(git_thread_create(&th[t], NULL, func, &id[t]));
+#else
+ cl_assert(func(&id[t]) == &id[t]);
+#endif
+ }
+
+#ifdef GIT_THREADS
+ for (t = 0; t < threads; ++t)
+ cl_git_pass(git_thread_join(&th[t], NULL));
+ memset(th, 0, threads * sizeof(git_thread));
+#endif
+
+ if (after_test) after_test();
+ }
+
+ git__free(id);
+ git__free(th);
+}
diff --git a/tests/threads/thread_helpers.h b/tests/threads/thread_helpers.h
new file mode 100644
index 000000000..3c13cfb6b
--- /dev/null
+++ b/tests/threads/thread_helpers.h
@@ -0,0 +1,8 @@
+#include "thread-utils.h"
+
+void run_in_parallel(
+ int repeats,
+ int threads,
+ void *(*func)(void *),
+ void (*before_test)(void),
+ void (*after_test)(void));
diff --git a/tests/trace/trace.c b/tests/trace/trace.c
new file mode 100644
index 000000000..87b325378
--- /dev/null
+++ b/tests/trace/trace.c
@@ -0,0 +1,87 @@
+#include "clar_libgit2.h"
+#include "trace.h"
+
+static int written = 0;
+
+static void trace_callback(git_trace_level_t level, const char *message)
+{
+ GIT_UNUSED(level);
+
+ cl_assert(strcmp(message, "Hello world!") == 0);
+
+ written = 1;
+}
+
+void test_trace_trace__initialize(void)
+{
+ git_trace_set(GIT_TRACE_INFO, trace_callback);
+ written = 0;
+}
+
+void test_trace_trace__cleanup(void)
+{
+ git_trace_set(GIT_TRACE_NONE, NULL);
+}
+
+void test_trace_trace__sets(void)
+{
+#ifdef GIT_TRACE
+ cl_assert(git_trace_level() == GIT_TRACE_INFO);
+#endif
+}
+
+void test_trace_trace__can_reset(void)
+{
+#ifdef GIT_TRACE
+ cl_assert(git_trace_level() == GIT_TRACE_INFO);
+ cl_git_pass(git_trace_set(GIT_TRACE_ERROR, trace_callback));
+
+ cl_assert(written == 0);
+ git_trace(GIT_TRACE_INFO, "Hello %s!", "world");
+ cl_assert(written == 0);
+
+ git_trace(GIT_TRACE_ERROR, "Hello %s!", "world");
+ cl_assert(written == 1);
+#endif
+}
+
+void test_trace_trace__can_unset(void)
+{
+#ifdef GIT_TRACE
+ cl_assert(git_trace_level() == GIT_TRACE_INFO);
+ cl_git_pass(git_trace_set(GIT_TRACE_NONE, NULL));
+
+ cl_assert(git_trace_level() == GIT_TRACE_NONE);
+
+ cl_assert(written == 0);
+ git_trace(GIT_TRACE_FATAL, "Hello %s!", "world");
+ cl_assert(written == 0);
+#endif
+}
+
+void test_trace_trace__skips_higher_level(void)
+{
+#ifdef GIT_TRACE
+ cl_assert(written == 0);
+ git_trace(GIT_TRACE_DEBUG, "Hello %s!", "world");
+ cl_assert(written == 0);
+#endif
+}
+
+void test_trace_trace__writes(void)
+{
+#ifdef GIT_TRACE
+ cl_assert(written == 0);
+ git_trace(GIT_TRACE_INFO, "Hello %s!", "world");
+ cl_assert(written == 1);
+#endif
+}
+
+void test_trace_trace__writes_lower_level(void)
+{
+#ifdef GIT_TRACE
+ cl_assert(written == 0);
+ git_trace(GIT_TRACE_ERROR, "Hello %s!", "world");
+ cl_assert(written == 1);
+#endif
+}
diff --git a/tests/valgrind-supp-mac.txt b/tests/valgrind-supp-mac.txt
new file mode 100644
index 000000000..0cdc975fa
--- /dev/null
+++ b/tests/valgrind-supp-mac.txt
@@ -0,0 +1,176 @@
+{
+ libgit2-giterr-set-buffer
+ Memcheck:Leak
+ ...
+ fun:git__realloc
+ fun:git_buf_try_grow
+ fun:git_buf_grow
+ fun:git_buf_vprintf
+ fun:giterr_set
+}
+{
+ mac-setenv-leak-1
+ Memcheck:Leak
+ fun:malloc_zone_malloc
+ fun:__setenv
+ fun:setenv
+}
+{
+ mac-setenv-leak-2
+ Memcheck:Leak
+ fun:malloc_zone_malloc
+ fun:malloc_set_zone_name
+ ...
+ fun:init__zone0
+ fun:setenv
+}
+{
+ mac-dyld-initializer-leak
+ Memcheck:Leak
+ fun:malloc
+ ...
+ fun:dyld_register_image_state_change_handler
+ fun:_dyld_initializer
+}
+{
+ mac-tz-leak-1
+ Memcheck:Leak
+ ...
+ fun:token_table_add
+ fun:notify_register_check
+ fun:notify_register_tz
+}
+{
+ mac-tz-leak-2
+ Memcheck:Leak
+ fun:malloc
+ fun:tzload
+}
+{
+ mac-tz-leak-3
+ Memcheck:Leak
+ fun:malloc
+ fun:tzsetwall_basic
+}
+{
+ mac-tz-leak-4
+ Memcheck:Leak
+ fun:malloc
+ fun:gmtsub
+}
+{
+ mac-system-init-leak-1
+ Memcheck:Leak
+ ...
+ fun:_libxpc_initializer
+ fun:libSystem_initializer
+}
+{
+ mac-system-init-leak-2
+ Memcheck:Leak
+ ...
+ fun:__keymgr_initializer
+ fun:libSystem_initializer
+}
+{
+ mac-puts-leak
+ Memcheck:Leak
+ fun:malloc
+ fun:__smakebuf
+ ...
+ fun:puts
+}
+{
+ mac-ssl-uninitialized-1
+ Memcheck:Cond
+ obj:/usr/lib/libcrypto.0.9.8.dylib
+ ...
+ fun:ssl23_connect
+}
+{
+ mac-ssl-uninitialized-2
+ Memcheck:Cond
+ ...
+ obj:/usr/lib/libssl.0.9.8.dylib
+ ...
+ fun:ssl23_connect
+}
+{
+ mac-ssl-uninitialized-3
+ Memcheck:Value8
+ obj:/usr/lib/libcrypto.0.9.8.dylib
+ ...
+ fun:ssl23_connect
+}
+{
+ mac-ssl-leak-1
+ Memcheck:Leak
+ ...
+ fun:ERR_load_strings
+}
+{
+ mac-ssl-leak-2
+ Memcheck:Leak
+ ...
+ fun:SSL_library_init
+}
+{
+ mac-ssl-leak-3
+ Memcheck:Leak
+ ...
+ fun:si_module_with_name
+ fun:getaddrinfo
+}
+{
+ mac-ssl-leak-4
+ Memcheck:Leak
+ fun:malloc
+ fun:CRYPTO_malloc
+ ...
+ fun:ssl3_get_server_certificate
+}
+{
+ mac-ssl-leak-5
+ Memcheck:Leak
+ fun:malloc
+ fun:CRYPTO_malloc
+ ...
+ fun:ERR_put_error
+}
+{
+ clar-printf-buf
+ Memcheck:Leak
+ fun:malloc
+ fun:__smakebuf
+ ...
+ fun:printf
+ fun:clar_print_init
+}
+{
+ molo-1
+ Memcheck:Leak
+ fun:malloc_zone_malloc
+ ...
+ fun:_objc_init
+}
+{
+ molo-2
+ Memcheck:Leak
+ fun:malloc_zone_calloc
+ ...
+ fun:_objc_init
+}
+{
+ molo-3
+ Memcheck:Leak
+ fun:malloc
+ ...
+ fun:_objc_init
+}
+{
+ molo-4
+ Memcheck:Leak
+ fun:malloc
+ ...
+ fun:dyld_register_image_state_change_handler
+}